How to Implement Resizable Panels Using the Astryx Resizable Component

Astryx provides a hook-first API for building resizable panels through the useResizable hook and ResizeHandle component, letting you add drag-to-resize behavior to any existing layout component with minimal markup.

The Astryx design system offers a lightweight, accessible solution for resizable panels that integrates directly with layout primitives like SideNav and LayoutPanel. Rather than wrapping components in heavy container elements, you connect resize behavior through a shared props object—keeping your React tree clean and your bundle size small. This guide covers the complete implementation based on the Facebook Astryx source code.

Core Architecture of Astryx Resizable Panels

Astryx resizable panels rely on three coordinated pieces:

The hook performs all state calculations, leaving components to render purely based on the provided props.

Configuring the useResizable Hook

Before rendering, define your resizable region's behavior through the hook's configuration object:

Property Type Purpose
defaultWidth / defaultHeight number Initial size in pixels (use width for horizontal, height for vertical splits).
minWidth / minHeight number Lower bound the user cannot drag below.
maxWidth / maxHeight number Upper bound the user cannot drag above.
collapsible boolean Whether the panel can collapse to its collapsedSize.
collapsedSize number Width/height when collapsed (default: 0).
snaps number[] Discrete widths/heights the panel jumps to during drag.
autoSaveId string Unique key for persisting size in localStorage (prefixed with astryx-resizable:).
direction 'horizontal' | 'vertical' Orientation for multi-region layouts.

The hook returns an object containing:

  • size – Current numeric size in pixels.
  • isCollapsed – Boolean collapse state.
  • collapse() / expand() – Programmatic control methods.
  • resize(newSize) – Direct size setter (respects constraints).
  • props – Object to spread onto resizable prop of layout components and ResizeHandle.

Basic Two-Pane Horizontal Layout

This example creates a collapsible sidebar with persistent size:

import { useResizable, SideNav, LayoutPanel, ResizeHandle } from '@astryxdesign/core';

export function TwoPaneExample() {
  const { props, collapse, expand } = useResizable({
    defaultWidth: 260,
    minWidth: 180,
    maxWidth: 480,
    collapsible: true,
    collapsedSize: 40,
    snaps: [180, 260, 360],
    autoSaveId: 'example-sidebar',
  });

  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <SideNav resizable={props}>
        <ResizeHandle direction="horizontal" resizable={props} />
        <nav>Sidebar content</nav>
      </SideNav>

      <LayoutPanel style={{ flex: 1 }}>
        <h1>Main area</h1>
        <button onClick={collapse}>Hide sidebar</button>
        <button onClick={expand}>Show sidebar</button>
      </LayoutPanel>
    </div>
  );
}

How this works:

  • SideNav receives resizable={props} and applies props._size to its container width.
  • ResizeHandle receives the same props object, linking drag gestures to the hook's state reducer.
  • The direction="horizontal" prop tells the handle to drag along the x-axis and render a vertical pill-grip.

Multi-Region Vertical Layout

For complex arrangements with multiple resizable boundaries, pass a regions map to useResizable:

import { useResizable, LayoutPanel, ResizeHandle } from '@astryxdesign/core';

export function ThreePaneExample() {
  const regions = useResizable({
    direction: 'vertical',
    regions: {
      left:   { defaultSize: 200, minSizePx: 150, collapsible: true },
      center: { defaultSize: 400, minSizePx: 300 },
      right:  { defaultSize: 200, minSizePx: 150, collapsible: true },
    },
    autoSaveId: 'three-pane',
  });

  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <LayoutPanel resizable={regions.left.props}>
        <ResizeHandle direction="vertical" resizable={regions.left.props} />
        <div>Left content</div>
      </LayoutPanel>

      <LayoutPanel resizable={regions.center.props}>
        <ResizeHandle direction="vertical" resizable={regions.center.props} />
        <div>Center content</div>
      </LayoutPanel>

      <LayoutPanel resizable={regions.right.props}>
        <div>Right content</div>
      </LayoutPanel>
    </div>
  );
}

