How the Astryx Resizable Component Handles Panel Resizing

The Astryx Resizable component uses the useResizable hook to manage panel resizing through a unified API that handles size clamping, snap points, collapse states, and localStorage persistence for both single and multi-region layouts.

The Astryx design system provides a robust solution for building draggable, collapsible panel interfaces in React applications. According to the facebook/astryx source code, the Resizable component delegates all interaction logic to a specialized hook that supports percentage-based defaults, collapsible regions, and automatic state persistence. This guide examines the internal architecture and implementation patterns that enable smooth panel resizing with snapping and persistence capabilities.

Core Architecture of the Resizable Hook

The resizing system centers on useResizable, a public hook exposed in packages/core/src/Resizable/useResizable.ts that provides two distinct overloads: one for single-region configurations (UseResizableSingleConfig) and one for multi-region layouts (UseResizableMultiConfig). The hook automatically delegates to internal implementations based on the configuration object provided.

Single-Region Resizing with useSingleResizable

For individual panels, the hook utilizes useSingleResizable to manage the complete lifecycle of a resizable region. This internal handler initializes the panel size from default values, persisted localStorage data, or percentage-based calculations, then enforces constraints through minSizePx and maxSizePx boundaries.

The hook tracks collapse state and supplies callback references for the resize-handle lifecycle: _onResizeStart, _onResizeMove, and _onResizeEnd. These callbacks handle drag initiation, delta calculations, and final size commitments, though onResizeEnd is typically a no-op since changes apply during the move phase.

Multi-Region Layout Management

When configuring multiple resizable areas, useMultiResizable iterates over a stable map of region configurations, invoking useSingleResizable for each key. This approach requires a static regions map to guarantee stable hook order across renders, returning a lookup object where each region maintains its own independent state and callback set.

Persistence and State Restoration

The persistence layer utilizes loadPersistedSize and persistSize helpers to store panel dimensions in localStorage under the prefix astryx-resizable:. When an autoSaveId is supplied in the configuration, the hook automatically retrieves previous dimensions on mount and writes updates after each resize operation, enabling panels to restore their exact width after page reloads.

Resize Handle Props and Callback Binding

The hook packages current size, collapse flags, and internal callbacks into a ResizableProps object. These props are deliberately prefixed with _ (such as _size, _minSizePx, _onResizeStart) to maintain a clean public API surface while providing the necessary data for Astryx's built-in ResizeHandle component or custom implementations.

Implementation Details and Edge Cases

Default Size Resolution and Percentage Handling

The resolveDefaultSize utility accepts numeric values, pixel strings (e.g., "260px"), or percentage strings (e.g., "20%"). For percentage values, the system calculates approximate pixel equivalents using window.innerWidth (or a fallback width during SSR), ensuring responsive initial sizing across different viewports.

Clamping and Snap Point Logic

The clampSize function enforces boundary constraints in two phases. First, it restricts the raw value to absolute min and max limits. Then, if snaps are defined, it selects the nearest snap point from the array, guaranteeing the panel never rests between discrete values. This creates a magnetic effect where panels automatically settle into predefined widths.

Collapse and Expand Mechanics

For collapsible panels, the system maintains a preCollapseSizeRef that stores the current size before shrinking to zero. When the collapse function executes, it preserves the previous dimension and sets the size to 0. The expand function restores this saved value (or falls back to the default) and re-applies clamping logic to ensure the panel returns to valid dimensions.

The collapse threshold activates during drag operations: if the drag delta moves the panel below collapsedSize, the hook automatically toggles the isCollapsed flag. Conversely, dragging above the threshold re-expands the panel and restores the saved dimension.

Drag Lifecycle and Delta Processing

During onResizeStart, the hook records the initial size to establish a baseline. onResizeMove processes the drag delta, checks against collapse thresholds, updates the internal size state, and fires external callbacks. Because the system applies size changes in real-time during the move phase, onResizeEnd requires no additional logic beyond optional cleanup.

Practical Implementation Examples

Basic Single-Panel Resizing

The following example demonstrates a vertical side navigation panel with snapping, collapsing, and persistence:

import {useResizable} from '@astryxdesign/core';

