Primitives
17

Accordion

Collapsible panels on <details>/<summary> — no state, no handlers, no JavaScript. The native pair already is a button that announces its expanded state, toggles on Enter and Space, and is findable by in-page search: the browser opens a closed panel to reveal a match.

plain markup — no library on the page
rendered by aura-react
rendered by aura-vue
rendered by aura-svelte
built from aura-angular's own class helpers
What is Aura?
Tokens and a stylesheet, wrapped by four thin packages.
Why four packages?
The same classes, so the same picture — whichever framework you already use.
Any dependencies?
None at runtime. The CSS is the component; the wrappers only type it.
What is next?
Published when there is something to publish.
What is Aura?
Tokens and a stylesheet, wrapped by four thin packages.
Why four packages?
The same classes, so the same picture — whichever framework you already use.
Any dependencies?
None at runtime. The CSS is the component; the wrappers only type it.
What is next?
Published when there is something to publish.
What is Aura?
Tokens and a stylesheet, wrapped by four thin packages.
Why four packages?
The same classes, so the same picture — whichever framework you already use.
Any dependencies?
None at runtime. The CSS is the component; the wrappers only type it.
What is next?
Published when there is something to publish.
What is Aura?
Tokens and a stylesheet, wrapped by four thin packages.
Why four packages?
The same classes, so the same picture — whichever framework you already use.
Any dependencies?
None at runtime. The CSS is the component; the wrappers only type it.
What is next?
Published when there is something to publish.
What is Aura?
Tokens and a stylesheet, wrapped by four thin packages.
Why four packages?
The same classes, so the same picture — whichever framework you already use.
Any dependencies?
None at runtime. The CSS is the component; the wrappers only type it.
What is next?
Published when there is something to publish.

Code

index.html
<div class="aura-accordion">
  <details class="aura-accordion__item" open name="faq">
    <summary class="aura-accordion__summary">What is Aura?</summary>
    <div class="aura-accordion__panel">Tokens and a stylesheet, wrapped by four thin packages.</div>
  </details>
  <details class="aura-accordion__item" name="faq">
    <summary class="aura-accordion__summary">Why four packages?</summary>
    <div class="aura-accordion__panel">The same classes, so the same picture — whichever framework you already use.</div>
  </details>
  <details class="aura-accordion__item" name="faq">
    <summary class="aura-accordion__summary">Any dependencies?</summary>
    <div class="aura-accordion__panel">None at runtime. The CSS is the component; the wrappers only type it.</div>
  </details>
  <details class="aura-accordion__item" name="faq" inert aria-disabled="true">
    <summary class="aura-accordion__summary">What is next?</summary>
    <div class="aura-accordion__panel">Published when there is something to publish.</div>
  </details>
</div>
Demo.tsx
import { Accordion } from '@ilomee/aura-react';

const ITEMS = [
  { id: 'what', label: 'What is Aura?', content: 'Tokens and a stylesheet, wrapped by four thin packages.', open: true },
  { id: 'why', label: 'Why four packages?', content: 'The same classes, so the same picture — whichever framework you already use.' },
  { id: 'deps', label: 'Any dependencies?', content: 'None at runtime. The CSS is the component; the wrappers only type it.' },
  { id: 'roadmap', label: 'What is next?', content: 'Published when there is something to publish.', disabled: true },
];

export default function Demo() {
  // `name` is pinned so every stack renders the same markup — left out, each framework mints
  // its own id and the cross-stack diff would have nothing to compare.
  return <Accordion items={ITEMS} exclusive name="faq" />;
}
Demo.vue
<script setup lang="ts">
import { Accordion } from '@ilomee/aura-vue';

const ITEMS = [
  { id: 'what', label: 'What is Aura?', content: 'Tokens and a stylesheet.', open: true },
  { id: 'why', label: 'Why four packages?', content: 'The same classes everywhere.' },
];
</script>

<template>
  <Accordion :items="ITEMS" exclusive />
</template>
Demo.svelte
<script lang="ts">
  import { Accordion } from '@ilomee/aura-svelte';

  const ITEMS = [
    { id: 'what', label: 'What is Aura?', content: 'Tokens and a stylesheet.', open: true },
    { id: 'why', label: 'Why four packages?', content: 'The same classes everywhere.' },
  ];
