Accordion
A disclosure list built entirely on native <details>/<summary> elements — zero JavaScript, zero
ARIA reinvention. The browser supplies the disclosure role, aria-expanded, Enter/Space toggling,
focus, and find-in-page expansion for free. Single-open (exclusive) mode uses the platform
exclusive-accordion feature (Baseline 2024): every <details> shares one auto-generated name, so
the browser closes siblings when one opens. multiple drops the shared name so each item opens
independently.
The examples are organised by layer: the class reference is the standalone
@oriui/css layer, and the Framework API is the @oriui/vue component. Every
example is live — flip its code between HTML (the standalone classes, also your htmx / Astro / Svelte /
plain-HTML usage), Vue; HTML is the default.
Classes
The accordion is a block class plus single-class token utilities — one class repoints one token, no base class needed. The Vue props in Framework API map 1:1 to these.
| Class | Type | Description |
|---|---|---|
ori-accordion | Block | The accordion container — a wrapper |
ori-color_* | Color | primary · secondary · success · warn · danger · info · surface |
ori-size-radius_* | Radius | zero · xs · sm · md · lg · xl · rounded |
ori-accordion__item | Part | A native element, one per item. Open state is the native open attribute, styled via details[open]. |
ori-accordion__trigger | Part | The native ; the default marker is suppressed and a custom chevron added; focus ring via :focus-visible. |
ori-accordion__title | Part | The item title text; takes the accent colour when its item is open. |
ori-accordion__icon | Part | The decorative chevron (aria-hidden); rotates when its item is open. |
ori-accordion__panel | Part | The disclosure body wrapper that holds the panel content. |
aria-disabled + tabindex | State | A disabled trigger carries real aria-disabled and tabindex=-1 attributes, not classes. |
Anatomy
div.ori-accordion (wrapper; carries the color + optional radius utilities)
details.ori-accordion__item (one per item; browser owns open attribute)
summary.ori-accordion__trigger
span.ori-accordion__title (heading text)
svg.ori-accordion__icon (decorative chevron; aria-hidden)
div.ori-accordion__panel (default scoped slot body)
Basic
A single-open accordion (default). Opening one item closes the previous one — enforced natively by
the shared name attribute, no JavaScript required.
What is oriUI?
How do I install it?
Does it work without Vue?
<!-- Single-open: give every <details> the same name. -->
<div class="ori-accordion ori-color_primary">
<details class="ori-accordion__item" name="faq">
<summary class="ori-accordion__trigger">
<span class="ori-accordion__title">What is oriUI?</span>
<svg class="ori-accordion__icon" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="m6 9 6 6 6-6" />
</svg>
</summary>
<div class="ori-accordion__panel">
<p>oriUI is a layered Vue 3 component library.</p>
</div>
</details>
<!-- repeat for each item, same name="faq" -->
</div>
Multiple open
multiple lets any number of items be open simultaneously — each <details> opens and closes
independently.
Step 1 — Install
Step 2 — Configure
Step 3 — Deploy
<!-- Multiple: omit the shared name so items open independently. -->
<div class="ori-accordion ori-color_primary">
<details class="ori-accordion__item">
<summary class="ori-accordion__trigger">
<span class="ori-accordion__title">Step 1 — Install</span>
<svg class="ori-accordion__icon" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="m6 9 6 6 6-6" />
</svg>
</summary>
<div class="ori-accordion__panel"><p>Details for Step 1 — Install.</p></div>
</details>
<!-- repeat without name for each item -->
</div>
Colors
Every semantic role. The accent color is applied to the open item's title and the rotating chevron.
Primary accent
Secondary accent
Success accent
Danger accent
Info accent
<!-- swap the color class: ori-color_primary → _secondary / _success / _danger / _info -->
<div class="ori-accordion ori-color_success">…</div>
Radius
Corner rounding via the radius prop. When omitted, the container uses the baked default md (the
.ori-accordion block bakes --ori-size-radius: md, so a bare block is never square).
zero — no rounding
sm — subtle rounding
md — medium rounding
lg — large rounding
xl — extra-large rounding
<!-- attach the radius class: ori-size-radius_md -->
<div class="ori-accordion ori-color_primary ori-size-radius_md">…</div>
Disabled items
Set disabled: true on individual items in the items array to block interaction on those
triggers. Disabled items render aria-disabled="true" and tabindex="-1" on their <summary>.
Billing
Security (restricted)
Notifications
<!-- disabled trigger: aria-disabled + tabindex=-1 on the <summary> -->
<details class="ori-accordion__item" name="settings">
<summary class="ori-accordion__trigger" aria-disabled="true" tabindex="-1">
<span class="ori-accordion__title">Security (restricted)</span>
<svg class="ori-accordion__icon" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="m6 9 6 6 6-6" />
</svg>
</summary>
<div class="ori-accordion__panel"></div>
</details>
Common patterns
Colors, radius, and rich slot content together — a common FAQ or settings pattern.
Is oriUI free to use?
Which frameworks are supported?
How does theming work?
<div class="ori-accordion ori-color_secondary ori-size-radius_md">
<details class="ori-accordion__item" name="faq">
<summary class="ori-accordion__trigger">
<span class="ori-accordion__title">Is oriUI free to use?</span>
<svg class="ori-accordion__icon" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="m6 9 6 6 6-6" />
</svg>
</summary>
<div class="ori-accordion__panel">
<p>Yes — MIT licensed. Use it in personal and commercial projects.</p>
</div>
</details>
<!-- repeat for each item -->
</div>
Accessibility
The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behaviour.
- Built entirely on native
<details>/<summary>: the browser suppliesrole="group"on the<details>androle="button"witharia-expandedon the<summary>— no ARIA reinvention. - Single-open mode is enforced by the platform exclusive-accordion (shared
nameattribute, Baseline 2024), not script — the browser closes siblings automatically. - Disabled items use
aria-disabled="true"andtabindex="-1"on the<summary>(state via real attributes, styled with attribute selectors). - The chevron SVG is
aria-hidden="true"— it is purely decorative; the title text is the accessible name for each section. - Focus is always visible via
:focus-visibleon the<summary>trigger (inset outline so it shows on the row); hover is gated behind@media (hover: hover).
| Key | Action |
|---|---|
Enter | Toggles the focused disclosure item open or closed (native). |
Space | Toggles the focused disclosure item open or closed (native). |
Tab | Moves focus to the next focusable element inside or after the accordion. |
Framework API
The props, events, slots, and standalone CSS surface of the Vue component. The standalone CSS layer has no component API — its surface is the classes above. (Svelte bindings are planned.)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
color | ThemeColor | 'primary' | Accent color for the open item's title and the rotating chevron. Repoints --ori-color via the ori-color_<color> utility class on the wrapper. |
items | Array<{ value: string | number; title: string; disabled?: boolean }> | — | Required. The disclosure items. value is used as the :key; title is the summary text; disabled sets aria-disabled="true" + tabindex="-1" and blocks pointer events on that trigger. |
multiple | boolean | false | false = exclusive single-open (shared name, browser closes siblings). true = each item opens/closes independently (no shared name). |
radius | RadiusSize | 'md' | Optional corner radius for the accordion container. Attaches ori-size-radius_<radius> when set; the bare block bakes in md as its default. |
Events & attributes
OriAccordion declares no custom emits. Open state is owned by the browser's native
<details> mechanism — there is no v-model and no Vue toggle. The component does not set
inheritAttrs: false, so extra attributes (class, data-*, id, …) fall through to the root
div.ori-accordion.
Slots
| Slot | Scope | Description |
|---|---|---|
title | { item: { value: string | number; title: string; disabled?: boolean } } | Scoped per-item trigger content, rendered inside each item's .ori-accordion__title. Receives the current item; falls back to the item.title string. |
default | { item: { value: string | number; title: string; disabled?: boolean } } | Scoped per-item panel body, rendered inside each item's .ori-accordion__panel. Receives the current item; falls back to an empty panel if unused. |