Radio Group
Group of checkable buttons that only allow one to be checked at a time.
Controls
<script lang="ts"> import { scale } from "svelte/transition"; import { Radio } from "@niklasburggraaff/svelte-runia";
let { value = $bindable(null), disabled = false, loop = true }: any = $props();</script>
<Radio.Group bind:value {disabled} {loop} aria-label="Select rune"> <div class="flex items-center gap-4"> <Radio.Button value="state" id="state" aria-labelledby="state-label" class="h-4 w-4 rounded-full border bg-[var(--sl-color-bg)] p-1 hover:bg-[var(--sl-color-gray-5)] disabled:opacity-50"> {#snippet indicator()} <div transition:scale class="h-full w-full rounded-full bg-orange-500"></div> {/snippet} </Radio.Button> <label id="state-label" for="state" class="text-xl"> <span class="font-runic text-runic-gradient font-bold">ᛋ</span> - sun (<code class="text-sm">$state</code >) </label> </div> <div class="flex items-center gap-4"> <Radio.Button value="derived" id="derived" aria-labelledby="derived-label" class="h-4 w-4 rounded-full border bg-[var(--sl-color-bg)] p-1 hover:bg-[var(--sl-color-gray-5)] disabled:opacity-50"> {#snippet indicator()} <div transition:scale class="h-full w-full rounded-full bg-orange-500"></div> {/snippet} </Radio.Button> <label id="derived-label" for="derived" class="text-xl"> <span class="font-runic text-runic-gradient font-bold">ᛞ</span> - day (<code class="text-sm">$derived</code >) </label> </div> <div class="flex items-center gap-4"> <Radio.Button value="effect" id="effect" aria-labelledby="effect-label" class="h-4 w-4 rounded-full border bg-[var(--sl-color-bg)] p-1 hover:bg-[var(--sl-color-gray-5)] disabled:opacity-50"> {#snippet indicator()} <div transition:scale class="h-full w-full rounded-full bg-orange-500"></div> {/snippet} </Radio.Button> <label id="effect-label" for="effect" class="text-xl"> <span class="font-runic text-runic-gradient font-bold">ᚷ</span> - gift (<code class="text-sm">$effect</code >) </label> </div> <div class="flex items-center gap-4"> <Radio.Button value="props" id="props" aria-labelledby="props-label" class="h-4 w-4 rounded-full border bg-[var(--sl-color-bg)] p-1 hover:bg-[var(--sl-color-gray-5)] disabled:opacity-50"> {#snippet indicator()} <div transition:scale class="h-full w-full rounded-full bg-orange-500"></div> {/snippet} </Radio.Button> <label id="props-label" for="props" class="text-xl"> <span class="font-runic text-runic-gradient font-bold">ᚠ</span> - cattle; wealth (<code class="text-sm">$props</code >) </label> </div></Radio.Group>Features
- Full keyboard navigation.
- Adds
inputelements to support forms. - Supports animating indicators.
- Supports horizontal/vertical orientation.
Usage
Importing Radio gives you access to the Group and Button components.
<script lang="ts"> import { Radio } from "@niklasburggraaff/svelte-runia";</script>
<Radio.Group aria-label="..."> <Radio.Button value="..." aria-label="..."> {#snippet indicator()} ... {/snippet} </Radio.Button></Radio.Group>- The
Groupcan contain multipleItem’s and must be passed aaria-label, oraria-labelledbyprop - Each
Itemmust be passed a uniquevalueprop and alabelledByContent,aria-label, oraria-labelledbyprop - Each
Itemcan be passed aindicatorsnippet- The
indicatorsnippet is rendered when theItemis checked.
- The
API
Group
The Group component contains the radio buttons. It manages the state of the radio group.
Props
name:string- The name of the radio group. Used as the name for the value in a form.
value:string | null = null(bindable)- The value of the checked item,
nullif no item is checked.
- The value of the checked item,
aria-label:string(2)- Description of what the group is a selection for.
aria-labelledby:string(1)- ID of element that describes what the group is a selection for.
required:boolean = false- When
true, indicates a radio button must be checked.
- When
disabled:boolean = false- When
true, prevents the user from interacting with the radio group.
- When
loop:boolean = true- When
true, keyboard navigation will loop focus from the last to the firstItemand vice versa.
- When
orientation:"vertical" | "horizontal" | undefined = undefined- The orientation of the radio group, which determines which arrow keys are used for keyboard navigation.
direction:"ltr" | rtl" = "ltr"- The reading direction of the radio group, used to correct keyboard navigation.
element:HTMLDivElement(bindable)- The rendered
divelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | The name of the radio group. Used as the name for the value in a form. |
value | string | null (bindable) | null | The value of the checked item, null if no item is checked. |
aria-label
(1)
| string | - | Description of what the group is a selection for. |
aria-labelledby
(1)
| string | - | ID of element that describes what the group is a selection for. |
required | boolean | false | When true, indicates a radio button must be checked. |
disabled | boolean | false | When true, prevents the user from interacting with the radio group. |
loop | boolean | true | When true, keyboard navigation will loop focus from the last to the first Item and vice versa. |
orientation | "vertical" | "horizontal" | undefined | undefined | The orientation of the radio group, which determines which arrow keys are used for keyboard navigation. |
direction | "ltr" | rtl" | 'ltr' | The reading direction of the radio group, used to correct keyboard navigation. |
element | HTMLDivElement (bindable) | - | The rendered div element. |
(1)- One of
aria-label aria-labelledby
is required.
Data attributes
data-disabled- Present when the accordion is disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the accordion. Not present when
orientation = undefined.
- The orientation of the accordion. Not present when
| Attribute | Values | Description |
|---|---|---|
[data-disabled]
| - | Present when the accordion is disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the accordion. Not present when orientation = undefined. |
Button
The Button component is an item that can be checked.
Then the indicator snippet is rendered inside the button when it is checked.
The rendered button and indicator can be fully styled.
The Button component also renders a hidden input element with type="radio".
This is to make the component work in forms.
The following is a pseudocode representing the implementation of the Button component.
<script lang="ts"> let checked = $...;</script>
<button> {#if expanded} {@render indicator(checked)} {/if}</button>
<input type="radio" aria-hidden={true} style={"opacity: 0"} />Props
value:string*(bindable)- The value used to identify the item.
indicator:Snippet<[boolean]>- When
true, the item is asectionelement. Avoid using with many (~6) items to prevent landmark region proliferation. This is especially helpful when content contain headings or a nested accordion.
- When
labelledByContent:true(2)- Indicates the button contains text describing what the button is an option for.
aria-label:string(1)- Description of what the button is an option for.
aria-labelledby:string(1)- ID of element that describes what the button is an option for.
disabled:boolean = false- When
true, prevents the user from interacting with the item.
- When
element:HTMLDivElement | HTMLElement(bindable)- The rendered
divorsectionelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
value * | string (bindable) | - | The value used to identify the item. |
indicator | Snippet<[boolean]> | - | When true, the item is a section element. Avoid using with many (~6) items to prevent landmark region proliferation. This is especially helpful when content contain headings or a nested accordion. |
labelledByContent
(1)
| true | - | Indicates the button contains text describing what the button is an option for. |
aria-label
(1)
| string | - | Description of what the button is an option for. |
aria-labelledby
(1)
| string | - | ID of element that describes what the button is an option for. |
disabled | boolean | false | When true, prevents the user from interacting with the item. |
element | HTMLDivElement | HTMLElement (bindable) | - | The rendered div or section element. |
(1)- One of
labelledByContent aria-label aria-labelledby
is required.
Data attributes
data-state: "checked" | "unchecked"- The state of the radio button.
"checked"when the item is checked and"unchecked"when the item is not checked.
- The state of the radio button.
data-disabled- Present when the radio button is disabled. This can be due to the whole radio group or the item being disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the radio group. Not present when
orientation = undefined.
- The orientation of the radio group. Not present when
data-svelte-runia-radio-button- Present on all
Button's. Used byGroupto find them.
- Present on all
data-svelte-runia-value: string- The value identifying the item.
| Attribute | Values | Description |
|---|---|---|
[data-state]
| "checked" | "unchecked" | The state of the radio button. "checked" when the item is checked and "unchecked" when the item is not checked. |
[data-disabled]
| - | Present when the radio button is disabled. This can be due to the whole radio group or the item being disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the radio group. Not present when orientation = undefined. |
[data-svelte-runia-radio-button]
| - | Present on all Button's. Used by Group to find them. |
[data-svelte-runia-value]
| string | The value identifying the item. |
Accessibility
Adheres to the WAI-ARIA Radio Group Pattern.
Keyboard interactions
- Enter/Space
:
Attempts to select the item.
- Tab/ShiftTab
:
Move focus to the next/previous focussable element.
- Arrow Down/Arrow Up
:
Move focus to the next/previous
Button. Ifloop = true, loops focus from the last to the firstButtonand vice versa. - Arrow Right/Arrow Left
:
Move focus to the
Buttonto the right/left (depending ondirection). Ifloop = true, loops focus from the last to the firstButtonand vice versa.
| Keys | Condition | Action |
|---|---|---|
| Enter / Space | If focus on a Button | Attempts to select the item. |
| Tab / ShiftTab | - | Move focus to the next/previous focussable element. |
| Arrow Down / Arrow Up | If focus on a Button (and orientation = undefined or orientation = "vertical") | Move focus to the next/previous Button. If loop = true, loops focus from the last to the first Button and vice versa. |
| Arrow Right / Arrow Left | If focus on a Button (and orientation = undefined or orientation = "horizontal") | Move focus to the Button to the right/left (depending on direction). If loop = true, loops focus from the last to the first Button and vice versa. |
Other events
Button. onclick:Attempts to select the item.
| Component | Event | Action |
|---|---|---|
Button | onclick | Attempts to select the item. |
ARIA attributes
Group
role: string- Set to
radiogroup.
- Set to
aria-required: boolean- Set based on the
requiredprop.
- Set based on the
| Attribute | Type | Description |
|---|---|---|
role | string | Set to radiogroup. |
aria-required | boolean | Set based on the required prop. |
Button
role: string- Set to
radio.
- Set to
aria-checked: "true" | "false""true"when the item is checked.
aria-label: string- Description of what the group is a selection for.
aria-labelledby: string- ID of element that describes what the group is a selection for.
| Attribute | Type | Description |
|---|---|---|
role | string | Set to radio. |
aria-checked | "true" | "false" | "true" when the item is checked. |
aria-label | string | Description of what the group is a selection for. |
aria-labelledby | string | ID of element that describes what the group is a selection for. |
Button input element
aria-hidden: boolean- Set to
truewhen theinputelement is hidden.
- Set to
aria-label: string- Description of what the button is an option for.
aria-labelledby: string- ID of element that describes what the button is an option for.
| Attribute | Type | Description |
|---|---|---|
aria-hidden | boolean | Set to true when the input element is hidden. |
aria-label | string | Description of what the button is an option for. |
aria-labelledby | string | ID of element that describes what the button is an option for. |
Examples
Check item by default
Use the value prop to set the initial item to be checked.
<Radio.Group value="state"> <Radio.Button value="state">...</Radio.Button> <Radio.Button value="derived">...</Radio.Button> <Radio.Button value="effect">...</Radio.Button> <Radio.Button value="props">...</Radio.Button></Radio.Group>Control the checked item
Bind the value prop of the Root component to a variable to control the checked item.
This allows both reading and writing the value of the checked item.
<script> import { Radio } from "@niklasburggraaff/svelte-runia"; let value = $state(null);</script>
<input bind:value />
<Radio.Group bind:value> <Radio.Button value="state">...</Radio.Button> <Radio.Button value="derived">...</Radio.Button> <Radio.Button value="effect">...</Radio.Button> <Radio.Button value="props">...</Radio.Button></Radio.Group>Add label
Set the aria-label prop to the description of what the toggle does when pressed.
<Radio.Group aria-label="Select rune"> <Radio.Button aria-label="state" value="state"> ... </Radio.Button> <Radio.Button aria-label="derived" value="derived"> ... </Radio.Button> <Radio.Button aria-label="effect" value="effect"> ... </Radio.Button> <Radio.Button aria-label="props" value="props"> ... </Radio.Button></Radio.Group>ᛋ - "sun" ($state)
ᛞ - "day" ($derived)
ᚠ - "cattle; wealth" ($props)
Label by element
Set the aria-labelledby prop to the id of an element that describes what the toggle does when pressed.
This is useful when using label elements especially as interactions are passed to the Button.
Remember to set the for prop of the input to the id of the Radio.Button.
<Radio.Group id="select-rune" aria-labelledby="select-rune-label"> <label id="select-rune-label" for="select-rune"> Select rune: </label> <Radio.Button aria-labelledby="state-label" id="state" value="state"> ... </Radio.Button> <label id="state-label" for="state" class="text-md"> State </label> <Radio.Button aria-labelledby="derived-label" id="derived" value="derived"> ... </Radio.Button> <label id="derived-label" for="derived" class="text-md"> Derived </label> <Radio.Button aria-labelledby="effect-label" id="effect" value="effect"> ... </Radio.Button> <label id="effect-label" for="effect" class="text-md"> Effect </label> <Radio.Button aria-labelledby="props-label" id="props" value="props"> ... </Radio.Button> <label id="props-label" for="props" class="text-md"> Props </label></Radio.Group>Label by content
Set the labelledByContent prop to true to indicate the toggle contains text describing what the toggle does when pressed.
<Radio.Group aria-label="Select rune"> <Radio.Button labelledByContent value="state"> Select ... </Radio.Button> <Radio.Button labelledByContent value="derived"> Select ... </Radio.Button> <Radio.Button labelledByContent value="effect"> Select ... </Radio.Button> <Radio.Button labelledByContent value="props"> Select ... </Radio.Button></Radio.Group>