function SideNav() {
  const {props, collapse, expand} = useResizable({
    defaultWidth: 260,
    minWidth: 180,
    maxWidth: 480,
    collapsible: true,
    collapsedSize: 40,
    snaps: [180, 260, 360, 480],
    autoSaveId: 'side-nav-width',
    onWidthChange: (w) => console.log('Width changed to', w),
  });

  return (
    <div
      style={{width: props._size, minWidth: props._minSizePx}}
      {...props}
    >
      {/* panel contents */}
      <button onClick={collapse}>Collapse</button>
      <button onClick={expand}>Expand</button>
    </div>
  );
}

The props object spreads _onResizeStart, _onResizeMove, and other internal callbacks onto the container, enabling interaction with Astryx's ResizeHandle component.

Multi-Region Split View Layout

For complex layouts with multiple resizable areas, use the multi-region configuration:

import {useResizable} from '@astryxdesign/core';

function SplitView() {
  const regions = useResizable({
    direction: 'horizontal',
    regions: {
      left: {defaultSize: '30%', minSizePx: 150, collapsible: true},
      right: {defaultSize: '70%', minSizePx: 300},
    },
    autoSaveId: 'splitview',
  });

  return (
    <div style={{display: 'flex'}}>
      <div {...regions.left.props}>Left pane</div>
      <div {...regions.right.props}>Right pane</div>
    </div>
  );
}

Each region maintains independent state while sharing the autoSaveId namespace for coordinated persistence.

Pre-Built Resizable Sidebar Template

The Astryx CLI provides a ready-made component that internally wires the hook:

import ResizableSidebar from '@astryxdesign/cli/assets/templates/blocks/components/Resizable/ResizableSidebar';

export default function App() {
  return <ResizableSidebar autoSaveId="app-sidebar" defaultWidth={260} collapsible />;
}

This template, located at packages/cli/assets/templates/blocks/components/Resizable/ResizableSidebar.tsx, demonstrates production-ready implementation of the resizable patterns.

Key Source Files

The panel resizing logic spans several critical files in the facebook/astryx repository:

Summary

  • Unified Hook Architecture: The useResizable hook in packages/core/src/Resizable/useResizable.ts provides a single entry point for both single and multi-region panel resizing, delegating to useSingleResizable or useMultiResizable based on configuration.

  • Automatic Persistence: Panel dimensions automatically save to localStorage under the astryx-resizable: prefix when autoSaveId is specified, restoring previous sizes across sessions.

  • Constraint Enforcement: The clampSize function enforces minSizePx/maxSizePx boundaries and snaps to predefined points, ensuring panels always rest at valid dimensions.

  • Collapse State Management: Collapsible panels store pre-collapse dimensions in preCollapseSizeRef, enabling full restoration when expanding from a collapsed state.

  • Real-time Updates: Size changes apply during onResizeMove callbacks, providing immediate visual feedback without waiting for drag completion.

Frequently Asked Questions

How does the Astryx Resizable component persist panel sizes across page reloads?

The hook checks for an autoSaveId configuration property and uses loadPersistedSize to retrieve previous dimensions from localStorage on initialization. After each resize operation, persistSize writes the current dimension using the key prefix astryx-resizable:. This occurs automatically without requiring manual intervention from the consuming component.

What is the difference between single-region and multi-region resizing in Astryx?

Single-region mode uses useSingleResizable to manage one independent panel with direct access to collapse, expand, and props methods. Multi-region mode utilizes useMultiResizable to handle multiple panels simultaneously by mapping over a static regions configuration object, returning a dictionary of region-specific hook results while maintaining stable React hook ordering.

How does snapping work when resizing panels near defined snap points?

The clampSize function first applies absolute min/max constraints, then calculates the nearest value from the snaps array if provided. When the user releases the drag handle, the panel automatically animates or snaps to the closest predefined width, preventing the panel from resting at intermediate sizes between snap points.

Can panels use percentage-based default sizes instead of fixed pixels?

Yes, the resolveDefaultSize utility supports percentage strings (e.g., "30%") alongside numeric pixel values. During initialization, the system converts percentages to approximate pixel values using window.innerWidth (or a fallback for SSR), allowing responsive default sizing that adapts to the viewport while maintaining pixel precision during drag operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →