OTTODesign System

ComponentsForm

Radio button

The radio button allows users to select exactly one option from a set of options.

Configurator

LiveRadio button: size, width, label position, texts and state
HTML
<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

1 2
  1. Radio button
  2. Label
LiveAnatomy
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

Bitte wähle eine Versandart aus.

Default, size 50

Error, size 50

Bitte wähle eine Versandart aus.
Livedefault and error in size 100 and 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

Livestates
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

Livefitting
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

Livelabel placement
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

Liveplacement
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

Livesingle selection and multiple selection
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

CautionAvoid using horizontal Radio Button lists
Don'tNest Radio Buttons

Fünf oder weniger Optionen:

DoUse Radio Buttons when there are five or fewer options.

Mehr als fünf Optionen:

CautionIf there a more than 5 options consider using a drop-down menu instead of radio buttons.
DoUse the actual label of the radio button.

This is the only way to ensure consistent behavior and layout.

LiveDo
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>
Expressversand
Don'tUse text as a label. The spacing may be off, and proper user interaction (i.e., a hitbox spanning the entire element and interactive states) cannot be ensured.

Content Guidelines

DoUse short, precise labels to describe the options.
Don'tUse overly descriptive labels to describe the options.

Accessibility

For information on accessibility, refer to the technical documentation.

Status

Implementation

Live demo

Schritt 2 von 3

Wie möchtest du deine Lieferung?

Lieferung Do., 02.10. bis Sa., 04.10. Bestellst du bis 12 Uhr, kommt dein Paket morgen. Du wählst den Tag, wir liefern.

Gesamtsumme

249,98 €

Weiter zur Zahlung
LiveReal OTTO components, rendered by the OTTO component runtime
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:

  • click
  • input
  • change
  • focus
  • blur
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-selection

For 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" />

This is an error message.
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 Enter while focused on the radio button triggers the submit action immediately (implicit submit) if an oc-button component or a normal HTML button with the type submit is 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>
    `;
  }
}