You can't animate height: auto — the transition just snaps. Use display: grid with grid-template-rows going from 0fr to 1fr, or measure scrollHeight and animate to a pixel value.
Drive the chevron rotation from the same timing curve as the panel. Even ~10 frames of lag between the two reads as broken, not smooth.
Decide single vs multi open: an accordion lets one panel open at a time, a disclosure lets many stay open. Sequential steps stay single; FAQ lists let several breathe.
The header is a <button>, not a <div>. Wire aria-expanded to reflect state and aria-controls to point at the panel, so Enter/Space toggle it and the focus ring shows.
When an item near the bottom expands, anchor the tapped header so the list doesn't jump under the user, and stagger the revealed content in.
Do / Don't
Do: Drive chevron rotation and panel height from one shared timing curve so they move as a unit
Do: Render the header as a real <button> with aria-expanded and aria-controls wired to the panel
Do: Match open behavior to content — one-at-a-time for steps, many-open for FAQ lists
Don't: Animate height: auto and expect a transition — use grid rows or a measured pixel height
Don't: Let the chevron trail the panel; even a few frames of lag feels janky
Don't: Let the list scroll-jump when a lower item expands — keep the tapped header anchored
The Design System Blueprint
An 11-page visual guide to consistent, scalable UI: colors, typography, spacing, components, motion.