Hook-based resizable panel system. useResizable() manages size state and ResizeHandle provides the interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.
Preview loads on approachPreview loads on approach
Exact source · resizable-showcase
// Copyright (c) Meta Platforms, Inc. and affiliates.
'use client';
import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
import {
Card,
Layout,
LayoutContent,
LayoutPanel,
VStack,
} from '@astryxdesign/core/Layout';
import {Text, Heading} from '@astryxdesign/core/Text';
export default function ResizableShowcase() {
const sidebar = useResizable({
defaultSize: 200,
minSize: 120,
maxSize: 400,
});
return (
<Card variant="muted" height={280} width={600}>
<Layout
height="fill"
start={
<>
<LayoutPanel width={sidebar.size} hasDivider={false}>
<VStack gap={2}>
<Heading level={4}>Sidebar</Heading>
<Text color="secondary">{Math.round(sidebar.size)}px wide</Text>
</VStack>
</LayoutPanel>
<ResizeHandle
direction="horizontal"
hasDivider
resizable={sidebar.props}
label="Resize sidebar"
/>
</>
}
content={
<LayoutContent>
<VStack gap={2}>
<Heading level={4}>Content</Heading>
<Text color="secondary">
Drag the handle to resize the sidebar.
</Text>
</VStack>
</LayoutContent>
}
/>
</Card>
);
}
Usage
Hook-based resizable panel system. useResizable() manages size state and ResizeHandle provides the interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.
Use percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) when a percentage needs exactly one pixel floor or ceiling. Reuse Table’s pixel() helper; numbers, exact Npx, and pixel(value) remain pixels. State, persistence, callbacks, paint, and ARIA remain resolved pixels.
Use useResizable() with existing Astryx layout components. Pass the returned props to the resizable prop on LayoutPanel or SideNav.
Provide an accessible label on each ResizeHandle when multiple handles exist (e.g. "Resize sidebar", "Resize terminal").
Wrap panels in extra container components for resize. The hook-first architecture avoids extra DOM; use it directly on existing components.
Typed props
useResizable
Prop
Type and behavior
defaultSize
SizeValue | ResizableSize · default 250
Initial size. Numbers, exact Npx, and pixel(value) are pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one and resolves once.
minSize
ResizableSize · default 50
Live minimum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one.
maxSize
ResizableSize · default Infinity
Live maximum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one.
collapsible
boolean · default false
Whether the region can collapse to size 0.
collapsedSize
number · default 40
Pixel threshold that triggers collapse during drag.
snaps
number[]
Pixel values to snap to during resize.
shrinkOrder
number
Cascade priority: lower number shrinks first.
autoSaveId
string
Key for persisting sizes and collapse state to localStorage.
ResizeHandle
Prop
Type and behavior
direction
'horizontal' | 'vertical' · default 'horizontal'
Layout direction: determines cursor and indicator orientation.
isReversed
boolean · default false
Reverse drag direction. Use when the handle controls a panel on the end/right/bottom side.
isDisabled
boolean · default false
Whether the handle is interactive.
hasDivider
boolean · default false
Show a full-length 1px divider line through the handle. Use when adjacent panels share the same background.
isAlwaysVisible
boolean · default true
Show the pill grip at rest instead of only on hover. Use when discoverability is important.
Which side of the divider the pill sits on. auto = content side (derived from isReversed), flips when collapsed. start = left/top, end = right/bottom, center = centered on divider.
label
string · default 'Resize handle'
Accessible label for the separator.
resizable
ResizableProps · required
Resize props from useResizable: connects handle to panel. Carries the region's axis ('horizontal' | 'vertical'), which must match this handle's direction.
children
ReactNode
Custom handle content. Overrides the default pill + divider.
xstyle
StyleXStyles
StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}.
Anatomy
Handle · required
Focusable separator that owns pointer and keyboard resize interaction.
Grip pill · optional
Default visible grip indicator; custom handle content can replace it.
Grab zone · required
Invisible enlarged pointer region aligned with the grip or divider.