Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/composable-group-legend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@cloudflare/kumo": minor
---

feat(radio, checkbox, switch): add composable Legend sub-component for group components

- Add `Radio.Legend`, `Checkbox.Legend`, and `Switch.Legend` sub-components
- Accepts `className` for full styling control (e.g. `className="sr-only"` to visually hide)
- Make `legend` string prop optional when using the sub-component instead
- Useful when a parent Field already provides a visible label and the legend would be redundant
- **Breaking:** `Switch.Group` no longer renders a visible border/padding/rounded container — now consistent with `Radio.Group` and `Checkbox.Group`. Use `className` to add a border if needed.
30 changes: 30 additions & 0 deletions packages/kumo-docs-astro/src/components/demos/CheckboxDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,36 @@ export function CheckboxGroupDemo() {
);
}

/** Shows Checkbox.Legend with sr-only to visually hide the legend while keeping it accessible, useful when a parent Field already provides a visible label */
export function CheckboxLegendSrOnlyDemo() {
const [preferences, setPreferences] = useState<string[]>(["email"]);
return (
<Checkbox.Group value={preferences} onValueChange={setPreferences}>
<Checkbox.Legend className="sr-only">
Notification preferences
</Checkbox.Legend>
<Checkbox.Item value="email" label="Email notifications" />
<Checkbox.Item value="sms" label="SMS notifications" />
<Checkbox.Item value="push" label="Push notifications" />
</Checkbox.Group>
);
}

/** Shows Checkbox.Legend with custom styling for full control over legend presentation */
export function CheckboxLegendCustomDemo() {
const [preferences, setPreferences] = useState<string[]>(["email"]);
return (
<Checkbox.Group value={preferences} onValueChange={setPreferences}>
<Checkbox.Legend className="text-sm font-normal text-kumo-subtle">
Notification preferences
</Checkbox.Legend>
<Checkbox.Item value="email" label="Email notifications" />
<Checkbox.Item value="sms" label="SMS notifications" />
<Checkbox.Item value="push" label="Push notifications" />
</Checkbox.Group>
);
}

