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.
| Name | Type | Default | Notes |
|---|---|---|---|
| items | AuraAccordionItem[] | 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. |
| exclusive | boolean | false | The 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. |
| name | string | — | The generated id comes from the framework itself, so it is stable across SSR and hydration. |
| renderPanel | (item: AuraAccordionItem) => ReactNode | — | 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. |
| Name | Type | Default | Notes |
|---|---|---|---|
| items | AuraAccordionItem[] | 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. |
| exclusive | boolean | false | |
| name | string | — | The 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. |
| Name | Type | Default | Notes |
|---|---|---|---|
| items | AuraAccordionItem[] | 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. |
| exclusive | boolean | false | |
| name | string | — | The generated id comes from the framework itself, so it is stable across SSR and hydration. |
| panelslot | Snippet<[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. |
| Name | Type | Default | Notes |
|---|---|---|---|
| items | AuraAccordionItem[] | 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. |
| exclusive | boolean | false | |
| name | string | — | |
| panelTemplate | TemplateRef<AuraAccordionPanelContext> | null | — | 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. |
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.
| Class | Role | Notes |
|---|---|---|
| .aura-accordion | base | Required. A modifier does nothing without it. |
| .aura-accordion__item | element | |
| .aura-accordion__summary | element | |
| .aura-accordion__panel | element |
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