Basic toggle
The smallest useful Collapsible. One useState, one wired button, one panel — with a content fade that runs in sync with the collapse. Copy this file when you need a disclosure and nothing else.
When to reach for basic-toggle
This is the shape for a single, self-contained disclosure — a "show more" under a teaser, an optional settings block, an explainer panel. Everything lives in one component: the useState that owns the open state, the button that flips it, and the Collapsible that animates it. Because the component is fully controlled, the toggle is yours to replace — swap useState for a store or a URL param and nothing else changes. The moment several panels need to coordinate — open one, close the rest — you've outgrown the single toggle; see the FAQ accordion for that shape.
import { Collapsible } from '@westopp/react-collapsible' import { useState } from 'react' // Tailwind note: closed: is a library prefix, not a Tailwind variant, so the // bare utilities it toggles must appear somewhere Tailwind scans for them to // be emitted: opacity-0 opacity-100 export const BasicToggle = () => { const [open, setOpen] = useState(false) return ( <div className='rounded-(--radius-callout) border border-(--color-line) bg-(--color-bg-elev) p-5'> <button type='button' aria-controls='basic-toggle-panel' aria-expanded={open} onClick={() => setOpen(!open)} className='cursor-pointer rounded-full bg-(--color-accent) px-[18px] py-[8px] text-[14px] font-medium text-(--color-accent-ink)' > {open ? 'Hide details' : 'Show details'} </button> <Collapsible id='basic-toggle-panel' open={open} transitionDuration='300ms' className='transition-opacity duration-300 open:opacity-100 closed:opacity-0' > <p className='pt-4 text-[14px] leading-relaxed text-(--color-ink-soft)'> This is the whole pattern: state in the parent, a wired trigger, and a panel that animates open and closed in pure CSS. The text you are reading fades in sync with the collapse, courtesy of the conditional classes. </p> </Collapsible> </div> ) }
What it covers
- Controlled open state — the parent's
useStateowns the boolean;Collapsiblerenders whatever it's handed and holds no state of its own. - A wired trigger — the button carries
aria-controlsmatching the panel'sidandaria-expandedmirroring the state. Dev builds warn if the wiring is missing. - A conditional fade —
open:opacity-100 closed:opacity-0flip on the same RAF-deferred state that drives the[open]attribute, so the fade transitions in sync with the height animation instead of snapping. The conditional classes page covers the prefixes in full. - A comfortable duration —
transitionDuration='300ms'overrides the stylesheet's 500ms default, and the fade'sduration-300matches it so nothing trails behind.