ShardsUI is in beta. APIs may change before 1.0.

Radio

One choice from a set of options.

Difficulty
<script lang="ts">
  import { Radio } from '@shardsui/svelte/radio'
  import { RadioGroup } from '@shardsui/svelte/radio-group'

  const id = $props.id()

  const levels = [
    { value: 'beginner', label: 'Beginner' },
    { value: 'intermediate', label: 'Intermediate' },
    { value: 'advanced', label: 'Advanced' }
  ]
</script>

<RadioGroup
  value="intermediate"
  class="flex flex-col items-start gap-1 text-gray-900"
  aria-labelledby={id}
>
  <div class="text-sm font-semibold" {id}>Difficulty</div>

  {#each levels as level (level.value)}
    <label class="flex items-center gap-2 text-sm font-normal">
      <Radio.Root
        value={level.value}
        class="flex size-4 shrink-0 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-950 data-checked:bg-gray-900 data-unchecked:border data-unchecked:border-gray-300"
      >
        <Radio.Indicator class="flex before:size-2 before:rounded-full before:bg-gray-50" />
      </Radio.Root>
      {level.label}
    </label>
  {/each}
</RadioGroup>

Anatomy

A Radio only works inside a Radio Group. Import both and nest the radios inside the group:

<script>
  import { Radio } from '@shardsui/svelte/radio'
  import { RadioGroup } from '@shardsui/svelte/radio-group'
</script>

<RadioGroup>
  <Radio.Root value="...">
    <Radio.Indicator />
  </Radio.Root>
</RadioGroup>

Usage guidelines

  • Form controls must have an accessible name: name the group and each radio with <label> elements, or with the Field and Fieldset components. See Labeling a radio group.

Examples

Labeling a radio group

Point the group at a sibling label with aria-labelledby:

<div id="storage-type-label">Storage type</div>
<RadioGroup aria-labelledby="storage-type-label">
  <!-- radios -->
</RadioGroup>

For each radio, an enclosing <label> takes the least markup. Radio.Root renders a <span> by default, so the label toggles the hidden <input type="radio"> natively:

<label>
  <Radio.Root value="ssd">
    <Radio.Indicator />
  </Radio.Root>
  SSD
</label>

Rendering as a native button

When each radio has its own label tied to it with for/id, render it as a native button with as="button". The id then lands on the button rather than the hidden input, so the label points at the focusable element:

<div id="storage-type">Storage type</div>
<RadioGroup value="ssd" aria-labelledby="storage-type">
  <div>
    <label for="storage-type-ssd">SSD</label>
    <Radio.Root value="ssd" id="storage-type-ssd" as="button">
      <Radio.Indicator />
    </Radio.Root>
  </div>
</RadioGroup>

Form integration

Field.Root names the group and wires it into the form; Fieldset.Legend labels it. Give each radio its own Field.Item, which scopes the enclosed Field.Label to that radio alone:

<Form>
  <Field.Root name="storageType">
    <Fieldset.Root>
      <Fieldset.Legend>Storage type</Fieldset.Legend>
      <RadioGroup>
        <Field.Item>
          <Field.Label>
            <Radio.Root value="ssd"><Radio.Indicator /></Radio.Root>
            SSD
          </Field.Label>
        </Field.Item>
        <Field.Item>
          <Field.Label>
            <Radio.Root value="hdd"><Radio.Indicator /></Radio.Root>
            HDD
          </Field.Label>
        </Field.Item>
      </RadioGroup>
    </Fieldset.Root>
  </Field.Root>
</Form>

API reference

RadioGroup

Provides a shared state to a series of radio buttons. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-disabledPresent when the group is disabled.
data-readonlyPresent when the group is read-only.
data-requiredPresent when the group is required.
data-validPresent when the field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-filledPresent when the group has a selection (when wrapped in Field.Root).
data-focusedPresent when focus is inside the group (when wrapped in Field.Root).

Root

Represents the radio button itself. Renders a <span> element and a hidden <input> beside.

PropTypeDefault
AttributeDescription
data-checkedPresent when selected.
data-uncheckedPresent when not selected.
data-disabledPresent when disabled.
data-readonlyPresent when read-only.
data-requiredPresent when required.
data-validPresent when the field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-filledPresent when the group has a selection (when wrapped in Field.Root).
data-focusedPresent when focus is inside the group (when wrapped in Field.Root).

Indicator

Indicates whether the radio button is selected. Renders a <span> element.

PropTypeDefault

Inherits the same data attributes as Root, plus:

AttributeDescription
data-starting-stylePresent when the indicator is animating in.
data-ending-stylePresent when the indicator is animating out.