Skip to content
Ndop UI

Accordion

A stack of headed sections that each show or hide their content.

Orders ship within two business days, and arrive in three to five.

import { Component } from '@angular/core';
import { NdIcon } from '@ndop/core';
import {
  Accordion,
  AccordionContent,
  AccordionHeader,
  AccordionItem,
  AccordionTrigger,
} from '@/ui/accordion/accordion';

/**
 * Frequently asked questions, one open at a time. The open one closes
 * again on its own button, since the accordion is collapsible.
 */
@Component({
  selector: 'example-accordion-demo',
  imports: [
    Accordion,
    AccordionContent,
    AccordionHeader,
    AccordionItem,
    AccordionTrigger,
    NdIcon,
  ],
  template: `
    <div
      ndAccordion
      collapsible
      defaultValue="shipping"
      class="w-md max-w-full"
    >
      @for (question of questions; track question.value) {
        <div ndAccordionItem [value]="question.value">
          <h2 ndAccordionHeader>
            <button ndAccordionTrigger>
              {{ question.title }}
              <svg ndIcon="chevron-down"></svg>
            </button>
          </h2>
          <div ndAccordionContent>{{ question.answer }}</div>
        </div>
      }
    </div>
  `,
})
export class AccordionDemoExample {
  protected readonly questions = [
    {
      value: 'shipping',
      title: 'How long does shipping take?',
      answer:
        'Orders ship within two business days, and arrive in three to five.',
    },
    {
      value: 'returns',
      title: 'What is your return policy?',
      answer: 'Return any item within 30 days of delivery for a full refund.',
    },
    {
      value: 'support',
      title: 'How do I reach support?',
      answer: 'Email us any time; we answer within a day.',
    },
  ];
}

An accordion stacks headed sections that each show or hide their content, such as a list of frequently asked questions. Each heading's button opens its section; by default one section is open at a time, multiple lets several be, and collapsible lets the open one close again. The arrow keys move between the buttons.

Installation

npx @ndop/cli add accordion

Usage

import {
  Accordion,
  AccordionContent,
  AccordionHeader,
  AccordionItem,
  AccordionTrigger,
} from '@/ui/accordion/accordion';
<div
  ndAccordion
  collapsible
  defaultValue="shipping"
  class="w-md max-w-full"
>
  @for (question of questions; track question.value) {
    <div ndAccordionItem [value]="question.value">
      <h2 ndAccordionHeader>
        <button ndAccordionTrigger>
          {{ question.title }}
          <svg ndIcon="chevron-down"></svg>
        </button>
      </h2>
      <div ndAccordionContent>{{ question.answer }}</div>
    </div>
  }
</div>

Examples

Accordion multiple

Sections that open independently, with one disabled.

Your name, photo and bio.

Password and two-factor authentication.
import { Component, signal } from '@angular/core';
import { NdIcon } from '@ndop/core';
import {
  Accordion,
  AccordionContent,
  AccordionHeader,
  AccordionItem,
  AccordionTrigger,
} from '@/ui/accordion/accordion';

/**
 * Sections that open independently, with `multiple`. One is disabled,
 * and the open sections are bound to a signal.
 */
@Component({
  selector: 'example-accordion-multiple',
  imports: [
    Accordion,
    AccordionContent,
    AccordionHeader,
    AccordionItem,
    AccordionTrigger,
    NdIcon,
  ],
  template: `
    <div ndAccordion multiple [(value)]="open" class="w-md max-w-full">
      <div ndAccordionItem value="profile">
        <h2 ndAccordionHeader>
          <button ndAccordionTrigger>
            Profile <svg ndIcon="chevron-down"></svg>
          </button>
        </h2>
        <div ndAccordionContent>Your name, photo and bio.</div>
      </div>
      <div ndAccordionItem value="billing" disabled>
        <h2 ndAccordionHeader>
          <button ndAccordionTrigger>
            Billing <svg ndIcon="chevron-down"></svg>
          </button>
        </h2>
        <div ndAccordionContent>Managed by your organization.</div>
      </div>
      <div ndAccordionItem value="security">
        <h2 ndAccordionHeader>
          <button ndAccordionTrigger>
            Security <svg ndIcon="chevron-down"></svg>
          </button>
        </h2>
        <div ndAccordionContent>Password and two-factor authentication.</div>
      </div>
    </div>
  `,
})
export class AccordionMultipleExample {
  protected readonly open = signal<readonly string[]>(['profile', 'security']);
}

Accessibility

Implements the Accordion pattern of the WAI-ARIA Authoring Practices.

Keyboard

Keys the component handles.

Key Action
TabMoves focus through the triggers and any open content, in document order.
EnterorSpaceOpens or closes the focused trigger's item.
ArrowDownMoves focus to the next enabled trigger, wrapping to the first.
ArrowUpMoves focus to the previous enabled trigger, wrapping to the last.
HomeMoves focus to the first enabled trigger.
EndMoves focus to the last enabled trigger.

Focus

  • Every enabled trigger is in the tab order; opening or closing an item keeps focus on its trigger.
  • Disabled triggers are skipped by the arrow keys.

Right-to-left

Nothing mirrors: the arrow keys follow the vertical order of the triggers.

API reference

Accordion

[ndAccordion]

An accordion (@ndop/primitives/accordion): a stack of headed sections that each show or hide their content. One section opens at a time unless it's multiple; collapsible lets the open one close.

Accordion binding Type Default
[defaultValue]
Initially open items when value isn't bound: one value or a list.
string | readonly string[] | nullnull
[multiple]
Lets several items be open at once.
booleanfalse
[collapsible]
Lets the open item close when only one item may be open.
booleanfalse
[disabled]
Disables every item.
booleanfalse
[(value)]
The open items' values. Two-way bindable with [(value)].
readonly string[][]
Data attribute Values
data-slotaccordion

AccordionItem

[ndAccordionItem]

One section of an accordion, with a line under it.

AccordionItem binding Type Default
[class]
Extra classes, merged last so they win.
string''
[value]required
The value value lists while it's open.
string—
[disabled]
Disables its trigger.
booleanfalse
Data attribute Values
data-slotaccordion-item
data-state —
data-disabled —

AccordionHeader

[ndAccordionHeader]

The heading around a section's trigger: an h2 to h6 that fits the page's outline.

AccordionHeader binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotaccordion-header
data-state —
data-disabled —

AccordionTrigger

button[ndAccordionTrigger]

The button that opens and closes its section. Put the chevron icon in it, last; it turns over while the section is open.

Manages aria-expanded, aria-controls, aria-disabled, id.

AccordionTrigger binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotaccordion-trigger
data-state —
data-disabled —

AccordionContent

[ndAccordionContent]role region

The content its trigger shows and hides, sliding open and closed.

Manages aria-labelledby, id, hidden.

AccordionContent binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotaccordion-content
data-state —
data-disabled —
data-animating —

Agent notes

Do

  • Put each trigger in a heading of the level that fits the page.
  • Keep the headings short enough to scan.
  • Use collapsible unless one section must always stay open.

Don't

  • Don't hide content everyone needs; show it.
  • Don't use it for a single section; use Collapsible.