Accordion

Collapsible panels. Single-open (default) or multiple-open (data-bi-accordion-multiple).

Overview

Accordion manages vertical space by letting users expand only the sections they care about. It displays a stack of collapsible panels where each panel has a clickable header and a hidden body that reveals on activation. Accordion is used for FAQ pages, settings groups, product specifications, progressive disclosure in complex forms, data-filtering options, dashboard help panels, ecommerce product details, and ERP/POS configuration sections across the Byteion ecosystem.

Why this exists

Without a shared Accordion pattern, teams build ad-hoc collapse panels with inconsistent behavior — some use details/summary, others build custom JS toggle panels, and the chevron rotation, focus management, and ARIA attributes are often forgotten. Accordion provides a consistent, accessible, and framework-optional collapsible panel system that works identically in HTML, React, and Vue with baked-in keyboard navigation, ARIA support, and single/multi-open modes.

Mental model

  • Single-open mode — only one panel is expanded at a time. When a new panel opens, the previously open panel closes automatically. Use this for space-saving scenarios like FAQ or filter panels.
  • Multi-open mode — multiple panels can remain open simultaneously. Each panel toggles independently. Use this for settings pages where users need to compare or reference multiple sections.
  • The chevron — rotates to indicate panel state. Down (or right) means closed; up (or down) means open. The icon is a visual affordance, not the only indicator — the header text should clearly describe the panel content.
  • Controlled vs uncontrolled — the Accordion can manage its own state internally (uncontrolled, with defaultValue) or reflect external state (controlled, with value and onValueChange).

Accordion vs Tabs vs Stepper

  • Accordion — inline disclosure where content appears below the header. Use for sections that users can read in any order, like FAQ, settings, or filters.
  • Tabs — content panels that replace each other without scroll. Use for mutually exclusive views where the user stays in the same viewport position.
  • Stepper — sequential steps with required ordering and validation. Use for multi-step forms where each step must be completed before advancing.

If the content is always needed and users should scan it all without toggling, do not use Accordion — keep the content visible. If panels represent linearly dependent steps, use Stepper instead.

When to use

  • FAQ pages — questions as headers, answers as panel bodies, single-open mode for compact layout
  • Settings groups — each category (Profile, Notifications, Billing) in its own panel, multi-open for comparing settings
  • Product specifications — technical details behind expandable labels for clean product pages
  • Progressive disclosure — complex forms where optional sections are hidden until needed
  • Filter and search panels — collapsible filter groups for data tables and dashboards
  • Ecommerce product details — shipping info, size guide, material care behind expandable sections
  • ERP / POS configuration — module settings, tax configuration, payment gateway options grouped by category

When not to use

  • Do not use for content that is always needed — if every panel contains critical information users need to see, keep it visible and use Section or Stack instead
  • Do not use for navigation — accordion is for inline content disclosure, not page navigation. Use Navbar, Sidebar, or Tabs for navigation
  • Do not use for sequential forms — if each panel depends on the previous one, use Stepper with validation
  • Do not hide all content by default — at least one panel should be open initially to orient the user
  • Do not deeply nest accordions — nested collapsible panels create a confusing UX. Use a single level of accordion or restructure the information

Basic example

A compact FAQ accordion with three questions and single-open mode. The first panel is open by default:

Byteion is a design system of 60+ components with HTML, React, and Vue support. Every component shares a single CSS token layer for consistent theming.
<div class="divide-y divide-border">
  <div>
    <button class="flex w-full items-center justify-between px-5 py-4 text-sm text-foreground hover:bg-muted/30" aria-expanded="true" aria-controls="panel-1" id="trigger-1">
      <span>What is Byteion?</span>
      <i class="ti ti-chevron-down text-muted-foreground transition-transform"></i>
    </button>
    <div id="panel-1" role="region" aria-labelledby="trigger-1" class="px-5 pb-4 text-sm text-muted-foreground">
      Byteion is a design system of 60+ components with HTML, React, and Vue support.
    </div>
  </div>
  <div>
    <button class="flex w-full items-center justify-between px-5 py-4 text-sm text-foreground hover:bg-muted/30" aria-expanded="false" aria-controls="panel-2" id="trigger-2">
      <span>Can I use it with Tailwind?</span>
      <i class="ti ti-chevron-down text-muted-foreground transition-transform"></i>
    </button>
    <div id="panel-2" role="region" aria-labelledby="trigger-2" class="px-5 pb-4 text-sm text-muted-foreground"></div>
  </div>
</div>

Single-open and multi-open

The Accordion supports two interaction modes via the type prop:

