diff --git a/packages/css/src/components/description-list/README.md b/packages/css/src/components/description-list/README.md
new file mode 100644
index 0000000000..04853b4d66
--- /dev/null
+++ b/packages/css/src/components/description-list/README.md
@@ -0,0 +1,18 @@
+
+
+# Description List
+
+A collection of terms and their details.
+
+## Design
+
+On a narrow screen, details appear indented below their term.
+From the medium breakpoint, terms and details appear next to each other.
+The column for the details is twice as wide as the one for the term.
+
+Details are set in bold text.
+
+## References
+
+- [MDN: `
`: The Description List element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
+- [WCAG: Using description lists](https://www.w3.org/WAI/WCAG22/Techniques/html/H40)
diff --git a/packages/css/src/components/description-list/description-list.scss b/packages/css/src/components/description-list/description-list.scss
new file mode 100644
index 0000000000..9f73928c20
--- /dev/null
+++ b/packages/css/src/components/description-list/description-list.scss
@@ -0,0 +1,55 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+@import "../../common/breakpoint";
+@import "../../common/text-rendering";
+
+@mixin reset {
+ box-sizing: border-box;
+ margin-block: 0;
+}
+
+.ams-description-list {
+ color: var(--ams-description-list-color);
+ display: grid;
+ font-family: var(--ams-description-list-font-family);
+ font-size: var(--ams-description-list-font-size);
+ font-weight: var(--ams-description-list-font-weight);
+ gap: var(--ams-description-list-gap);
+ line-height: var(--ams-description-list-line-height);
+
+ @media screen and (min-width: $ams-breakpoint-medium) {
+ grid-template-columns: 1fr 2fr;
+ }
+
+ @include reset;
+ @include text-rendering;
+}
+
+.ams-description-list--inverse-color {
+ color: var(--ams-description-list-inverse-color);
+}
+
+.ams-description-list__term {
+ @media screen and (min-width: $ams-breakpoint-medium) {
+ grid-column-start: 1;
+ }
+}
+
+@mixin reset-details {
+ margin-inline: 0;
+}
+
+.ams-description-list__details {
+ font-weight: var(--ams-description-list-details-font-weight);
+ padding-inline-start: var(--ams-description-list-details-padding-inline-start);
+
+ @media screen and (min-width: $ams-breakpoint-medium) {
+ grid-column-start: 2;
+ padding-inline-start: 0;
+ }
+
+ @include reset-details;
+}
diff --git a/packages/css/src/components/index.scss b/packages/css/src/components/index.scss
index eebfc194b6..800d1ae507 100644
--- a/packages/css/src/components/index.scss
+++ b/packages/css/src/components/index.scss
@@ -7,6 +7,7 @@
@import "./document/document";
@import "./avatar/avatar";
@import "./form-field-character-counter/form-field-character-counter";
+@import "./description-list/description-list";
@import "./row/row";
@import "./radio/radio";
@import "./tabs/tabs";
diff --git a/packages/react/src/DescriptionList/DescriptionList.test.tsx b/packages/react/src/DescriptionList/DescriptionList.test.tsx
new file mode 100644
index 0000000000..d5ea286553
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionList.test.tsx
@@ -0,0 +1,49 @@
+import { render } from '@testing-library/react'
+import { createRef } from 'react'
+import { DescriptionList } from './DescriptionList'
+import '@testing-library/jest-dom'
+
+describe('Description list', () => {
+ it('renders', () => {
+ const { container } = render()
+
+ const component = container.querySelector(':only-child')
+
+ expect(component).toBeInTheDocument()
+ expect(component).toBeVisible()
+ })
+
+ it('renders a design system BEM class name', () => {
+ const { container } = render()
+
+ const component = container.querySelector(':only-child')
+
+ expect(component).toHaveClass('ams-description-list')
+ })
+
+ it('renders an additional class name', () => {
+ const { container } = render()
+
+ const component = container.querySelector(':only-child')
+
+ expect(component).toHaveClass('ams-description-list extra')
+ })
+
+ it('supports ForwardRef in React', () => {
+ const ref = createRef()
+
+ const { container } = render()
+
+ const component = container.querySelector(':only-child')
+
+ expect(ref.current).toBe(component)
+ })
+
+ it('renders the right inverse color class', () => {
+ const { container } = render()
+
+ const component = container.querySelector(':only-child')
+
+ expect(component).toHaveClass('ams-description-list--inverse-color')
+ })
+})
diff --git a/packages/react/src/DescriptionList/DescriptionList.tsx b/packages/react/src/DescriptionList/DescriptionList.tsx
new file mode 100644
index 0000000000..c94f3f9191
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionList.tsx
@@ -0,0 +1,33 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import clsx from 'clsx'
+import { forwardRef } from 'react'
+import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
+import { DescriptionListDetails } from './DescriptionListDetails'
+import { DescriptionListTerm } from './DescriptionListTerm'
+
+export type DescriptionListProps = {
+ inverseColor?: boolean
+} & PropsWithChildren>
+
+const DescriptionListRoot = forwardRef(
+ ({ children, className, inverseColor, ...restProps }: DescriptionListProps, ref: ForwardedRef) => (
+
+ {children}
+
+ ),
+)
+
+DescriptionListRoot.displayName = 'DescriptionList'
+
+export const DescriptionList = Object.assign(DescriptionListRoot, {
+ Term: DescriptionListTerm,
+ Details: DescriptionListDetails,
+})
diff --git a/packages/react/src/DescriptionList/DescriptionListDetails.test.tsx b/packages/react/src/DescriptionList/DescriptionListDetails.test.tsx
new file mode 100644
index 0000000000..3237521eee
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionListDetails.test.tsx
@@ -0,0 +1,41 @@
+import { render, screen } from '@testing-library/react'
+import { createRef } from 'react'
+import { DescriptionList } from './DescriptionList'
+import '@testing-library/jest-dom'
+
+describe('Description list details', () => {
+ it('renders', () => {
+ render(Test)
+
+ const component = screen.getByRole('definition')
+
+ expect(component).toBeInTheDocument()
+ expect(component).toBeVisible()
+ })
+
+ it('renders a design system BEM class name', () => {
+ render(Test)
+
+ const component = screen.getByRole('definition')
+
+ expect(component).toHaveClass('ams-description-list__details')
+ })
+
+ it('renders an additional class name', () => {
+ render(Test)
+
+ const component = screen.getByRole('definition')
+
+ expect(component).toHaveClass('ams-description-list__details extra')
+ })
+
+ it('supports ForwardRef in React', () => {
+ const ref = createRef()
+
+ render(Test)
+
+ const component = screen.getByRole('definition')
+
+ expect(ref.current).toBe(component)
+ })
+})
diff --git a/packages/react/src/DescriptionList/DescriptionListDetails.tsx b/packages/react/src/DescriptionList/DescriptionListDetails.tsx
new file mode 100644
index 0000000000..79a800891a
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionListDetails.tsx
@@ -0,0 +1,20 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import clsx from 'clsx'
+import { forwardRef } from 'react'
+import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
+
+export type DescriptionListDetailsProps = PropsWithChildren>
+
+export const DescriptionListDetails = forwardRef(
+ ({ children, className, ...restProps }: DescriptionListDetailsProps, ref: ForwardedRef) => (
+ -
+ {children}
+
+ ),
+)
+
+DescriptionListDetails.displayName = 'DescriptionList.Details'
diff --git a/packages/react/src/DescriptionList/DescriptionListTerm.test.tsx b/packages/react/src/DescriptionList/DescriptionListTerm.test.tsx
new file mode 100644
index 0000000000..fff06b9099
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionListTerm.test.tsx
@@ -0,0 +1,41 @@
+import { render, screen } from '@testing-library/react'
+import { createRef } from 'react'
+import { DescriptionList } from './DescriptionList'
+import '@testing-library/jest-dom'
+
+describe('Description list term', () => {
+ it('renders', () => {
+ render(Test)
+
+ const component = screen.getByRole('term')
+
+ expect(component).toBeInTheDocument()
+ expect(component).toBeVisible()
+ })
+
+ it('renders a design system BEM class name', () => {
+ render(Test)
+
+ const component = screen.getByRole('term')
+
+ expect(component).toHaveClass('ams-description-list__term')
+ })
+
+ it('renders an additional class name', () => {
+ render(Test)
+
+ const component = screen.getByRole('term')
+
+ expect(component).toHaveClass('ams-description-list__term extra')
+ })
+
+ it('supports ForwardRef in React', () => {
+ const ref = createRef()
+
+ render(Test)
+
+ const component = screen.getByRole('term')
+
+ expect(ref.current).toBe(component)
+ })
+})
diff --git a/packages/react/src/DescriptionList/DescriptionListTerm.tsx b/packages/react/src/DescriptionList/DescriptionListTerm.tsx
new file mode 100644
index 0000000000..0326783e25
--- /dev/null
+++ b/packages/react/src/DescriptionList/DescriptionListTerm.tsx
@@ -0,0 +1,20 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import clsx from 'clsx'
+import { forwardRef } from 'react'
+import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
+
+export type DescriptionListTermProps = PropsWithChildren>
+
+export const DescriptionListTerm = forwardRef(
+ ({ children, className, ...restProps }: DescriptionListTermProps, ref: ForwardedRef) => (
+ -
+ {children}
+
+ ),
+)
+
+DescriptionListTerm.displayName = 'DescriptionList.Term'
diff --git a/packages/react/src/DescriptionList/README.md b/packages/react/src/DescriptionList/README.md
new file mode 100644
index 0000000000..f88e8f6f60
--- /dev/null
+++ b/packages/react/src/DescriptionList/README.md
@@ -0,0 +1,5 @@
+
+
+# React Description List component
+
+[Description List documentation](../../../css/src/components/description-list/README.md)
diff --git a/packages/react/src/DescriptionList/index.ts b/packages/react/src/DescriptionList/index.ts
new file mode 100644
index 0000000000..fa57776c93
--- /dev/null
+++ b/packages/react/src/DescriptionList/index.ts
@@ -0,0 +1,4 @@
+export { DescriptionList } from './DescriptionList'
+export type { DescriptionListProps } from './DescriptionList'
+export type { DescriptionListTermProps } from './DescriptionListTerm'
+export type { DescriptionListDetailsProps } from './DescriptionListDetails'
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 57cd966e21..a71afe61bc 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -6,6 +6,7 @@
/* Append here */
export * from './Avatar'
export * from './FormFieldCharacterCounter'
+export * from './DescriptionList'
export * from './Row'
export * from './Radio'
export * from './Tabs'
diff --git a/proprietary/tokens/src/components/ams/description-list.tokens.json b/proprietary/tokens/src/components/ams/description-list.tokens.json
new file mode 100644
index 0000000000..9248ca9d8a
--- /dev/null
+++ b/proprietary/tokens/src/components/ams/description-list.tokens.json
@@ -0,0 +1,20 @@
+{
+ "ams": {
+ "description-list": {
+ "color": { "value": "{ams.color.primary-black}" },
+ "font-family": { "value": "{ams.text.font-family}" },
+ "font-size": { "value": "{ams.text.level.5.font-size}" },
+ "font-weight": { "value": "{ams.text.font-weight.normal}" },
+ "gap": { "value": "{ams.space.stack.md}" },
+ "inverse-color": { "value": "{ams.color.primary-white}" },
+ "line-height": { "value": "{ams.text.level.5.line-height}" },
+ "row": {
+ "gap": { "value": "{ams.space.stack.md}" }
+ },
+ "details": {
+ "font-weight": { "value": "{ams.text.font-weight.bold}" },
+ "padding-inline-start": { "value": "{ams.space.inside.xl}" }
+ }
+ }
+ }
+}
diff --git a/storybook/src/components/DescriptionList/DescriptionList.docs.mdx b/storybook/src/components/DescriptionList/DescriptionList.docs.mdx
new file mode 100644
index 0000000000..1326476a3f
--- /dev/null
+++ b/storybook/src/components/DescriptionList/DescriptionList.docs.mdx
@@ -0,0 +1,24 @@
+import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks";
+import * as DescriptionListStories from "./DescriptionList.stories.tsx";
+import README from "../../../../packages/css/src/components/description-list/README.md?raw";
+
+
+
+{README}
+
+
+
+
+
+## Multiple details
+
+A term may have multiple details.
+
+
+
+### Inverse colour
+
+Set the `inverseColor` prop if the Description List sits on a dark background.
+This ensures the colour of the text provides enough contrast.
+
+
diff --git a/storybook/src/components/DescriptionList/DescriptionList.stories.tsx b/storybook/src/components/DescriptionList/DescriptionList.stories.tsx
new file mode 100644
index 0000000000..31af9ae4dd
--- /dev/null
+++ b/storybook/src/components/DescriptionList/DescriptionList.stories.tsx
@@ -0,0 +1,59 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import { DescriptionList } from '@amsterdam/design-system-react'
+import { Meta, StoryObj } from '@storybook/react'
+import { exampleParagraph } from '../shared/exampleContent'
+
+const paragraph = exampleParagraph()
+
+const meta = {
+ title: 'Components/Text/Description List',
+ component: DescriptionList,
+ decorators: [
+ (Story, context) => (
+
+
+
+ ),
+ ],
+ args: {
+ children: [
+ Gebied,
+ Gemeente Amsterdam,
+ Stadsdeel,
+ West,
+ Opmerkingen,
+ {paragraph},
+ ],
+ inverseColor: false,
+ },
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {}
+
+export const MultipleDetails: Story = {
+ args: {
+ children: [
+ Gebied,
+ Gemeente Amsterdam,
+ Stadsdeel,
+ Noord,
+ Oost,
+ Zuid,
+ West,
+ ],
+ },
+}
+
+export const InvertedColor: Story = {
+ args: {
+ inverseColor: true,
+ },
+}