Collapse
A row that opens. It holds no state, registers no key handler and measures no heights: <details> owns the toggle, <summary> owns the keyboard and the focus, and the open/close animation is a CSS transition on a pseudo-element the browser hands over.
- Stable
- React 19
- Zero JS
- Native details
General
Appearance
General
Code
<Collapse title="General">Language, region and the login items.</Collapse><details class="wayra-collapse" data-variant="boxed" data-marker="chevron"> <summary>General</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details>title is the header and becomes the whole content of the <summary>; the
children are the panel. There is no Collapse.Title and no Collapse.Content
— the element pair already says which is which.
Variants
Section titled “Variants”boxed
plain
Code
<Collapse title="boxed">…</Collapse><Collapse variant="plain" title="plain">…</Collapse><details class="wayra-collapse" data-variant="boxed" data-marker="chevron"> <summary>boxed</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details><details class="wayra-collapse" data-variant="boxed" data-marker="chevron" data-variant="plain"> <summary>plain</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details>boxed is the settings-group card: hairline, --wayra-radius-box, and the
surface painted in base-100. plain drops the box for a dense list whose
container draws the separators — which is exactly what
Accordion does.
Markers
Section titled “Markers”chevron
plus
none
plus, open
Code
<Collapse title="chevron">…</Collapse><Collapse marker="plus" title="plus">…</Collapse><Collapse marker="none" title="none">…</Collapse><details class="wayra-collapse" data-variant="boxed" data-marker="chevron"> <summary>chevron</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details><details class="wayra-collapse" data-variant="boxed" data-marker="chevron" data-marker="plus"> <summary>plus</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details><details class="wayra-collapse" data-variant="boxed" data-marker="chevron" data-marker="none"> <summary>none</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details><details class="wayra-collapse" data-variant="boxed" data-marker="chevron" data-marker="plus" open> <summary>plus, open</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details>Two pseudo-elements on the summary, so the marker costs no DOM and no icon
file. The chevron turns half a circle and the plus loses its upright
bar; both move through a single compositable property (rotate, scale), the
rule the whole library animates by.
Right to left, the marker moves to the inline end — the left — and the chevron still points down, then up. Its two strokes are drawn on physical sides on purpose: up and down do not mirror, and logical sides would turn the arrow to face along the line.
Open, name and onToggle
Section titled “Open, name and onToggle”General
Code
<Collapse open title="General">…</Collapse>
<Collapse onToggle={(event) => console.log(event.currentTarget.open)}>…</Collapse><details class="wayra-collapse" data-variant="boxed" data-marker="chevron" open> <summary>General</summary> <div class="wayra-collapse-content">Language, region and the login items.</div></details>These are not library props. open is the DOM attribute, onToggle is the DOM
event, and name is what makes a group exclusive — all three come from
ComponentProps<"details"> and are passed straight through. That is why there
is no defaultOpen, and no onOpen / onClose pair: the element already
reports its own state.
The height animation
Section titled “The height animation”The panel does not jump: it grows. The whole thing is CSS, in one of the
library’s five @supports blocks.
@supports selector(::details-content) { .wayra-collapse { interpolate-size: allow-keywords; } .wayra-collapse::details-content { block-size: 0; overflow: hidden; transition: block-size …, opacity …, content-visibility … allow-discrete; } .wayra-collapse[open]::details-content { block-size: auto; }}interpolate-size is declared on the component and never on :root: it
inherits into the pseudo-element from there, and a library has no business
turning on keyword interpolation for the consumer’s whole document.
Styling contract
Section titled “Styling contract”The class and the data-* attributes are the API. The stylesheet is written
against them and the behaviour was the platform’s all along, so hand-authored
markup gets the same row and the same keyboard:
Hand-written
Code
<Collapse title="Hand-written">No component anywhere.</Collapse><details class="wayra-collapse" data-variant="boxed" data-marker="chevron"><summary>Hand-written</summary><div class="wayra-collapse-content">No component anywhere.</div></details>The summary is styled by tag (.wayra-collapse > summary), so there is no
class to remember for it; the panel wrapper carries
.wayra-collapse-content, and it is a real element rather than
::details-content because the pseudo-element does not exist below the
animation tier and the padding has to survive there too. unstyled drops both
classes and keeps the data-* attributes.
API reference
Section titled “API reference”CollapseProps extends ComponentProps<"details"> minus title, which here
is the header rather than the native tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"boxed" | "plain" |
"boxed" |
Card with a hairline, or a bare row. |
marker |
"chevron" | "plus" | "none" |
"chevron" |
The state indicator, drawn in CSS. |
title |
ReactNode |
— | The content of the <summary>. |
headingLevel |
2 | 3 | 4 | 5 | 6 |
— | Wraps the title in a heading, inside the summary. |
children |
ReactNode |
— | The panel. |
unstyled |
boolean |
false |
Drops the classes, keeps the data-*. |
There is no color: a disclosure organizes content, it does not report state.
There is no size either — it takes the width of its container and has one
step, like Card.
Accessibility
Section titled “Accessibility”- The keyboard is the platform’s.
<summary>is in the tab order, and Enter and Space toggle it. The component adds notabindex, noaria-expandedand no key handler; adding them would only compete with what the browser already announces. headingLevelputs a heading inside the summary, which is where the content model allows one. Screen reader users then reach each section through heading navigation — the thing the accordion pattern actually asks for. Use it whenever several collapses form a group.- The focus ring is the library’s, drawn on the summary from
styles/states.css, and it falls back to a system outline under forced colours. There, theplusmarker’s two bars, which are painted backgrounds that mode would turn intoCanvas, take the summary’s system text colour. - Reduced motion re-points variables. Every transition in the file reads
--wayra-collapse-duration, soprefers-reduced-motionsets it to0sinstead of trying to out-rank the marker rules withtransition: none— a media query adds no specificity, and that rule would have lost. The panel then opens at once and the markers turn at once, while the content keeps a 160ms fade of its own: a cross-fade, never a cut.