Panel

Resizable layout regions with collapsible sidebars.

import { Panel } from 'ui-lab-components/panel';
"use client";
 
import { useState } from 'react';
import { Panel } from 'ui-lab-components/panel';
 
function NavItems() {
  return (
    <div className="grid h-full w-full min-w-0 content-start gap-2 p-3">
      <div className="h-7 rounded-sm border border-dashed border-background-600 bg-background-900" />
      <div className="h-7 rounded-sm border border-dashed border-background-600 bg-background-900" />
      <div className="h-7 rounded-sm border border-dashed border-background-600 bg-background-900" />
      <div className="h-7 rounded-sm border border-dashed border-background-600 bg-background-900" />
    </div>
  );
}
 
export default function Example() {
  const [open, setOpen] = useState(true);
  const [inspectorOpen, setInspectorOpen] = useState(false);
 
  return (
    <Panel collapseAt={480} spacing="none">
      <Panel.Sidebar
        id="navigation"
        side="left"
        size={184}
        open={open}
        onOpenChange={setOpen}
        aria-label="Navigation"
      >
        <NavItems />
      </Panel.Sidebar>
 
      <div className={`flex min-w-0 flex-1 overflow-hidden ${inspectorOpen ? 'gap-2' : ''}`}>
        <section className="flex min-w-0 flex-1 flex-col overflow-hidden rounded-sm border border-background-700 bg-background-950">
          <header className="flex shrink-0 items-center justify-between border-b border-background-700 px-2 py-2">
            <div className="flex items-center gap-2">
              <Panel.Toggle aria-controls="navigation">
                <button aria-label="Toggle navigation" className="h-7 w-7 cursor-pointer rounded-sm bg-background-700 hover:bg-background-600" />
              </Panel.Toggle>
              <div className="h-4 w-24 shrink-0 rounded-sm border border-dashed border-background-600 bg-background-900" />
            </div>
            <Panel.Toggle aria-controls="inspector">
              <button aria-label="Toggle inspector" className="h-7 w-7 cursor-pointer rounded-sm bg-background-700 hover:bg-background-600" />
            </Panel.Toggle>
          </header>
 
          <main className="grid min-h-0 min-w-0 flex-1 content-start gap-3 overflow-auto p-3">
            <div className="h-22 rounded-sm border border-dashed border-background-600 bg-background-900" />
            <div className="grid min-w-0 grid-cols-2 gap-3">
              <div className="h-22 rounded-sm border border-dashed border-background-600 bg-background-900" />
              <div className="h-22 rounded-sm border border-dashed border-background-600 bg-background-900" />
            </div>
          </main>
 
          <footer className="flex shrink-0 justify-between border-t border-background-700 px-2 py-2">
            <div className="h-4 w-14 shrink-0 rounded-sm border border-dashed border-background-600 bg-background-900" />
            <div className="h-4 w-10 shrink-0 rounded-sm border border-dashed border-background-600 bg-background-900" />
          </footer>
        </section>
 
        <Panel.Sidebar
          id="inspector"
          side="right"
          size={184}
          className="basis-0 shrink-0 overflow-hidden rounded-sm border-0 bg-background-950 data-[open=true]:basis-[var(--size)] data-[open=true]:border data-[open=true]:border-background-700 data-[open=true]:transition-[flex-basis]"
          open={inspectorOpen}
          onOpenChange={setInspectorOpen}
          aria-label="Inspector"
        >
          <NavItems />
        </Panel.Sidebar>
      </div>
    </Panel>
  );
}
 

panel/01-sidebar-toggle

A semantic content region alongside a responsive, collapsible navigation sidebar.

Loading interactive preview
import type { CSSProperties } from 'react';
import { Frame } from 'ui-lab-components/frame';
import { Panel } from 'ui-lab-components/panel';
 
const CELL_STYLE = {
  '--fill': 'var(--background-900)',
  '--stroke-color': 'var(--background-600)',
} as CSSProperties;
 
function FrameCell({ className, style }: { className?: string; style?: CSSProperties }) {
  return (
    <Frame pathStroke="dashed" className={className} style={{ ...CELL_STYLE, ...style }}>
      <div className="size-full" />
    </Frame>
  );
}
 
function NavCells() {
  return (
    <div className="grid h-full w-full min-w-0 content-start gap-2 p-3">
      {Array.from({ length: 4 }, (_, index) => <FrameCell key={index} style={{ height: 28 }} />)}
    </div>
  );
}
 
export default function Example() {
  return (
    <Panel collapseAt={480} spacing="none">
      <Panel.Sidebar
        id="navigation"
        side="left"
        size={184}
        defaultOpen
        aria-label="Navigation"
        className="border-r border-background-700"
      >
        <NavCells />
      </Panel.Sidebar>
 
      <section className="flex min-w-0 flex-1 flex-col overflow-hidden">
        <header className="flex shrink-0 items-center justify-between border-b border-background-700 px-2 py-2">
          <div className="flex items-center gap-2">
            <Panel.Toggle aria-controls="navigation">
              <button aria-label="Toggle navigation" className="h-7 w-7 cursor-pointer rounded-sm bg-background-700 hover:bg-background-600" />
            </Panel.Toggle>
            <FrameCell className="shrink-0" style={{ width: 96, height: 16 }} />
          </div>
          <FrameCell className="shrink-0" style={{ width: 28, height: 28 }} />
        </header>
 
        <main className="grid min-h-0 min-w-0 flex-1 content-start gap-3 overflow-auto p-3">
          <FrameCell style={{ height: 88 }} />
          <div className="grid min-w-0 grid-cols-2 gap-3">
            <FrameCell style={{ height: 88 }} />
            <FrameCell style={{ height: 88 }} />
          </div>
        </main>
 
        <footer className="flex shrink-0 justify-between border-t border-background-700 px-2 py-2">
          <FrameCell className="shrink-0" style={{ width: 56, height: 16 }} />
          <FrameCell className="shrink-0" style={{ width: 40, height: 16 }} />
        </footer>
      </section>
    </Panel>
  );
}
 

