Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Inspector

An editor's screen parts: the header bar, the stage it works on and the inspector of settings beside it.

The inspector pattern is the set of parts an editor is made of: a HeaderBar, the Stage the work sits on, and the Inspector of settings beside it. Put them in AppShell and an editor works from a 390px phone, where the inspector becomes a sheet, to a wide desktop.

How the parts fit

  • The header

    HeaderBar holds the app's mark, the document's title and its actions. The title truncates when space runs out, and the actions fold into a "More" menu when they don't fit.

  • The stage

    Stage fits the artwork at its shape into the space it has. Artwork may overlap the canvas's edges, which clip it, and a crop mode puts handles on its frame to resize it.

  • The inspector

    Inspector is a panel of titled sections, each an Inspector.Section, holding rows of controls. A section can offer Reset for its own settings.

  • Its rows

    Each Inspector.Row is 50px tall, with the label at the start and the control at the end. A slider labels itself with its value badge.

HeaderBar

When to use it

Use it for

  • The top of an app window or editor: the document's name and the actions that apply to it

Not for

  • A marketing site's navigation: use NavBar
  • Actions for one mode: use FloatingToolbar

Examples

Document header

An editor's header over its canvas: Rename opens a form dialog, and Undo and Redo, after a separator, walk back through the names.

Spring launch

Spring launch

Settings header

A plain header for a settings window: the title, and one action that stays focusable once it's done.

Settings

Two settings changed

Variants and states

Editor header

The mark and title, text and icon actions, the canvas shape select, then undo, redo and help after a separator.

Quarterly review

When it narrows

At 400px the lowest-priority actions fold into the More menu; the title keeps its words.

Quarterly review

Props

HeaderBar

PropTypeDefaultDescription
title

required

ReactNodeWhat's open, such as the document's name. It truncates when space runs out.
markReactNodeThe app's own icon, 24px, before the title.
dragRegionbooleanIn the desktop app, lets the window be dragged from anywhere in the bar (its buttons still take presses) and leaves room for the window controls.
childrenReactNodeThe actions, at the end: HeaderBar.Actions, HeaderBar.Separator and icon buttons.

It also takes Omit<ComponentPropsWithRef<"header">, "title">.

HeaderBar.Action

PropTypeDefaultDescription
label

required

stringWhat it does, such as "Crop". The bar shows it as text unless iconOnly; the menu always does.
onSelect

required

() => voidRuns the action, from the bar or from the "…" menu.
iconComponentType<IconComponentProps>An icon for it, shown in the menu and, with iconOnly, in the bar.
iconOnlybooleanShows only the icon in the bar, with the label as its tooltip and name. Needs icon.
disabledbooleanKeeps it from being chosen. The bar's button stays focusable (aria-disabled), so pressing the last Undo or Delete doesn't drop keyboard focus.
prioritynumber0Higher stays in the bar longer. Defaults to 0.

HeaderBar.Actions

PropTypeDefaultDescription
moreLabelstring"More"Accessible name of the "More" button. Defaults to "More".
size"sm" | "control" | "button"Height of the "More" button, to match the items: "sm" (28), "control" (34) or "button" (36, the default). Compact density shortens them like any control.
menuReactNodeYour own items for the "More" menu, which then always shows, such as every choice with actions on them. Without it, the menu lists the items that don't fit and shows only when something doesn't.
gapSpaceValueSpace between children.

It also takes ComponentPropsWithRef<"div">.

Also exported

ExportDescription
HeaderBarSeparator(): ReactElementA 1px × 24 line between groups of actions.

Accessibility

A header holding a title and buttons. Actions that don't fit move into a "More actions" menu (a menu button), never off screen; icon-only actions are named by their labels, with tooltips. With dragRegion the desktop window drags from the bar while its buttons keep taking presses.

KeysWhat they do
Enter or SpacePresses the focused action, or opens More actions with the actions that don't fit
Arrow keysMove through More actions while it's open, where Enter chooses an action
EscapeCloses More actions and puts focus back on its button

Motion

It doesn't move.

With reduced motion: Nothing changes.

Layout

It takes its container's width at 56px tall (--bt-h-header), padded 16px a side, or past the window controls at the start with dragRegion in the desktop app. The 24px mark and the actions keep their size and never wrap: as the bar narrows the lowest-priority actions fold into More actions first, and only then does the title truncate, with its full text in its title.

See it in the Deckhand demo.

Inspector

When to use it

Use it for

  • The settings beside a canvas or a preview: an editor's right-hand panel
  • Any long list of controls grouped into sections that reset to defaults

Not for

  • A form that's submitted: use the form fields with a submit button
  • A few controls in a toolbar: use HeaderBar or FloatingToolbar

Examples

Export settings

A small inspector: one section of rows that resets to its defaults, and a footer with the main action.

Image

Format
Quality90%

Nothing exported yet

Placement row

A slide editor's placement section: a split row with a tilt pad and the chip that lays the picture flat, beside an anchor grid.

Team photo

Tilted, anchored to the center

Placement

Tilt
Arrow keys tilt it; Backspace sets it level.
Anchor

Variants and states

Slide settings

Sections on nested surfaces, rows of controls, a split row for the tilt pad and the anchor grid, and a footer that stays put while the sections scroll.

Picture

Edge
Scale75%
Corners16

Placement

Turn0°
Tilt
Arrow keys tilt it; Backspace sets it level.
Anchor

Background

Hues

Props

Inspector

PropTypeDefaultDescription
label

required

