oriUI

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.

ClassTypeDescription
ori-accordionBlockThe accordion container — a wrapper
that carries the colour and optional radius utilities; hairline border with clipped corners.
ori-color_*Colorprimary · secondary · success · warn · danger · info · surface
ori-size-radius_*Radiuszero · xs · sm · md · lg · xl · rounded
ori-accordion__itemPartA native
element, one per item. Open state is the native open attribute, styled via details[open].
ori-accordion__triggerPartThe native ; the default marker is suppressed and a custom chevron added; focus ring via :focus-visible.
ori-accordion__titlePartThe item title text; takes the accent colour when its item is open.
ori-accordion__iconPartThe decorative chevron (aria-hidden); rotates when its item is open.
ori-accordion__panelPartThe disclosure body wrapper that holds the panel content.
aria-disabled + tabindexStateA disabled trigger carries real aria-disabled and tabindex=-1 attributes, not classes.

Anatomy

text
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?
html
<!-- 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>
vue
<OriAccordion
    :items="[
        { value: 'one', title: 'What is oriUI?' },
        { value: 'two', title: 'How do I install it?' },
        { value: 'three', title: 'Does it work without Vue?' }
    ]"
>
    <template #default="{ item }">
        <p v-if="item.value === 'one'">oriUI is a layered Vue 3 component library.</p>
        <p v-else-if="item.value === 'two'">Run <code>npm i @oriui/vue</code>.</p>
        <p v-else>Yes — import <code>@oriui/css</code> and use the ori-* classes anywhere.</p>
    </template>
</OriAccordion>

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
html
<!-- 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>
vue
<OriAccordion
    multiple
    :items="[
        { value: 'step1', title: 'Step 1 — Install' },
        { value: 'step2', title: 'Step 2 — Configure' },
        { value: 'step3', title: 'Step 3 — Deploy' }
    ]"
>
    <template #default="{ item }">
        <p>Details for {{ item.title }}.</p>
    </template>
</OriAccordion>

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
html
<!-- swap the color class: ori-color_primary → _secondary / _success / _danger / _info -->
<div class="ori-accordion ori-color_success">…</div>
vue
<OriAccordion color="primary" :items="items" />
<OriAccordion color="secondary" :items="items" />
<OriAccordion color="success" :items="items" />
<OriAccordion color="danger" :items="items" />
<OriAccordion color="info" :items="items" />

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
html
<!-- attach the radius class: ori-size-radius_md -->
<div class="ori-accordion ori-color_primary ori-size-radius_md">…</div>
vue
<OriAccordion radius="zero" :items="items" />
<OriAccordion radius="md" :items="items" />
<OriAccordion radius="xl" :items="items" />

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
html
<!-- 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>
vue
<OriAccordion
    :items="[
        { value: 'billing', title: 'Billing' },
        { value: 'security', title: 'Security (restricted)', disabled: true },
        { value: 'notifications', title: 'Notifications' }
    ]"
/>

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?
html
<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>
vue
<OriAccordion
    color="secondary"
    radius="md"
    :items="[
        { value: 'cost', title: 'Is oriUI free to use?' },
        { value: 'framework', title: 'Which frameworks are supported?' },
        { value: 'theming', title: 'How does theming work?' }
    ]"
>
    <template #default="{ item }">
        <p v-if="item.value === 'cost'">
            Yes — MIT licensed. Use it in personal and commercial projects.
        </p>
        <p v-else-if="item.value === 'framework'">
            Vue 3 via <code>@oriui/vue</code>; any framework or plain HTML via
            <code>@oriui/css</code>.
        </p>
        <p v-else>
            Zero-runtime: toggle a class or attribute and a CSS custom property does the rest.
        </p>
    </template>
</OriAccordion>

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 supplies role="group" on the <details> and role="button" with aria-expanded on the <summary> — no ARIA reinvention.
  • Single-open mode is enforced by the platform exclusive-accordion (shared name attribute, Baseline 2024), not script — the browser closes siblings automatically.
  • Disabled items use aria-disabled="true" and tabindex="-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-visible on the <summary> trigger (inset outline so it shows on the row); hover is gated behind @media (hover: hover).
KeyAction
EnterToggles the focused disclosure item open or closed (native).
SpaceToggles the focused disclosure item open or closed (native).
TabMoves 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

PropTypeDefaultDescription
colorThemeColor'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.
itemsArray<{ 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.
multiplebooleanfalsefalse = exclusive single-open (shared name, browser closes siblings). true = each item opens/closes independently (no shared name).
radiusRadiusSize'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

SlotScopeDescription
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.