Resizable
Two panes and the handle that trades room between them, with minimums, collapsing and a keyboard.
import { Resizable, ResizablePane } from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 560px; height: 300px; background: Tokens.color-background;
VerticalLayout { padding: 24px;
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
split := Resizable { handle-label: "Resize the panes"; first-minimum: 120px; second-minimum: 160px;
ResizablePane { rect: split.first; background: Tokens.color-surface-1; Text { text: "One"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } }
ResizablePane { rect: split.second; Text { text: "Two"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } } } } }}Usage
import { Resizable, ResizablePane } from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component AppWindow inherits Window { width: 640px; height: 400px; background: Tokens.color-background;
split := Resizable { handle-label: "Resize the tool column"; first-minimum: 180px; second-minimum: 320px;
ResizablePane { rect: split.first; // the tool column }
ResizablePane { rect: split.second; // the editor } }}Slint has one @children slot, so the split cannot address its panes the way a
web component addresses two named slots. It publishes their geometry instead —
first and second, a PaneRect each (x, y, width, height in the
split’s own coordinates) — and you wrap your own content in a ResizablePane
bound to one of them. Nesting is the same move: drop another Resizable inside
a pane and it fills it, with its own ratio, its own minimums and its own handle.
The state is one number. ratio is the first pane’s share of the resizable
axis, and every path into it — dragging the handle, the arrow keys,
Home / End, the accessible increment, decrement and
set-value actions — goes through set-ratio, which bounds the move against both
minimums first. A pane can never be squeezed below the size its content needs,
and a move that changes nothing stays silent: a key held at a bound does not
spray changed. A ratio written from outside is corrected in place rather
than left contradicting the panes it claims to describe, so what you read back
is the split you are looking at.
Examples
A vertical split
orientation decides which way the panes sit: ResizableOrientation.horizontal
puts them side by side with an upright handle between them, and
ResizableOrientation.vertical stacks them. The handle answers the arrows that
run along its own axis and leaves the other pair to whatever else is on screen.
import { Resizable, ResizableOrientation, ResizablePane,} from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 480px; height: 340px; background: Tokens.color-background;
VerticalLayout { padding: 24px;
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
split := Resizable { orientation: ResizableOrientation.vertical; handle-label: "Resize the preview"; first-minimum: 80px; second-minimum: 80px; ratio: 0.6;
ResizablePane { rect: split.first; background: Tokens.color-surface-1; Text { text: "Preview"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } }
ResizablePane { rect: split.second; Text { text: "Console"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } } } } }}Minimums
first-minimum and second-minimum are the smallest each pane may become. A
split too small to honour both keeps the first pane’s and lets the second go
under — one of them has to give, and the first is the one the ratio measures.
import { Resizable, ResizablePane } from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 560px; height: 300px; background: Tokens.color-background;
VerticalLayout { padding: 24px; spacing: 12px;
Text { text: "Drag the handle: neither side goes under 200 pixels."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; }
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
split := Resizable { handle-label: "Resize the panes"; first-minimum: 200px; second-minimum: 200px;
ResizablePane { rect: split.first; background: Tokens.color-surface-1; Text { text: "At least 200px"; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; } }
ResizablePane { rect: split.second; Text { text: "At least 200px"; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; } } } } }}A pane that shuts
A first-collapsible or second-collapsible pane has one more rest position
below its minimum: shut, at collapsed-length. It still holds at the minimum
until the move asks for less than halfway to that, and reopens at the minimum on
the way back — the sidebar-collapse gesture, with the same hysteresis in both
directions. first-collapsed and second-collapsed say which state it is in,
so a rail can bind its contents to them.
import { Icon } from "@glint/components/icon.slint";import { Resizable, ResizablePane } from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";import { IconSet } from "@lucide";
export component Demo inherits Window { width: 560px; height: 320px; background: Tokens.color-background;
VerticalLayout { padding: 24px; spacing: 12px;
Text { text: "Drag the handle left, or press Home with it focused."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; }
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
split := Resizable { handle-label: "Resize the tool column"; handle-grip: true; first-collapsible: true; first-minimum: 180px; collapsed-length: 52px; ratio: 0.35;
ResizablePane { rect: split.first; background: Tokens.color-surface-1;
// The rail the column shuts to, and the column itself. if split.first-collapsed: VerticalLayout { alignment: start; padding: 16px; Icon { icon: IconSet.PanelLeft; size: 18px; tint: Tokens.color-muted-foreground; } } if !split.first-collapsed: VerticalLayout { alignment: start; padding: 16px; spacing: 8px; Text { text: "Files"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } Text { text: "main.slint"; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; } } }
ResizablePane { rect: split.second; Text { text: "Editor"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } } } } }}Nested splits
A Resizable dropped inside a pane fills it and keeps its own ratio, minimums
and handle. handle-thickness is the room reserved between the panes and also
the handle’s hit area.
import { Resizable, ResizableOrientation, ResizablePane,} from "@glint/components/resizable.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 600px; height: 340px; background: Tokens.color-background;
VerticalLayout { padding: 24px;
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
outer := Resizable { handle-label: "Resize the sidebar"; first-minimum: 120px; second-minimum: 200px; ratio: 0.3;
ResizablePane { rect: outer.first; background: Tokens.color-surface-1; Text { text: "Sidebar"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } }
ResizablePane { rect: outer.second;
inner := Resizable { orientation: ResizableOrientation.vertical; handle-label: "Resize the console"; first-minimum: 80px; second-minimum: 60px; ratio: 0.65;
ResizablePane { rect: inner.first; Text { text: "Editor"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } }
ResizablePane { rect: inner.second; background: Tokens.color-surface-1; Text { text: "Console"; color: Tokens.color-foreground; font-weight: Tokens.typography-weight-semibold; } } } } } } }}Disabled
disabled: true dims the handle and stops it responding — to the pointer, to
the keyboard and to the accessible actions alike, since all three go through the
same door.
import { Resizable, ResizablePane } from "@glint/components/resizable.slint";import { Switch } from "@glint/components/switch.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 560px; height: 320px; background: Tokens.color-background;
in-out property <bool> locked: true;
VerticalLayout { padding: 24px; spacing: 12px;
Switch { label: "Lock the layout"; checked <=> root.locked; }
Rectangle { border-width: 1px; border-color: Tokens.color-border-hairline; border-radius: Tokens.radius-md; clip: true;
split := Resizable { handle-label: "Resize the panes"; handle-grip: true; disabled: root.locked; first-minimum: 120px; second-minimum: 120px;
ResizablePane { rect: split.first; background: Tokens.color-surface-1; Text { text: "One"; color: Tokens.color-foreground; } }
ResizablePane { rect: split.second; Text { text: "Two"; color: Tokens.color-foreground; } } } } }}API Reference
Properties
| Property | Type | Default | Description |
|---|---|---|---|
orientation | in ResizableOrientation | ResizableOrientation.horizontal | Which way the panes sit; also the axis the handle moves along. |
ratio | in-out float | 0.5 | Two-way; the first pane's share of the resizable axis, 0..1. Writes from outside are bounded by the minimums, same as a drag. |
first-minimum | in length | 0px | Smallest the first pane may become. |
second-minimum | in length | 0px | Smallest the second pane may become. |
first-collapsible | in bool | false | Whether each pane may be shut altogether. A collapsible pane still holds at its minimum, but a move that asks for less than halfway between that minimum and collapsed-length snaps it shut instead, and the way back out reopens it at the minimum — the sidebar-collapse gesture, with the same hysteresis in both directions. |
second-collapsible | in bool | false | |
collapsed-length | in length | 0px | What a shut pane measures along the resizable axis: a rail's width, or 0px for a pane that disappears. One value for both, because a split that collapses on both ends collapses to the same rail. |
first-collapsed | out bool | no default | True while that pane is shut. Bind a rail's contents to it, or persist it beside ratio. |
second-collapsed | out bool | no default | |
handle-thickness | in length | 8px | Room reserved for the handle between the panes; also its hit area. |
handle-grip | in bool | false | Draw a grip on the handle, so the split can be found before the pointer is on it. |
keyboard-step | in length | 16px | How far one arrow key — or one accessible increment / decrement — moves the split. |
disabled | in bool | false | When true, the handle dims and stops responding. |
handle-label | in string | @tr("Resize panes") | Accessible name of the handle; override to name and translate it. |
first | out PaneRect | no default | The panes' boxes — hand each to a ResizablePane. |
second | out PaneRect | no default | |
first-length | out length | no default | Size of each pane along the resizable axis — the bounded truth, as opposed to ratio, which is only the intent. Everything the split says about itself, to a consumer's layout and to assistive technology alike, is derived from here, so a ratio that has not been reconciled yet can never be drawn — or announced. |
second-length | out length | no default |
Callbacks
| Callback | Description |
|---|---|
changed(float) | Fired with the new ratio whenever the user moves the split. |
Functions
| Function | Description |
|---|---|
set-ratio(r: float) | The one door every user interaction goes through: bound, then publish. |
increment() | Gives the first pane one keyboard-step more, bounded. |
decrement() | Takes one keyboard-step from it, bounded. |
Enums
| Enum | Values |
|---|---|
ResizableOrientation | horizontal, vertical |
ResizablePane
One side of the split: it takes a PaneRect from the Resizable above it and
carries your content in its @children slot, clipped to that box.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
rect | in PaneRect | no default | The geometry to occupy — split.first or split.second. |
PaneRect is a data type rather than a component — x, y, width and
height, in the split’s own coordinates. Feed it to a ResizablePane; nothing
else reads it.
Accessibility
- The handle is the control, and it speaks as a slider. Slint has no
separator role (ADR-0015), and a slider is the role that carries a value, the
range it lives in, the axis it runs along and the actions that move it. Name
it with
handle-label. - The value is the first pane’s percentage of the axis, and it is read off
the pane rather than off
ratio— so the value can never fall outside the bounds announced beside it.accessible-value-minimumandaccessible-value-maximumare the first pane’s reachable bounds expressed the same way — including a collapsed bound when either pane is collapsible — andaccessible-value-stepiskeyboard-stepas a percentage. - Accessible actions. Increment and decrement move the split one
keyboard-step; set-value takes a percentage. All three go through the same bounding door the pointer and the keyboard do, and all three are refused whiledisabled. - Keyboard. Tab focuses the handle —
resizable.focus()lands there too, it being the split’s only focusable part. Left / Right step a horizontal split and Up / Down a vertical one, bykeyboard-step; Home and End run it to either end, which for a collapsible pane is shut rather than its minimum. A step has no threshold to cross the way a drag does, so a collapsible pane sitting on its minimum shuts on the next step in and reopens at that minimum on the step back out. - Keyboard-only focus ring. The ring is drawn around the handle when the split is reached from the keyboard, and not when it is pressed with a pointer.
handle-gripis findability, not affordance. The chip astride the hairline is wider than the hit strip on purpose — it makes the split findable before the pointer is on it — and it draws only; the handle still owns every pointer event over it.