stringNames the panel for screen readers, such as "Slide settings".
footerReactNodeWhat stays at the bottom while the sections scroll, such as <Inspector.Footer> with Copy and Export.

It also takes ComponentPropsWithRef<"div">.

Inspector.Footer

It also takes ComponentPropsWithRef<"div">.

Inspector.Row

PropTypeDefaultDescription
labelReactNodeWhat the row sets, shown at its start. Leave it out when the control brings its own, as Slider does.
extraReactNodeSomething beside the label, such as a Chip that resets this row.
blockbooleanLets the row grow to fit what it holds, such as a swatch grid, padded 16 on every side.
childrenReactNodeThe control. Without a label, it fills the row.

It also takes Omit<ComponentPropsWithRef<"div">, "children">.

Inspector.RowSplit

It also takes ComponentPropsWithRef<"div">.

Inspector.Section

PropTypeDefaultDescription
title

required

stringThe section's heading, such as "Background".
onReset(() => void)Puts back the section's defaults. Given, the header shows a Reset action.
resettablebooleantrueWhether anything in it differs from its defaults; while it doesn't, Reset is dimmed. Defaults to true.
resetLabelstring"Reset"The Reset action's word. Defaults to "Reset"; it's read with the title, as in "Reset Background".

It also takes Omit<ComponentPropsWithRef<"section">, "title">.

Accessibility

A named region; each section is a region named by its heading (an h2). Reset in a header is named with the section ("Reset Background") and stays focusable while dimmed. The sections scroll inside the panel, so the footer's actions are always in reach.

KeysWhat they do
Enter or SpacePresses a section's Reset, which puts its rows back to their defaults; dimmed, it keeps focus and does nothing

Motion

It scales to 97% while it's pressed, over `--bt-dur-press`, and springs back when it's let go.

With reduced motion: A section's Reset button still shrinks to 97% while it's pressed, but at once instead of easing.

Layout

It's 340px wide (--bt-w-inspector), never wider than its container, and as tall as its container lets it be: the sections scroll inside with faded edges while the footer stays at the bottom. Rows are 50px tall (--bt-h-row), split rows 64px (--bt-h-row-split) and block rows grow to fit; row labels and section titles truncate with their full text in their title, and each section's Reset is 28px tall.

See it in the Deckhand demo.

Stage

When to use it

Use it for

  • The preview in an editor: a slide, a picture, a page
  • Anything shown at a fixed shape that has to fit whatever room it gets

Not for

  • Content that scrolls: use a page layout
  • A thumbnail in a grid: use FitBox

Examples

Canvas shapes

The canvas at each shape it's given: pick one and it reshapes, staying centered in its space.

1920 × 1080

Crop canvas

A canvas in crop mode, its size kept in state while the handles move.

1200 × 1200

Slide editor

A slide editor's preview: a title field and background swatches, an inspector's width, change what the canvas shows.

Spring launch

Background · Lilac

Slide preview

A slide preview at its output size, with the canvas exposed for a toolbar to anchor to.

Spring launch

Variants and states

Canvas and crop

Change the shape and the canvas reshapes on spring.smooth; Resize fades the background and shows the handles and the toolbar.

Quarterly review

Props

Stage

PropTypeDefaultDescription
label

required

stringNames the canvas for screen readers, such as "Slide preview".
size

required

StageSizeThe canvas size in output pixels; the canvas shows at this shape, as large as fits.
backgroundstringThe canvas's CSS background: a color, a gradient or an image. It fades out in crop mode.
croppingbooleanCrop mode: the background fades, and handles on the canvas's frame resize it.
onSizeChange((size: StageSize) => void)Called while a crop handle is dragged, with the new size.
onSizeCommit((size: StageSize) => void)Called when a crop drag ends, with the size it settled on, for one undo step per drag.
minSizenumber64The smallest side a crop can leave, in output pixels. Defaults to 64.
canvasRefRef<HTMLDivElement>The canvas element, to anchor a FloatingToolbar under it.
childrenReactNodeWhat's drawn on the canvas. It may overlap the canvas's edges, which clip it.

It also takes Omit<ComponentPropsWithRef<"div">, "children">.

StageSize

A canvas size in output pixels, such as 1600 × 900.

FieldTypeDefaultDescription
width

required

numberWidth in output pixels.
height

required

numberHeight in output pixels.

Also exported

ExportDescription
useStageLayout(dependency?: string | number | boolean): StageLayoutKeeps a Motion element on a Stage canvas in shape while the canvas reshapes. The canvas reshapes with a layout animation, which stretches what's on it; spread this on the element and it measures with the canvas and scales evenly from its old box to its new one instead. Pass what else moves it, such as its anchor, as dependency. With reduced motion, it turns the animation off as the canvas does.

Accessibility

The canvas is an image named by label; what it shows is set from the controls around it. The crop handles take the pointer; the keyboard sets the same size through exact fields, such as width and height in a canvas Select's footer.

It takes no keyboard input of its own.

Motion

It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.

With reduced motion: The canvas takes its new shape at once, and in crop mode its background goes and its handles show without fading.

Layout

It fills its parent's box, so give the parent a height, and draws the largest canvas of its shape that fits 32px inside it, centered, r12 with a card shadow; artwork may overlap the canvas's edges, which clip it. In crop mode the eight 12px handles each have a 24px hit area.

See it in the Deckhand demo.

  • App shell

    The frame of a whole app, as an editor or a dashboard, and the page that goes in it.

  • Slider

    A labeled slider row: the label and its value badge beside a track you drag, click or step with the keyboard.