Skip to content

Resizable

Accessible resizable panel groups and layouts with keyboard support.

Panel One
Panel Two

Installation

bash
npx @galaxy-stack/nebula-cli@latest add resizable
bash
pnpm dlx @galaxy-stack/nebula-cli@latest add resizable
bash
yarn dlx @galaxy-stack/nebula-cli@latest add resizable
bash
bunx @galaxy-stack/nebula-cli@latest add resizable

Usage

vue
<script setup lang="ts">
import {
  ResizablePanelGroup,
  ResizablePanel,
  ResizableHandle,
} from '@/components/ui/resizable'
</script>

<template>
  <ResizablePanelGroup direction="horizontal" class="max-w-md rounded-lg border">
    <ResizablePanel :default-size="50">
      <div class="flex h-[200px] items-center justify-center p-6">
        <span class="font-semibold">One</span>
      </div>
    </ResizablePanel>
    <ResizableHandle />
    <ResizablePanel :default-size="50">
      <div class="flex h-[200px] items-center justify-center p-6">
        <span class="font-semibold">Two</span>
      </div>
    </ResizablePanel>
  </ResizablePanelGroup>
</template>
tsx
import {
  ResizablePanelGroup,
  ResizablePanel,
  ResizableHandle,
} from "@/components/ui/resizable"

export default function App() {
  return (
    <ResizablePanelGroup
      direction="horizontal"
      className="max-w-md rounded-lg border"
    >
      <ResizablePanel defaultSize={50}>
        <div className="flex h-[200px] items-center justify-center p-6">
          <span className="font-semibold">One</span>
        </div>
      </ResizablePanel>
      <ResizableHandle />
      <ResizablePanel defaultSize={50}>
        <div className="flex h-[200px] items-center justify-center p-6">
          <span className="font-semibold">Two</span>
        </div>
      </ResizablePanel>
    </ResizablePanelGroup>
  )
}
typescript
import { Component } from '@angular/core';
import {
  ResizablePanelGroupComponent,
  ResizablePanelComponent,
  ResizableHandleComponent,
} from '@/components/ui/resizable';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    ResizablePanelGroupComponent,
    ResizablePanelComponent,
    ResizableHandleComponent,
  ],
  template: `
    <ui-resizable-panel-group direction="horizontal" class="max-w-md rounded-lg border">
      <ui-resizable-panel [defaultSize]="50">
        <div class="flex h-[200px] items-center justify-center p-6">
          <span class="font-semibold">One</span>
        </div>
      </ui-resizable-panel>
      <ui-resizable-handle />
      <ui-resizable-panel [defaultSize]="50">
        <div class="flex h-[200px] items-center justify-center p-6">
          <span class="font-semibold">Two</span>
        </div>
      </ui-resizable-panel>
    </ui-resizable-panel-group>
  `
})
export class AppComponent {}

Examples

Vertical

vue
<ResizablePanelGroup direction="vertical" class="min-h-[200px] max-w-md rounded-lg border">
  <ResizablePanel :default-size="25">
    <div class="flex h-full items-center justify-center p-6">
      <span class="font-semibold">Header</span>
    </div>
  </ResizablePanel>
  <ResizableHandle />
  <ResizablePanel :default-size="75">
    <div class="flex h-full items-center justify-center p-6">
      <span class="font-semibold">Content</span>
    </div>
  </ResizablePanel>
</ResizablePanelGroup>
tsx
<ResizablePanelGroup
  direction="vertical"
  className="min-h-[200px] max-w-md rounded-lg border"
>
  <ResizablePanel defaultSize={25}>
    <div className="flex h-full items-center justify-center p-6">
      <span className="font-semibold">Header</span>
    </div>
  </ResizablePanel>
  <ResizableHandle />
  <ResizablePanel defaultSize={75}>
    <div className="flex h-full items-center justify-center p-6">
      <span className="font-semibold">Content</span>
    </div>
  </ResizablePanel>
</ResizablePanelGroup>
html
<ui-resizable-panel-group direction="vertical" class="min-h-[200px] max-w-md rounded-lg border">
  <ui-resizable-panel [defaultSize]="25">
    <div class="flex h-full items-center justify-center p-6">
      <span class="font-semibold">Header</span>
    </div>
  </ui-resizable-panel>
  <ui-resizable-handle />
  <ui-resizable-panel [defaultSize]="75">
    <div class="flex h-full items-center justify-center p-6">
      <span class="font-semibold">Content</span>
    </div>
  </ui-resizable-panel>