panel/02-resizable-workspace

Two semantic work regions connected to a resize handle through aria-controls.

Loading interactive preview
import type { CSSProperties } from 'react';
import { Frame } from 'ui-lab-components/frame';
import { Panel } from 'ui-lab-components/panel';
 
const CELL_STYLE = {
  '--fill': 'var(--background-900)',
  '--stroke-color': 'var(--background-600)',
} as CSSProperties;
 
function FrameCell({ className, style }: { className?: string; style?: CSSProperties }) {
  return <Frame pathStroke="dashed" className={className} style={{ ...CELL_STYLE, ...style }}><div className="size-full" /></Frame>;
}
 
function NavCells() {
  return <div className="grid h-full w-full min-w-0 content-start gap-2 p-3">{Array.from({ length: 4 }, (_, index) => <FrameCell key={index} style={{ height: 28 }} />)}</div>;
}
 
export default function Example() {
  return (
    <Panel spacing="none">
      <div className="flex min-w-0 flex-1 gap-2 overflow-hidden">
        <section className="flex min-w-0 flex-1 flex-col overflow-hidden">
          <header className="flex shrink-0 items-center justify-between border-b border-background-700 px-2 py-2">
            <div className="flex items-center gap-2"><FrameCell className="shrink-0" style={{ width: 96, height: 16 }} /></div>
            <FrameCell className="shrink-0" style={{ width: 28, height: 28 }} />
          </header>
          <Panel orientation="horizontal" spacing="none" className="min-h-0 flex-1">
            <aside id="workspace-source" style={{ flexBasis: '32%', minWidth: '20%', maxWidth: '45%' }}><NavCells /></aside>
            <Panel.Resize aria-controls="workspace-source workspace-preview" aria-label="Resize workspace columns" />
            <Panel id="workspace-preview" orientation="vertical" spacing="none" style={{ flexBasis: '68%', minWidth: '45%' }}>
              <section id="workspace-primary" style={{ flexBasis: '64%', minHeight: '42%' }} className="p-3"><FrameCell className="h-full" /></section>
              <Panel.Resize aria-controls="workspace-primary workspace-secondary" aria-label="Resize workspace rows" />
              <section id="workspace-secondary" style={{ flexBasis: '36%', minHeight: '22%', maxHeight: '58%' }} className="p-3"><FrameCell className="h-full" /></section>
            </Panel>
          </Panel>
        </section>
      </div>
    </Panel>
  );
}
 

panel/03-nested-dashboard

Nested horizontal and vertical panels for dense application layouts.

Loading interactive preview
import type { CSSProperties } from 'react';
import { Frame } from 'ui-lab-components/frame';
import { Panel } from 'ui-lab-components/panel';
 
const CELL_STYLE = {
  '--fill': 'var(--background-900)',
  '--stroke-color': 'var(--background-600)',
} as CSSProperties;
 
function FrameCell({ className, style }: { className?: string; style?: CSSProperties }) {
  return <Frame pathStroke="dashed" className={className} style={{ ...CELL_STYLE, ...style }}><div className="size-full" /></Frame>;
}
 
function NavCells() {
  return <div className="grid h-full w-full min-w-0 content-start gap-2 p-3">{Array.from({ length: 4 }, (_, index) => <FrameCell key={index} style={{ height: 28 }} />)}</div>;
}
 
export default function Example() {
  return (
    <Panel spacing="none">
      <section className="flex min-w-0 flex-1 flex-col overflow-hidden">
        <header className="flex shrink-0 items-center justify-between border-b border-background-700 px-2 py-2">
          <div className="flex items-center gap-2"><FrameCell className="shrink-0" style={{ width: 96, height: 16 }} /></div>
          <FrameCell className="shrink-0" style={{ width: 28, height: 28 }} />
        </header>
        <Panel orientation="horizontal" spacing="none" className="min-h-0 flex-1">
          <Panel id="dashboard-primary" orientation="vertical" spacing="none" style={{ flexBasis: '70%', minWidth: '46%' }}>
            <section id="dashboard-summary" style={{ flexBasis: '36%', minHeight: '24%' }} className="p-3">
              <div className="grid h-full w-full min-w-0 grid-cols-3 gap-3"><FrameCell className="h-full" /><FrameCell className="h-full" /><FrameCell className="h-full" /></div>
            </section>
            <Panel.Resize aria-controls="dashboard-summary dashboard-chart" aria-label="Resize dashboard rows" />
            <section id="dashboard-chart" style={{ flexBasis: '64%', minHeight: '40%' }} className="p-3"><FrameCell className="h-full" /></section>
          </Panel>
          <Panel.Resize aria-controls="dashboard-primary dashboard-activity" aria-label="Resize dashboard columns" />
          <aside id="dashboard-activity" style={{ flexBasis: '30%', minWidth: '20%', maxWidth: '42%' }}><NavCells /></aside>
        </Panel>
      </section>
    </Panel>
  );
}