diff --git a/playwright/visual.test.ts-snapshots/VisualList-VisualList-Default-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/VisualList-VisualList-Default-1-chromium-linux.png
new file mode 100644
index 00000000..8d0d0877
Binary files /dev/null and b/playwright/visual.test.ts-snapshots/VisualList-VisualList-Default-1-chromium-linux.png differ
diff --git a/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Default-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Default-1-chromium-linux.png
new file mode 100644
index 00000000..e9590432
Binary files /dev/null and b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Default-1-chromium-linux.png differ
diff --git a/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Destructive-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Destructive-1-chromium-linux.png
new file mode 100644
index 00000000..f5ae0157
Binary files /dev/null and b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Destructive-1-chromium-linux.png differ
diff --git a/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Multiline-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Multiline-1-chromium-linux.png
new file mode 100644
index 00000000..c966df3c
Binary files /dev/null and b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Multiline-1-chromium-linux.png differ
diff --git a/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Success-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Success-1-chromium-linux.png
new file mode 100644
index 00000000..19b96ffb
Binary files /dev/null and b/playwright/visual.test.ts-snapshots/VisualList-VisualListItem-Success-1-chromium-linux.png differ
diff --git a/src/components/VisualList/VisualList.module.css b/src/components/VisualList/VisualList.module.css
new file mode 100644
index 00000000..046eab81
--- /dev/null
+++ b/src/components/VisualList/VisualList.module.css
@@ -0,0 +1,26 @@
+/*
+Copyright 2024 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.visual-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--cpd-space-1x);
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ border-radius: var(--cpd-space-3x);
+ overflow: hidden;
+}
diff --git a/src/components/VisualList/VisualList.stories.tsx b/src/components/VisualList/VisualList.stories.tsx
new file mode 100644
index 00000000..46e80296
--- /dev/null
+++ b/src/components/VisualList/VisualList.stories.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { VisualList as VisualListComponent } from "./VisualList";
+import { Meta, StoryFn } from "@storybook/react";
+import React, { ComponentProps } from "react";
+import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
+import { VisualListItem } from "./VisualListItem.tsx";
+
+export default {
+ title: "VisualList/VisualList",
+ component: VisualListComponent,
+ tags: ["autodocs"],
+ argTypes: {},
+ args: {
+ children: (
+ <>
+ First item
+ Second item
+ Third item
+ >
+ ),
+ },
+} as Meta;
+
+const Template: StoryFn = (
+ args: ComponentProps,
+) => {
+ return ;
+};
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/src/components/VisualList/VisualList.test.tsx b/src/components/VisualList/VisualList.test.tsx
new file mode 100644
index 00000000..81689692
--- /dev/null
+++ b/src/components/VisualList/VisualList.test.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { composeStories } from "@storybook/react";
+import * as stories from "./VisualList.stories";
+import { describe, expect, it } from "vitest";
+import { render } from "@testing-library/react";
+import React from "react";
+
+const { Default } = composeStories(stories);
+
+describe("VisualList", () => {
+ it("renders a Default VisualList", () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/src/components/VisualList/VisualList.tsx b/src/components/VisualList/VisualList.tsx
new file mode 100644
index 00000000..10638470
--- /dev/null
+++ b/src/components/VisualList/VisualList.tsx
@@ -0,0 +1,41 @@
+/*
+Copyright 2024 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, { JSX, PropsWithChildren } from "react";
+import styles from "./VisualList.module.css";
+import classNames from "classnames";
+
+interface VisualListProps extends React.HTMLProps {
+ /**
+ * The CSS class name.
+ */
+ className?: string;
+}
+
+/**
+ * A list component.
+ */
+export function VisualList({
+ className,
+ children,
+ ...props
+}: PropsWithChildren): JSX.Element {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/VisualList/VisualListItem.module.css b/src/components/VisualList/VisualListItem.module.css
new file mode 100644
index 00000000..b083dbe6
--- /dev/null
+++ b/src/components/VisualList/VisualListItem.module.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.visual-list-item {
+ display: flex;
+ gap: var(--cpd-space-3x);
+ padding: var(--cpd-space-3x) var(--cpd-space-4x);
+ background-color: var(--cpd-color-bg-subtle-secondary);
+ font: var(--cpd-font-body-md-medium);
+}
+
+.visual-list-item-icon {
+ flex-shrink: 0;
+ color: var(--cpd-color-icon-secondary);
+}
+
+.visual-list-item-icon-success {
+ color: var(--cpd-color-icon-success-primary);
+}
+
+.visual-list-item-icon-destructive {
+ color: var(--cpd-color-icon-critical-primary);
+}
diff --git a/src/components/VisualList/VisualListItem.stories.tsx b/src/components/VisualList/VisualListItem.stories.tsx
new file mode 100644
index 00000000..2d2cbe62
--- /dev/null
+++ b/src/components/VisualList/VisualListItem.stories.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { VisualListItem as VisualListItemComponent } from "./VisualListItem";
+import { Meta, StoryFn } from "@storybook/react";
+import React, { ComponentProps } from "react";
+import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
+
+export default {
+ title: "VisualList/VisualListItem",
+ component: VisualListItemComponent,
+ tags: ["autodocs"],
+ argTypes: {},
+ args: {
+ Icon: InfoIcon,
+ children: "List item description",
+ },
+} as Meta;
+
+const Template: StoryFn = (
+ args: ComponentProps,
+) => {
+ return ;
+};
+
+export const Default = Template.bind({});
+Default.args = {};
+
+export const Success = Template.bind({});
+Success.args = {
+ success: true,
+};
+
+export const Destructive = Template.bind({});
+Destructive.args = {
+ destructive: true,
+};
+
+export const Multiline = Template.bind({});
+Multiline.args = {
+ children:
+ "List item with a looooooooooong very looooooooooong loooooooooooooong description",
+};
diff --git a/src/components/VisualList/VisualListItem.test.tsx b/src/components/VisualList/VisualListItem.test.tsx
new file mode 100644
index 00000000..ed9a97ad
--- /dev/null
+++ b/src/components/VisualList/VisualListItem.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { composeStories } from "@storybook/react";
+import * as stories from "./VisualListItem.stories";
+import { describe, expect, it } from "vitest";
+import { render } from "@testing-library/react";
+import React from "react";
+
+const { Default, Success, Destructive, Multiline } = composeStories(stories);
+
+describe("VisualListItem", () => {
+ it("renders a Default VisualListItem", () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+ it("renders a Success VisualListItem", () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+ it("renders a Destructive VisualListItem", () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+ it("renders a Multiline VisualListItem", () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/src/components/VisualList/VisualListItem.tsx b/src/components/VisualList/VisualListItem.tsx
new file mode 100644
index 00000000..4cfcfcdf
--- /dev/null
+++ b/src/components/VisualList/VisualListItem.tsx
@@ -0,0 +1,63 @@
+/*
+Copyright 2024 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, { ComponentType, JSX, PropsWithChildren } from "react";
+import styles from "./VisualListItem.module.css";
+import classNames from "classnames";
+
+interface VisualListItemProps extends React.HTMLProps {
+ /**
+ * The CSS class name.
+ */
+ className?: string;
+ /**
+ * The icon component.
+ */
+ Icon: ComponentType>;
+
+ success?: boolean;
+ destructive?: boolean;
+}
+
+/**
+ * A list component.
+ */
+export function VisualListItem({
+ className,
+ children,
+ Icon,
+ success = false,
+ destructive = false,
+ ...props
+}: PropsWithChildren): JSX.Element {
+ return (
+
+`;
diff --git a/src/components/VisualList/index.tsx b/src/components/VisualList/index.tsx
new file mode 100644
index 00000000..1a69f4f3
--- /dev/null
+++ b/src/components/VisualList/index.tsx
@@ -0,0 +1,18 @@
+/*
+Copyright 2024 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export { VisualList } from "./VisualList";
+export { VisualListItem } from "./VisualListItem";
diff --git a/src/index.ts b/src/index.ts
index 557e658a..0b662cb0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -53,6 +53,7 @@ export { Toast } from "./components/Toast/Toast";
export { Dropdown } from "./components/Dropdown";
export { InlineSpinner } from "./components/InlineSpinner";
export { Breadcrumb } from "./components/Breadcrumb";
+export { VisualList, VisualListItem } from "./components/VisualList";
export {
TextControl,