# Accordion

> A family of composable components for expandable sections with built-in keyboard navigation, ARIA support, and animated panel behavior.

# Accordion

## Overview

Use the Accordion family when you need a group of expandable sections with built-in keyboard support, ARIA wiring, and animated panel open and close behavior. The family is composed, not standalone — the root component manages shared state and keyboard navigation, while child components define each individual section. Accordion works well for FAQs, settings panels, sidebar navigation, product feature lists, and any content that benefits from progressive disclosure.

## Authoring Structure

```
Accordion
└── Accordion Item
    ├── Accordion Header
    │   └── Accordion Trigger
    └── Accordion Content
```

### Placement Rules

| Component | Placement | Role |
|---|---|---|
| **Accordion** | Top-level wrapper. | Owns state, keyboard navigation, and shared options. |
| **Accordion Item** | Direct child of `Accordion`. | The runtime looks for direct item children when it initializes. |
| **Accordion Header** | Inside `Accordion Item`. | Provides heading semantics (`` – ``) for the trigger. |
| **Accordion Trigger** | Usually inside `Accordion Header`. | Interactive control users click or focus to toggle a panel. |
| **Accordion Content** | Inside `Accordion Item`, after the header. | The panel that opens and closes. |

## Quick Start

<CodeExample title="Basic — header-generated trigger" language="jsx">
{`<OmeAccordion>
  {#slot default}
    <OmeAccordionItem>
      {#slot default}
        <OmeAccordionHeader
          structure='{{"level":"2","triggerTag":"strong"}}'
          content='{{"label":"Account settings"}}'
        />
        <OmeAccordionContent>
          {#slot default}Manage your account preferences here.{/slot}
        </OmeAccordionContent>
      {/slot}
    </OmeAccordionItem>
  {/slot}
</OmeAccordion>`}
</CodeExample>

When the `AccordionHeader` has an empty slot and a `content.label` value, it auto-generates a trigger for you — no need to nest `AccordionTrigger` manually.

<CodeExample title="Explicit structure with custom trigger content" language="jsx">
{`<OmeAccordion settings='{{"type":"single"}}'>
  {#slot default}
    <OmeAccordionItem>
      {#slot default}
        <OmeAccordionHeader structure='{{"level":"2"}}'>
          {#slot default}
            <OmeAccordionTrigger>
              {#slot default}Shipping{/slot}
            </OmeAccordionTrigger>
          {/slot}
        </OmeAccordionHeader>
        <OmeAccordionContent>
          {#slot default}
            
              Shipping content goes here.
            
          {/slot}
        </OmeAccordionContent>
      {/slot}
    </OmeAccordionItem>
  {/slot}
</OmeAccordion>`}
</CodeExample>

Use the explicit structure when you need custom markup inside the trigger — icons, badges, or any content beyond a plain label.

---

## Family Components

### Accordion (Root)

The root component wraps all items and manages shared behavior: expansion mode (single or multiple), keyboard navigation, loop wrapping, and animation timing. It does not render any visible UI itself — only a container element.

<PropsTable group="Structure">
  <Prop name="tag" type="string" defaultValue="div">
    HTML tag for the root element.
  </Prop>
</PropsTable>

<PropsTable group="Settings">
  <Prop name="type" type={`"single" | "multiple"`} defaultValue="single">
    Expansion mode. `"single"` allows only one item open at a time ( Disclosure pattern). `"multiple"` allows any number of items open simultaneously.
  </Prop>
  <Prop name="orientation" type={`"vertical" | "horizontal"`} defaultValue="vertical">
    Orientation of the accordion. Affects which arrow keys navigate between triggers and how focus moves.
  </Prop>
  <Prop name="loop" type="boolean" defaultValue="true">
    Whether keyboard navigation wraps from the last trigger back to the first, and vice versa.
  </Prop>
  <Prop name="disabled" type="boolean" defaultValue="false">
    Disables all items in the accordion when `true`. Individual items can still override this with their own `disabled` prop.
  </Prop>
  <Prop name="animationDuration" type="string" defaultValue='"300"'>
    Duration of the expand/collapse animation in milliseconds, as a string.
  </Prop>
</PropsTable>

<PropsTable group="Styling">
  <Prop name="class" type="class" defaultValue="ome-accordion-default">
    CSS class applied to the root element.
  </Prop>
</PropsTable>