</script>

<Accordion items={ITEMS} exclusive />

Used as aura-accordion.

demo.component.ts
import { AuraAccordion } from '@ilomee/aura-angular';

@Component({
  standalone: true,
  imports: [AuraAccordion],
  template: `<aura-accordion [items]="items" [exclusive]="true" />`,
})
export class DemoComponent {
  items = [
    { id: 'what', label: 'What is Aura?', content: 'Tokens and a stylesheet.', open: true },
    { id: 'why', label: 'Why four packages?', content: 'The same classes everywhere.' },
  ];
}

A panel's content travels with its item rather than being projected: Angular's <ng-content> renders into the first match only, so a slot per panel would silently drop every panel after the first.

Props

Plain markup has no props — the classes below are the whole API. The four packages do nothing more than set these same classes for you.
NameTypeDefaultNotes
itemsAuraAccordionItem[] | nullEach item is { id, label, content?, open?, disabled? }. A disabled item renders inert plus aria-disabled on its <details> — declarative, because this component owns no JavaScript and <summary> has no disabled of its own.
exclusivebooleanfalseThe browser's own name on <details>: same-named panels close each other, with no state to keep in sync. Where unsupported, every panel opens independently — the content stays reachable.
namestringThe generated id comes from the framework itself, so it is stable across SSR and hydration.
renderPanel(item: AuraAccordionItem) => ReactNodeA panel that needs more than a sentence — a list, a link, a table. Replaces item.content and keeps everything around it: the <details>, the <summary>, the classes and the inert on a disabled item stay the component’s, so the hatch cannot break a panel’s semantics or the cross-stack DOM diff. The same shape renderOption has on Select.
NameTypeDefaultNotes
itemsAuraAccordionItem[] | nullEach item is { id, label, content?, open?, disabled? }. A disabled item renders inert plus aria-disabled on its <details> — declarative, because this component owns no JavaScript and <summary> has no disabled of its own.
exclusivebooleanfalse
namestringThe generated id comes from the framework itself, so it is stable across SSR and hydration.
panelslot{ item: AuraAccordionItem }A panel that needs more than a sentence — a list, a link, a table. Replaces item.content and keeps everything around it: the <details>, the <summary>, the classes and the inert on a disabled item stay the component’s, so the hatch cannot break a panel’s semantics or the cross-stack DOM diff. The same shape renderOption has on Select.
NameTypeDefaultNotes
itemsAuraAccordionItem[] | nullEach item is { id, label, content?, open?, disabled? }. A disabled item renders inert plus aria-disabled on its <details> — declarative, because this component owns no JavaScript and <summary> has no disabled of its own.
exclusivebooleanfalse
namestringThe generated id comes from the framework itself, so it is stable across SSR and hydration.
panelslotSnippet<[AuraAccordionItem]>A panel that needs more than a sentence — a list, a link, a table. Replaces item.content and keeps everything around it: the <details>, the <summary>, the classes and the inert on a disabled item stay the component’s, so the hatch cannot break a panel’s semantics or the cross-stack DOM diff. The same shape renderOption has on Select.
NameTypeDefaultNotes
itemsAuraAccordionItem[] | null[]Each item is { id, label, content?, open?, disabled? }. A disabled item renders inert plus aria-disabled on its <details> — declarative, because this component owns no JavaScript and <summary> has no disabled of its own.
exclusivebooleanfalse
namestring
panelTemplateTemplateRef<AuraAccordionPanelContext> | nullA panel that needs more than a sentence — a list, a link, a table. Replaces item.content and keeps everything around it: the <details>, the <summary>, the classes and the inert on a disabled item stay the component’s, so the hatch cannot break a panel’s semantics or the cross-stack DOM diff. The same shape renderOption has on Select.

Emitted classes

Every framework emits the same classes — that is what makes the four libraries look identical. You can use them directly, without any component library.

ClassRoleNotes
.aura-accordionbaseRequired. A modifier does nothing without it.
.aura-accordion__itemelement
.aura-accordion__summaryelement
.aura-accordion__panelelement

Tokens and rules

components.css
frame
1px solid var(--border) · var(--r2)
chevron
drawn from two borders — no icon set needed
open state
[open] — the browser owns it