Custom transitions
The collapse animation is one CSS transition on the panel's outer container. That makes retuning it ordinary CSS work: a prop for duration, inline style or a selector for easing, and your own utilities for whatever the content does on the way in.
Duration, per instance
Out of the box every Collapsible animates over 500ms — a number that comes from the stylesheet, not the component. To change it for one instance, pass transitionDuration with any CSS time value: '150ms', '0.5s', '2s'.
import { useState } from 'react' import { Collapsible } from '@westopp/react-collapsible' const QuickNote = () => { const [open, setOpen] = useState(false) return ( <> <button aria-controls="custom-transitions-note" aria-expanded={open} onClick={() => setOpen((o) => !o)} > Toggle note </button> <Collapsible id="custom-transitions-note" open={open} transitionDuration="150ms"> <p>This note opens and closes in 150ms.</p> </Collapsible> </> ) }
The value is inlined as transition-duration on the outer container, so it needs no CSS, outranks every stylesheet rule, and wins over a transitionDuration set through style — the prop is applied last on purpose. It covers both properties the stylesheet transitions — the grid track and the discrete display (or visibility) swap — so the hide stays in step with the size animation at any speed.
Same panel, three durations — switch and toggle to feel the difference. 150ms reads as instant; 1.5s is slow enough to watch the grid track sweep.
Easing, and everything else
Duration gets a prop because it is the knob everyone reaches for. Everything else rides on one fact: the outer container is the transitioning element, and it carries both your id and your style prop. Per-instance easing is an inline style away:
<Collapsible id="custom-transitions-drawer" open={open} transitionDuration="300ms" style={{ transitionTimingFunction: 'ease-in-out' }} > <DrawerContent /> </Collapsible>
Prefer stylesheets? The id lands on that same element, so a selector reaches anything an inline style can:
#custom-transitions-drawer { transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1); }
Retuning every panel at once
The generated class names are stable API — the full list is on CSS classes — so a site-wide retune is a normal override. Target the .collapsible-y / .collapsible-x family, including the -hidden variants if you use hideMode='hide', and load the rule after styles.css: the selectors have equal specificity, so order decides.
/* After @westopp/react-collapsible/styles.css */ .collapsible-y, .collapsible-x, .collapsible-y-hidden, .collapsible-x-hidden { transition-duration: 250ms; transition-timing-function: ease-out; }
A per-instance transitionDuration prop still beats this — it is an inline style, and inline wins.
Choreographing the content
The panel resizing is half the picture; what the content does during it is the other half. Conditional classes — the open: and closed: prefixes in className — flip on the same deferred tick as the collapse animation. Give the content its own transition utilities and they animate alongside the track instead of snapping ahead of it. A transition-delay staggers the two: here the fade trails the panel by 150ms, so the text appears once there is room for it.
<Collapsible id="custom-transitions-card" open={open} className="transition-opacity duration-300 delay-150 closed:opacity-0" > <Card /> </Collapsible>
Respecting reduced motion
styles.css ships no prefers-reduced-motion rule — motion policy stays yours to set, not the library's. Honoring it is three lines:
@media (prefers-reduced-motion: reduce) { .collapsible-y, .collapsible-x, .collapsible-y-hidden, .collapsible-x-hidden { transition-duration: 0s !important; } }
Zeroing transition-duration keeps every behavior — panels still open, close, and hide — while removing the movement. The !important is load-bearing: transitionDuration is applied inline, and only an important declaration outranks an inline style, so the rule holds even for panels with custom durations.