diff --git a/.changeset/fast-bats-poke.md b/.changeset/fast-bats-poke.md
new file mode 100644
index 0000000000..d9c7be75ce
--- /dev/null
+++ b/.changeset/fast-bats-poke.md
@@ -0,0 +1,6 @@
+---
+'@swisspost/design-system-documentation': minor
+'@swisspost/design-system-components': minor
+---
+
+Created the `post-list` and `post-list-item` components.
diff --git a/packages/components/cypress/e2e/list.cy.ts b/packages/components/cypress/e2e/list.cy.ts
new file mode 100644
index 0000000000..0bf19446f9
--- /dev/null
+++ b/packages/components/cypress/e2e/list.cy.ts
@@ -0,0 +1,60 @@
+describe('PostList Component', { baseUrl: null, includeShadowDom: false }, () => {
+ beforeEach(() => {
+ // Visit the page where the component is rendered
+ cy.visit('./cypress/fixtures/post-list.test.html');
+ });
+
+ it('should render the post-list component', () => {
+ // Check if the post-list component is rendered
+ cy.get('post-list').should('exist');
+ });
+
+ it('should have an id for the first div in post-list', () => {
+ // Ensure the first div inside post-list has an id attribute
+ cy.get('post-list')
+ .find('div')
+ .first()
+ .should('have.attr', 'id')
+ .and('not.be.empty')
+ .then($div => {
+ const id = $div['id'];
+ cy.log(`First div ID: ${id}`);
+ });
+ });
+
+ it('should throw an error if the title is missing', () => {
+ // Check for the mandatory title accessibility error if no title is provided
+ cy.on('uncaught:exception', err => {
+ expect(err.message).to.include(
+ 'Please provide a title to the list component. Title is mandatory for accessibility purposes.',
+ );
+ return false;
+ });
+ cy.get('post-list').within(() => {
+ cy.get('[slot="post-list-item"]').first().invoke('remove');
+ });
+ });
+
+ it('should hide the title when title-hidden is set', () => {
+ // Set the `title-hidden` property and check if the title is hidden
+ cy.get('post-list').invoke('attr', 'title-hidden', true);
+ cy.get('post-list div').first().should('have.class', 'visually-hidden');
+ });
+
+ it('should render horizontally when the horizontal attribute is set', () => {
+ // Set the `horizontal` property and verify if the list has a horizontal layout
+ cy.get('post-list').invoke('attr', 'horizontal', true);
+ cy.get('post-list').should('have.attr', 'horizontal', 'true');
+ });
+
+ it('should ensure post-list-item components have the correct slot and role', () => {
+ // Verify that the `post-list-item` components have the correct slot and role attributes
+ cy.get('post-list').within(() => {
+ cy.get('post-list-item').each($el => {
+ cy.wrap($el)
+ .should('have.attr', 'slot', 'post-list-item')
+ .and('have.attr', 'role', 'listitem');
+ });
+ });
+ });
+});
diff --git a/packages/components/cypress/fixtures/post-list.test.html b/packages/components/cypress/fixtures/post-list.test.html
new file mode 100644
index 0000000000..bd2da7fc62
--- /dev/null
+++ b/packages/components/cypress/fixtures/post-list.test.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Document
+
+
+
+
+
TITLE
+ Title 1
+ Title 2
+ Title 3
+ Title 4
+ Title 5
+
+
+
diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts
index 5b08fc08e8..882e95de01 100644
--- a/packages/components/src/components.d.ts
+++ b/packages/components/src/components.d.ts
@@ -217,6 +217,18 @@ export namespace Components {
*/
"url": string;
}
+ interface PostList {
+ /**
+ * The list can become horizontal by setting `horizontal="true"` or just `horizontal`
+ */
+ "horizontal": boolean;
+ /**
+ * If `true`, the list title will be hidden. Otherwise, it will be displayed.`
+ */
+ "titleHidden": boolean;
+ }
+ interface PostListItem {
+ }
interface PostLogo {
/**
* The URL to which the user is redirected upon clicking the logo.
@@ -500,6 +512,18 @@ declare global {
prototype: HTMLPostLanguageOptionElement;
new (): HTMLPostLanguageOptionElement;
};
+ interface HTMLPostListElement extends Components.PostList, HTMLStencilElement {
+ }
+ var HTMLPostListElement: {
+ prototype: HTMLPostListElement;
+ new (): HTMLPostListElement;
+ };
+ interface HTMLPostListItemElement extends Components.PostListItem, HTMLStencilElement {
+ }
+ var HTMLPostListItemElement: {
+ prototype: HTMLPostListItemElement;
+ new (): HTMLPostListItemElement;
+ };
interface HTMLPostLogoElement extends Components.PostLogo, HTMLStencilElement {
}
var HTMLPostLogoElement: {
@@ -598,6 +622,8 @@ declare global {
"post-collapsible-trigger": HTMLPostCollapsibleTriggerElement;
"post-icon": HTMLPostIconElement;
"post-language-option": HTMLPostLanguageOptionElement;
+ "post-list": HTMLPostListElement;
+ "post-list-item": HTMLPostListItemElement;
"post-logo": HTMLPostLogoElement;
"post-popover": HTMLPostPopoverElement;
"post-popovercontainer": HTMLPostPopovercontainerElement;
@@ -795,6 +821,18 @@ declare namespace LocalJSX {
*/
"url"?: string;
}
+ interface PostList {
+ /**
+ * The list can become horizontal by setting `horizontal="true"` or just `horizontal`
+ */
+ "horizontal"?: boolean;
+ /**
+ * If `true`, the list title will be hidden. Otherwise, it will be displayed.`
+ */
+ "titleHidden"?: boolean;
+ }
+ interface PostListItem {
+ }
interface PostLogo {
/**
* The URL to which the user is redirected upon clicking the logo.
@@ -919,6 +957,8 @@ declare namespace LocalJSX {
"post-collapsible-trigger": PostCollapsibleTrigger;
"post-icon": PostIcon;
"post-language-option": PostLanguageOption;
+ "post-list": PostList;
+ "post-list-item": PostListItem;
"post-logo": PostLogo;
"post-popover": PostPopover;
"post-popovercontainer": PostPopovercontainer;
@@ -949,6 +989,8 @@ declare module "@stencil/core" {
*/
"post-icon": LocalJSX.PostIcon & JSXBase.HTMLAttributes;
"post-language-option": LocalJSX.PostLanguageOption & JSXBase.HTMLAttributes;
+ "post-list": LocalJSX.PostList & JSXBase.HTMLAttributes;
+ "post-list-item": LocalJSX.PostListItem & JSXBase.HTMLAttributes;
"post-logo": LocalJSX.PostLogo & JSXBase.HTMLAttributes;
"post-popover": LocalJSX.PostPopover & JSXBase.HTMLAttributes;
"post-popovercontainer": LocalJSX.PostPopovercontainer & JSXBase.HTMLAttributes;
diff --git a/packages/components/src/components/post-list-item/post-list-item.scss b/packages/components/src/components/post-list-item/post-list-item.scss
new file mode 100644
index 0000000000..79ff2d5269
--- /dev/null
+++ b/packages/components/src/components/post-list-item/post-list-item.scss
@@ -0,0 +1,3 @@
+:host {
+ display: flex;
+}
diff --git a/packages/components/src/components/post-list-item/post-list-item.tsx b/packages/components/src/components/post-list-item/post-list-item.tsx
new file mode 100644
index 0000000000..fdb5c6b012
--- /dev/null
+++ b/packages/components/src/components/post-list-item/post-list-item.tsx
@@ -0,0 +1,26 @@
+import { Component, Element, Host, h } from '@stencil/core';
+
+/**
+ * @slot default- Slot for placing the content of the list item.
+ */
+
+@Component({
+ tag: 'post-list-item',
+ styleUrl: 'post-list-item.scss',
+ shadow: true,
+})
+export class PostListItem {
+ @Element() host: HTMLPostListItemElement;
+
+ connectedCallback() {
+ this.host.setAttribute('slot', 'post-list-item');
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/packages/components/src/components/post-list-item/readme.md b/packages/components/src/components/post-list-item/readme.md
new file mode 100644
index 0000000000..5031db9cd5
--- /dev/null
+++ b/packages/components/src/components/post-list-item/readme.md
@@ -0,0 +1,17 @@
+# post-list-item
+
+
+
+
+
+
+## Slots
+
+| Slot | Description |
+| ----------------------------------------------------------- | ----------- |
+| `"default- Slot for placing the content of the list item."` | |
+
+
+----------------------------------------------
+
+*Built with [StencilJS](https://stenciljs.com/)*
diff --git a/packages/components/src/components/post-list/post-list.scss b/packages/components/src/components/post-list/post-list.scss
new file mode 100644
index 0000000000..d492599b77
--- /dev/null
+++ b/packages/components/src/components/post-list/post-list.scss
@@ -0,0 +1,27 @@
+@use '@swisspost/design-system-styles/core' as post;
+
+post-list {
+ display: flex;
+ gap: var(--post-list-title-gap);
+ flex-direction: column;
+ align-items: flex-start;
+
+ & > div[role='list'] {
+ flex-direction: column;
+ display: flex;
+ gap: var(--post-list-item-gap);
+ }
+
+ &[horizontal]:not([horizontal='false']) {
+ flex-direction: row;
+ align-items: baseline;
+ & > div[role='list'] {
+ flex-direction: row;
+ align-items: center;
+ }
+ }
+
+ > .list-title.visually-hidden {
+ @include post.visually-hidden();
+ }
+}
diff --git a/packages/components/src/components/post-list/post-list.tsx b/packages/components/src/components/post-list/post-list.tsx
new file mode 100644
index 0000000000..d919db8288
--- /dev/null
+++ b/packages/components/src/components/post-list/post-list.tsx
@@ -0,0 +1,69 @@
+import { Component, Element, Prop, Host, State, h } from '@stencil/core';
+import { version } from '@root/package.json';
+
+/**
+ * @slot default - Slot for placing the list title.
+ * @slot post-list-item - Slot for placing post-list-item components.
+ */
+
+@Component({
+ tag: 'post-list',
+ styleUrl: 'post-list.scss',
+ shadow: false,
+})
+export class PostList {
+ @Element() host: HTMLPostListElement;
+
+ /**
+ * The unique title of the list that is also referenced in the labelledby
+ */
+ @State() uuid: string;
+
+ /**
+ * If `true`, the list title will be hidden. Otherwise, it will be displayed.`
+ */
+ @Prop() readonly titleHidden: boolean = false;
+
+ /**
+ * The list can become horizontal by setting `horizontal="true"` or just `horizontal`
+ */
+ @Prop() readonly horizontal: boolean = false;
+
+ titleEl: HTMLElement;
+
+ componentWillLoad() {
+ /**
+ * Get the id set on the host element or use a random id by default
+ */
+ this.uuid = `list-${crypto.randomUUID()}`;
+ }
+
+ componentDidLoad() {
+ this.checkTitle();
+ }
+
+ private checkTitle() {
+ if (!this.titleEl.innerText) {
+ throw new Error(
+ 'Please provide a title to the list component. Title is mandatory for accessibility purposes.',
+ );
+ }
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/packages/components/src/components/post-list/readme.md b/packages/components/src/components/post-list/readme.md
new file mode 100644
index 0000000000..faf4bd36b5
--- /dev/null
+++ b/packages/components/src/components/post-list/readme.md
@@ -0,0 +1,26 @@
+# post-list
+
+
+
+
+
+
+## Properties
+
+| Property | Attribute | Description | Type | Default |
+| ------------- | -------------- | ---------------------------------------------------------------------------------- | --------- | ------- |
+| `horizontal` | `horizontal` | The list can become horizontal by setting `horizontal="true"` or just `horizontal` | `boolean` | `false` |
+| `titleHidden` | `title-hidden` | If `true`, the list title will be hidden. Otherwise, it will be displayed.` | `boolean` | `false` |
+
+
+## Slots
+
+| Slot | Description |
+| ------------------ | ------------------------------------------- |
+| `"default"` | Slot for placing the list title. |
+| `"post-list-item"` | Slot for placing post-list-item components. |
+
+
+----------------------------------------------
+
+*Built with [StencilJS](https://stenciljs.com/)*
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index b2c2fe0350..85a4eb32ff 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -19,3 +19,5 @@ export { PostTabHeader } from './components/post-tab-header/post-tab-header';
export { PostTabPanel } from './components/post-tab-panel/post-tab-panel';
export { PostTooltip } from './components/post-tooltip/post-tooltip';
export { PostTag } from './components/post-tag/post-tag';
+export { PostList } from './components/post-list/post-list';
+export { PostListItem } from './components/post-list-item/post-list-item';
diff --git a/packages/documentation/cypress/snapshots/components/list.snapshot.ts b/packages/documentation/cypress/snapshots/components/list.snapshot.ts
new file mode 100644
index 0000000000..c279b69d45
--- /dev/null
+++ b/packages/documentation/cypress/snapshots/components/list.snapshot.ts
@@ -0,0 +1,7 @@
+describe('List', () => {
+ it('default', () => {
+ cy.visit('/iframe.html?id=snapshots--list');
+ cy.get('post-list', { timeout: 30000 }).should('be.visible');
+ cy.percySnapshot('List', { widths: [1440] });
+ });
+});
diff --git a/packages/documentation/src/stories/components/list/list.docs.mdx b/packages/documentation/src/stories/components/list/list.docs.mdx
new file mode 100644
index 0000000000..28b696738e
--- /dev/null
+++ b/packages/documentation/src/stories/components/list/list.docs.mdx
@@ -0,0 +1,53 @@
+import { Canvas, Controls, Meta } from '@storybook/blocks';
+import * as ListStories from './list.stories';
+
+
+
+
+ # List
+
+
+
+
+The `` is a container for `` components.
+
+
+
+## ``
+
+
+
+## Installation
+
+The `` element is part of the `@swisspost/design-system-components` package.
+For more information, read the [getting started with components guide](/?path=/docs/edfb619b-fda1-4570-bf25-20830303d483--docs).
+
+## Examples
+
+
Horizontal List
+
Set the `horizontal` property to `true`to make the list horizontal.
+
+
+
Hidden Title
+
The title can be visually hidden but should always be present for accessibility reasons.
+
Set the `title-hidden` property to `true`to visually hide the title.
+
+
+
+
List Styling
+
+--post-list-title-gap
+
+ Define the gap between the title and the list items using the `--post-list-title-gap` property (
+ e.g., `--post-list-title-gap: 2rem;`).
+
+
+--post-list-item-gap
+
+ Define the gap of the list items using the `--post-list-item-gap` property (e.g.
+ `--post-list-item-gap: 1rem 0.5rem;`).
+
+
+
diff --git a/packages/documentation/src/stories/components/list/list.snapshot.stories.ts b/packages/documentation/src/stories/components/list/list.snapshot.stories.ts
new file mode 100644
index 0000000000..67e1efc59b
--- /dev/null
+++ b/packages/documentation/src/stories/components/list/list.snapshot.stories.ts
@@ -0,0 +1,52 @@
+import type { Args, StoryContext, StoryObj, StoryFn } from '@storybook/web-components';
+import meta from './list.stories';
+import { html } from 'lit';
+import { bombArgs } from '@/utils';
+
+const { id, ...metaWithoutId } = meta;
+
+export default {
+ ...metaWithoutId,
+ title: 'Snapshots',
+};
+
+type Story = StoryObj;
+
+export const PostList: Story = {
+ render: () => {
+ return html`
+