Skip to content

Collapse and disclosure chevrons

The page below is the real demo.html from the kit. Use its “Toggle dark” button for the dark theme.

Current rule (v2.0.1, 2026-11-13): The decision-399 version reserves the content footprint and animates the inner opacity/transform, avoiding grid/height animation and layout shift. Closed content remains mounted but inert and aria-hidden. Toggle reversals retarget the same transition, with base enter and base × .75 exit. This supersedes the retained grid-row/unmount recipe below. Where compact document flow is required, change layout instantly with a native disclosure; do not tween layout. Supersedes the retained implementation/timing claims below; see docs/current-rules.md#cr-motion-01 and motion-flow.

Opening or closing a group animates its height through grid rows (1fr to 0fr, never height) with a fade, and its arrow turns with it. The inner padding and negative margin keep focus rings of edge items from being clipped.

Open demo.html in a browser (it links ../../tokens/tokens.css) and use “Toggle dark” to check both themes.

File What it is
collapse.css The .sw-collapse grid-rows animation, verbatim, plus the chevron rotation rules the app writes as Tailwind classes (.sw-chevron* are the CSS equivalents).
demo.html A standalone page with the live animation in light and dark, replayable.
react/ Copies of the real app files (see react/README.md for the imports to replace).
previews/ Animated WebP and GIF previews, light and dark (regenerated by scripts/make-previews.mjs).
  • components/ui/Collapse.tsx (used by components/shell/SidebarSection.tsx “More” and components/shell/Disclosure.tsx).
  • Chevrons: SidebarSection.tsx (180 deg), components/ui/CollapsibleSection.tsx and components/card-designer/InspectorSection.tsx (90 deg), features/hub/landing/FaqSection.tsx (plus to 45 deg), features/docs/docs.css (sw-doc-nav-chevron).

Opening a group keeps its neighbours in view and shows what moved; the turning arrow reports the state before the content finishes moving. Closing is faster than opening.

Current rule (v2.0.1, 2026-11-13): The retained table/snippet below records the original app copy. Use the current executable files and the complete flow recipe for decision 399; see current rules.

Part Value
Open grid rows 0fr to 1fr and opacity, --sw-dur-base 180 ms, --sw-ease-standard
Close to 0fr and opacity 0, --sw-dur-fast 120 ms, --sw-ease-exit, forwards
Chevrons transform 180 ms (sidebar, sections), 200 ms (FAQ plus), 160 ms (docs nav)

The open prop or aria-expanded toggles data-state=“open|closed”; usePresence` keeps the region mounted for the 120 ms exit and makes it inert, so collapsed content is never reachable by Tab.

The collapse animation is inside no-preference, so the region opens and closes at once; chevrons jump to their final angle.

None: colour-neutral.

  • Do animate grid rows (1fr to 0fr), not height.
  • Do keep aria-controls pointing at a wrapper that always exists.
  • Do make closed content unreachable (unmount, or inert while closing).
  • Do not stagger children of an opening group.

Current rule (v2.0.1, 2026-11-13): The retained table/snippet below records the original app copy. Use the current executable files and the complete flow recipe for decision 399; see current rules.

<button aria-expanded="true" aria-controls="more">More <svg class="sw-chevron sw-chevron-flip">…</svg></button>
<div id="more" class="sw-collapse" data-state="open"><div class="sw-collapse-inner">…items…</div></div>

To close set data-state="closed" (and inert); hide or remove it on animationend.

Every other animation in the product, and the rules behind them: ../../docs/motion.md. Index of the kit: ../README.md.

The decision-399 version reserves the content footprint and animates the inner opacity/transform, avoiding grid/height animation and layout shift. Closed content remains mounted but inert and aria-hidden. Toggle reversals retarget the same transition, with base enter and base × .75 exit. This supersedes the retained grid-row/unmount recipe below. Where compact document flow is required, change layout instantly with a native disclosure; do not tween layout.

Complete rules, sequencing, keyboard and verification. The demo includes real static posters and explicit image Play/Pause controls.

These aliases are local additions; global tokens are unchanged. Promote through the token batch when adopted.

Local custom property Definition
--sw-collapse-exit calc(var(--sw-dur-base) * .75)
--sw-local-duration-160 calc(var(--sw-dur-base) * 0.88888889)
--sw-local-duration-200 calc(var(--sw-dur-base) * 1.11111111)

Copied from motion/collapse/ on every build: the stylesheet, demo.html, react/ copies of the app files and the previews/.