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.Content width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Content (min: 100, max: 500)
</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Content (min: 150)</div>
</Resizable.Content>
</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 content
- File explorers with resizable navigation
- Dashboard layouts with customizable widgets
- Documentation sites with adjustable sidebars
- Chat applications with flexible content
- Data tables with resizable columns
Features
- WAI ARIA Separator pattern implementation
- Keyboard navigation with arrow keys and Home/End
- Collapsible contents with custom thresholds
- Responsive resizing with min/max constraints
- Content 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.Content> | Individual content component that can be resized |
<Resizable.Handle> | Interactive handle component for resizing adjacent contents |
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.Content width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Content (min: 100, max: 500)
</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Content (min: 150)</div>
</Resizable.Content>
</Resizable.Root>
</div>
);
});
This example demonstrates:
- Basic content structure
- Handle placement
- Size constraints
- Default horizontal orientation
<Resizable.Root>
<Resizable.Content width={200} minWidth={100} maxWidth={500}>
<div>Left Content</div>
</Resizable.Content>
<Resizable.Handle />
<Resizable.Content minWidth={150}>
<div>Right Content</div>
</Resizable.Content>
</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.Content height={100} minHeight={50}>
<div style={{ padding: "20px", color: "black" }}>Header</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content height={50}>
<div style={{ padding: "20px", color: "black" }}>Main Content</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content height={150}>
<div style={{ padding: "20px", color: "black" }}>Footer</div>
</Resizable.Content>
</Resizable.Root>
</div>
);
});
Collapsible Content
Enable content 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.Content
width={200}
minWidth={150}
collapsible
collapsedSize={50}
collapseThreshold={0.05}
onCollapse$={$(() => {
console.log("Content collapsed");
})}
onExpand$={$(() => {
console.log("Content expanded");
})}
>
<div style={{ padding: "20px", color: "black" }}>
Collapsible Content (min: 150, collapsed: 50)
</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content minWidth={200}>
<div style={{ padding: "20px", color: "black" }}>
Regular Content (min: 200)
</div>
</Resizable.Content>
</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.Content width={200}>
<div style={{ padding: "20px", color: "black" }}>Left Content</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content>
<Resizable.Root orientation="vertical">
<Resizable.Content height={200}>
<div style={{ padding: "20px", color: "black" }}>Top Content</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content>
<div style={{ padding: "20px", color: "black" }}>Bottom Content</div>
</Resizable.Content>
</Resizable.Root>
</Resizable.Content>
</Resizable.Root>
</div>
);
});
State Management
Track and respond to content 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.Content
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.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content
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.Content>
</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.Content width={200} minWidth={100} maxWidth={500}>
<div style={{ padding: "20px", color: "black" }}>
Left Content (min: 100, max: 500)
</div>
</Resizable.Content>
<Resizable.Handle class="resizable-handle" />
<Resizable.Content minWidth={150}>
<div style={{ padding: "20px", color: "black" }}>Right Content (min: 150)</div>
</Resizable.Content>
</Resizable.Root>
</div>
);
});
Keyboard Navigation
The component supports full keyboard interaction:
- Tab: Focus the handle
- Arrow keys: Adjust content sizes
- Shift + Arrow: Larger size adjustments
- Home/End: Collapse/expand contents
- Enter/Space: Toggle collapse (for collapsible contents)
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-content][data-collapsed="true"] {
/* Collapsed content state */
}
API Reference
Resizable Content
Inherits from: <div />
Props
Prop | Type | Default | Description |
---|---|---|---|
width | number | - | Default width of the content (in pixels) |
height | number | - | Default height of the content (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 content 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 content is resized |
onCollapse$ | QRL<() => void> | - | Callback fired when the content is collapsed |
onExpand$ | QRL<() => void> | - | Callback fired when the content is expanded |
Data Attributes
Attribute | Description |
---|---|
data-orientation | The orientation of the content |
data-min-size | Minimum size constraint for the content |
data-max-size | Maximum size constraint for the content |
data-collapsible | Indicates whether the content is collapsible |
data-collapsed-size | Size when content is collapsed |
data-collapse-threshold | Threshold for triggering collapse |
data-is-collapsed | Current collapsed state of the content |
Resizable Root
Inherits from: <div />
Props
Prop | Type | Default | Description |
---|---|---|---|
orientation | "horizontal" | "vertical" | - | Direction in which the contents can be resized |
disabled | boolean | - | When true, prevents resizing of contents |
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 |