Appearance
useCollapse
Animate an element's height between 0 and its measured content height.
Demo
Usage
tsx
import { useCollapse } from '@reause/core'
import { useState } from 'react'
function Demo() {
const [expanded, setExpanded] = useState(false)
const { state, getCollapseProps } = useCollapse({ expanded })
return (
<>
<button onClick={() => setExpanded(prev => !prev)}>Toggle</button>
<div {...getCollapseProps()}>
<p>Collapsible content</p>
</div>
</>
)
}Important: Re-evaluated on every render. Spread
getCollapseProps()directly on the element instead of storing its result.
Behavior
getCollapseProps(input?)
Returns element props required to control accessibility and height animation: { style, ref, onTransitionEnd, 'aria-hidden', inert }
style: Mergesinput.stylewith internal styles. Internal transition styles (height,overflow,display) overrideinput.style, while preserving customborderandpadding.ref: Merges internal node measurements withinput.ref(supports Callback & Object Ref).onTransitionEnd: Internal transition completion handler.
Warning: Do not override
onTransitionEndwithout chaining the original handler; otherwise,statewill remain stuck inenteringorexiting.
aria-hidden&inert: Set to!expanded. When collapsed, content is hidden from accessibility trees and interactive focus.
Key Options
keepMounted (boolean, default: false)
Controls style behavior when collapsed (does not control DOM unmounting):
| Option | Collapsed Styles | Behavior |
|---|---|---|
false (default) | { height: 0, overflow: 'hidden', display: 'none' } | Removed from layout & rendered tree |
true | { height: 0, overflow: 'hidden' } | Retains layout box in DOM tree |
Unmounting the DOM node remains the responsibility of the consumer.
transitionDuration (number, optional)
If omitted, duration is auto-calculated via getAutoHeightDuration(height) based on scrollHeight:
ts
const constant = height / 36
const duration = Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10)Unmeasurable heights (or non-numeric values) evaluate to 0ms to prevent bogus animation timing.
Internal Technical Notes
- Synchronous Layout Measurement: Uses React DOM's
flushSyncto measure and repaint the element synchronously before applying the exit transition, preventing height-jump glitches during collapse. - Event Handler Stability: Callback props (
onTransitionStart,onTransitionEnd) use latest-value refs rather thanuseEffectEvent, ensuring compatibility with React >= 18.0. - Utilities Exported:
getElementHeight(ref): ReturnsscrollHeightor'auto'.isMeasured(size): Type guard identifying transitionable elements (0is treated as unmeasurable).
Type Declarations
Toggle
ts
export interface UseCollapseInput {
expanded: boolean;
transitionDuration?: number;
transitionTimingFunction?: string;
onTransitionEnd?: () => void;
onTransitionStart?: () => void;
keepMounted?: boolean;
}
export type UseCollapseState = 'entering' | 'entered' | 'exiting' | 'exited'
export interface UseCollapseReturnValue {
state: UseCollapseState;
getCollapseProps: (input?: GetCollapsePropsInput) => GetCollapsePropsReturnValue;
}
interface GetCollapsePropsInput {
style?: CSSProperties;
ref?: Ref<HTMLDivElement>;
}
interface GetCollapsePropsReturnValue {
'aria-hidden': boolean;
'inert': boolean;
'ref': RefCallback<HTMLDivElement>;
'onTransitionEnd': (event: TransitionEvent<Element>) => void;
'style': CSSProperties;
}
export function getElementHeight(elementRef: RefObject<HTMLElement | null>)
export function isMeasured(size: number | string): size is number
export function useCollapse({ transitionDuration, transitionTimingFunction = 'ease', onTransitionEnd, onTransitionStart, expanded, keepMounted, }: UseCollapseInput): UseCollapseReturnValue