Resizable
A draggable divider that allows users to adjust the size of adjacent sections.
import { component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div
style={{
width: "100%",
height: "250px"
}}
>
<Resizable.Root class="resizable-root">
<Resizable.Panel width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Panel (min: 100, max: 500)
</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Panel (min: 150)</div>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
When to use Resizable
Resizable is ideal when:
- Users need to customize layout proportions
- Content sections need flexible sizing
- Space allocation needs to be adjustable
- Complex layouts require nested resize capabilities
Real-world examples:
- Code editors with adjustable panels
- File explorers with resizable navigation
- Dashboard layouts with customizable widgets
- Documentation sites with adjustable sidebars
- Chat applications with flexible panels
- Data tables with resizable columns
Features
- WAI ARIA Separator pattern implementation
- Keyboard navigation with arrow keys and Home/End
- Collapsible panels with custom thresholds
- Responsive resizing with min/max constraints
- Panel size synchronization across handles
- Dynamic orientation (horizontal/vertical)
- Fine-grained resizing control with Shift key
- Progressive size adjustments with step values
- Custom collapse/expand behaviors with callbacks
- Flexible initial sizing with auto-distribution
- Accessible resize handles with ARIA attributes
Anatomy
Part | Description |
---|---|
<Resizable.Root> | Container component that manages the resizable layout and state |
<Resizable.Panel> | Individual panel component that can be resized |
<Resizable.Handle> | Interactive handle component for resizing adjacent panels |
Examples
Basic Usage
Start with this example if you're new to Resizable. It shows the minimal setup needed for a functional component.
import { component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div
style={{
width: "100%",
height: "250px"
}}
>
<Resizable.Root class="resizable-root">
<Resizable.Panel width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Panel (min: 100, max: 500)
</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Panel (min: 150)</div>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
This example demonstrates:
- Basic panel structure
- Handle placement
- Size constraints
- Default horizontal orientation
<Resizable.Root>
<Resizable.Panel width={200} minWidth={100} maxWidth={500}>
<div>Left Panel</div>
</Resizable.Panel>
<Resizable.Handle />
<Resizable.Panel minWidth={150}>
<div>Right Panel</div>
</Resizable.Panel>
</Resizable.Root>
Vertical Layout
Change the resize direction to vertical for stacked layouts.
import { component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div
style={{
width: "100%",
padding: "20px"
}}
>
<Resizable.Root orientation="vertical" class="resizable-root">
<Resizable.Panel height={100} minHeight={50}>
<div style={{ padding: "20px", color: "black" }}>Header</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel height={50}>
<div style={{ padding: "20px", color: "black" }}>Main Content</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel height={150}>
<div style={{ padding: "20px", color: "black" }}>Footer</div>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
Collapsible Panels
Enable panel collapsing with customizable thresholds and sizes.
import { $, component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div style={{ width: "100%", height: "350px" }}>
<Resizable.Root orientation="horizontal" class="resizable-root">
<Resizable.Panel
width={200}
minWidth={150}
collapsible
collapsedSize={50}
collapseThreshold={0.05}
onCollapse$={$(() => {
console.log("Panel collapsed");
})}
onExpand$={$(() => {
console.log("Panel expanded");
})}
>
<div style={{ padding: "20px", color: "black" }}>
Collapsible Panel (min: 150, collapsed: 50)
</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel minWidth={200}>
<div style={{ padding: "20px", color: "black" }}>Regular Panel (min: 200)</div>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
This demonstrates:
- Collapse functionality
- Custom collapse thresholds
- Collapse/expand callbacks
- Visual feedback
Nested Layouts
Create complex layouts by nesting resizable components.
import { component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div
style={{
width: "100%",
height: "350px"
}}
>
<Resizable.Root orientation="horizontal" class="resizable-root">
<Resizable.Panel width={200}>
<div style={{ padding: "20px", color: "black" }}>Left Panel</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel>
<Resizable.Root orientation="vertical">
<Resizable.Panel height={200}>
<div style={{ padding: "20px", color: "black" }}>Top Panel</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel>
<div style={{ padding: "20px", color: "black" }}>Bottom Panel</div>
</Resizable.Panel>
</Resizable.Root>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
State Management
Track and respond to panel size changes.
import { $, component$, useSignal, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
const leftPanelSize = useSignal(0);
const rightPanelSize = useSignal(0);
useStyles$(styles);
return (
<div
style={{
width: "100%",
height: "250px"
}}
>
<Resizable.Root class="resizable-root">
<Resizable.Panel
width={200}
minWidth={100}
maxWidth={500}
onResize$={$((size: number) => {
console.log("Left panel size:", `${size}px`);
leftPanelSize.value = size;
})}
>
<div style={{ padding: "20px", color: "black" }}>
Left Panel (min: 100, max: 500)
</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel
minWidth={150}
onResize$={$((size: number) => {
console.log("Right panel size:", `${size}px`);
rightPanelSize.value = size;
})}
>
<div style={{ padding: "20px", color: "black" }}>Right Panel (min: 150)</div>
</Resizable.Panel>
</Resizable.Root>
{leftPanelSize.value > 0 && rightPanelSize.value > 0 && (
<>
<p>Left panel size: {Math.floor(leftPanelSize.value)} px</p>
<p>Right panel size: {Math.floor(rightPanelSize.value)} px</p>
</>
)}
</div>
);
});
Disabled State
Prevent resizing when needed.
import { component$, useStyles$ } from "@builder.io/qwik";
import { Resizable } from "@kunai-consulting/qwik";
import styles from "./resizable-custom.css?inline";
export default component$(() => {
useStyles$(styles);
return (
<div
style={{
width: "100%",
height: "250px"
}}
>
<Resizable.Root disabled class="resizable-root">
<Resizable.Panel width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Panel (min: 100, max: 500)
</div>
</Resizable.Panel>
<Resizable.Handle class="resizable-handle" />
<Resizable.Panel minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Panel (min: 150)</div>
</Resizable.Panel>
</Resizable.Root>
</div>
);
});
Keyboard Navigation
The component supports full keyboard interaction:
- Tab: Focus the handle
- Arrow keys: Adjust panel sizes
- Shift + Arrow: Larger size adjustments
- Home/End: Collapse/expand panels
- Enter/Space: Toggle collapse (for collapsible panels)
Accessibility
Built following the WAI-ARIA window splitter pattern, including:
- Proper role attributes
- ARIA states
- Keyboard navigation
- Focus management
- Screen reader announcements
- Pointer capture for smooth dragging
Styling
The component uses data attributes for styling:
[data-qds-resizable-root] {
/* Root styles */
}
[data-qds-resizable-handle][data-dragging="true"] {
/* Handle drag state */
}
[data-qds-resizable-panel][data-collapsed="true"] {
/* Collapsed panel state */
}
API Reference
Resizable Panel
Inherits from: <div />
Props
Prop | Type | Default | Description |
---|---|---|---|
width | number | - | Default width of the panel (in pixels) |
height | number | - | Default height of the panel (in pixels) |
minWidth | number | - | Minimum width constraint (in pixels) |
maxWidth | number | - | Maximum width constraint (in pixels) |
minHeight | number | - | Minimum height constraint (in pixels) |
maxHeight | number | - | Maximum height constraint (in pixels) |
collapsible | boolean | - | Whether the panel can be collapsed |
collapsed | boolean | - | Initial collapsed state |
collapsedSize | number | - | Width to collapse to (in pixels) |
collapseThreshold | number | - | Threshold for collapse trigger |
onResize$ | QRL<(size: number) => void> | - | Callback fired when the panel is resized |
onCollapse$ | QRL<() => void> | - | Callback fired when the panel is collapsed |
onExpand$ | QRL<() => void> | - | Callback fired when the panel is expanded |
Data Attributes
Attribute | Description |
---|---|
data-orientation | The orientation of the panel |
data-min-size | Minimum size constraint for the panel |
data-max-size | Maximum size constraint for the panel |
data-collapsible | Indicates whether the panel is collapsible |
data-collapsed-size | Size when panel is collapsed |
data-collapse-threshold | Threshold for triggering collapse |
data-is-collapsed | Current collapsed state of the panel |
Resizable Root
Inherits from: <div />
Props
Prop | Type | Default | Description |
---|---|---|---|
orientation | "horizontal" | "vertical" | - | Direction in which the panels can be resized |
disabled | boolean | - | When true, prevents resizing of panels |
storageKey | string | - | Key for persisting layout in localStorage |
Data Attributes
Attribute | Description |
---|---|
data-orientation | The orientation of the resizable container |
data-disabled | Indicates whether resizing is disabled |
Resizable Handle
Inherits from: <button />
Data Attributes
Attribute | Description |
---|---|
data-orientation | The orientation of the handle |
data-dragging | Indicates whether the handle is being dragged |
data-disabled | Indicates whether the handle is disabled |