Key implementation details:

  • Each region receives isolated state—resizing the left pane does not affect the center pane's stored default.
  • Only two ResizeHandle components are needed for three panes; each handle controls the boundary between adjacent regions.
  • The direction="vertical" prop configures the handle for y-axis dragging with a horizontal pill-grip.

Adding Snap Points and Persistence

Combine snaps with autoSaveId for a polished production experience:

const { props, isCollapsed, size } = useResizable({
  defaultWidth: 260,
  minWidth: 180,
  maxWidth: 480,
  snaps: [180, 260, 360, 480],
  autoSaveId: 'project-nav-panel',
});

// Display current state
<span>{isCollapsed ? 'Collapsed' : `${size}px wide`}</span>

The hook automatically:

  1. Reads saved size from localStorage on mount (key: astryx-resizable:project-nav-panel).
  2. Applies snap logic during drag—when the cursor approaches within 10px of a snap point, the panel jumps to that width.
  3. Writes updated size after every resize or collapse event.

Accessibility and Keyboard Support

The ResizeHandle component in Astryx provides full keyboard accessibility out of the box:

  • Arrow keys – Resize by 10px increments (Shift + Arrow for 50px).
  • Home – Jump to minimum size.
  • End – Jump to maximum size.
  • Enter – Toggle collapse/expand (when collapsible: true).

Screen reader announcements for size changes are handled through the props object, which includes appropriate aria-valuenow, aria-valuemin, and aria-valuemax attributes.

Programmatic Control Methods

The hook exposes methods for non-drag interactions:

Method Behavior
collapse() Animates to collapsedSize and sets isCollapsed: true.
expand() Restores to the pre-collapse size or defaultWidth if never expanded.
resize(targetSize) Immediately sets size, clamped to minWidth/maxWidth.
toggle() Switches between collapsed and expanded states.

Use these to build UI controls like collapse buttons, keyboard shortcuts, or responsive breakpoints that adjust panel sizes automatically.

Resizable Panel Best Practices

Based on the Astryx source in Resizable.doc.mjs:

  • Always provide minWidth/minHeight – Prevents users from collapsing panels to unusable sizes unless explicitly intended.
  • Use autoSaveId for persistent UI – Navigation panels, sidebars, and tool palettes benefit from remembering user preferences.
  • Place ResizeHandle as first child – Ensures the handle renders above content and receives proper z-index stacking.
  • Match direction prop to layout – Mismatching the handle direction with the actual resize axis produces broken interactions.

Summary

  • Hook-first designuseResizable in packages/core/src/Resizable/useResizable.ts centralizes all resizable panel state.
  • Props-based integration – Pass the hook's props object to any Astryx layout component's resizable prop and to ResizeHandle.
  • Minimal DOM overhead – No wrapper elements required; existing components gain resize behavior through props alone.
  • Built-in accessibility – Keyboard navigation, screen reader support, and focus management handled automatically.
  • Persistence readyautoSaveId enables localStorage integration with no additional code.

Frequently Asked Questions

How do I prevent a resizable panel from collapsing completely?

Omit the collapsible: true option from your useResizable config, or set collapsedSize equal to your minWidth value. Without collapsible, the hook ignores collapse gestures and the collapse() method becomes a no-op.

Can I use Astryx resizable panels without SideNav or LayoutPanel?

Yes—any component that accepts a resizable prop can participate. The hook's props object contains _size and styling instructions that standard Astryx layout primitives understand. For custom components, read props.style or props._size directly in your component's render logic.

Does Astryx support nested resizable regions?

Astryx supports arbitrary nesting: a horizontal split can contain a panel that itself contains a vertical split. Each useResizable call manages its own state independently. Ensure unique autoSaveId values for each nested region to prevent localStorage collisions.

What happens if localStorage is unavailable or cleared?

The hook gracefully falls back to defaultWidth or defaultHeight. No errors are thrown, and the panel initializes at its configured default size. The next successful resize re-creates the localStorage entry.

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 →