Mode Behavior Best for
singleOnly one panel open at a time. Opening a new panel closes the current one.FAQ, filters, progressive disclosure
multipleMultiple panels can be open simultaneously. Each panel toggles independently.Settings pages, comparison panels

Anatomy

  • Group wrapper (Accordion) — the container that manages single/multi-open state and connects panels via shared context
  • Item (AccordionItem) — one collapsible unit with a header and body
  • Header (AccordionTrigger) — the clickable button with chevron icon. Has aria-expanded and controls the panel
  • Body (AccordionContent) — the collapsible content area with role="region" and aria-labelledby
  • Chevron — rotates to indicate open/closed state. Uses ti ti-chevron-down with a transition-transform rotation

Usage guidelines

  • Keep panel headers concise — 1–5 words that clearly describe the panel content
  • Start with the first panel open to give users an immediate sense of the accordion pattern
  • Use single-open mode for space-saving (FAQ, filters, specifications)
  • Use multi-open mode for settings and configuration panels where users need to reference multiple sections
  • Do not hide critical form inputs inside collapsed panels. Required fields should always be visible
  • Use dividers (divide-y divide-border) to separate panels visually
  • If using accordion for a form, do not clear form data when collapsing a panel

Do and Don't

Do

  • Use single-open mode for FAQ and filter panels
  • Keep panel headers concise and descriptive
  • Start with the first panel open as a visual cue
  • Use chevron rotation to signal open/closed state
  • Add aria-expanded and aria-controls for accessibility

Don't

  • Hide critical or required content behind collapsed panels
  • Use accordion for page navigation — use Navbar or Sidebar instead
  • Nest accordions more than one level deep
  • Clear form state when collapsing a panel
  • Use accordion for sequential form steps — use Stepper instead

Accessibility

The Accordion follows the WAI-ARIA Accordion pattern. Each trigger is a <button> element with:

  • aria-expanded — true when the panel is open, false when closed
  • aria-controls — references the id of the panel it controls
  • id — each trigger has a unique ID referenced by the panel's aria-labelledby
  • Panel region — each body has role="region" and aria-labelledby pointing to the trigger ID
  • Keyboard navigation — Enter or Space toggles the panel. Tab and Shift+Tab move between triggers
  • Chevron alone is not sufficient — the aria-expanded attribute must communicate state to screen readers

Customization

Accordion styling follows Byteion layout tokens. Borders use divide-border / border-border, chevron color follows text-muted-foreground, and hover states use hover:bg-muted/30. Customize border radius, spacing, and icon rotation via standard Tailwind utility classes. The chevron transition can be replaced with any animation library — the ti ti-chevron-down icon is a convention, not a requirement.

HTML example

Static accordion requires JavaScript for toggle behavior. Use @byteion/js with data-bi-accordion or your own toggle script:

<div class="divide-y divide-border rounded-xl border border-border" data-bi-accordion="single">
  <div data-bi-accordion-item>
    <button class="flex w-full items-center justify-between px-5 py-4 text-sm text-foreground hover:bg-muted/30" data-bi-accordion-trigger aria-expanded="false" aria-controls="panel-1" id="trigger-1">
      <span>Billing settings</span>
      <i class="ti ti-chevron-down text-muted-foreground transition-transform"></i>
    </button>
    <div id="panel-1" role="region" aria-labelledby="trigger-1" class="px-5 pb-4 text-sm text-muted-foreground" data-bi-accordion-content>
      Manage your payment methods, invoices, and billing history.
    </div>
  </div>
  <div data-bi-accordion-item>
    <button class="flex w-full items-center justify-between px-5 py-4 text-sm text-foreground hover:bg-muted/30" data-bi-accordion-trigger aria-expanded="false" aria-controls="panel-2" id="trigger-2">
      <span>Team permissions</span>
      <i class="ti ti-chevron-down text-muted-foreground transition-transform"></i>
    </button>
    <div id="panel-2" role="region" aria-labelledby="trigger-2" class="px-5 pb-4 text-sm text-muted-foreground" data-bi-accordion-content>
      Invite team members and configure access levels.
    </div>
  </div>
</div>

React example

React exports Accordion, AccordionItem, AccordionTrigger, and AccordionContent from @byteion/react/client. Use type="single" for single-open mode and type="multiple" for multi-open:

import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@byteion/react/client';

