EXEPERTAI LAB

Research alpha

Time Machine
EXEPERTAI LAB
GALLERY / COLLECTION
← Browse Astryx gallery
Resizable·hook·@astryxdesign/core

useResizable

Hook for adding drag-to-resize behavior to layout regions. Supports single-region and multi-region configurations with snap points, collapsible panels, localStorage persistence, and cascade resize ordering.

Open in Playground @astryxdesign/core/Resizable

Signature

Call shape
useResizable<const Config extends UseResizableSingleConfig>(config: SingleResizableArgument<Config>): ResizableRegion
Parameters
NameType and behavior
defaultSizeSizeValue | ResizableSize · optional · 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 one pixel floor or ceiling. A percentage resolves ONCE into pixels — against containerRef when supplied, against the viewport otherwise — and does not track its basis afterwards. The released broad number | string type remains compatible; runtime validation is authoritative.
minSizeResizableSize · optional · default 50
Minimum size. 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. Percentage minimums re-resolve when their basis changes and clamp the current pixel selection.
maxSizeResizableSize · optional · default Infinity
Maximum size. 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. Percentage maximums re-resolve when their basis changes and clamp the current pixel selection.
containerRefRefObject<HTMLElement | null>
The element a percentage is a share of. Caller-owned: the hook never infers one. Omitted, percentages use the viewport, which is the released behaviour. The ref may point at a different element over time — the basis follows it. Until that element is actually laid out (not yet mounted, display:none, detached) percentages use a temporary 1200px basis rather than its zero measurement, and nothing is persisted from it.
direction'horizontal' | 'vertical' · optional · default 'horizontal'
Which axis this region resizes along. Selects the container's inline or block content-box size as the percentage basis, and must match the direction given to ResizeHandle.
collapsibleboolean · optional · default false
Whether dragging below the collapsed threshold collapses the region to zero.
snapsnumber[]
Pixel values to snap to during drag.
autoSaveIdstring
Key for localStorage persistence of size and collapse state across sessions.
defaultIsCollapsedboolean · optional · default false
Initial collapse state (uncontrolled). A persisted entry wins over it.
isCollapsedboolean
Controlled collapse state. collapse(), expand() and a drag past the threshold then report through onCollapseChange instead of changing state internally.
onCollapseChange(isCollapsed: boolean) => void
Called once per collapse state change, via drag or programmatically.
Returns
NameType and behavior
sizenumber
Current size in pixels.
isCollapsedboolean
Whether the region is currently collapsed.
collapse() => void
Programmatically collapse the region.
expand() => void
Expand from collapsed state.
resize(size: number) => void
Resize to a specific pixel value.
propsResizableProps
Props to spread on the resizable component or pass to ResizeHandle.

Showcases and examples

2 documented examples

Resizable

Horizontal resizable split with a draggable handle between two panels.

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 for adding drag-to-resize behavior to layout regions. Supports single-region and multi-region configurations with snap points, collapsible panels, localStorage persistence, and cascade resize ordering.

  • Use percent(40, {min: pixel(333)}) for a 40% size with a 333px floor, or percent(10, {max: pixel(400)}) for a 10% size with a 400px ceiling. The options argument is required and carries a floor XOR a ceiling.
  • A structured default is an initial choice only; a structured minSize or maxSize remains live. State, persistence, callbacks, resize(), paint, and ARIA all use resolved pixel numbers.
  • Import percent and Table’s same pixel binding from @astryxdesign/core/Resizable/utils when constructing configuration in a Server Component; the root package exposes one pixel symbol and one percent symbol without collision.
  • Use with Layout or AppShell sidebar for resizable navigation panels.
  • Set autoSaveId to persist user-chosen sizes across page reloads.
  • Set minSize too small; content becomes unreadable. Prefer collapsible for panels that can hide entirely.