diff --git a/packages/css/src/components/field-set/README.md b/packages/css/src/components/field-set/README.md
new file mode 100644
index 0000000000..6120ee5d5a
--- /dev/null
+++ b/packages/css/src/components/field-set/README.md
@@ -0,0 +1,17 @@
+
+
+# Field Set
+
+A component to group related form inputs.
+
+## Guidelines
+
+- Use Field Set when you need to show a relationship between multiple form inputs. For example, you may need to group a set of text inputs into a single Field Set when asking for an address.
+
+## Relevant WCAG Requirements
+
+- [WCAG 1.3.5](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html): Field Set labels the purpose of a group of inputs.
+
+## References
+
+- [Providing a description for groups of form controls using fieldset and legend elements](https://www.w3.org/WAI/WCAG22/Techniques/html/H71)
diff --git a/packages/css/src/components/field-set/field-set.scss b/packages/css/src/components/field-set/field-set.scss
new file mode 100644
index 0000000000..f90ffc60d0
--- /dev/null
+++ b/packages/css/src/components/field-set/field-set.scss
@@ -0,0 +1,48 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+@import "../../common/hyphenation";
+@import "../../common/text-rendering";
+
+@mixin reset {
+ border: 0;
+ margin-inline: 0;
+ padding-block: 0;
+ padding-inline: 0;
+}
+
+.ams-field-set {
+ break-inside: avoid;
+
+ @include reset;
+}
+
+.ams-field-set--invalid {
+ border-inline-start: var(--ams-field-set-invalid-border-inline-start);
+ padding-inline-start: var(--ams-field-set-invalid-padding-inline-start);
+}
+
+@mixin reset-legend {
+ float: left; // [1]
+ padding-inline: 0;
+ width: 100%; // [1]
+}
+
+// [1] This combination allows the fieldset border to go around the legend, instead of through it.
+
+.ams-field-set__legend {
+ color: var(--ams-field-set-legend-color);
+ font-family: var(--ams-field-set-legend-font-family);
+ font-size: var(--ams-field-set-legend-font-size);
+ font-weight: var(--ams-field-set-legend-font-weight);
+ line-height: var(--ams-field-set-legend-line-height);
+ margin-block-end: var(
+ --ams-field-set-legend-margin-block-end
+ ); /* Because of a bug in Chrome we can’t use display grid or flex for this gap */
+
+ @include hyphenation;
+ @include text-rendering;
+ @include reset-legend;
+}
diff --git a/packages/css/src/components/field/README.md b/packages/css/src/components/field/README.md
index 581cad51f1..9f76f2e374 100644
--- a/packages/css/src/components/field/README.md
+++ b/packages/css/src/components/field/README.md
@@ -6,4 +6,4 @@ Wraps a single input and its related elements. May indicate that the input has a
## Guidelines
-Only use Field to wrap a single input. Use [Fieldset](/docs/components-forms-fieldset--docs) to wrap multiple inputs.
+Only use Field to wrap a single input. Use [Field Set](/docs/components-forms-field-set--docs) to wrap multiple inputs.
diff --git a/packages/css/src/components/fieldset/README.md b/packages/css/src/components/fieldset/README.md
deleted file mode 100644
index 18331daa6d..0000000000
--- a/packages/css/src/components/fieldset/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-# Fieldset
-
-A component to group related form inputs.
-
-## Guidelines
-
-- Use Fieldset when you need to show a relationship between multiple form inputs. For example, you may need to group a set of text inputs into a single fieldset when asking for an address.
-- When grouping radio inputs, use `role="radiogroup"` on Fieldset to have this grouping explicitly announced as a radio group. Fieldset has a default role of `group`.
-
-## Relevant WCAG Requirements
-
-- [WCAG 1.3.5](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html): Fieldset labels the purpose of a group of inputs.
-
-## References
-
-- [Providing a description for groups of form controls using fieldset and legend elements](https://www.w3.org/WAI/WCAG22/Techniques/html/H71)
diff --git a/packages/css/src/components/fieldset/fieldset.scss b/packages/css/src/components/fieldset/fieldset.scss
deleted file mode 100644
index 5091408cc5..0000000000
--- a/packages/css/src/components/fieldset/fieldset.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * @license EUPL-1.2+
- * Copyright Gemeente Amsterdam
- */
-
-@import "../../common/hyphenation";
-@import "../../common/text-rendering";
-
-@mixin reset {
- border: 0;
- margin-inline: 0;
- padding-block: 0;
- padding-inline: 0;
-}
-
-.ams-fieldset {
- @include reset;
-}
-
-@mixin reset-legend {
- padding-inline: 0;
-}
-
-.ams-fieldset__legend {
- color: var(--ams-fieldset-legend-color);
- font-family: var(--ams-fieldset-legend-font-family);
- font-size: var(--ams-fieldset-legend-font-size);
- font-weight: var(--ams-fieldset-legend-font-weight);
- line-height: var(--ams-fieldset-legend-line-height);
- margin-block-end: 1rem; /* Because of a bug in Chrome we can’t use display grid or flex for this gap */
-
- @include hyphenation;
- @include text-rendering;
- @include reset-legend;
-}
diff --git a/packages/css/src/components/index.scss b/packages/css/src/components/index.scss
index b95b00fbdd..a3ac841829 100644
--- a/packages/css/src/components/index.scss
+++ b/packages/css/src/components/index.scss
@@ -19,7 +19,7 @@
@import "./column/column";
@import "./margin/margin";
@import "./gap/gap";
-@import "./fieldset/fieldset";
+@import "./field-set/field-set";
@import "./link-list/link-list";
@import "./badge/badge";
@import "./table/table";
diff --git a/packages/react/src/Fieldset/Fieldset.test.tsx b/packages/react/src/FieldSet/FieldSet.test.tsx
similarity index 64%
rename from packages/react/src/Fieldset/Fieldset.test.tsx
rename to packages/react/src/FieldSet/FieldSet.test.tsx
index bb07860e57..3d59b50c52 100644
--- a/packages/react/src/Fieldset/Fieldset.test.tsx
+++ b/packages/react/src/FieldSet/FieldSet.test.tsx
@@ -1,11 +1,11 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
-import { Fieldset } from './Fieldset'
+import { FieldSet } from './FieldSet'
import '@testing-library/jest-dom'
-describe('Fieldset', () => {
+describe('FieldSet', () => {
it('renders', () => {
- render(
)
+ render()
const component = screen.getByRole('group', { name: 'Test' })
@@ -14,33 +14,33 @@ describe('Fieldset', () => {
})
it('renders a design system BEM class name', () => {
- render()
+ render()
const component = screen.getByRole('group', { name: 'Test' })
- expect(component).toHaveClass('ams-fieldset')
+ expect(component).toHaveClass('ams-field-set')
})
it('renders an additional class name', () => {
- render()
+ render()
const component = screen.getByRole('group', { name: 'Test' })
- expect(component).toHaveClass('ams-fieldset extra')
+ expect(component).toHaveClass('ams-field-set extra')
})
it('renders the correct legend class name', () => {
- const { container } = render()
+ const { container } = render()
const component = container.querySelector('legend')
- expect(component).toHaveClass('ams-fieldset__legend')
+ expect(component).toHaveClass('ams-field-set__legend')
})
it('supports ForwardRef in React', () => {
const ref = createRef()
- render()
+ render()
const component = screen.getByRole('group', { name: 'Test' })
diff --git a/packages/react/src/FieldSet/FieldSet.tsx b/packages/react/src/FieldSet/FieldSet.tsx
new file mode 100644
index 0000000000..cb424ad202
--- /dev/null
+++ b/packages/react/src/FieldSet/FieldSet.tsx
@@ -0,0 +1,30 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import clsx from 'clsx'
+import { forwardRef } from 'react'
+import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
+
+export type FieldSetProps = PropsWithChildren> & {
+ /** Whether the field set has an input with a validation error */
+ invalid?: boolean
+ /** The text for the caption. */
+ legend: string
+}
+
+export const FieldSet = forwardRef(
+ ({ children, className, invalid, legend, ...restProps }: FieldSetProps, ref: ForwardedRef) => (
+
+ ),
+)
+
+FieldSet.displayName = 'FieldSet'
diff --git a/packages/react/src/FieldSet/README.md b/packages/react/src/FieldSet/README.md
new file mode 100644
index 0000000000..e43373132f
--- /dev/null
+++ b/packages/react/src/FieldSet/README.md
@@ -0,0 +1,5 @@
+
+
+# React Field Set component
+
+[Field Set documentation](../../../css/src/components/field-set/README.md)
diff --git a/packages/react/src/FieldSet/index.ts b/packages/react/src/FieldSet/index.ts
new file mode 100644
index 0000000000..7460a8bcd1
--- /dev/null
+++ b/packages/react/src/FieldSet/index.ts
@@ -0,0 +1,2 @@
+export { FieldSet } from './FieldSet'
+export type { FieldSetProps } from './FieldSet'
diff --git a/packages/react/src/Fieldset/Fieldset.tsx b/packages/react/src/Fieldset/Fieldset.tsx
deleted file mode 100644
index fb37061dde..0000000000
--- a/packages/react/src/Fieldset/Fieldset.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @license EUPL-1.2+
- * Copyright Gemeente Amsterdam
- */
-
-import clsx from 'clsx'
-import { forwardRef } from 'react'
-import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
-
-export type FieldsetProps = PropsWithChildren> & {
- /** The text for the caption. */
- legend: string
-}
-
-export const Fieldset = forwardRef(
- ({ children, className, legend, ...restProps }: FieldsetProps, ref: ForwardedRef) => (
-
- ),
-)
-
-Fieldset.displayName = 'Fieldset'
diff --git a/packages/react/src/Fieldset/README.md b/packages/react/src/Fieldset/README.md
deleted file mode 100644
index a0f4adaa77..0000000000
--- a/packages/react/src/Fieldset/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-# React Fieldset component
-
-[Fieldset documentation](../../../css/src/components/fieldset/README.md)
diff --git a/packages/react/src/Fieldset/index.ts b/packages/react/src/Fieldset/index.ts
deleted file mode 100644
index cd5d24b492..0000000000
--- a/packages/react/src/Fieldset/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Fieldset } from './Fieldset'
-export type { FieldsetProps } from './Fieldset'
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index d6b0c721f6..9c8a57cd88 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -16,7 +16,7 @@ export * from './Radio'
export * from './Tabs'
export * from './TextArea'
export * from './Column'
-export * from './Fieldset'
+export * from './FieldSet'
export * from './LinkList'
export * from './Badge'
export * from './Table'
diff --git a/proprietary/tokens/src/components/ams/fieldset.tokens.json b/proprietary/tokens/src/components/ams/field-set.tokens.json
similarity index 53%
rename from proprietary/tokens/src/components/ams/fieldset.tokens.json
rename to proprietary/tokens/src/components/ams/field-set.tokens.json
index 33c360931b..cd9e5e7393 100644
--- a/proprietary/tokens/src/components/ams/fieldset.tokens.json
+++ b/proprietary/tokens/src/components/ams/field-set.tokens.json
@@ -1,12 +1,21 @@
{
"ams": {
- "fieldset": {
+ "field-set": {
+ "invalid": {
+ "border-inline-start": {
+ "value": "{ams.border.width.lg} solid {ams.color.primary-red}"
+ },
+ "padding-inline-start": {
+ "value": "{ams.space.inside.md}"
+ }
+ },
"legend": {
"color": { "value": "{ams.color.primary-black}" },
"font-family": { "value": "{ams.text.font-family}" },
"font-size": { "value": "{ams.text.level.4.font-size}" },
"font-weight": { "value": "{ams.text.font-weight.bold}" },
- "line-height": { "value": "{ams.text.level.4.line-height}" }
+ "line-height": { "value": "{ams.text.level.4.line-height}" },
+ "margin-block-end": { "value": "{ams.space.inside.md}" }
}
}
}
diff --git a/storybook/src/components/Field/Field.docs.mdx b/storybook/src/components/Field/Field.docs.mdx
index b465ae15f5..c2283e9caf 100644
--- a/storybook/src/components/Field/Field.docs.mdx
+++ b/storybook/src/components/Field/Field.docs.mdx
@@ -10,6 +10,15 @@ import README from "../../../../packages/css/src/components/field/README.md?raw"
+## With Description
+
+A Field can have a description.
+Make sure to connect this description to the input in the Field,
+otherwise this won’t be read by a screen reader.
+Add an `aria-describedby` attribute to the input and provide the `id` of the describing element as its value.
+
+
+
## With Error
A Field can indicate if the contained input has a validation error.
diff --git a/storybook/src/components/Field/Field.stories.tsx b/storybook/src/components/Field/Field.stories.tsx
index 1eb2bb8480..230d45a298 100644
--- a/storybook/src/components/Field/Field.stories.tsx
+++ b/storybook/src/components/Field/Field.stories.tsx
@@ -13,32 +13,42 @@ const meta = {
args: {
invalid: false,
},
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
render: (args) => (
+
+
+ ),
+}
+
+export const WithDescription: Story = {
+ render: (args) => (
+
+
Typ geen persoonsgegevens in deze omschrijving. We vragen dit later in dit formulier aan u.
-
+
),
-} satisfies Meta
-
-export default meta
-
-type Story = StoryObj
-
-export const Default: Story = {}
+}
export const WithError: Story = {
args: { invalid: true },
render: (args) => (
-
+
Typ geen persoonsgegevens in deze omschrijving. We vragen dit later in dit formulier aan u.
-
+
),
}
diff --git a/storybook/src/components/FieldSet/FieldSet.docs.mdx b/storybook/src/components/FieldSet/FieldSet.docs.mdx
new file mode 100644
index 0000000000..afb02c04d3
--- /dev/null
+++ b/storybook/src/components/FieldSet/FieldSet.docs.mdx
@@ -0,0 +1,50 @@
+import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks";
+import * as FieldSetStories from "./FieldSet.stories.tsx";
+import README from "../../../../packages/css/src/components/field-set/README.md?raw";
+
+
+
+{README}
+
+
+
+
+
+## Examples
+
+## With Description
+
+A Field Set can have a description.
+Make sure to connect this description to the Field Set or a specific input,
+otherwise this won’t be read by a screen reader.
+Add an `aria-describedby` attribute to the Field Set
+and provide the `id` of the describing element as its value.
+
+
+
+## With Error
+
+A Field Set can indicate whether any of the inputs it contains has a validation error.
+
+
+
+### Radio group
+
+Use a Field Set to group radio buttons.
+When grouping radio inputs, use `role="radiogroup"` on Field Set to have this grouping explicitly announced as a radio group (the default role is `group`).
+
+Using `role="radiogroup"` also allows you to use `aria-required` on Field Set, which isn’t allowed for role `group`.
+Always also set `aria-required` on the individual radio buttons though, to make sure it’s read by screen readers.
+
+
+
+### Checkbox group
+
+Use a Field Set to group checkboxes.
+
+Please note: [NVDA has bug](https://github.com/nvaccess/nvda/issues/12718) which causes it to
+not report a description connected to a Field Set when it contains checkboxes.
+
+Try to avoid using descriptions for Field Sets containing checkboxes for this reason.
+
+
diff --git a/storybook/src/components/FieldSet/FieldSet.stories.tsx b/storybook/src/components/FieldSet/FieldSet.stories.tsx
new file mode 100644
index 0000000000..18e470fa76
--- /dev/null
+++ b/storybook/src/components/FieldSet/FieldSet.stories.tsx
@@ -0,0 +1,130 @@
+/**
+ * @license EUPL-1.2+
+ * Copyright Gemeente Amsterdam
+ */
+
+import { Checkbox, Column, FieldSet, Label, Paragraph, Radio, TextInput } from '@amsterdam/design-system-react/src'
+import { Meta, StoryObj } from '@storybook/react'
+
+const meta = {
+ title: 'Components/Forms/Field Set',
+ component: FieldSet,
+ args: {
+ invalid: false,
+ legend: 'Wat is uw naam?',
+ },
+ decorators: [
+ (Story) => (
+
+ ),
+ ],
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ render: (args) => (
+
+ ),
+}
+
+export const WithDescription: Story = {
+ render: (args) => (
+
+ ),
+}
+
+export const WithError: Story = {
+ args: { invalid: true },
+ render: (args) => (
+
+ ),
+}
+
+export const RadioGroup: Story = {
+ args: {
+ legend: 'Waar gaat uw melding over?',
+ },
+ render: (args) => (
+
+ ),
+}
+
+export const CheckboxGroup: Story = {
+ args: {
+ legend: 'Waar gaat uw melding over?',
+ },
+ render: (args) => (
+
+ ),
+}
diff --git a/storybook/src/components/Fieldset/Fieldset.docs.mdx b/storybook/src/components/Fieldset/Fieldset.docs.mdx
deleted file mode 100644
index d38cbc9591..0000000000
--- a/storybook/src/components/Fieldset/Fieldset.docs.mdx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks";
-import * as FieldsetStories from "./Fieldset.stories.tsx";
-import README from "../../../../packages/css/src/components/fieldset/README.md?raw";
-
-
-
-{README}
-
-
-
-
-
-## Examples
-
-### Checkbox group
-
-Fieldset is used to group related form inputs, like checkboxes.
-
-
diff --git a/storybook/src/components/Fieldset/Fieldset.stories.tsx b/storybook/src/components/Fieldset/Fieldset.stories.tsx
deleted file mode 100644
index 563ed7ce5d..0000000000
--- a/storybook/src/components/Fieldset/Fieldset.stories.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @license EUPL-1.2+
- * Copyright Gemeente Amsterdam
- */
-
-import { Checkbox, Fieldset } from '@amsterdam/design-system-react/src'
-import { Meta, StoryObj } from '@storybook/react'
-
-const meta = {
- title: 'Components/Forms/Fieldset',
- component: Fieldset,
- args: {
- children: 'Body van de fieldset',
- legend: 'Label van de fieldset',
- },
-} satisfies Meta
-
-export default meta
-
-type Story = StoryObj
-
-export const Default: Story = {}
-
-export const CheckboxGroup: Story = {
- args: {
- children: [
- Horecabedrijf,
- Ander soort bedrijf,
- Evenement,
- Iets anders,
- ],
- legend: 'Waar gaat uw melding over?',
- },
-}
diff --git a/storybook/src/components/Radio/Radio.docs.mdx b/storybook/src/components/Radio/Radio.docs.mdx
index 63dd7c1e33..e7e50ba2ad 100644
--- a/storybook/src/components/Radio/Radio.docs.mdx
+++ b/storybook/src/components/Radio/Radio.docs.mdx
@@ -10,7 +10,7 @@ import README from "../../../../packages/css/src/components/radio/README.md?raw"
-Group radios together with a [Fieldset](/docs/components-forms-fieldset--docs) that describes them.
+Group radios together with a [Field Set](/docs/components-forms-field-set--docs) that describes them.
This is usually a question, like ‘Where do you live?’.
diff --git a/storybook/src/components/Radio/Radio.stories.tsx b/storybook/src/components/Radio/Radio.stories.tsx
index 05ef66884f..1f1a215b5b 100644
--- a/storybook/src/components/Radio/Radio.stories.tsx
+++ b/storybook/src/components/Radio/Radio.stories.tsx
@@ -3,7 +3,7 @@
* Copyright Gemeente Amsterdam
*/
-import { Fieldset, Radio } from '@amsterdam/design-system-react/src'
+import { FieldSet, Radio } from '@amsterdam/design-system-react/src'
import { useArgs } from '@storybook/preview-api'
import { Meta, StoryObj } from '@storybook/react'
@@ -72,7 +72,7 @@ export const RadioGroup: Story = {
},
},
render: () => (
-
),
parameters: {
docs: {