#### Expansion Modes

- **`single`** — Opening one item automatically closes the previously open item. Only one panel is visible at a time. This is the classic accordion behavior.
- **`multiple`** — Each item toggles independently. Opening one does not affect others. Use this for disclosure-style UIs where users may want several sections visible.

#### Keyboard Behavior

When a trigger has focus:

| Key | Action |
|---|---|
| `Enter` / `Space` | Toggle the associated panel. |
| `ArrowDown` | Move focus to the next trigger (vertical orientation). |
| `ArrowUp` | Move focus to the previous trigger (vertical orientation). |
| `ArrowRight` | Move focus to the next trigger (horizontal orientation). |
| `ArrowLeft` | Move focus to the previous trigger (horizontal orientation). |
| `Home` | Move focus to the first trigger. |
| `End` | Move focus to the last trigger. |

When `loop` is `true`, pressing `ArrowDown` on the last trigger wraps to the first, and `ArrowUp` on the first wraps to the last. In RTL layouts, horizontal arrow keys are automatically reversed.

#### Accessibility

The root component generates unique IDs for each item and wires up ARIA attributes automatically:

- Each trigger has `aria-expanded` and `aria-controls` pointing to its content panel.
- Each content panel has `role="region"` and `aria-labelledby` pointing to its header.
- `data-ome-state` attributes sync open/closed state for CSS targeting.

---

### Accordion Item

Each `AccordionItem` represents a single expandable section. It is the direct child of the root `Accordion` and the parent of `AccordionHeader` and `AccordionContent`. The runtime discovers items by scanning for direct item children at initialization time.

<PropsTable group="Structure">
  <Prop name="tag" type="string" defaultValue="div">
    HTML tag for the item wrapper.
  </Prop>
</PropsTable>

<PropsTable group="Settings">
  <Prop name="disabled" type="boolean" defaultValue="false">
    Disables this specific item. Overrides the root `disabled` prop on a per-item basis.
  </Prop>
  <Prop name="open" type="boolean" defaultValue="false">
    Initial open state. In `single` mode, only the last item with `open="true"` will actually render open.
  </Prop>
  <Prop name="toggleClass" type="string" defaultValue='""'>
    Additional CSS class added to the item when it is in the open state. Useful for CSS-driven animations or visibility changes.
  </Prop>
</PropsTable>

<PropsTable group="Styling">
  <Prop name="class" type="class" defaultValue="ome-accordion-item-default">
    CSS class applied to the item element.
  </Prop>
</PropsTable>

---

### Accordion Header

The header provides semantic heading markup (`` – ``) for the trigger. It determines the heading level and can auto-generate a trigger when its slot is empty but a `content.label` value is provided. This is the simplest way to create an accordion — just set the label and move on.

<PropsTable group="Structure">
  <Prop name="tag" type="string" defaultValue="div">
    HTML tag for the header wrapper.
  </Prop>
  <Prop name="level" type="string" defaultValue='"3"'>
    Heading level for the header (`"1"` through `"6"`). Renders as `` – `` accordingly.
  </Prop>
  <Prop name="triggerTag" type="string" defaultValue="span">
    Tag used for the auto-generated trigger's inner label wrapper when the header creates a trigger from `content.label`.
  </Prop>
</PropsTable>

<PropsTable group="Content">
  <Prop name="label" type="string" defaultValue='""'>
    Fallback trigger label. When the header slot is empty, this value is used to generate a trigger automatically.
  </Prop>
</PropsTable>

<PropsTable group="Styling">
  <Prop name="class" type="class" defaultValue="ome-accordion-header-default">
    CSS class applied to the header element.
  </Prop>
</PropsTable>

#### Header Fallback Rendering

When the header slot is empty and `content.label` has a value, the header auto-generates a trigger element. This means you can build a working accordion with just `AccordionHeader` and `AccordionContent` — no explicit `AccordionTrigger` needed:

```
Accordion Header (slot empty + label set)
  └── Auto-generated Trigger
        └── Label text
        └── Chevron icon
```

---

### Accordion Trigger

The interactive element users click, tap, or focus to toggle a panel. When used inside `AccordionHeader`, it receives heading semantics from the parent. When the slot is empty, the trigger renders a label wrapper and chevron icon using its `content.label` value.