</ui-resizable-panel-group>

Handle

You can show the visual grip with withHandle.

vue
<ResizablePanelGroup direction="horizontal" class="max-w-md rounded-lg border">
  <ResizablePanel :default-size="50">
    <div class="flex h-[200px] items-center justify-center p-6">
      <span class="font-semibold">Sidebar</span>
    </div>
  </ResizablePanel>
  <ResizableHandle :with-handle="true" />
  <ResizablePanel :default-size="50">
    <div class="flex h-[200px] items-center justify-center p-6">
      <span class="font-semibold">Content</span>
    </div>
  </ResizablePanel>
</ResizablePanelGroup>
tsx
<ResizablePanelGroup
  direction="horizontal"
  className="max-w-md rounded-lg border"
>
  <ResizablePanel defaultSize={50}>
    <div className="flex h-[200px] items-center justify-center p-6">
      <span className="font-semibold">Sidebar</span>
    </div>
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={50}>
    <div className="flex h-[200px] items-center justify-center p-6">
      <span className="font-semibold">Content</span>
    </div>
  </ResizablePanel>
</ResizablePanelGroup>
html
<ui-resizable-panel-group direction="horizontal" class="max-w-md rounded-lg border">
  <ui-resizable-panel [defaultSize]="50">
    <div class="flex h-[200px] items-center justify-center p-6">
      <span class="font-semibold">Sidebar</span>
    </div>
  </ui-resizable-panel>
  <ui-resizable-handle [withHandle]="true" />
  <ui-resizable-panel [defaultSize]="50">
    <div class="flex h-[200px] items-center justify-center p-6">
      <span class="font-semibold">Content</span>
    </div>
  </ui-resizable-panel>
</ui-resizable-panel-group>

API Reference

ResizablePanelGroup

PropVueReactAngularDescription
direction'horizontal' | 'vertical''horizontal' | 'vertical''horizontal' | 'vertical'Panel group layout direction
autoSaveIdstringstring-Persist layout using local storage
keyboardResizeBynumbernumber-Resize step when using keyboard interaction
gutterSize--numberGutter size in pixels for angular-split
class / classNamestringstringstringContainer classes
layout / onLayout / layoutChange(sizes: number[]) => void(sizes: number[]) => void(sizes: number[]) => voidEmitted after layout changes

ResizablePanel

PropVueReactAngularDescription
defaultSizenumbernumbernumberInitial panel size percentage
size--numberAngular backward-compatible alias for initial size
minSizenumbernumbernumberMinimum panel size percentage
maxSizenumbernumbernumberMaximum panel size percentage
collapsiblebooleanbooleanbooleanWhether the panel may collapse
collapsedSizenumbernumbernumberSize when collapsed
ordernumbernumbernumberPanel ordering index
class / classNamestringstringstringPanel classes
resize / onResize(size: number, prevSize?: number) => voidframework primitive event-Emitted when the panel resizes
collapse / expand() => voidframework primitive event-Emitted on collapse or expand

ResizableHandle

PropVueReactAngularDescription
disabledbooleanbooleanbooleanDisable resizing through the handle or gutter
withHandlebooleanbooleanbooleanShow visual handle affordance
class / classNamestringstringstringHandle classes
dragging(dragging: boolean) => voidframework primitive event-Emitted while dragging

Notes

  • Vue now uses Radix Vue Splitter.
  • React uses react-resizable-panels.
  • Angular uses angular-split and is normalized toward the shared API.
  • Angular currently keeps size as a backward-compatible alias for defaultSize.
  • Angular exposes collapsible and collapsedSize in the normalized API, but they are not yet fully enforced by the wrapper.

Accessibility

Keyboard Interactions

KeyDescription
ArrowLeft / ArrowRightResize horizontal layouts
ArrowUp / ArrowDownResize vertical layouts
EnterReset toward default panel sizes when supported by the underlying primitive
Home / EndJump toward min/max panel sizes when supported by the underlying primitive

License

MIT © 2025 Bùi Trọng Hiếu (kevinbui)

Released under the MIT License.