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
input
elements 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
Group
can contain multipleItem
’s and must be passed aaria-label
, oraria-labelledby
prop - Each
Item
must be passed a uniquevalue
prop and alabelledByContent
,aria-label
, oraria-labelledby
prop - Each
Item
can be passed aindicator
snippet- The
indicator
snippet is rendered when theItem
is 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,
null
if 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 firstItem
and 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
div
element.
- 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 asection
element. 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
div
orsection
element.
- 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 byGroup
to 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 firstButton
and vice versa. - Arrow Right/Arrow Left
:
Move focus to the
Button
to the right/left (depending ondirection
). Ifloop = true
, loops focus from the last to the firstButton
and 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
required
prop.
- 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
true
when theinput
element 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>