<PropsTable group="Structure">
  <Prop name="tag" type="string" defaultValue="span">
    Tag for the fallback label wrapper only. **This does not change the outer button element** — the trigger always renders a `<button>` as its interactive root.
  </Prop>
</PropsTable>

<PropsTable group="Settings">
  <Prop name="disabled" type="boolean" defaultValue="false">
    Disables this trigger independently of the item or root `disabled` setting.
  </Prop>
</PropsTable>

<PropsTable group="Content">
  <Prop name="label" type="string" defaultValue='""'>
    Fallback label text. Used when the trigger slot is empty to render a text label and chevron.
  </Prop>
</PropsTable>

<PropsTable group="Styling">
  <Prop name="class" type="class" defaultValue="ome-accordion-trigger-default">
    CSS class applied to the trigger button element.
  </Prop>
  <Prop name="labelClass" type="class" defaultValue="ome-accordion-trigger-default__label">
    CSS class applied to the label text element inside the trigger.
  </Prop>
  <Prop name="chevronClass" type="class" defaultValue="ome-accordion-trigger-default__chevron">
    CSS class applied to the chevron icon inside the trigger.
  </Prop>
</PropsTable>

#### Trigger Fallback Rendering

When the trigger slot is empty and `content.label` is set, the trigger renders:

```
<button class="ome-accordion-trigger-default">
  Label text
  ▼
</button>
```

---

### Accordion Content

The content panel that expands and collapses. It is animated by default using the root's `animationDuration` setting. Content is hidden via `height: 0` and `overflow: hidden` when collapsed — it remains in the DOM at all times.

<PropsTable group="Structure">
  <Prop name="tag" type="string" defaultValue="div">
    HTML tag for the content panel.
  </Prop>
</PropsTable>

<PropsTable group="Settings">
  <Prop name="hiddenUntilFound" type="boolean" defaultValue="false">
    When `true`, the collapsed panel uses the browser's `hidden="until-found"` attribute instead of `height: 0`. This allows browser find-in-page (`Ctrl+F` / `Cmd+F`) to search inside collapsed panels and expand them automatically when a match is found. Use this when your accordion contains searchable reference content.
  </Prop>
</PropsTable>

<PropsTable group="Styling">
  <Prop name="class" type="class" defaultValue="ome-accordion-content-default">
    CSS class applied to the content element.
  </Prop>
</PropsTable>

---

## Common Mistakes

<CommonMistake title="Wrapping items in an extra container">
  Do not place another block between `Accordion` and `AccordionItem`. The runtime scans for **direct** item children at initialization. Adding a wrapper (like a Group or Stack block) between them breaks item discovery and the accordion will not initialize.
</CommonMistake>

<CommonMistake title="Expecting trigger tag to change the button">
  `AccordionTrigger` `structure.tag` only changes the inner fallback label wrapper — the outer interactive element is always a `<button>`. If you need a different element, use the trigger slot to provide your own content instead.
</CommonMistake>

<CommonMistake title="Using hiddenUntilFound as a visibility toggle">
  `AccordionContent` `settings.hiddenUntilFound` is specifically for browser find-in-page integration. It is not a general-purpose visibility toggle and may cause unexpected behavior if used outside its intended purpose.
</CommonMistake>

---

## FAQs

<details>
<summary>Can I nest accordions inside each other?</summary>

Yes. You can place an `Accordion` inside an `AccordionContent` panel. Each nested accordion manages its own state independently. Make sure to use appropriate heading levels — if the outer accordion uses `level="2"`, the inner accordion should use `level="3"` to maintain a correct document outline.

</details>

<details>
<summary>How do I style individual items differently?</summary>

Use the `class` prop on `AccordionItem` to apply a custom class, and the `toggleClass` prop to add an additional class when the item is open. You can also target items with CSS using `data-ome-state="open"` or `data-ome-state="closed"` attributes that the runtime adds automatically.

</details>

<details>
<summary>Can I start with multiple items open?</summary>

In `multiple` mode, yes — set `open="true"` on each item you want expanded on load. In `single` mode, only one item can be open at a time, so only the last item with `open="true"` will actually render expanded.

</details>

<details>
<summary>What happens if I don't include a Header?</summary>

The accordion will still function, but you will lose heading semantics and ARIA labeling. The content panel will not have an `aria-labelledby` association, which reduces accessibility. Always include an `AccordionHeader` (or ensure your custom trigger provides equivalent semantics).

</details>
