Configurator
<oc-radio-button-v2><input type="radio" name="rb-cfg" aria-label="oc-auto"><label slot="label">Expressversand</label></oc-radio-button-v2>Usage
Anatomy
- Radio button
- Label
HTML
<div class="anatomy" style="display:block;width:max-content;margin:28px auto">
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-an" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<span class="anatomy-pin" style="left:-16px;top:50%">1</span>
<span class="anatomy-pin" style="left:calc(100% + 16px);top:50%">2</span>
<ol class="anatomy-key"><li data-n="1">Radio button</li><li data-n="2">Label</li></ol>Variants
There are two variants of the Radio Button. Both come in size50 and size100.
- Default
- Error: The error variant is not only visualized by the error color but also by an error icon, which is due to accessibility.
Default, size 100
Error, size 100
Default, size 50
Error, size 50
HTML
<p class="demo-label">Default, size 100</p>
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-var-1" aria-label="oc-auto"><label slot="label">Standardversand</label></oc-radio-button-v2>
<p class="demo-label">Error, size 100</p>
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-var-2" aria-label="oc-auto"><label slot="label">Expressversand</label><span slot="error">Bitte wähle eine Versandart aus.</span></oc-radio-button-v2>
<p class="demo-label">Default, size 50</p>
<oc-radio-button-v2 size="50" width-behavior="fit"><input type="radio" name="rb-var-3" aria-label="oc-auto"><label slot="label">Standardversand</label></oc-radio-button-v2>
<p class="demo-label">Error, size 50</p>
<oc-radio-button-v2 size="50" width-behavior="fit"><input type="radio" name="rb-var-4" aria-label="oc-auto"><label slot="label">Expressversand</label><span slot="error">Bitte wähle eine Versandart aus.</span></oc-radio-button-v2>
Behavior
States
Enabled (hover, press, focus it)
Error
Disabled
HTML
<p class="demo-label">Enabled (hover, press, focus it)</p>
<oc-radio-button-v2><input type="radio" name="rb-st-a" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-st-b" checked aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<p class="demo-label">Error</p>
<oc-radio-button-v2><input type="radio" name="rb-st-c" aria-label="oc-auto"><label slot="label">Label</label><span slot="error"></span></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-st-d" checked aria-label="oc-auto"><label slot="label">Label</label><span slot="error"></span></oc-radio-button-v2>
<p class="demo-label">Disabled</p>
<oc-radio-button-v2><input type="radio" name="rb-st-e" disabled aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-st-f" checked disabled aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
Interaction
Users can check a radio button by tapping anywhere on it. The label is also part of the hitbox and ensures proper support in assistive technologies, improving usability and accessibility. Checking a radio button automatically unchecks other radio buttons in same group. There is also a disabled variant of the radio button that cannot be clicked or tapped.
Fitting
The radio button is 1.5rem high and 1.5rem wide. The label is set to fit-content by default, but it can be overwritten to fill-parent. Note, that the whole line is interactive in this case.
fit
fill (default)
Long label wraps
HTML
<p class="demo-label">fit</p>
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-fit-a" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<p class="demo-label">fill (default)</p>
<oc-radio-button-v2><input type="radio" name="rb-fit-b" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<p class="demo-label">Long label wraps</p>
<oc-radio-button-v2><input type="radio" name="rb-fit-c" aria-label="oc-auto"><label slot="label">Lieferung an eine abweichende Lieferadresse</label></oc-radio-button-v2>
Placement
The radio button has a mandatory label which can be placed left or right with a small gap to the radio button. Ideally, there should be a 24px gap between radio buttons if they are placed vertically.
Label right (default)
Label left
HTML
<p class="demo-label">Label right (default)</p>
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-pl-a" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<p class="demo-label">Label left</p>
<oc-radio-button-v2 width-behavior="fit" label-placement="left"><input type="radio" name="rb-pl-b" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
Vertical list, 24px gap
HTML
<p class="demo-label">Vertical list, 24px gap</p>
<oc-form-group-v1 orientation="vertical" gap="var(--oc-base-dimension-24)" oc-aria-label="Versandart">
<oc-radio-button-v2><input type="radio" name="rb-place" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-place" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-place" aria-label="oc-auto"><label slot="label">Label</label></oc-radio-button-v2>
</oc-form-group-v1>
Radio button vs. checkbox
Use radio buttons when only one option can be selected from a list (single selection). When multiple option can be selected you should use checkboxes (multiple selection).
Checkbox: several choices
Radio button: one choice
HTML
<p class="demo-label">Checkbox: several choices</p>
<oc-checkbox-v2><input type="checkbox" checked aria-label="oc-auto"><label slot="label">Damen</label></oc-checkbox-v2>
<oc-checkbox-v2><input type="checkbox" checked aria-label="oc-auto"><label slot="label">Herren</label></oc-checkbox-v2>
<oc-checkbox-v2><input type="checkbox" aria-label="oc-auto"><label slot="label">Kinder</label></oc-checkbox-v2>
<p class="demo-label">Radio button: one choice</p>
<oc-radio-button-v2><input type="radio" name="rb-vs-cb" aria-label="oc-auto"><label slot="label">Standardversand</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-vs-cb" checked aria-label="oc-auto"><label slot="label">Expressversand</label></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-vs-cb" aria-label="oc-auto"><label slot="label">Abholung</label></oc-radio-button-v2>
Best Practices
Fünf oder weniger Optionen:
Mehr als fünf Optionen:
This is the only way to ensure consistent behavior and layout.
HTML
<oc-radio-button-v2 width-behavior="fit"><input type="radio" name="rb-click" aria-label="oc-auto"><label slot="label">Auch der Text ist klickbar</label></oc-radio-button-v2>
Content Guidelines
Accessibility
For information on accessibility, refer to the technical documentation.
Status
Related components
Implementation
Live demo
Schritt 2 von 3
Wie möchtest du deine Lieferung?
Gesamtsumme
249,98 €
HTML
<div style="max-width:480px;margin:0 auto;display:grid;gap:24px">
<div style="display:grid;gap:4px">
<p class="oc-copy-75 oc-text-color-secondary">Schritt 2 von 3</p>
<p class="oc-headline-100">Wie möchtest du deine Lieferung?</p>
<oc-form-group-v1 orientation="vertical" gap="var(--oc-base-dimension-24)" oc-aria-label="Versandart">
<oc-radio-button-v2><input type="radio" name="rb-demo" checked aria-label="oc-auto"><label slot="label">Standardversand · kostenlos</label><span slot="hint">Lieferung Do., 02.10. bis Sa., 04.10.</span></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-demo" aria-label="oc-auto"><label slot="label">Expressversand · 9,95 €</label><span slot="hint">Bestellst du bis 12 Uhr, kommt dein Paket morgen.</span></oc-radio-button-v2>
<oc-radio-button-v2><input type="radio" name="rb-demo" aria-label="oc-auto"><label slot="label">Wunschtermin · 4,95 €</label><span slot="hint">Du wählst den Tag, wir liefern.</span></oc-radio-button-v2>
</oc-form-group-v1>
<oc-divider-v1></oc-divider-v1>
<p class="oc-copy-100"><b>Gesamtsumme</b></p>
<p class="oc-copy-100"><b>249,98 €</b></p>
<oc-button-v1 variant="primary">Weiter zur Zahlung</oc-button-v1>
Code
| Version | Tag | Status | API |
|---|---|---|---|
| v2 | <oc-radio-button-v2> |
Stable, allowed for generation | RadioButtonV2 |
| v1 | <oc-radio-button-v1> |
Deprecated, NOT allowed for generation | RadioButtonV1 |
Only the latest version (v2) is allowed for generation. Older versions are kept for reference and are deprecated.
Overview (v2)
Radio button
The radio button component presents mutually exclusive options where a single choice is required.
It combines a slotted input type="radio" with optional hint and validation slots while preserving native form semantics.
Default variation
HTML
<oc-radio-button-v2>
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto"/>
</oc-radio-button-v2>Configuration
This component is configurable, allowing you to tailor its features and appearance to your specific needs. To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the radio button component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.
Info
See the Radio button UX documentation for detailed user experience guidelines.
Layout considerations
This component has visual overflow.
It extends beyond its bounding box and is clipped by parent containers with overflow: hidden.
Ensure the parent container has sufficient padding to accommodate the component's full visual area.
Accessibility
The radio button component inherits accessibility from the slotted input, so provide a descriptive <label> or aria-label, propagate required and disabled states through the input, and pair radio buttons with meaningful legends or group labels.
Refer to the built-in accessibility features guide for keyboard and screen-reader expectations.
Configuration (v2)
Radio button configuration
Configure the radio button component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.
HTML
<oc-radio-button-v2>
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto"/>
</oc-radio-button-v2>Interactive configurator (Storybook controls); every option is listed in the API section of this file.
Migration (v2)
Migration from radio button v1 to v2
The oc-radio-button component has been updated from oc-radio-button-v1 to oc-radio-button-v2.
This migration guide provides step-by-step instructions to update your project to the latest version.
Skip to:
What's new
Native input element
The component now uses a native <input type="radio"> element, providing:
- Better form integration and native form submission
- Native validation support
- Improved accessibility with screen readers
- Support for standard input attributes like
autofocus,form,formnovalidate
Events
All native radio button events are now available:
clickinputchangefocusblur
Flexible label content
The label slot allows for more flexible label content, including HTML elements:
HTML
<oc-radio-button-v2>
<input type="radio" name="option" value="damen" aria-label="oc-auto" />
<label slot="label"> Damen <span class="oc-text-color-secondary">374637</span> </label>
</oc-radio-button-v2>Required radio groups
For required radio groups, use the aria-required attribute on the parent <fieldset> element:
HTML
<fieldset
role="radiogroup"
aria-required="true"
aria-label="Select an option"
aria-invalid="true"
aria-description="Please select an option"
>
<oc-radio-button-v2>
<input type="radio" name="required-group" value="1" aria-label="oc-auto" />
<label slot="label">Option 1</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="required-group" value="2" aria-label="oc-auto" />
<label slot="label">Option 2</label>
</oc-radio-button-v2>
</fieldset>Design
No changes were applied to the design of the component.
API changes
Changed attributes
| v1 Attribute | v2 Attribute | Notes |
|---|---|---|
fit-content |
width-behavior="fit" |
Default is "fill" |
label-placement |
label-placement |
No change |
Changed slots
| v1 Slot | v2 Slot | Notes |
|---|---|---|
default (label text) |
label |
Label text now uses the label slot |
| — | default |
Now contains the native <input type="radio"> element |
| — | hint |
New slot for hint text |
| — | error |
New slot for validation message |
Removed attributes
The following attributes have been removed and should now be set on the native <input> element:
| v1 Attribute | v2 Equivalent | Notes |
|---|---|---|
checked |
Set checked on the <input> element |
Moved to native input |
disabled |
Set disabled on the <input> element |
Moved to native input |
name |
Set name on the <input> element |
Moved to native input |
value |
Set value on the <input> element |
Moved to native input |
oc-aria-label |
Set aria-label on the <input> element |
Renamed to standard attribute |
oc-aria-required |
Set aria-required on the parent <fieldset> element |
Moved to parent fieldset |
variant |
The error state is now automatically applied when the error slot has content |
Auto-applied based on slot |
How to migrate
The main change is that v2 uses a native <input type="radio"> element in the default slot instead of managing the radio button state internally.
This provides better accessibility, form integration, and native event support.
Migrate a basic radio button
<!-- From: -->
<oc-radio-button-v1 name="size" value="M" checked>Medium</oc-radio-button-v1>
<!-- To: -->
<oc-radio-button-v2>
<input type="radio" name="size" value="M" checked aria-label="oc-auto" />
<label slot="label">Medium</label>
</oc-radio-button-v2>
Migrate a radio button group
<!-- From: -->
<fieldset>
<legend>Size</legend>
<oc-radio-button-v1 name="size" value="S">Small</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">Medium</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="L">Large</oc-radio-button-v1>
</fieldset>
<!-- To: -->
<fieldset role="radiogroup" aria-label="Size">
<oc-radio-button-v2>
<input type="radio" name="size" value="S" aria-label="oc-auto" />
<label slot="label">Small</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="size" value="M" aria-label="oc-auto" />
<label slot="label">Medium</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="size" value="L" aria-label="oc-auto" />
<label slot="label">Large</label>
</oc-radio-button-v2>
</fieldset>
Migrate a radio button with hint and validation message
<!-- From: -->
<oc-radio-button-v1 name="option" value="1" variant="error"> Option 1 </oc-radio-button-v1>
<!-- To: -->
<oc-radio-button-v2>
<input type="radio" name="option" value="1" aria-label="oc-auto" />
<label slot="label">Option 1</label>
<span slot="hint">Additional hint text</span>
<span slot="error">Please select an option</span>
</oc-radio-button-v2>
Migrate a radio button with a disabled state
<!-- From: -->
<oc-radio-button-v1 disabled>Disabled option</oc-radio-button-v1>
<!-- To: -->
<oc-radio-button-v2>
<input type="radio" disabled aria-label="oc-auto" />
<label slot="label">Disabled option</label>
</oc-radio-button-v2>
Migrate a radio button with width behavior
<!-- From: -->
<oc-radio-button-v1 fit-content>Fit content</oc-radio-button-v1>
<!-- To: -->
<oc-radio-button-v2 width-behavior="fit">
<input type="radio" aria-label="oc-auto" />
<label slot="label">Fit content</label>
</oc-radio-button-v2>
API v1 (v1, deprecated, not for generation)
Radio Button v1 API
API: <oc-radio-button-v1> (RadioButtonV1)
The Radio Button component allows users to select exactly one option from a set, with attributes for state, styling, accessibility, and a slot for setting the label text of the Radio Button .
Attributes / properties
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
variant |
"default" | "error" |
"default" |
no | Sets the main styling and behavior of the radio button. |
value |
string |
"on" |
no | Sets the initial value of the radio button to the provided value for form processing. |
checked |
boolean |
false |
no | Toggles the state of the radio button between checked and unchecked. When set to true, the radio button is initially checked.Accessible in CSS via the custom-state mixin. |
disabled |
boolean |
false |
no | Disables the radio button, preventing user interaction and input. |
fit-content |
boolean |
undefined |
no | Sets the with behavior of the radio button. When set to true, the radio button will take the width of its content. |
label-placement |
"left" | "right" |
"right" |
no | Sets the placement direction of the label provided via the default slot relative to the radio button. |
name |
string |
undefined |
no | Sets the name tag to identify the radio button when submitting a form. |
oc-aria-label |
string |
undefined |
no | Sets the ARIA label of the radio button. |
oc-aria-required |
boolean |
no | Sets the ARIA required attribute of the radio button. |
Slots
| Slot | Required | Description |
|---|---|---|
default |
yes | Sets the text content of the radio button label to provide a brief description of the input field. |
Events
| Event | Detail type | Description |
|---|---|---|
oc-property-change |
OcRadioButtonV1Events["oc-property-change"] |
Whenever a property value changes, this event triggers. Use this event to track all property changes within the component. Refer to the Events documentation for more information. |
oc-mount |
{ component: string; } |
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime. |
oc-unmount |
{ component: string; } |
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime. |
API v2
Radio Button v2 API
API: <oc-radio-button-v2> (RadioButtonV2)
The Radio Button component allows users to select exactly one option from a set. Provides attributes for state, styling, accessibility, and slots for the label, input element, hints, and errors.
Attributes / properties
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
size |
"50" | "100" |
"100" |
no | The size of the radio button. |
width-behavior |
"fill" | "fit" |
"fill" |
no | Defines the width behavior of the radio button component. - "fill": The component expands to fill the available width. - "fit": The component width fits its content. |
label-placement |
"left" | "right" |
"right" |
no | The placement direction of the label provided via the default slot relative to the radio button indicator. |
Slots
| Slot | Required | Description |
|---|---|---|
default |
yes | Requires the following interactive element: - <input type="radio"> for single-selectionFor more information on interactive components, refer to the interactive component slots documentation. |
label |
no | The text content of the radio button label. Provides a brief description of the option represented by the radio button. |
hint |
no | A hint text displayed below the radio button. Provides additional information to help users understand the option. |
error |
no | An error message displayed below the radio button. Informs users about validation errors or requirements. |
Events
| Event | Detail type | Description |
|---|---|---|
oc-property-change |
OcRadioButtonV2Events["oc-property-change"] |
Whenever a property value changes, this event triggers. Use this event to track all property changes within the component. Refer to the Events documentation for more information. |
oc-mount |
{ component: string; } |
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime. |
oc-unmount |
{ component: string; } |
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime. |
Variations (v2)
Variations
Listed below are the most common variations of the radio button component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
Default
Story: components-radio-button-variations--default · tags: components
The standard configuration with the label placed on the right side.
HTML
<oc-radio-button-v2>
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto"/>
</oc-radio-button-v2>Label placement: left
Story: components-radio-button-variations--label-placement-left · tags: components
The standard configuration with the label placed on the left side.
Args: label-placement=left
HTML
<oc-radio-button-v2 label-placement="left">
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto"/>
</oc-radio-button-v2>Error
Story: components-radio-button-variations--error · tags: components
Displays an error state with an error message.
Args: errorSlot=<span slot='error'>This is an error message.</span>, defaultSlot=<input type="radio" aria-label="oc-auto" />
HTML
<oc-radio-button-v2>
<label slot="label">my label</label>
<span slot='error'>This is an error message.</span>
<input type="radio" aria-label="oc-auto" />
</oc-radio-button-v2>Disabled
Story: components-radio-button-variations--disabled · tags: components
The disabled state prevents user interaction.
Args: defaultSlot=<input type="radio" aria-label="oc-auto" disabled />
HTML
<oc-radio-button-v2>
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto" disabled />
</oc-radio-button-v2>Size 50
Story: components-radio-button-variations--size-50 · tags: components
A size 50 variant of the radio button.
Args: defaultSlot=<input type="radio" aria-label="oc-auto" />, size=50
HTML
<oc-radio-button-v2 size="50">
<label slot="label">my label</label>
<input type="radio" aria-label="oc-auto" />
</oc-radio-button-v2>Demo: required
Story: components-radio-button-variations--demo-required · tags: components
The demo showcases a required radiogroup with multiple radio button options.
HTML
<fieldset
role="radiogroup"
aria-required="true"
aria-label="radio options demo"
style="display: flex; flex-direction: column"
aria-invalid="true"
aria-description="Please select an option"
class="oc-gap-100"
>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option1" aria-label="oc-auto" />
<label slot="label">Option 1</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option2" aria-label="oc-auto" />
<label slot="label">Option 2</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option3" aria-label="oc-auto" />
<label slot="label">Option 3</label>
</oc-radio-button-v2>
</fieldset>Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: required",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<fieldset
role="radiogroup"
aria-required="true"
aria-label="radio options demo"
style="display: flex; flex-direction: column"
aria-invalid="true"
aria-description="Please select an option"
class="oc-gap-100"
>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option1" aria-label="oc-auto" />
<label slot="label">Option 1</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option2" aria-label="oc-auto" />
<label slot="label">Option 2</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="demo-required" value="option3" aria-label="oc-auto" />
<label slot="label">Option 3</label>
</oc-radio-button-v2>
</fieldset>
`;
}
}
Demo: filter
Story: components-radio-button-variations--demo-filter · tags: components
The demo filter showcases how to use radio buttons for filtering options with label placement variations.
HTML
<oc-form-group-v1 orientation="vertical">
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Damen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Damen<span class="oc-text-color-secondary">374637</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Herren" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Herren<span class="oc-text-color-secondary">234324</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Mädchen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Mädchen<span class="oc-text-color-secondary">2323</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Jungen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Jungen<span class="oc-text-color-secondary">1224</span></label
>
</oc-radio-button-v2>
</oc-form-group-v1>
<oc-form-group-v1 class="oc-mt-200" orientation="vertical">
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Damen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Damen<span class="oc-text-color-secondary">374637</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Herren" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Herren<span class="oc-text-color-secondary">234324</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Mädchen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Mädchen<span class="oc-text-color-secondary">2323</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Jungen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Jungen<span class="oc-text-color-secondary">1224</span></label
>
</oc-radio-button-v2>
</oc-form-group-v1>
<style>
.my-label {
display: flex;
justify-content: space-between;
}
</style>Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: filter",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<oc-form-group-v1 orientation="vertical">
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Damen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Damen<span class="oc-text-color-secondary">374637</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Herren" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Herren<span class="oc-text-color-secondary">234324</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Mädchen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Mädchen<span class="oc-text-color-secondary">2323</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="zielgruppe" value="Jungen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Jungen<span class="oc-text-color-secondary">1224</span></label
>
</oc-radio-button-v2>
</oc-form-group-v1>
<oc-form-group-v1 class="oc-mt-200" orientation="vertical">
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Damen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Damen<span class="oc-text-color-secondary">374637</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Herren" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Herren<span class="oc-text-color-secondary">234324</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Mädchen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Mädchen<span class="oc-text-color-secondary">2323</span></label
>
</oc-radio-button-v2>
<oc-radio-button-v2 label-placement="left">
<input type="radio" name="zielgruppe" value="Jungen" aria-label="oc-auto" />
<label slot="label" class="my-label"
>Jungen<span class="oc-text-color-secondary">1224</span></label
>
</oc-radio-button-v2>
</oc-form-group-v1>
<style>
.my-label {
display: flex;
justify-content: space-between;
}
</style>
`;
}
}
Demo: delivery addresses
Story: components-radio-button-variations--demo-delivery-addresses · tags: components
The demo showcases using radio buttons to select from multiple delivery addresses with a nested checkbox for setting a default.
HTML
<div
role="radiogroup"
aria-required="true"
aria-label="choose your delivery address"
style="display: flex; flex-direction: column"
class="oc-gap-100"
>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" aria-label="oc-auto" />
<label slot="label">Home 1</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" checked aria-label="oc-auto" />
<label slot="label">Home 2</label>
<oc-checkbox-v2 slot="label" class="oc-secondary-action">
<input type="checkbox" aria-label="oc-auto" />
<label slot="label"> set as default delivery address</label>
</oc-checkbox-v2>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" aria-label="oc-auto" />
<label slot="label">Home 3</label>
</oc-radio-button-v2>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: delivery addresses",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<div
role="radiogroup"
aria-required="true"
aria-label="choose your delivery address"
style="display: flex; flex-direction: column"
class="oc-gap-100"
>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" aria-label="oc-auto" />
<label slot="label">Home 1</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" checked aria-label="oc-auto" />
<label slot="label">Home 2</label>
<oc-checkbox-v2 slot="label" class="oc-secondary-action">
<input type="checkbox" aria-label="oc-auto" />
<label slot="label"> set as default delivery address</label>
</oc-checkbox-v2>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" name="address" value="home1" aria-label="oc-auto" />
<label slot="label">Home 3</label>
</oc-radio-button-v2>
</div>
`;
}
}
Interaction tests (RadioButtonV2.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Should Handle User Selection
Story: components-radio-button-interaction-tests--should-handle-user-selection · tags: play-fn
HTML
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="Samsung" />
<label slot="label">Samsung</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="LG" />
<label slot="label">LG</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="S" />
<label slot="label">S</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="M" />
<label slot="label">M</label>
</oc-radio-button-v2>Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html`
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="Samsung" />
<label slot="label">Samsung</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="LG" />
<label slot="label">LG</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="S" />
<label slot="label">S</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="M" />
<label slot="label">M</label>
</oc-radio-button-v2>
`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v2"));
const getInput = (element: Element) => element.querySelector("input") as HTMLInputElement;
await expect(getInput(brand1).checked).toBeFalsy();
await expect(getInput(brand2).checked).toBeFalsy();
await expect(getInput(size1).checked).toBeFalsy();
await expect(getInput(size2).checked).toBeFalsy();
await click(getInput(brand1));
await click(getInput(size1));
await expect(getInput(brand1).checked).toBeTruthy();
await expect(getInput(brand2).checked).toBeFalsy();
await expect(getInput(size1).checked).toBeTruthy();
await expect(getInput(size2).checked).toBeFalsy();
await click(getInput(brand2));
await click(getInput(size2));
await expect(getInput(brand1).checked).toBeFalsy();
await expect(getInput(brand2).checked).toBeTruthy();
await expect(getInput(size1).checked).toBeFalsy();
await expect(getInput(size2).checked).toBeTruthy();
}
}
Should Have Correct Form Data
Story: components-radio-button-interaction-tests--should-have-correct-form-data · tags: play-fn
HTML
<form>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="Samsung" />
<label slot="label">Samsung</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="LG" />
<label slot="label">LG</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="S" />
<label slot="label">S</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="M" />
<label slot="label">M</label>
</oc-radio-button-v2>
</form>Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="Samsung" />
<label slot="label">Samsung</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="brand" value="LG" />
<label slot="label">LG</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="S" />
<label slot="label">S</label>
</oc-radio-button-v2>
<oc-radio-button-v2>
<input type="radio" aria-label="oc-auto" name="size" value="M" />
<label slot="label">M</label>
</oc-radio-button-v2>
</form>`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v2"));
const getInput = (element: Element) => element.querySelector("input") as HTMLInputElement;
await click(getInput(brand1));
await click(getInput(size1));
await click(getInput(brand2));
await click(getInput(size2));
const form = canvasElement.getElementsByTagName("form").item(0);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const data = Array.from(new FormData(form).entries());
await expect(data).toStrictEqual([["brand", "LG"], ["size", "M"]]);
}
}
Should Have Pointer Events Set
Story: components-radio-button-interaction-tests--should-have-pointer-events-set · tags:
V1 (v1, deprecated, not for generation)
Radio button v1
Important
This is a deprecated version of the radio button component. For the latest version, see the updated component documentation. Refer to this migration guide to update your project to the latest version.
The radio button component allows users to select exactly one option from a set, with attributes for state, styling, accessibility, and a slot for setting the label text of the Radio button.
Default variation
<oc-radio-button-v1 name="myName" label-placement="right" value="on" variant="default">my label</oc-radio-button-v1>
Configuration
The radio button component offers a variety of styling variants such as default and error.
This component is configurable, allowing you to tailor its features and appearance to your specific needs.
To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the radio button component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.
Important
If the radio button component is used in a form, pressing
Enterwhile focused on the radio button triggers the submit action immediately (implicit submit) if anoc-buttoncomponent or a normal HTML button with the typesubmitis also present in the form.
Layout considerations
This component has visual overflow.
It extends beyond its bounding box and is clipped by parent containers with overflow: hidden.
Ensure the parent container has sufficient padding to accommodate the component's full visual area.
Accessibility
The radio button component comes with a set of built-in accessibility features to ensure a seamless experience for all users.
Use oc-aria-label
To make the radio button recognizable for screen readers, use the oc-aria-label attribute to provide clear and descriptive context information.
See the general accessibility documentation for guidance on using oc-aria-label, including how it works with link and masked link behavior.
Use oc-aria-required
Set the oc-aria-required attribute to indicate that the radio button is required for form submission.
This helps screen readers announce the required state to users.
V1/Configuration (v1, deprecated, not for generation)
Radio button configuration
Configure the radio button component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.
<oc-radio-button-v1 name="myName" label-placement="right" value="on" variant="default">my label</oc-radio-button-v1>
Interactive configurator (Storybook controls); every option is listed in the API section of this file.
V1/Interaction tests (RadioButtonV1.form.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Should Fire Keydown Event
Story: components-radio-button-v1-interaction-tests--should-fire-keydown-event · tags: play-fn
<form>
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit"></button>
</form>
Story source (TypeScript, verbatim from Storybook)
{
render() {
return html`
<form>
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit"></button>
</form>
`;
},
async play({
canvasElement
}) {
canvasElement.getElementsByTagName("form").item(0)!.addEventListener("submit", ev => ev.preventDefault());
const radioButton = canvasElement.getElementsByTagName("oc-radio-button-v1").item(0)!;
let fired = 0;
radioButton.addEventListener("keydown", () => fired += 1);
radioButton.dispatchEvent(new KeyboardEvent("keydown", {
key: "Enter"
}));
await expect(fired, "keydown should be fired one time").toBe(1);
}
}
Should Submit Like Native Inputs
Story: components-radio-button-v1-interaction-tests--should-submit-like-native-inputs · tags: play-fn
<form id="form1">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit" data-should-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<form id="form2">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<input type="submit" data-should-submit />
<button type="submit" data-should-not-submit></button>
</form>
<form id="form3">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
Should submit the form
</form>
<form id="form4">Should submit the form</form>
<oc-radio-button-v1 name="name" form="form4">{Enter}</oc-radio-button-v1>
<form id="form5">
<button type="submit" data-should-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<oc-radio-button-v1 name="name" form="form5">{Enter}</oc-radio-button-v1>
<form id="form6">
<input type="submit" data-should-submit />
<button type="submit" data-should-not-submit></button>
</form>
<oc-radio-button-v1 name="name" form="form6">{Enter}</oc-radio-button-v1>
<form id="form7">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit" disabled data-should-not-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<form id="form8">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<input type="submit" disabled data-should-not-submit />
<input type="submit" data-should-not-submit />
</form>
Story source (TypeScript, verbatim from Storybook)
{
render() {
return html`
<form id="form1">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit" data-should-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<form id="form2">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<input type="submit" data-should-submit />
<button type="submit" data-should-not-submit></button>
</form>
<form id="form3">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
Should submit the form
</form>
<form id="form4">Should submit the form</form>
<oc-radio-button-v1 name="name" form="form4">{Enter}</oc-radio-button-v1>
<form id="form5">
<button type="submit" data-should-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<oc-radio-button-v1 name="name" form="form5">{Enter}</oc-radio-button-v1>
<form id="form6">
<input type="submit" data-should-submit />
<button type="submit" data-should-not-submit></button>
</form>
<oc-radio-button-v1 name="name" form="form6">{Enter}</oc-radio-button-v1>
<form id="form7">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<button type="submit" disabled data-should-not-submit></button>
<button type="submit" data-should-not-submit></button>
</form>
<form id="form8">
<oc-radio-button-v1 name="name">{Enter}</oc-radio-button-v1>
<input type="submit" disabled data-should-not-submit />
<input type="submit" data-should-not-submit />
</form>
`;
},
async play({
canvasElement
}) {
const submits: {
form: string;
submitter: HTMLElement | null;
}[] = [];
canvasElement.querySelectorAll("form").forEach(form => form.addEventListener("submit", ev => {
ev.preventDefault();
submits.push({
form: form.id,
submitter: ev.submitter
});
}));
const radioButtons = Array.from(canvasElement.querySelectorAll(`oc-radio-button-v1[name="name"]`)) as (HTMLOcTextFieldV1Element & {
internals: ElementInternals;
})[];
for (let i = 0; i < radioButtons.length; i += 1) {
const radioButton = radioButtons[i];
// eslint-disable-next-line no-await-in-loop
await expect(radioButton.internals.form?.id, "radioButton should have correct associated form").toBe(`form${i + 1}`);
}
// eslint-disable-next-line no-restricted-syntax
for (const radioButton of radioButtons) {
const input = radioButton.shadowRoot!.querySelector("input")!;
// eslint-disable-next-line no-await-in-loop
input.dispatchEvent(new KeyboardEvent("keydown", {
key: "Enter"
}));
}
await expect(submits.length, "number of submits").toBe(6);
await expect(submits[0].form).toBe("form1");
await expect(submits[0].submitter?.dataset.shouldSubmit).toBeDefined();
await expect(submits[1].form).toBe("form2");
await expect(submits[1].submitter?.dataset.shouldSubmit).toBeDefined();
await expect(submits[2].form).toBe("form3");
await expect(submits[2].submitter).toBe(null);
await expect(submits[3].form).toBe("form4");
await expect(submits[3].submitter).toBe(null);
await expect(submits[4].form).toBe("form5");
await expect(submits[4].submitter?.dataset.shouldSubmit).toBeDefined();
await expect(submits[5].form).toBe("form6");
await expect(submits[5].submitter?.dataset.shouldSubmit).toBeDefined();
}
}
V1/Interaction tests (RadioButtonV1.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Should Handle User Selection
Story: components-radio-button-v1-interaction-tests--should-handle-user-selection · tags: play-fn
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html`
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v1"));
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeFalsy();
await expect(size1.checked).toBeFalsy();
await expect(size2.checked).toBeFalsy();
await click(deepQuerySelector(brand1, ".radio")!);
await click(deepQuerySelector(size1, ".radio")!);
await expect(brand1.checked).toBeTruthy();
await expect(brand2.checked).toBeFalsy();
await expect(size1.checked).toBeTruthy();
await expect(size2.checked).toBeFalsy();
await click(deepQuerySelector(brand2, ".radio")!);
await click(deepQuerySelector(size2, ".radio")!);
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeTruthy();
await expect(size1.checked).toBeFalsy();
await expect(size2.checked).toBeTruthy();
}
}
Should Have Correct Form Data
Story: components-radio-button-v1-interaction-tests--should-have-correct-form-data · tags: play-fn
<form>
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
</form>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form>
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
</form>`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v1"));
await click(deepQuerySelector(brand1, ".radio")!);
await click(deepQuerySelector(size1, ".radio")!);
await click(deepQuerySelector(brand2, ".radio")!);
await click(deepQuerySelector(size2, ".radio")!);
const form = canvasElement.getElementsByTagName("form").item(0);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const data = Array.from(new FormData(form).entries());
await expect(data).toStrictEqual([["brand", "LG"], ["size", "M"]]);
}
}
Should Be Accessible With Keyboard
Story: components-radio-button-v1-interaction-tests--should-be-accessible-with-keyboard · tags: play-fn
<form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>`;
},
async play({
canvasElement
}) {
const [brand1, brand2, brand3] = Array.from(canvasElement.getElementsByTagName(`oc-radio-button-v1`));
brand1.focus();
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeFalsy();
await expect(brand3.checked).toBeFalsy();
await click();
await expect(brand1.checked).toBeTruthy();
await expect(brand2.checked).toBeFalsy();
await expect(brand3.checked).toBeFalsy();
await keydown("ArrowRight");
await keydown("ArrowRight");
await keydown("ArrowRight");
await keydown("ArrowRight");
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeTruthy();
await expect(brand3.checked).toBeFalsy();
await keydown("ArrowDown");
await keydown("ArrowDown");
await keydown("ArrowDown");
await keydown("ArrowDown");
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeFalsy();
await expect(brand3.checked).toBeTruthy();
await keydown("ArrowLeft");
await keydown("ArrowLeft");
await keydown("ArrowLeft");
await keydown("ArrowLeft");
await expect(brand1.checked).toBeFalsy();
await expect(brand2.checked).toBeTruthy();
await expect(brand3.checked).toBeFalsy();
await keydown("ArrowUp");
await keydown("ArrowUp");
await keydown("ArrowUp");
await keydown("ArrowUp");
await expect(brand1.checked).toBeTruthy();
await expect(brand2.checked).toBeFalsy();
await expect(brand3.checked).toBeFalsy();
}
}
Should Handle Interaction Via Tab
Story: components-radio-button-v1-interaction-tests--should-handle-interaction-via-tab · tags: play-fn
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html`
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v1"));
brand1.focus();
await expect(brand1).toHaveFocus();
await click();
await expect(brand1.checked).toBeTruthy();
await keydown("ArrowRight");
await expect(brand2, "focus").toHaveFocus();
await expect(brand2.checked).toBeTruthy();
await keydown("ArrowLeft");
await expect(brand1).toHaveFocus();
await expect(brand1.checked, "checked").toBeTruthy();
size1.focus();
await expect(size1).toHaveFocus();
await click();
await keydown("ArrowRight");
await expect(size2).toHaveFocus();
await expect(size2.checked).toBeTruthy();
await tab(true);
await expect(brand1).toHaveFocus();
}
}
Should Have Correct Tab Index
Story: components-radio-button-v1-interaction-tests--should-have-correct-tab-index · tags: play-fn
<form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>`;
},
async play({
canvasElement
}) {
const [brand1, brand2, brand3] = Array.from(canvasElement.getElementsByTagName(`oc-radio-button-v1`));
brand1.focus();
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(-1);
await click();
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(-1);
await keydown("ArrowRight");
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(-1);
await keydown("ArrowDown");
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(0);
await keydown("ArrowLeft");
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(-1);
await keydown("ArrowUp");
await expect(deepQuerySelector(brand1, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3, "input")!.tabIndex).toBe(-1);
}
}
Should Work With Two Forms
Story: components-radio-button-v1-interaction-tests--should-work-with-two-forms · tags: play-fn
<form id="form1" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>
<form id="form2" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung2">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG2">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple2">Apple</oc-radio-button-v1>
</form>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form id="form1" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</form>
<form id="form2" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung2">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG2">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple2">Apple</oc-radio-button-v1>
</form>`;
},
async play({
canvasElement
}) {
const [brand1Form1, brand2Form1, brand3Form1, brand1Form2, brand2Form2, brand3Form2] = Array.from(canvasElement.getElementsByTagName(`oc-radio-button-v1`));
await expect(deepQuerySelector(brand1Form1, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand2Form1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3Form1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand1Form2, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand2Form2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3Form2, "input")!.tabIndex).toBe(-1);
await expect(brand1Form1.checked).toBeFalsy();
await expect(brand2Form1.checked).toBeFalsy();
await expect(brand3Form1.checked).toBeFalsy();
await expect(brand1Form2.checked).toBeFalsy();
await expect(brand2Form2.checked).toBeFalsy();
await expect(brand3Form2.checked).toBeFalsy();
brand2Form1.focus();
await click();
brand3Form2.focus();
await click();
await expect(deepQuerySelector(brand1Form1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand2Form1, "input")!.tabIndex).toBe(0);
await expect(deepQuerySelector(brand3Form1, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand1Form2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand2Form2, "input")!.tabIndex).toBe(-1);
await expect(deepQuerySelector(brand3Form2, "input")!.tabIndex).toBe(0);
await expect(brand1Form1.checked).toBeFalsy();
await expect(brand2Form1.checked).toBeTruthy();
await expect(brand3Form1.checked).toBeFalsy();
await expect(brand1Form2.checked).toBeFalsy();
await expect(brand2Form2.checked).toBeFalsy();
await expect(brand3Form2.checked).toBeTruthy();
}
}
Should Work Without Forms
Story: components-radio-button-v1-interaction-tests--should-work-without-forms · tags: play-fn
<div id="non-form" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</div>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <div id="non-form" style="display: flex; gap: 8px; margin-bottom: 24px">
<oc-radio-button-v1 name="brand" value="Samsung1">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG1">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Apple1">Apple</oc-radio-button-v1>
</div>`;
},
async play({
canvasElement
}) {
const [brand1Form1, brand2Form1, brand3Form1] = Array.from(canvasElement.getElementsByTagName(`oc-radio-button-v1`));
await expect(brand1Form1.checked).toBeFalsy();
await expect(brand2Form1.checked).toBeFalsy();
await expect(brand3Form1.checked).toBeFalsy();
brand1Form1.focus();
await click(deepQuerySelector(brand1Form1, "input")!);
await expect(brand1Form1.checked).toBeTruthy();
await expect(brand2Form1.checked).toBeFalsy();
await expect(brand3Form1.checked).toBeFalsy();
await keydown("ArrowRight");
await expect(brand1Form1.checked).toBeFalsy();
await expect(brand2Form1.checked).toBeTruthy();
await expect(brand3Form1.checked).toBeFalsy();
await keydown("ArrowRight");
await expect(brand1Form1.checked).toBeFalsy();
await expect(brand2Form1.checked).toBeFalsy();
await expect(brand3Form1.checked).toBeTruthy();
await keydown("ArrowRight");
await expect(brand1Form1.checked).toBeTruthy();
await expect(brand2Form1.checked).toBeFalsy();
await expect(brand3Form1.checked).toBeFalsy();
}
}
Should Be Handle Adding Radios Dynamic
Story: components-radio-button-v1-interaction-tests--should-be-handle-adding-radios-dynamic · tags: play-fn
<form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung" checked>Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
</form>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
docs: {
disable: true
}
},
render() {
return html` <form id="form1" style="display: flex; gap: 8px;">
<oc-radio-button-v1 name="brand" value="Samsung" checked>Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
</form>`;
},
async play({
canvasElement
}) {
const [brand1, brand2, size1, size2] = Array.from(canvasElement.getElementsByTagName(`oc-radio-button-v1`));
await expect(brand1.checked).toBeTruthy();
await expect(brand2.checked).toBeFalsy();
await expect(size1.checked).toBeFalsy();
await expect(size2.checked).toBeFalsy();
await macrotasks(1);
const form = canvasElement.querySelector("form")!;
const brand3 = document.createElement("oc-radio-button-v1");
brand3.checked = true;
brand3.name = "brand";
brand3.value = "Apple";
brand3.textContent = "Apple";
brand2.insertAdjacentElement("afterend", brand3);
const size3 = document.createElement("oc-radio-button-v1");
size3.checked = true;
size3.name = "size";
size3.value = "L";
size3.textContent = "L";
form.append(size3);
await macrotasks(1);
await expect(brand1.checked, "Samsung should be unchecked").toBeFalsy();
await expect(brand2.checked, "LG should be unchecked").toBeFalsy();
await expect(brand3.checked, "Apple should be checked").toBeTruthy();
await expect(deepQuerySelector(brand1, "input")!.tabIndex, "Samsung should correct tabindex").toBe(-1);
await expect(deepQuerySelector(brand2, "input")!.tabIndex, "LG should have correct tabindex").toBe(-1);
await expect(deepQuerySelector(brand3, "input")!.tabIndex, "Apple should have correct tabindex").toBe(0);
await expect(size1.checked, "S should be unchecked").toBeFalsy();
await expect(size2.checked, "M should be unchecked").toBeFalsy();
await expect(size3.checked, "L should be checked").toBeTruthy();
await expect(deepQuerySelector(size1, "input")!.tabIndex, "S should correct tabindex").toBe(-1);
await expect(deepQuerySelector(size2, "input")!.tabIndex, "M should have correct tabindex").toBe(-1);
await expect(deepQuerySelector(size3, "input")!.tabIndex, "L should have correct tabindex").toBe(0);
}
}
Handle Default Value And Default Checked
Story: components-radio-button-v1-interaction-tests--handle-default-value-and-default-checked · tags: play-fn
<oc-radio-button-v1>no defaults set</oc-radio-button-v1>
<oc-radio-button-v1 value="test" checked>Test Label</oc-radio-button-v1>
Story source (TypeScript, verbatim from Storybook)
{
parameters: {
chromatic: {
disableSnapshot: true
}
},
render() {
return html`
<oc-radio-button-v1>no defaults set</oc-radio-button-v1>
<oc-radio-button-v1 value="test" checked>Test Label</oc-radio-button-v1>
`;
},
async play({
canvasElement
}) {
const [sutWithoutAttributes, sutWithAttributes] = Array.from(canvasElement.getElementsByTagName("oc-radio-button-v1")) as (HTMLOcRadioButtonV1Element & {
defaultChecked: boolean;
defaultValue: string;
})[];
// TEST sutWithoutAttributes
await expect(sutWithoutAttributes.checked).toBeFalsy();
await expect(sutWithoutAttributes.defaultChecked).toBeFalsy();
await expect(sutWithoutAttributes.hasAttribute("checked")).toBeFalsy();
await expect(sutWithoutAttributes.value).toBe("on");
await expect(sutWithoutAttributes.defaultValue).toBe("");
await expect(sutWithoutAttributes.hasAttribute("value")).toBeFalsy();
sutWithoutAttributes.checked = true;
sutWithoutAttributes.value = "new on";
await expect(sutWithoutAttributes.checked).toBeTruthy();
await expect(sutWithoutAttributes.defaultChecked).toBeFalsy();
await expect(sutWithoutAttributes.hasAttribute("checked")).toBeFalsy();
await expect(sutWithoutAttributes.value).toBe("new on");
// await expect(sutWithoutAttributes.defaultValue).toBe("new on");
// await expect(sutWithoutAttributes.getAttribute("value")).toBe("new on");
// TEST sutWithAttributes
await expect(sutWithAttributes.checked).toBeTruthy();
await expect(sutWithAttributes.defaultChecked).toBeTruthy();
await expect(sutWithAttributes.hasAttribute("checked")).toBeTruthy();
await expect(sutWithAttributes.value).toBe("test");
await expect(sutWithAttributes.defaultValue).toBe("test");
await expect(sutWithAttributes.getAttribute("value")).toBe("test");
sutWithAttributes.checked = false;
sutWithAttributes.value = "new test";
await expect(sutWithAttributes.checked).toBeFalsy();
await expect(sutWithAttributes.defaultChecked).toBeTruthy();
await expect(sutWithAttributes.hasAttribute("checked")).toBeTruthy();
await expect(sutWithAttributes.value).toBe("new test");
// await expect(sutWithAttributes.defaultValue).toBe("new test");
// await expect(sutWithAttributes.getAttribute("value")).toBe("new test");
// TEST programmatic created
const sutCreated = document.createElement("oc-radio-button-v1") as HTMLOcRadioButtonV1Element & {
defaultChecked: boolean;
defaultValue: string;
};
sutCreated.innerText = "created";
sutCreated.checked = true;
canvasElement.append(sutCreated);
await expect(sutCreated.checked).toBeTruthy();
await expect(sutCreated.defaultChecked).toBeFalsy();
await expect(sutCreated.hasAttribute("checked")).toBeFalsy();
await expect(sutCreated.value).toBe("on");
await expect(sutCreated.defaultValue).toBe("");
await expect(sutCreated.hasAttribute("value")).toBeFalsy();
sutCreated.defaultChecked = true;
sutCreated.defaultValue = "foo value";
await expect(sutCreated.checked).toBeTruthy();
await expect(sutCreated.defaultChecked).toBeTruthy();
await expect(sutCreated.hasAttribute("checked")).toBeTruthy();
await expect(sutCreated.value).toBe("foo value");
await expect(sutCreated.defaultValue).toBe("foo value");
await expect(sutCreated.getAttribute("value")).toBe("foo value");
}
}
V1/Variations (v1, deprecated, not for generation)
Variations
Listed below are the most common variations of the radio button component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
Default
Story: components-radio-button-v1-variations--default · tags: components
The default configuration uses the default variant with the default value "on".
<oc-radio-button-v1 name="myName" label-placement="right" value="on" variant="default">my label</oc-radio-button-v1>
Label placement: left
Story: components-radio-button-v1-variations--label-placement-left · tags: components
The default configuration uses the default variant with the default value "on" as well as the label placed on the left hand side.
Args: label-placement=left
<oc-radio-button-v1 name="myName" label-placement="left" value="on" variant="default">my label</oc-radio-button-v1>
Error
Story: components-radio-button-v1-variations--error · tags: components
The error variant uses a red color to indicate an error state.
Args: variant=error
<oc-radio-button-v1 name="myName" label-placement="right" value="on" variant="error">my label</oc-radio-button-v1>
Disabled
Story: components-radio-button-v1-variations--disabled · tags: components
The default variant with the disabled attribute set to true, preventing user input.
Args: disabled=true
<oc-radio-button-v1 name="myName" label-placement="right" value="on" variant="default" disabled>
my label
</oc-radio-button-v1>
Demo: form
Story: components-radio-button-v1-variations--demo-form · tags: components
The demo form showcases a form using multiple Radio Button groups, with a reset and a submit button.
<form novalidate lang="de">
<fieldset class="oc-mb-150">
<legend>Marke*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Philips">Philips</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Sony">Sony</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Hisense">Hisense</oc-radio-button-v1>
</oc-form-group-v1>
</fieldset>
<fieldset class="oc-mb-150">
<legend>Größe*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="L">L</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="XL">XL</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="XXL">XXL</oc-radio-button-v1>
</oc-form-group-v1>
</fieldset>
<fieldset class="oc-mb-150">
<legend>Lorem*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="lorem" value="1"
>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
takimata sanctus est Lorem ipsum dolor sit amet.</oc-radio-button-v1
>
<oc-radio-button-v1 name="lorem" value="2"
>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua.</oc-radio-button-v1
>
<oc-radio-button-v1 name="lorem" value="3"
>Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua.</oc-radio-button-v1
>
</oc-form-group-v1>
</fieldset>
<div style="display: flex; gap: 16px">
<oc-button-v1 variant="secondary" type="reset">Reset</oc-button-v1>
<oc-button-v1 type="submit">Submit</oc-button-v1>
</div>
</form>
<script>
(() => {
const [form] = document.getElementsByTagName("form");
const [group1, group2, group3] = document.getElementsByTagName("oc-form-group-v1");
form.addEventListener("change", async () => {
// wait for the next microtask to get the updated checked state
await new Promise((resolve) => setTimeout(resolve));
let formData = new FormData(form);
if (formData.has("brand")) group1.validationMessage = "";
if (formData.has("size")) group2.validationMessage = "";
if (formData.has("lorem")) group3.validationMessage = "";
});
form.addEventListener("submit", (ev) => {
let formData = new FormData(form);
group1.validationMessage = formData.has("brand") ? "" : "Please select a brand.";
group2.validationMessage = formData.has("size") ? "" : "Please select a size.";
group3.validationMessage = formData.has("lorem") ? "" : "Please select a lorem.";
if (formData.has("brand") && formData.has("size") && formData.has("lorem")) {
let data = "";
formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
console.log(data);
alert("sending:\\n" + data);
}
ev.preventDefault();
});
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: form",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<form novalidate lang="de">
<fieldset class="oc-mb-150">
<legend>Marke*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="brand" value="Samsung">Samsung</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="LG">LG</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Philips">Philips</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Sony">Sony</oc-radio-button-v1>
<oc-radio-button-v1 name="brand" value="Hisense">Hisense</oc-radio-button-v1>
</oc-form-group-v1>
</fieldset>
<fieldset class="oc-mb-150">
<legend>Größe*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="size" value="S">S</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="M">M</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="L">L</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="XL">XL</oc-radio-button-v1>
<oc-radio-button-v1 name="size" value="XXL">XXL</oc-radio-button-v1>
</oc-form-group-v1>
</fieldset>
<fieldset class="oc-mb-150">
<legend>Lorem*</legend>
<oc-form-group-v1 orientation="horizontal" flex-behavior="shrink">
<oc-radio-button-v1 name="lorem" value="1"
>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
takimata sanctus est Lorem ipsum dolor sit amet.</oc-radio-button-v1
>
<oc-radio-button-v1 name="lorem" value="2"
>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua.</oc-radio-button-v1
>
<oc-radio-button-v1 name="lorem" value="3"
>Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua.</oc-radio-button-v1
>
</oc-form-group-v1>
</fieldset>
<div style="display: flex; gap: 16px">
<oc-button-v1 variant="secondary" type="reset">Reset</oc-button-v1>
<oc-button-v1 type="submit">Submit</oc-button-v1>
</div>
</form>
<script>
(() => {
const [form] = document.getElementsByTagName("form");
const [group1, group2, group3] = document.getElementsByTagName("oc-form-group-v1");
form.addEventListener("change", async () => {
// wait for the next microtask to get the updated checked state
await new Promise((resolve) => setTimeout(resolve));
let formData = new FormData(form);
if (formData.has("brand")) group1.validationMessage = "";
if (formData.has("size")) group2.validationMessage = "";
if (formData.has("lorem")) group3.validationMessage = "";
});
form.addEventListener("submit", (ev) => {
let formData = new FormData(form);
group1.validationMessage = formData.has("brand") ? "" : "Please select a brand.";
group2.validationMessage = formData.has("size") ? "" : "Please select a size.";
group3.validationMessage = formData.has("lorem") ? "" : "Please select a lorem.";
if (formData.has("brand") && formData.has("size") && formData.has("lorem")) {
let data = "";
formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
console.log(data);
alert("sending:\\n" + data);
}
ev.preventDefault();
});
})();
</script>
`;
}
}
Demo: filter
Story: components-radio-button-v1-variations--demo-filter · tags: components
The demo filter showcases how to use Radio Buttons for filtering.
<oc-form-group-v1 orientation="vertical">
<oc-radio-button-v1
name="zielgruppe"
value="Damen"
oc-aria-label="Damen 374637 Treffer"
class="flex-label"
>Damen<span class="number">374637</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Herren"
oc-aria-label="Herren 234324 Treffer"
class="flex-label"
>Herren<span class="number">234324</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Mädchen"
oc-aria-label="Mädchen 2323 Treffer"
class="flex-label"
>Mädchen<span class="number">2323</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Jungen"
oc-aria-label="Jungen 1224 Treffer"
class="flex-label"
>Jungen<span class="number">1224</span>
</oc-radio-button-v1>
</oc-form-group-v1>
<oc-form-group-v1 class="oc-mt-200" orientation="vertical">
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Damen"
oc-aria-label="Damen 374637 Treffer"
class="flex-label"
>Damen<span class="number">374637</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Herren"
oc-aria-label="Herren 234324 Treffer"
class="flex-label"
>Herren<span class="number">234324</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Mädchen"
oc-aria-label="Mädchen 2323 Treffer"
class="flex-label"
>Mädchen<span class="number">2323</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Jungen"
oc-aria-label="Jungen 1224 Treffer"
class="flex-label"
>Jungen<span class="number">1224</span>
</oc-radio-button-v1>
</oc-form-group-v1>
<style>
.flex-label::part(default) {
display: flex;
justify-content: space-between;
}
.number {
color: var(--oc-base-color-gray-200);
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: filter",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<oc-form-group-v1 orientation="vertical">
<oc-radio-button-v1
name="zielgruppe"
value="Damen"
oc-aria-label="Damen 374637 Treffer"
class="flex-label"
>Damen<span class="number">374637</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Herren"
oc-aria-label="Herren 234324 Treffer"
class="flex-label"
>Herren<span class="number">234324</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Mädchen"
oc-aria-label="Mädchen 2323 Treffer"
class="flex-label"
>Mädchen<span class="number">2323</span>
</oc-radio-button-v1>
<oc-radio-button-v1
name="zielgruppe"
value="Jungen"
oc-aria-label="Jungen 1224 Treffer"
class="flex-label"
>Jungen<span class="number">1224</span>
</oc-radio-button-v1>
</oc-form-group-v1>
<oc-form-group-v1 class="oc-mt-200" orientation="vertical">
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Damen"
oc-aria-label="Damen 374637 Treffer"
class="flex-label"
>Damen<span class="number">374637</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Herren"
oc-aria-label="Herren 234324 Treffer"
class="flex-label"
>Herren<span class="number">234324</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Mädchen"
oc-aria-label="Mädchen 2323 Treffer"
class="flex-label"
>Mädchen<span class="number">2323</span>
</oc-radio-button-v1>
<oc-radio-button-v1
label-placement="left"
name="zielgruppe"
value="Jungen"
oc-aria-label="Jungen 1224 Treffer"
class="flex-label"
>Jungen<span class="number">1224</span>
</oc-radio-button-v1>
</oc-form-group-v1>
<style>
.flex-label::part(default) {
display: flex;
justify-content: space-between;
}
.number {
color: var(--oc-base-color-gray-200);
}
</style>
`;
}
}