A panel layout with drag-to-resize columns (and an optional second row), with sizes persisted to localStorage. Sizes are kept in fr units, so the layout stays proportional when the container resizes.
import { ResizableGrid } from 'solid-dumb-kit'No CSS import needed — handle styles are injected once at runtime.
import { ResizableGrid } from 'solid-dumb-kit'
<div style={{ height: '100vh' }}>
<ResizableGrid
storageKey="app:layout"
cols={[
{ id: 'tree', min: 180, initial: 1, content: () => <Tree /> },
{ id: 'main', min: 320, initial: 3, content: () => <Main /> },
{ id: 'aside', min: 200, initial: 1, content: () => <Aside /> },
]}
rows={[
{ id: 'log', min: 120, initial: 1, content: () => <Log /> },
]}
rowInitial={2} // top row gets 2fr of height
row2Initial={1} // bottom row gets 1fr
rowMin={120}
/>
</div>The grid fills its parent (
h-full w-full). Give the parent a height.
| Prop | Type | Default | Description |
|---|---|---|---|
cols |
GridPanel[] |
— (required) | Columns of the first row (2–3 recommended). |
storageKey |
string |
— (required) | localStorage key the sizes are saved under. |
rows |
GridPanel[] |
— | Optional second row of panels (1–3). Enables a horizontal splitter. |
rowInitial |
number |
1 |
First row height, in fr. |
row2Initial |
number |
1 |
Second row height, in fr. |
rowMin |
number |
100 |
Min row height, px. |
class |
string |
— | Extra class on the grid container. |
| Field | Type | Default | Description |
|---|---|---|---|
id |
string |
— (required) | Unique panel id. |
content |
() => JSX.Element |
— (required) | Render prop for the panel body. |
min |
number |
100 |
Minimum size in px (width for columns, width for second-row columns). |
initial |
number |
1 |
Initial size in fr. |
contentis a function (() => <Panel/>), not a JSX value. The component intentionally never reads panel JSX in resize handlers, so dragging never re-creates your panels.
- Columns/rows are laid out with CSS Grid using
frtracks separated by a 6px handle. - Dragging a handle redistributes
frbetween the two adjacent tracks, clamped so neither drops below itsmin(converted from px tofragainst the live container size). - Each panel body is
overflow: autowithmin-width/height: 0, so long content scrolls instead of blowing out the track.
Sizes are stored as { cols, rows?, rowSplit? } under storageKey. On load they're validated with valibot; if the stored data is corrupt or its column count no longer matches the current cols, it falls back to the defaults from initial. Change storageKey (or your cols length) to invalidate old saved layouts.
- Resizing is mouse-only (uses
mousedown/mousemove); no touch handles. - Designed for 2–3 columns and an optional second row — not an arbitrary nested split tree.
- Handle hover/active tint uses
currentColorat low opacity, so it blends with your theme.