diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 82fb2e5451..41b4dc1dca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -107,7 +107,7 @@ Package versioning and publishing is done through Lerna, by the maintainers of t
## Adding a new component
-Scaffold all necessary files for a new component at once through `npm plop`.
+Scaffold all necessary files for a new component at once through `npx plop`.
Enter the name of your component when prompted.
This will create files for the design tokens, CSS and React components, and React Stories.
diff --git a/packages/css/src/image/README.md b/packages/css/src/image/README.md
new file mode 100644
index 0000000000..ce7feeb1d1
--- /dev/null
+++ b/packages/css/src/image/README.md
@@ -0,0 +1,18 @@
+# Image
+
+Toont een afbeelding.
+
+## Richtlijnen
+
+- Vergeet niet om een beschrijving van de afbeelding op te nemen in het `alt`-attribuut.
+ Dit zorgt ervoor dat gebruikers van schermlezers deze informatie ook tot zich kunnen nemen.
+ Daarnaast kan het helpen bij zoekmachineoptimalisatie.
+- Alleen voor decoratieve afbeeldingen is zo’n beschrijving niet nodig. Gebruik in dit geval `alt=""`.
+ Denk aan afbeeldingen die weinig toevoegen aan de nabije tekst of afbeeldingen die louter bijdragen aan het ontwerp of de sfeer van de pagina (bron: [W3C Web Accessibility Initiative](https://www.w3.org/WAI/tutorials/images/decorative/)).
+- Zorg ervoor dat de afbeelding een beeldverhouding heeft die ondersteund wordt door het [Aspect Ratio](?path=/docs/layout-aspect-ratio--docs) component.
+
+## Relevante WCAG-eisen
+
+- [WCAG 1.1.1](https://www.w3.org/TR/WCAG22/#non-text-content): niet-tekstuele content heeft een tekstueel alternatief
+- [WCAG 1.4.5](https://www.w3.org/TR/WCAG22/#images-of-text): gebruik tekst in plaats van afbeeldingen van tekst
+- [WCAG 1.4.9](https://www.w3.org/TR/WCAG22/#images-of-text-no-exception): gebruik afbeeldingen van tekst alleen als er geen alternatief is
diff --git a/packages/css/src/image/image.scss b/packages/css/src/image/image.scss
new file mode 100644
index 0000000000..c80f4df812
--- /dev/null
+++ b/packages/css/src/image/image.scss
@@ -0,0 +1,16 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright (c) 2023 Gemeente Amsterdam
+ */
+
+.amsterdam-image {
+ font-style: italic; /* [3] */
+ height: auto; /* [1] */
+ max-width: 100%; /* [1] */
+ vertical-align: middle; /* [2] */
+}
+
+// [1] Allow for fluid image sizing while maintaining aspect ratio governed by width/height attributes
+// [2] Remove ‘phantom’ whitespace
+// [3] Italicise alt text to visually offset it from surrounding copy
+// Source: https://x.com/csswizardry/status/1717841334462005661
diff --git a/packages/css/src/index.scss b/packages/css/src/index.scss
index c1cdf7b5ae..f2a4aa7fd4 100644
--- a/packages/css/src/index.scss
+++ b/packages/css/src/index.scss
@@ -4,6 +4,7 @@
*/
/* Append here */
+@import "./image/image";
@import "./pagination/pagination";
@import "./accordion/accordion";
@import "./alert/alert";
diff --git a/packages/react/src/Image/Image.test.tsx b/packages/react/src/Image/Image.test.tsx
new file mode 100644
index 0000000000..6f07a7932f
--- /dev/null
+++ b/packages/react/src/Image/Image.test.tsx
@@ -0,0 +1,33 @@
+import { render } from '@testing-library/react'
+import { createRef } from 'react'
+import { Image } from './Image'
+import '@testing-library/jest-dom'
+
+describe('Image', () => {
+ 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('amsterdam-image')
+ })
+
+ it('renders an additional class name', () => {
+ const { container } = render()
+ const component = container.querySelector(':only-child')
+ expect(component).toHaveClass('extra')
+ expect(component).toHaveClass('amsterdam-image')
+ })
+
+ it('supports ForwardRef in React', () => {
+ const ref = createRef()
+ const { container } = render()
+ const component = container.querySelector(':only-child')
+ expect(ref.current).toBe(component)
+ })
+})
diff --git a/packages/react/src/Image/Image.tsx b/packages/react/src/Image/Image.tsx
new file mode 100644
index 0000000000..6e0f6a1f19
--- /dev/null
+++ b/packages/react/src/Image/Image.tsx
@@ -0,0 +1,15 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright (c) 2023 Gemeente Amsterdam
+ */
+
+import clsx from 'clsx'
+import { ForwardedRef, forwardRef, ImgHTMLAttributes } from 'react'
+
+export interface ImageProps extends ImgHTMLAttributes {}
+
+export const Image = forwardRef(({ className, ...restProps }: ImageProps, ref: ForwardedRef) => (
+
+))
+
+Image.displayName = 'Image'
diff --git a/packages/react/src/Image/README.md b/packages/react/src/Image/README.md
new file mode 100644
index 0000000000..f64b03820c
--- /dev/null
+++ b/packages/react/src/Image/README.md
@@ -0,0 +1,3 @@
+# React Image component
+
+[Image documentation](../../../css/src/image/README.md)
diff --git a/packages/react/src/Image/index.ts b/packages/react/src/Image/index.ts
new file mode 100644
index 0000000000..f9de85a6b9
--- /dev/null
+++ b/packages/react/src/Image/index.ts
@@ -0,0 +1,2 @@
+export { Image } from './Image'
+export type { ImageProps } from './Image'
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 87b49ab25c..9147a66b5d 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -4,6 +4,7 @@
*/
/* Append here */
+export * from './Image'
export * from './Pagination'
export * from './Screen'
export * from './Switch'
diff --git a/storybook/storybook-react/src/AspectRatio/AspectRatio.stories.tsx b/storybook/storybook-react/src/AspectRatio/AspectRatio.stories.tsx
index e125a1a321..54be316010 100644
--- a/storybook/storybook-react/src/AspectRatio/AspectRatio.stories.tsx
+++ b/storybook/storybook-react/src/AspectRatio/AspectRatio.stories.tsx
@@ -3,7 +3,7 @@
* Copyright (c) 2023 Gemeente Amsterdam
*/
-import { AspectRatio } from '@amsterdam/design-system-react'
+import { AspectRatio, Image } from '@amsterdam/design-system-react'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
@@ -57,7 +57,7 @@ const StoryTemplate: Story = {
],
render: ({ ratio }) => (
-
+
),
}
diff --git a/storybook/storybook-react/src/Card/Card.stories.tsx b/storybook/storybook-react/src/Card/Card.stories.tsx
index 57fc9695a3..744c26100f 100644
--- a/storybook/storybook-react/src/Card/Card.stories.tsx
+++ b/storybook/storybook-react/src/Card/Card.stories.tsx
@@ -3,7 +3,7 @@
* Copyright (c) 2023 Gemeente Amsterdam
*/
-import { AspectRatio, Card, Heading, Paragraph } from '@amsterdam/design-system-react'
+import { AspectRatio, Card, Heading, Image, Paragraph } from '@amsterdam/design-system-react'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
@@ -57,7 +57,7 @@ export const ImageCard: Story = {
args: {
children: [
-
+
,
diff --git a/storybook/storybook-react/src/Grid/Grid.stories.tsx b/storybook/storybook-react/src/Grid/Grid.stories.tsx
index 553b7f8006..c0c33af605 100644
--- a/storybook/storybook-react/src/Grid/Grid.stories.tsx
+++ b/storybook/storybook-react/src/Grid/Grid.stories.tsx
@@ -3,8 +3,7 @@
* Copyright (c) 2023 Gemeente Amsterdam
*/
-import { Screen } from '@amsterdam/design-system-react'
-import { Grid } from '@amsterdam/design-system-react'
+import { Grid, Image, Screen } from '@amsterdam/design-system-react'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
@@ -40,7 +39,7 @@ export const Cells: Story = {
children: Array.from(Array(3).keys()).map((i) => (
)),
diff --git a/storybook/storybook-react/src/Image/Image.docs.mdx b/storybook/storybook-react/src/Image/Image.docs.mdx
new file mode 100644
index 0000000000..359093682a
--- /dev/null
+++ b/storybook/storybook-react/src/Image/Image.docs.mdx
@@ -0,0 +1,11 @@
+import { Controls, Markdown, Meta, Primary } from "@storybook/blocks";
+import * as ImageStories from "./Image.stories.tsx";
+import README from "../../../../packages/css/src/image/README.md?raw";
+
+
+
+{README}
+
+
+
+
diff --git a/storybook/storybook-react/src/Image/Image.stories.tsx b/storybook/storybook-react/src/Image/Image.stories.tsx
new file mode 100644
index 0000000000..a2fcc2ac90
--- /dev/null
+++ b/storybook/storybook-react/src/Image/Image.stories.tsx
@@ -0,0 +1,23 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright (c) 2023 Gemeente Amsterdam
+ */
+
+import { Image } from '@amsterdam/design-system-react'
+import { Meta, StoryObj } from '@storybook/react'
+
+const meta = {
+ title: 'Media/Image',
+ component: Image,
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ alt: '',
+ src: 'https://picsum.photos/1600/900',
+ },
+}