Accordion
Set of interactive headings that allow you to expand/collapse the associated content.
Controls
<script lang="ts"> import { slide } from "svelte/transition"; import { Accordion } from "@niklasburggraaff/svelte-runia";
let { type, value = $bindable(), values = $bindable(), collapsible, disabled, loop }: any = $props();</script>
{#snippet chevronDown()} <svg class="size-8 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/> </svg>{/snippet}
<Accordion.Root type={type} bind:value={value} bind:values={values} collapsible={collapsible} disabled={disabled} loop={loop}> <Accordion.Item value="furthark-essentials" class="border-b"> <Accordion.Header level={3}> <Accordion.Trigger class="flex w-full items-center justify-between bg-transparent py-2 text-xl [&[data-state=open]>svg]:rotate-180"> Furthark Essentials {@render chevronDown()} </Accordion.Trigger> </Accordion.Header> <Accordion.Content> <div class="px-1.5 pb-4" transition:slide={{ duration: 200 }}> The Elder Futhark, used from the 2nd to 8th centuries, comprised 24 runic characters, each bearing significance in magic, divination, and communication. </div> </Accordion.Content> </Accordion.Item> <Accordion.Item value="norse-myths" class="border-b"> <Accordion.Header level={3}> <Accordion.Trigger class="flex w-full items-center justify-between bg-transparent py-2 text-xl [&[data-state=open]>svg]:rotate-180"> Norse Myths 101 {@render chevronDown()} </Accordion.Trigger> </Accordion.Header> <Accordion.Content> <div class="px-1.5 pb-4" transition:slide={{ duration: 200 }}> Norse mythology, a tapestry of gods, giants, and heroes, offers insights into ancient Scandinavian beliefs, from the creation of the cosmos to the battles of Ragnarök. </div> </Accordion.Content> </Accordion.Item> <Accordion.Item value="viking-explorers" class="border-b"> <Accordion.Header level={3}> <Accordion.Trigger class="flex w-full items-center justify-between bg-transparent py-2 text-xl [&[data-state=open]>svg]:rotate-180"> Viking Explorers {@render chevronDown()} </Accordion.Trigger> </Accordion.Header> <Accordion.Content> <div class="px-1.5 pb-4" transition:slide={{ duration: 200 }}> Vikings, from the 8th to 11th centuries, sailed seas, established trade routes, and left their mark on history through exploration and expansion. </div> </Accordion.Content> </Accordion.Item></Accordion.Root>Features
- Full keyboard navigation.
- Supports animating content.
- Can expand one or multiple items
- Supports horizontal/vertical orientation.
- Supports Right to Left direction.
Usage
Importing Accordion gives you access to the Root, Item, Header, Trigger, and Content components.
<script lang="ts"> import { Accordion } from "@niklasburggraaff/svelte-runia";</script>
<Accordion.Root type="single"> <Accordion.Item value="..."> <Accordion.Header level={3}> <Accordion.Trigger>[Heading]</Accordion.Trigger> </Accordion.Header> <Accordion.Content>[Content]</Accordion.Content> </Accordion.Item></Accordion.Root>- The
Rootcan contain multipleItem’s and must be passed atypeprop - Each
Itemmust contain aHeaderandContentand must be passed a uniquevalueprop - A
Headermust contain aTriggerand is must be passed alevelprop
API
Root
The Root component contains all other components of an accordion. It manages the state of the accordion.
Props
type:"single" | "multiple"*- Determines if a single or multiple items can have expanded content.
value:string | null = null(bindable)- If
type = "single": The value of the expanded item,nullif the accordion is collapsed.
- If
values:string[] = [](bindable)- If
type = "multiple": The values of the expanded items.
- If
collapsible:boolean = false- If
type = "single": Whentrue, prevents expanded content from being collapsed.
- If
disabled:boolean = false- When
true, prevents the user from interacting with the accordion.
- When
loop:boolean = true- When
true, keyboard navigation will loop focus from the last to the firstTriggerand vice versa.
- When
orientation:"vertical" | "horizontal" = "vertical"- The orientation of the accordion, which determines which arrow keys are used for keyboard navigation.
direction:"ltr" | rtl" = "ltr"- If
orientation = "horizontal": The reading direction of the accordion, used to correct keyboard navigation.
- If
element:HTMLDivElement(bindable)- The rendered
divelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
type * | "single" | "multiple" | - | Determines if a single or multiple items can have expanded content. |
value | string | null (bindable) | null | If type = "single": The value of the expanded item, null if the accordion is collapsed. |
values | string[] (bindable) | [] | If type = "multiple": The values of the expanded items. |
collapsible | boolean | false | If type = "single": When true, prevents expanded content from being collapsed. |
disabled | boolean | false | When true, prevents the user from interacting with the accordion. |
loop | boolean | true | When true, keyboard navigation will loop focus from the last to the first Trigger and vice versa. |
orientation | "vertical" | "horizontal" | 'vertical' | The orientation of the accordion, which determines which arrow keys are used for keyboard navigation. |
direction | "ltr" | rtl" | 'ltr' | If orientation = "horizontal": The reading direction of the accordion, used to correct keyboard navigation. |
element | HTMLDivElement (bindable) | - | The rendered div element. |
Data attributes
data-disabled- Present when the accordion is disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the accordion.
| Attribute | Values | Description |
|---|---|---|
[data-disabled]
| - | Present when the accordion is disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the accordion. |
Item
The Item component represents one item in the accordion with a heading and associated content.
Props
value:string*(bindable)- The value used to identify the item.
disabled:boolean = false- When
true, prevents the user from interacting with the item.
- When
region:boolean = false- 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
element:HTMLDivElement | HTMLElement(bindable)- The rendered
divorsectionelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
value * | string (bindable) | - | The value used to identify the item. |
disabled | boolean | false | When true, prevents the user from interacting with the item. |
region | boolean | false | 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. |
element | HTMLDivElement | HTMLElement (bindable) | - | The rendered div or section element. |
Data attributes
data-state: "open" | "closed"- The state of the item.
"open"when the item's content is expanded and"closed"when the item's content is collapsed.
- The state of the item.
data-disabled- Present when the item is disabled. This can be due to the whole accordion or this item being disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the accordion.
| Attribute | Values | Description |
|---|---|---|
[data-state]
| "open" | "closed" | The state of the item. "open" when the item's content is expanded and "closed" when the item's content is collapsed. |
[data-disabled]
| - | Present when the item is disabled. This can be due to the whole accordion or this item being disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the accordion. |
Header
The Header component is the heading for the content of an item.
Props
level:2 | 3 | 4 | 5 | 6*- The level of heading for the element (
1not supported as a page should have a singleh1element).
- The level of heading for the element (
useHeading:boolean = true- When
true, the heading is adivelement witharia-levelset to the heading level, instead of a native heading element.
- When
element:HTMLHeadingElement | HTMLDivElement(bindable)- The rendered
headingordivelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
level * | 2 | 3 | 4 | 5 | 6 | - | The level of heading for the element (1 not supported as a page should have a single h1 element). |
useHeading | boolean | true | When true, the heading is a div element with aria-level set to the heading level, instead of a native heading element. |
element | HTMLHeadingElement | HTMLDivElement (bindable) | - | The rendered heading or div element. |
Data attributes
No data attributes.
Trigger
The Trigger component toggle button that expands/collapses the item's content.
Props
element:HTMLButtonElement(bindable)- The rendered
buttonelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
element | HTMLButtonElement (bindable) | - | The rendered button element. |
Data attributes
data-state: "open" | "closed"- The state of the item.
"open"when the item's content is expanded and"closed"when the item's content is collapsed.
- The state of the item.
data-disabled- Present when the item is disabled. This can be due to the whole accordion or the item being disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the accordion.
data-svelte-runia-accordion-trigger- Present on all
Trigger's. Used byRootto find them.
- Present on all
data-svelte-runia-value: string- The value identifying the item.
| Attribute | Values | Description |
|---|---|---|
[data-state]
| "open" | "closed" | The state of the item. "open" when the item's content is expanded and "closed" when the item's content is collapsed. |
[data-disabled]
| - | Present when the item is disabled. This can be due to the whole accordion or the item being disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the accordion. |
[data-svelte-runia-accordion-trigger]
| - | Present on all Trigger's. Used by Root to find them. |
[data-svelte-runia-value]
| string | The value identifying the item. |
Content
The Content component is the content of an items that can be expanded/collapsed.
The following is a pseudocode representing the implementation of the Content component.
<script lang="ts"> let expanded = $...; // The `id` used for the `aria-controls` attribute in the `Trigger` component. let id = $...;</script>
<div {id}> {#if expanded} {@render children()} {/if}</div>Props
element:HTMLDivElement(bindable)- The rendered
divelement.
- The rendered
| Prop | Type | Default | Description |
|---|---|---|---|
element | HTMLDivElement (bindable) | - | The rendered div element. |
Data attributes
data-state: "open" | "closed"- The state of the item.
"open"when the item's content is expanded and"closed"when the item's content is collapsed.
- The state of the item.
data-disabled- Present when the item is disabled. This can be due to the whole accordion or the item being disabled.
data-orientation: "vertical" | "horizontal"- The orientation of the accordion.
| Attribute | Values | Description |
|---|---|---|
[data-state]
| "open" | "closed" | The state of the item. "open" when the item's content is expanded and "closed" when the item's content is collapsed. |
[data-disabled]
| - | Present when the item is disabled. This can be due to the whole accordion or the item being disabled. |
[data-orientation]
| "vertical" | "horizontal" | The orientation of the accordion. |
Accessibility
Adheres to the WAI-ARIA Accordion Pattern (Sections With Show/Hide Functionality).
Keyboard interactions
- Enter/Space
:
Attempts to toggle the associated content.
- Tab/ShiftTab
:
Move focus to the next/previous focussable element.
- Arrow Down/Arrow Up
:
Move focus to the next/previous
Trigger. Ifloop = true, loops focus from the last to the firstTriggerand vice versa. - Arrow Right/Arrow Left
:
Move focus to the
Triggerto the right/left (depending ondirection). Ifloop = true, loops focus from the last to the firstTriggerand vice versa. - Home/End
:
Move focus to the first/last
Trigger.
| Keys | Condition | Action |
|---|---|---|
| Enter / Space | If focus on a Trigger | Attempts to toggle the associated content. |
| Tab / ShiftTab | - | Move focus to the next/previous focussable element. |
| Arrow Down / Arrow Up | If focus on a Trigger (and the orientation = "vertical") | Move focus to the next/previous Trigger. If loop = true, loops focus from the last to the first Trigger and vice versa. |
| Arrow Right / Arrow Left | If orientation = "horizontal" and focus is on a Trigger | Move focus to the Trigger to the right/left (depending on direction). If loop = true, loops focus from the last to the first Trigger and vice versa. |
| Home / End | If focus on a Trigger | Move focus to the first/last Trigger. |
Other events
Trigger. onclick:Attempts to toggle the associated content.
| Component | Event | Action |
|---|---|---|
Trigger | onclick | Attempts to toggle the associated content. |
ARIA attributes
Root
No ARIA attributes.
Item
aria-labelledby: string- ID of the corresponding
Triggerelement.
- ID of the corresponding
| Attribute | Type | Description |
|---|---|---|
aria-labelledby | string | ID of the corresponding Trigger element. |
Header
aria-level: number- If
useHeading = false: the heading level.
- If
| Attribute | Type | Description |
|---|---|---|
aria-level | number | If useHeading = false: the heading level. |
Trigger
aria-disabled: "true" | "false""true"when the accordion or item is disabled, or if the content is expanded andcollapsible = false.
aria-controls: string- Present when the accordion or item is disabled.
aria-expanded: "true" | "false""true"when the content of the accordion panel is visible.
| Attribute | Type | Description |
|---|---|---|
aria-disabled | "true" | "false" | "true" when the accordion or item is disabled, or if the content is expanded and collapsible = false. |
aria-controls | string | Present when the accordion or item is disabled. |
aria-expanded | "true" | "false" | "true" when the content of the accordion panel is visible. |
Content
No ARIA attributes.
Examples
Allow collapsing all items
Use the collapsible prop to allow the currently expanded content to be collapsed.
<Accordion.Root type="single" collapsible> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Allow expanding multiple items
Set type type prop to "multiple" to allow multiple items to be expanded.
<Accordion.Root type="multiple"> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Expand an item by default
Use the value prop to set the initial item to be expanded.
<Accordion.Root type="single" value="furthark-essentials"> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Multiple
For type = "multiple" instead use the values prop to set the initial items to be expanded.
<Accordion.Root type="multiple" values={["furthark-essentials", "norse-myths"]}> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Control the expanded item
Bind the value prop of the Root component to a variable to control the expanded item.
This allows both reading and writing the value of the expanded item.
<script> import { Accordion } from "@niklasburggraaff/svelte-runia"; let value = $state(null);</script>
<input bind:value />
<Accordion.Root bind:value type="single"> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Multiple
For type = "multiple" instead use the values prop to control the expanded items.
<script> import { Accordion } from "@niklasburggraaff/svelte-runia";
let values = $state([]); let value = $state(""); $effect(() => { value = values.join(", "); });
$effect(() => { updateValues(value); }); function updateValues(newValue: string) { values = newValue.split(",").map((v) => v.trim()); }</script>
<input bind:value />
<Accordion.Root bind:values type="multiple"> <Accordion.Item value="furthark-essentials">...</Accordion.Item> <Accordion.Item value="norse-myths">...</Accordion.Item> <Accordion.Item value="viking-explorers">...</Accordion.Item></Accordion.Root>Animate content
Apply a Svelte transition, i.e. slide which works well with the accordion, to a child of the Content component.
<script lang="ts"> import { slide } from "svelte/transition";</script>
<Accordion.Root type="single" collapsible> <Accordion.Item value="furthark-essentials"> <Accordion.Header level={3}>...</Accordion.Header> <Accordion.Content> <div transition:slide={{ duration: 200 }}> ... </div> </Accordion.Content> </Accordion.Item> ...</Accordion.Root>Animate trigger icon
The data-state data attribute can be used to animate the trigger icon.
It will be set to "open" when the content is expanded.
{#snippet chevronDown()}...{/snippet}
<Accordion.Root type="single" collapsible> <Accordion.Item value="furthark-essentials"> <Accordion.Header level={3}> <Accordion.Trigger class="[&[data-state=open]>span>svg]:rotate-180 flex w-full items-center justify-between bg-transparent py-2 text-xl"> Furthark Essentials <span class="transition-transform duration-200"> {@render chevronDown()} </span> </Accordion.Trigger> </Accordion.Header> <Accordion.Content>...</Accordion.Content> </Accordion.Item> ...</Accordion.Root>