export function CheckboxGroupErrorDemo() {
return (
<Checkbox.Group
Expand Down
27 changes: 27 additions & 0 deletions packages/kumo-docs-astro/src/components/demos/RadioDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ export function RadioCardDemo() {
);
}

/** Shows Radio.Legend with sr-only to visually hide the legend while keeping it accessible, useful when a parent Field already provides a visible label */
export function RadioLegendSrOnlyDemo() {
const [value, setValue] = useState("all");
return (
<Radio.Group defaultValue="all" value={value} onValueChange={setValue}>
<Radio.Legend className="sr-only">Paths</Radio.Legend>
<Radio.Item label="Allow all paths" value="all" />
<Radio.Item label="Restrict to specific paths" value="specific" />
</Radio.Group>
);
}

/** Shows Radio.Legend with custom styling for full control over legend presentation */
export function RadioLegendCustomDemo() {
const [value, setValue] = useState("email");
return (
<Radio.Group value={value} onValueChange={setValue}>
<Radio.Legend className="text-sm font-normal text-kumo-subtle">
Notification preference
</Radio.Legend>
<Radio.Item label="Email" value="email" />
<Radio.Item label="SMS" value="sms" />
<Radio.Item label="Push notification" value="push" />
</Radio.Group>
);
}

/** Shows radio card appearance in horizontal layout */
export function RadioCardHorizontalDemo() {
const [value, setValue] = useState("free");
Expand Down
37 changes: 37 additions & 0 deletions packages/kumo-docs-astro/src/components/demos/SwitchDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,43 @@ export function SwitchCustomIdDemo() {
);
}

/** Shows a Switch.Group with a legend for grouping related switches */
export function SwitchGroupDemo() {
return (
<Switch.Group legend="Notification settings">
<Switch.Item label="Email notifications" />
<Switch.Item label="SMS notifications" />
<Switch.Item label="Push notifications" />
</Switch.Group>
);
}

/** Shows Switch.Legend with sr-only to visually hide the legend while keeping it accessible, useful when a parent Field already provides a visible label */
export function SwitchLegendSrOnlyDemo() {
return (
<Switch.Group>
<Switch.Legend className="sr-only">Notification settings</Switch.Legend>
<Switch.Item label="Email notifications" />
<Switch.Item label="SMS notifications" />
<Switch.Item label="Push notifications" />
</Switch.Group>
);
}

/** Shows Switch.Legend with custom styling for full control over legend presentation */
export function SwitchLegendCustomDemo() {
return (
<Switch.Group>
<Switch.Legend className="text-sm font-normal text-kumo-subtle">
Notification settings
</Switch.Legend>
<Switch.Item label="Email notifications" />
<Switch.Item label="SMS notifications" />
<Switch.Item label="Push notifications" />
</Switch.Group>
);
}

/** All sizes comparison */
export function SwitchSizesDemo() {
return (
Expand Down
52 changes: 44 additions & 8 deletions packages/kumo-docs-astro/src/pages/components/checkbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
CheckboxErrorDemo,
CheckboxGroupDemo,
CheckboxGroupErrorDemo,
CheckboxLegendSrOnlyDemo,
CheckboxLegendCustomDemo,
} from "~/components/demos/CheckboxDemo";

{/* Hero Demo */}
Expand All @@ -38,7 +40,7 @@ import {

### Barrel

<CodeBlock code={`import { Checkbox } from "@cloudflare/kumo";`} lang="tsx" />
<CodeBlock code={`import { Checkbox } from "@cloudflare/kumo";`} lang="tsx" />

### Granular

Expand Down Expand Up @@ -128,13 +130,37 @@ export default function Example() {

### Checkbox Group with Error

<p>
Show validation errors at the group level. Error replaces description
when present.
</p>
<ComponentExample demo="CheckboxGroupErrorDemo">
<CheckboxGroupErrorDemo client:visible />
</ComponentExample>
<p>
Show validation errors at the group level. Error replaces description when
present.
</p>
<ComponentExample demo="CheckboxGroupErrorDemo">
<CheckboxGroupErrorDemo client:visible />
</ComponentExample>

### Visually Hidden Legend

<p>
Use `Checkbox.Legend` with `className="sr-only"` to keep the legend accessible
to screen readers while hiding it visually. This is useful when the group is
already labeled by a parent `Field` or heading, and showing the legend would
create a redundant label.
</p>
<ComponentExample demo="CheckboxLegendSrOnlyDemo">
<CheckboxLegendSrOnlyDemo client:visible />
</ComponentExample>

### Custom Legend Styling

<p>
`Checkbox.Legend` accepts `className` for full control over legend
presentation. Use it instead of the `legend` string prop when you need custom
typography, colors, or layout.
</p>
<ComponentExample demo="CheckboxLegendCustomDemo">
<CheckboxLegendCustomDemo client:visible />
</ComponentExample>

</ComponentSection>

{/* API Reference */}
Expand All @@ -155,6 +181,15 @@ export default function Example() {
</p>
<PropsTable component="Checkbox.Group" />

### Checkbox.Legend

<p>
Composable legend sub-component for Checkbox.Group. Accepts `className` for
full styling control (e.g. `className="sr-only"` to visually hide). Use
instead of the `legend` string prop when you need custom legend styling.
</p>
<PropsTable component="Checkbox.Legend" />

### Checkbox.Item

<p>Individual checkbox within Checkbox.Group.</p>
Expand Down Expand Up @@ -197,5 +232,6 @@ export default function Example() {
proper grouping announcement.
</p>
</div>

</div>
</ComponentSection>
35 changes: 35 additions & 0 deletions packages/kumo-docs-astro/src/pages/components/radio.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
RadioControlPositionDemo,
RadioCardDemo,
RadioCardHorizontalDemo,
RadioLegendSrOnlyDemo,
RadioLegendCustomDemo,
} from "~/components/demos/RadioDemo";

{/* Hero Demo */}
Expand Down Expand Up @@ -144,6 +146,29 @@ export default function Example() {
<RadioDisabledDemo client:visible />
</ComponentExample>

### Visually Hidden Legend

<p>
Use `Radio.Legend` with `className="sr-only"` to keep the legend accessible to
screen readers while hiding it visually. This is useful when the radio group
is already labeled by a parent `Field` or heading, and showing the legend
would create a redundant label.
</p>
<ComponentExample demo="RadioLegendSrOnlyDemo">
<RadioLegendSrOnlyDemo client:visible />
</ComponentExample>

### Custom Legend Styling

<p>
`Radio.Legend` accepts `className` for full control over legend presentation.
Use it instead of the `legend` string prop when you need custom typography,
colors, or layout.
</p>
<ComponentExample demo="RadioLegendCustomDemo">
<RadioLegendCustomDemo client:visible />
</ComponentExample>

</ComponentSection>

{/* API Reference */}
Expand All @@ -157,6 +182,15 @@ export default function Example() {
<p>Container for radio buttons with legend, description, and error support.</p>
<PropsTable component="Radio.Group" />

### Radio.Legend

<p>
Composable legend sub-component for Radio.Group. Accepts `className` for full
styling control (e.g. `className="sr-only"` to visually hide). Use instead of
the `legend` string prop when you need custom legend styling.
</p>
<PropsTable component="Radio.Legend" />

### Radio.Item

<p>Individual radio button within Radio.Group.</p>
Expand Down Expand Up @@ -195,5 +229,6 @@ export default function Example() {
Each radio is announced with its label and selection state. The group legend provides context for all options.
</p>
</div>

</div>
</ComponentSection>
63 changes: 62 additions & 1 deletion packages/kumo-docs-astro/src/pages/components/switch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import {
SwitchVariantsDemo,
SwitchSizesDemo,
SwitchCustomIdDemo,
SwitchGroupDemo,
SwitchLegendSrOnlyDemo,
SwitchLegendCustomDemo,
} from "~/components/demos/SwitchDemo";

{/* Hero Demo */}
Expand Down Expand Up @@ -137,6 +140,39 @@ export default function Example() {
<SwitchCustomIdDemo client:visible />
</ComponentExample>

### Switch Group

<p>
Group related switches with `Switch.Group`. Provides a shared legend,
description, and error message for the group.
</p>
<ComponentExample demo="SwitchGroupDemo">
<SwitchGroupDemo client:visible />
</ComponentExample>

### Visually Hidden Legend

<p>
Use `Switch.Legend` with `className="sr-only"` to keep the legend accessible
to screen readers while hiding it visually. This is useful when the group is
already labeled by a parent `Field` or heading, and showing the legend would
create a redundant label.
</p>
<ComponentExample demo="SwitchLegendSrOnlyDemo">
<SwitchLegendSrOnlyDemo client:visible />
</ComponentExample>

### Custom Legend Styling

<p>
`Switch.Legend` accepts `className` for full control over legend presentation.
Use it instead of the `legend` string prop when you need custom typography,
colors, or layout.
</p>
<ComponentExample demo="SwitchLegendCustomDemo">
<SwitchLegendCustomDemo client:visible />
</ComponentExample>

</ComponentSection>

{/* API Reference */}
Expand All @@ -145,5 +181,30 @@ export default function Example() {

## API Reference

<PropsTable component="Switch" />
### Switch

<p>Individual switch toggle with built-in label.</p>
<PropsTable component="Switch" />

### Switch.Group

<p>
Container for multiple switches with legend, description, and error support.
</p>
<PropsTable component="Switch.Group" />

### Switch.Legend

<p>
Composable legend sub-component for Switch.Group. Accepts `className` for full
styling control (e.g. `className="sr-only"` to visually hide). Use instead of
the `legend` string prop when you need custom legend styling.
</p>
<PropsTable component="Switch.Legend" />

### Switch.Item

<p>Individual switch within Switch.Group.</p>
<PropsTable component="Switch.Item" />

</ComponentSection>
Loading
Loading