Horizontal collapse
One prop turns the collapse sideways: direction="x" animates width instead of height. The component itself doesn't change — same controlled open, same CSS Grid technique — but width brings two layout questions height never asked: what lets the container actually get narrower, and what stops the content from re-wrapping while it does.
What direction="x" changes
The animated track flips. Instead of grid-template-rows going 0fr to 1fr, the outer container animates grid-template-columns between the same two values — and because the inner wrapper has min-width: 0, the shrinking column track can crush the content's width the way the row track crushes its height. It's the technique from how it works, rotated ninety degrees: overflow: hidden clips, @starting-style covers entry, no JavaScript measures anything.
Everything else carries over unchanged — open stays controlled, and transitionDuration, hideMode, and conditional classes behave exactly as they do vertically.
Width needs a flex parent
Vertical collapse works anywhere because a div's height is content-driven: when the row track crushes the content, the box gets shorter and everything below moves up. Width doesn't work that way. In normal block flow a div stretches to its parent's full width regardless of what's inside it — the column track can animate to 0fr and the content disappears, but the box stays full-width until the transition ends and display: none snaps it out of layout. You get a vanishing act inside an empty strip, then a jump — not a collapse.
The fix is to let the container shrink-wrap its track. A flex row does exactly that: flex items size to their content by default, so as the track narrows, the container narrows with it and siblings reflow into the freed space. Put the Collapsible in a display: flex parent — the only layout change horizontal collapse typically needs.
import { useState } from 'react' import { Collapsible } from '@westopp/react-collapsible' const Drawer = () => { const [open, setOpen] = useState(true) return ( // The flex row lets the Collapsible shrink-wrap its animating track. <div style={{ display: 'flex' }}> <Collapsible id="horizontal-collapse-drawer" open={open} direction="x"> <nav style={{ whiteSpace: 'nowrap', padding: 16 }}>Drawer content</nav> </Collapsible> <main style={{ flex: 1, minWidth: 0 }}> <button type="button" aria-controls="horizontal-collapse-drawer" aria-expanded={open} onClick={() => setOpen((o) => !o)} > {open ? 'Hide drawer' : 'Show drawer'} </button> </main> </div> ) }
Keep the content stable
During the animation the inner wrapper gets narrower frame by frame, and anything width-dependent recalculates at every one of those frames. Text is the worst offender: each frame it re-wraps into a narrower, taller column, so the panel shudders shut instead of sliding. Toggle the checkbox below and replay the animation to see the difference.
Content area — it grows as the panel gives up width.
Both fixes are the same idea — make the content's width independent of the track, so the track slides across stable content and the container's overflow: hidden clips it. Use white-space: nowrap for nav labels and short lines, or a fixed width for anything with real layout. The nowrap in the drawer sample above is doing exactly this job.
// Short lines: stop them wrapping, let the track clip them. <nav style={{ whiteSpace: 'nowrap' }}>...</nav> // Real layouts: pin the width so the panel slides over stable content. <div style={{ width: 280 }}>...</div>
The sidebar pattern
Put it together and you have the collapsible rail: a flex row where the Collapsible is the sidebar and the main content grows with flex: 1. As the rail collapses, main absorbs the width continuously — the page reflows with the animation rather than snapping when it ends. initialOpen — alongside state that starts true — renders the rail open on first paint without playing the entry animation, and minWidth: 0 on main lets it shrink below its content's natural width when the rail reopens.
The main area grows with flex-1, so every pixel the rail gives up lands here — the page reflows with the animation instead of after it.
import { useState } from 'react' import { Collapsible } from '@westopp/react-collapsible' const AppShell = () => { const [open, setOpen] = useState(true) return ( <div style={{ display: 'flex', minHeight: '100vh' }}> <Collapsible id="horizontal-collapse-rail" open={open} direction="x" initialOpen> <nav style={{ height: '100%', whiteSpace: 'nowrap', padding: 16 }}> {/* nav links */} </nav> </Collapsible> <main style={{ flex: 1, minWidth: 0 }}> <button type="button" aria-controls="horizontal-collapse-rail" aria-expanded={open} onClick={() => setOpen((o) => !o)} > {open ? 'Hide navigation' : 'Show navigation'} </button> {/* page content */} </main> </div> ) }