export default function SettingsAccordion() {
  return (
    <Accordion type="single" defaultValue="billing" className="divide-y divide-border rounded-xl border border-border">
      <AccordionItem value="billing">
        <AccordionTrigger>Billing settings</AccordionTrigger>
        <AccordionContent>
          Manage your payment methods, invoices, and billing history.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="team">
        <AccordionTrigger>Team permissions</AccordionTrigger>
        <AccordionContent>
          Invite team members and configure access levels.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="notifications">
        <AccordionTrigger>Notification preferences</AccordionTrigger>
        <AccordionContent>
          Choose which updates you receive via email or in-app.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Vue example

Vue exports the same Accordion API from @byteion/vue. Use default-value for the initially open panel:

<script setup lang="ts">
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@byteion/vue';
</script>

<template>
  <Accordion type="single" default-value="billing" class="divide-y divide-border rounded-xl border border-border">
    <AccordionItem value="billing">
      <AccordionTrigger>Billing settings</AccordionTrigger>
      <AccordionContent>
        Manage your payment methods, invoices, and billing history.
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="team">
      <AccordionTrigger>Team permissions</AccordionTrigger>
      <AccordionContent>
        Invite team members and configure access levels.
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</template>

API Reference

The Accordion component is available as a compound set in both @byteion/react/client and @byteion/vue:

Component Props / Emits Description
Accordiontype, value, defaultValue, onValueChangeRoot wrapper that manages panel state
AccordionItemvalueIndividual collapsible panel wrapper
AccordionTrigger(none)Clickable button that toggles the panel
AccordionContent(none)Collapsible panel body

Developer tips

  • Use controlled mode for URL-synced state — store the open panel value in query params via useSearchParams or vue-router query for deep-linkable accordion panels
  • Do not clear form state on collapse — if form inputs live inside accordion panels, preserve their values when collapsing and re-expanding
  • Use type="multiple" for settings — settings panels often need cross-reference; multi-open lets users keep multiple sections visible
  • Animate the chevron with transition-transform — a smooth 200–300ms rotation feels natural and gives users visual feedback
  • Test with long panel labels — long trigger text should wrap or truncate gracefully without breaking the layout
  • Test keyboard navigation — verify Tab moves between triggers and Enter/Space toggles panels in both single and multi-open modes

Common mistakes

  • Hiding critical content behind collapsed panels — required form fields, legal notices, and pricing info should always be visible
  • Using accordion for page navigation — accordion is for inline content disclosure, not navigating between pages. Use Navbar or Sidebar
  • Using accordion for sequential forms — if each step depends on the previous one, use Stepper with proper validation
  • Starting with all panels closed — at least one panel should be open by default to orient the user to the accordion pattern
  • Nested accordions — nested collapsible panels create a confusing visual hierarchy. Restructure the content or use tabs
  • Missing ARIA attributes — aria-expanded, aria-controls, aria-labelledby, and role="region" are required for screen reader support

Production usage tips

  • Test both single and multi-open modes — verify that single mode closes the previous panel and that multi mode allows multiple panels open simultaneously
  • Test dark mode and branded themes — borders, dividers, and chevron colors use token values that adapt automatically
  • Test keyboard accessibility — verify Tab navigation, Enter/Space toggle, and focus management (focus must remain on the trigger, not jump to content)
  • Test with JavaScript disabled — static accordion should not break the page; all content should be visible in a degraded state or fallback gracefully
  • Test mobile layout — accordion panels should stack vertically on small screens without layout shift
  • Keep React and Vue APIs aligned with the core HTML schema — type, value, and defaultValue props map to data-bi-accordion attributes

Premium examples

Pro patterns available in Byteion Pro:

  • Accordion with animated height transition — smooth CSS grid-template-rows animation for panel open/close without JS height calculations
  • Accordion with step validation indicators — each panel shows a completion badge (checkmark, warning, error) based on form validation state
  • Nested accordion for deep hierarchies — grouped accordion panels with expandable sub-panels for complex documentation or help centers
  • FAQ schema accordion — accordion panels with structured data (FAQPage schema) for SEO on public docs
  • Settings page accordion with saved state — panels remember their open/closed state via localStorage between sessions
  • Ecommerce product details accordion — shipping, returns, sizing, and care instructions in a single-open accordion below the product description
  • ERP / POS configuration accordion — tax settings, payment gateways, and module toggles grouped in a multi-open settings accordion
  • Tabs — mutually exclusive content panels that replace each other without scroll
  • Stepper — sequential multi-step flow with validation and required ordering
  • Card — surface container for grouping related content
  • Drawer — slide-in panel for secondary content and workflows
  • Section — vertical spacing between content blocks

API Reference

Requires data-bi-component="accordion" for JS behavior.

Runtime events

Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.

HTML / JS events

  • bi:accordion:hide
  • bi:accordion:show

React callbacks

  • onValueChange(value: string[]) => void

Vue emits

  • update:modelValue[v: string | string[]]

Remote / AJAX

This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.

Unlock Premium Patterns

Get access to copy-paste ready Pro blocks, SaaS templates, and advanced component patterns.

Explore Pro Components