Skip to content
Glint UI

Resizable

Two panes and the handle that trades room between them, with minimums, collapsing and a keyboard.

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.

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.

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.

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.

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.

API Reference

Properties

PropertyTypeDefaultDescription
orientationin ResizableOrientationResizableOrientation.horizontalWhich way the panes sit; also the axis the handle moves along.
ratioin-out float0.5Two-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-minimumin length0pxSmallest the first pane may become.
second-minimumin length0pxSmallest the second pane may become.
first-collapsiblein boolfalseWhether 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-collapsiblein boolfalse
collapsed-lengthin length0pxWhat 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-collapsedout boolno defaultTrue while that pane is shut. Bind a rail's contents to it, or persist it beside ratio.
second-collapsedout boolno default
handle-thicknessin length8pxRoom reserved for the handle between the panes; also its hit area.
handle-gripin boolfalseDraw a grip on the handle, so the split can be found before the pointer is on it.
keyboard-stepin length16pxHow far one arrow key — or one accessible increment / decrement — moves the split.
disabledin boolfalseWhen true, the handle dims and stops responding.
handle-labelin string@tr("Resize panes")Accessible name of the handle; override to name and translate it.
firstout PaneRectno defaultThe panes' boxes — hand each to a ResizablePane.
secondout PaneRectno default
first-lengthout lengthno defaultSize 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-lengthout lengthno default

Callbacks

CallbackDescription
changed(float)Fired with the new ratio whenever the user moves the split.

Functions

FunctionDescription
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

EnumValues
ResizableOrientationhorizontal, 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

PropertyTypeDefaultDescription
rectin PaneRectno defaultThe 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-minimum and accessible-value-maximum are the first pane’s reachable bounds expressed the same way — including a collapsed bound when either pane is collapsible — and accessible-value-step is keyboard-step as 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 while disabled.
  • 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, by keyboard-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-grip is 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.