Skip to content

Resizable

Let people resize or collapse panels with a pointer or keyboard, and save the layout.

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

It's shadcn's resizable on react-resizable-panels 4, which installs with it and owns the behavior: pointer and touch drags, limits, collapsing and the keyboard separator.

Use resizable panels for workspaces where people decide how much room each part gets: an editor and its terminal, a mail list and its reading pane, a board and a detail panel. Drag the sidebar past its minimum and it snaps shut to an icon rail. For a sidebar that opens and closes but never resizes, use the Sidebar. For content that overflows a fixed box, use a Scroll Area.

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@youj/react'

export function Mail() {
  return (
    <ResizablePanelGroup orientation="horizontal">
      <ResizablePanel id="list" defaultSize="32%" minSize="16rem">
        <MessageList />
      </ResizablePanel>
      <ResizableHandle aria-label="Resize message list" />
      <ResizablePanel id="thread" minSize="40%">
        <Thread />
      </ResizablePanel>
    </ResizablePanelGroup>
  )
}

The group fills its parent, so give the parent a height. Sizes take pixels as numbers, or strings in %, px or rem. Groups nest: put a vertical group inside a panel of a horizontal one, as the demo's editor and terminal do.

Reference

PropsonCollapsedChange, withHandle (2)

Props

ResizablePanel props
PropType
onCollapsedChange(collapsed: boolean) => void

Fires when a collapsible panel collapses or re-opens (by drag, keyboard or the imperative API).

–
Other props pass through: extends ResizablePrimitive.PanelProps.

ResizableHandle

ResizableHandle props
PropType
withHandleboolean

Show a grip on the line. Use it where the separator isn't otherwise obvious.

–
Other props pass through: extends ResizablePrimitive.SeparatorProps.

ResizablePanelGroup takes react-resizable-panels' Group props: orientation, defaultLayout, onLayoutChange, onLayoutChanged and groupRef. ResizablePanel also takes the library's Panel props, such as defaultSize, minSize, maxSize, collapsible, collapsedSize and panelRef, whose handle (ResizablePanelHandle) can collapse(), expand() and resize() from code.

MotionKeyboard and imperative resize, pointer drag, separator wakes… · reduced motion

Motion

Motion tokens
MomentTokens
Keyboard and imperative resizethe panels glide (flex-grow), including Enter to collapse or restore
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Pointer dragfollows the pointer exactly, snap included
no token

pointer-driven

pointer-driven
Separator wakesthe 1px line grows a 3px rail after a beat of hover, so crossing it doesn't flash
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Dragged or focusedthe rail turns the ring color at once
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Gripscales to 1.1 while held, and its dots fuse into bars (GripIcon)
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Every resize is instant, and the grip doesn't scale while held.

Reduced motion

Keyboard, Enter and imperative resizes jump to their new sizes, and the grip holds its size while held. Dragging is unchanged, because it follows the pointer either way.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • Each separator is a focusable role="separator" with aria-valuenow, aria-valuemin and aria-valuemax, and an orientation. Name it with aria-label ("Resize sidebar") when its purpose isn't obvious from the layout.
  • Arrow keys resize in 5% steps, Home and End go to the limits, and Enter collapses or restores a collapsible panel. F6 moves to the next separator.
  • A double-click on the separator resets the panels to their default sizes.
  • The visible line is 1px, but the hit area is 27px wide, and 37px on touch screens.
  • Focus shows as the accent rail, the same as a drag.