Accordion

The accordion component provides a way to display a list of items that can be expanded or collapsed.

WAI-ARIA: Accordion Pattern

Features

Multiple items

The accordion component supports multiple expanded items, if configured to do so.

Keyboard navigation

  • Space or Enter on a collapsed item expands it.
  • Escape on an expanded item collapses it.
  • Arrow Up or Arrow Down navigates between items.
  • Home or End navigates to the first or last item.

Example

Elephant
Elephants are herbivorous and consume a variety of plant materials, including grass, leaves, and fruits.

Sources

+page.svelte

<script lang="ts">
  import Accordion from './Accordion.svelte'
  import AccordionItem from './AccordionItem.svelte'
</script>

<Accordion>
  <AccordionItem heading="Lion" icon="🦁">
    Lions are carnivorous and primarily feed on large ungulates, such as zebras
    and wildebeests.
  </AccordionItem>
  <AccordionItem heading="Elephant" icon="🐘" open>
    Elephants are herbivorous and consume a variety of plant materials,
    including grass, leaves, and fruits.
  </AccordionItem>
  <AccordionItem heading="Giraffe" icon="🦒" disabled>
    Giraffes are herbivorous and consume a variety of leaves and buds of acacia,
    mimosa, and wild apricot trees.
  </AccordionItem>
  <AccordionItem heading="Tiger" icon="🐯">
    Tigers are carnivorous and mainly prey on ungulates, such as deer and wild
    boar.
  </AccordionItem>
  <AccordionItem heading="Bear" icon="🐻" disabled>
    Bears are omnivorous and feed on a variety of plant and animal foods and
    insects.
  </AccordionItem>
</Accordion>

Accordion.svelte

<script lang="ts">
  import { createAccordion } from 'louisette'
  import { setContext } from 'svelte'

  const { accordionAttrs, ...accordionContext } = createAccordion()
  setContext('accordion', accordionContext)
</script>

<div {...$accordionAttrs}>
  <slot />
</div>

AccordionItem.svelte

<script lang="ts">
  import { createKey, type Accordion } from 'louisette'
  import { getContext } from 'svelte'

  export let icon: string = ''
  export let heading: string
  export let disabled: boolean = false
  export let open: boolean = false

  // Generate a random key for this accordion item
  const key = createKey()

  const { triggerAttrs, contentAttrs, expanded, expand, disable } =
    getContext<Accordion>('accordion')

  if (open) {
    expand(key)
  }

  if (disabled) {
    disable(key)
  }
</script>

<div
  class="border border-neutral-200 bg-white text-neutral-900 shadow-sm first:rounded-t-lg last:rounded-b-lg dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-100 [&:not(:first-child)]:border-t-0"
>
  <div
    {...$triggerAttrs(key)}
    class="flex cursor-pointer select-none items-center rounded-sm px-5 py-4 font-semibold leading-5 transition-colors duration-200 ease-in-out hover:bg-neutral-100 focus-visible:bg-neutral-100 dark:hover:bg-neutral-700 dark:focus-visible:bg-neutral-700"
    class:is-disabled={disabled}
  >
    {#if icon}
      <span class="mr-3" aria-hidden="true">{icon}</span>
    {/if}
    {heading}
    <span class="ml-auto" class:rotate-180={$expanded.includes(key)}>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        stroke-width="1.5"
        stroke="currentColor"
        class="h-4 w-4"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          d="M19.5 8.25l-7.5 7.5-7.5-7.5"
        />
      </svg>
    </span>
  </div>
  <div
    {...$contentAttrs(key)}
    class="px-5 py-4 text-sm leading-5 transition-colors duration-200 ease-in-out"
    class:hidden={!$expanded.includes(key)}
  >
    <slot />
  </div>
</div>

<style lang="postcss">
  .is-disabled {
    @apply pointer-events-none opacity-50;
  }
</style>