IX Surfaces

Star on GitHubFeedback
IX SurfacesNative HTML and a hookot-resize

Resizable

Panes and the rule between them. Take the rule and it inks; while you hold it, each pane's share is drawn as a dimension.

Report an issue with Resizable

Example

The brief

A new identity for a ferry line that has run between the islands since 1911.

The notes

Keep the flag. Lose the anchor. The timetable is the real product.

Halden

The line, its flag and its timetable.

Quay

Where the timetable is read.

The ferry line has run between the islands since 1911, and for most of that time its timetable was the only thing anyone read. The flag stayed; the anchor came and went with each new owner. What the islanders asked for was simple: a timetable large enough to read from the quay, the boats' names where they could find them, and nothing on the hulls that the type did not already say.

At rest

The brief

A new identity for a ferry line that has run between the islands since 1911.

The notes

Keep the flag. Lose the anchor. The timetable is the real product.

The rule taken

The brief

A new identity for a ferry line that has run between the islands since 1911.

The notes

Keep the flag. Lose the anchor. The timetable is the real product.

fit, a narrow pane

Halden

The line, its flag and its timetable.

Quay

Where the timetable is read.

fit, a wide pane

Halden

The line, its flag and its timetable.

Quay

Where the timetable is read.

flow, the break moved

The ferry line has run between the islands since 1911, and for most of that time its timetable was the only thing anyone read. The flag stayed; the anchor came and went with each new owner. What the islanders asked for was simple: a timetable large enough to read from the quay, the boats' names where they could find them, and nothing on the hulls that the type did not already say.

Install

Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.

npx shadcn@latest add https://thedirectors.agency/ui/r/resizable.json

Usage

resizable-example.tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup, ResizableTitle } from "@/components/ui/resizable"const story ="The ferry line has run between the islands since 1911, and for most of that time its timetable was the only thing anyone read. The flag stayed; the anchor came and went with each new owner. What the islanders asked for was simple: a timetable large enough to read from the quay, the boats' names where they could find them, and nothing on the hulls that the type did not already say."function Rule({ at = 50, force }: { at?: number; force?: boolean }) {return (<ResizablePanelGroup direction="horizontal"><ResizablePanel defaultSize={at} minSize={20}><p className="ot-mp mb-2 text-ink">The brief</p><p>A new identity for a ferry line that has run between the islands since 1911.</p></ResizablePanel><ResizableHandle aria-label="Width of the brief" data-force={force ? "hover" : undefined} /><ResizablePanel defaultSize={100 - at} minSize={20}><p className="ot-mp mb-2 text-ink">The notes</p><p>Keep the flag. Lose the anchor. The timetable is the real product.</p></ResizablePanel></ResizablePanelGroup>)}function Fit({ at = 62 }: { at?: number }) {return (<ResizablePanelGroup direction="horizontal" variant="fit"><ResizablePanel defaultSize={at} minSize={15}><ResizableTitle>Halden</ResizableTitle><p>The line, its flag and its timetable.</p></ResizablePanel><ResizableHandle aria-label="Width of Halden" /><ResizablePanel defaultSize={100 - at} minSize={15}><ResizableTitle>Quay</ResizableTitle><p>Where the timetable is read.</p></ResizablePanel></ResizablePanelGroup>)}function Flow({ at = 50 }: { at?: number }) {return (<ResizablePanelGroup direction="horizontal" variant="flow" text={story}><ResizablePanel defaultSize={at} minSize={25} /><ResizableHandle aria-label="Width of the first column" /><ResizablePanel defaultSize={100 - at} minSize={25} /></ResizablePanelGroup>)}export default function Example() {return (<div className="grid gap-10"><Rule /><Fit /><Flow /></div>)}

Contract

  • Underneath: hook (a separator you can drag or move with the arrow keys).
  • Anatomy: .ot-resize holding two .ot-resize-pane, <div class="ot-resize-handle" role="separator" tabindex="0" aria-valuenow> between them, and .ot-resize-dim (aria-hidden). Script sets --split (a percentage) and data-dragging.
  • Take the rule and it inks; while held, each pane's share is drawn as a dimension, after Paul Rand.
  • Keyboard: Left and Right move it by 5, Home and End go to 20 and 80.
  • The frame is a construction line, dotted in rule-strong, after Rand's working drawings: it marks where the panes may go rather than boxing them.
  • Variants go on data-variant (ResizablePanelGroup variant): rule (the default), fit, flow.
  • Fit, after "It has to be design." set heavy and condensed, and wood type locked up to the measure: each pane's <h3 class="ot-resize-title" data-slot="resizable-title"><span> is set in 800 to fill its pane edge to edge. The script moves Archivo's width axis first (62% as the pane narrows, 125% as it widens), and changes the size only past those ends, so narrowing a pane condenses its word before it shrinks it. It sets data-set once measured, and re-sets on every resize and when <html> changes its face.
  • Flow, after the newspaper's continued column: the group's text runs through the panes as .ot-resize-flow paragraphs of .ot-resize-line lines (aria-hidden; the whole text once, in the first, as .ot-sr). The first pane holds as many lines as its height does, and the rest carry on at the top of the next, at its measure, the first of them in ink; move the rule and the break moves. pretext lays the lines; until it has, the first pane holds the plain paragraph. The group takes a fixed height (17rem).
  • Forced colours: the rules and dimensions are drawn in CanvasText (Highlight when taken).

Props

Props of Resizable
PropTypeDefaultWhat it does
ResizablePanelGroup direction"horizontal" | "vertical"Which way the panels sit. Panels and handles must be its direct children, a handle between each pair, in that order (panel, handle, panel). They are told apart by place, not type.
ResizablePanelGroup variant"rule" | "fit" | "flow""rule"rule: panes and the rules between them. fit: each pane's ResizableTitle is set heavy to the pane's measure, as wood type is locked up: it condenses as the pane narrows and extends as it widens, and changes size only past the ends of the width axis. flow: the group's text runs through the panes as continued columns; the first holds as many lines as its height allows, the rest carry on in the next, and moving the rule moves the break. Flow sets the group's height.
ResizablePanelGroup textstringIn flow, the one paragraph that runs through the panes. Plain text: pretext measures it. A reader of the page gets it whole, once, in the first pane.
ResizableTitleh3A pane's title. In fit it fills its pane edge to edge; keep it to a word or two. Elsewhere it is a plain heading.
ResizablePanel defaultSizenumberIts share of the group in percent. Panels without one share what's left.
ResizablePanel minSizenumber10The least it can be, in percent.
ResizableHandleseparatorrole=separator with aria-valuenow, min and max. Arrow keys move it in fives (Shift, tens); Home and End go to the limits. Give it an aria-label naming the panel it sizes.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
Resizable, aspect ratioPaul Rand dimension linesShares measured; a printer's crop marks
Fit resizable"It has to be design." heavy condensed; wood type locked to the measureThe title fills its pane, condensing before it shrinks
Flow resizableThe newspaper's continued columnOne paragraph runs through the panes; the rule moves the break

How it moves

How it moves
ComponentArticulationWhat moves
ResizableMeasureThe rule inks; the dimensions appear while held
Fit resizableMeasureThe titles condense and extend with the rule, frame by frame
Flow resizableMeasureThe lines re-break under the hand as the rule moves