react-collapsible/Guides/Custom transitions
Guides~4 min

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'.

TSXquick-note.tsx
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.

This panel is animating over 500ms. Pick a different duration and toggle again — it applies on the next toggle, no remount needed.

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:

TSXdrawer.tsx
<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:

CSSdrawer.css
#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.

CSSapp.css
/* 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.

TSXcard.tsx
<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:

CSSapp.css
@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.