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
Root
can contain multipleItem
’s and must be passed atype
prop - Each
Item
must contain aHeader
andContent
and must be passed a uniquevalue
prop - A
Header
must contain aTrigger
and is must be passed alevel
prop
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,null
if 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 firstTrigger
and 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
div
element.
- 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 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
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. |
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 (
1
not supported as a page should have a singleh1
element).
- The level of heading for the element (
useHeading
:boolean = true
- When
true
, the heading is adiv
element witharia-level
set to the heading level, instead of a native heading element.
- When
element
:HTMLHeadingElement | HTMLDivElement
(bindable)- The rendered
heading
ordiv
element.
- 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
button
element.
- 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 byRoot
to 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
div
element.
- 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 firstTrigger
and vice versa. - Arrow Right/Arrow Left
:
Move focus to the
Trigger
to the right/left (depending ondirection
). Ifloop = true
, loops focus from the last to the firstTrigger
and 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
Trigger
element.
- 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>