Skip to content

Accordion

Set of interactive headings that allow you to expand/collapse the associated content.

Controls

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.

MyComponent.svelte
<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 multiple Item’s and must be passed a type prop
  • Each Item must contain a Header and Content and must be passed a unique value prop
  • A Header must contain a Trigger and is must be passed a level prop

API

Root

div

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.
  • 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.
PropTypeDefaultDescription
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.
AttributeValuesDescription
[data-disabled] -Present when the accordion is disabled.
[data-orientation] "vertical" | "horizontal" The orientation of the accordion.

Item

div
|
section

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.
  • 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.
PropTypeDefaultDescription
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.
  • 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.
AttributeValuesDescription
[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.
h2
h3
h4
h5
h6
|
div

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 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.
PropTypeDefaultDescription
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

button
Focussable

The Trigger component toggle button that expands/collapses the item's content.

Props

  • element:HTMLButtonElement (bindable)
    • The rendered button element.
PropTypeDefaultDescription
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.
  • 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.
AttributeValuesDescription
[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

div

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.

AccordionContent.svelte
<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.
PropTypeDefaultDescription
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.
  • 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.
AttributeValuesDescription
[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. If loop = true, loops focus from the last to the first Trigger and vice versa.

  • Arrow Right/Arrow Left :

    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 :

    Move focus to the first/last Trigger.

KeysConditionAction
Enter / SpaceIf focus on a TriggerAttempts to toggle the associated content.
Tab / ShiftTab-Move focus to the next/previous focussable element.
Arrow Down / Arrow UpIf 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 LeftIf orientation = "horizontal" and focus is on a TriggerMove 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 / EndIf focus on a TriggerMove focus to the first/last Trigger.

Other events

  • Trigger. onclick :

    Attempts to toggle the associated content.

ComponentEventAction
TriggeronclickAttempts to toggle the associated content.

ARIA attributes

Root

No ARIA attributes.

Item

  • aria-labelledby: string
    • ID of the corresponding Trigger element.
AttributeTypeDescription
aria-labelledby string ID of the corresponding Trigger element.

Header

  • aria-level: number
    • If useHeading = false: the heading level.
AttributeTypeDescription
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 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.
AttributeTypeDescription
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.

MyComponent.svelte
<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.

MyComponent.svelte
<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.

MyAccordion.svelte
<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>