Stack, Inline and Grid
The three flow primitives that lay out everything in buttery, and the divider between their children.
Stack, Inline and Grid lay out their children in a column, a row or a grid, with a gap from the spacing scale between them. Use them for every arrangement of siblings, so space belongs to the parent and a component looks the same wherever it's placed.
Choosing one
Stack
For a column: a form's fields, a card's title over its text.
Inline
For a row: a button group, an icon beside its label.
wraplets it wrap, anduniformmakes every child as wide as the widest, for a row of equal buttons.Grid
For columns that change with the room, stepping at 640px and 1024px of the container it's in.
columns={{ base: 1, md: 2, lg: 3 }} gives a Grid one column below 640px of its container, two from 640px and three from 1024px.
All three take align and justify, and render a div unless you pass asChild to lay out an element of your own, such as a ul.
Lists. When you lay out a ul or ol with asChild, give it role="list". The role drops the bullets and the indent, and keeps the list announced as a list in Safari, which drops that along with the bullets.
Stack, Inline and Grid together in a plan picker:
import type { ReactElement } from "react";
import { Grid, Inline, Stack, Text } from "@butteryui/core";
import { Button } from "@butteryui/react";
export function PlanPicker(): ReactElement {
return (
<Stack gap={4}>
<Text variant="title-md">Choose a plan</Text>
<Grid columns={{ base: 1, md: 3 }} gap={4}>
<Text variant="body">Starter</Text>
<Text variant="body">Roaster</Text>
<Text variant="body">Wholesale</Text>
</Grid>
<Inline gap={2} justify="end">
<Button variant="soft">Compare plans</Button>
<Button variant="primary">Continue</Button>
</Inline>
</Stack>
);
}Divider draws a hairline between children, across a stack or down a row. It's decorative unless it separates groups a screen reader should know about.
Stack
When to use it
Use it for
- Stacking text, fields or sections vertically
- Any vertical group whose spacing should come from the scale
Not for
- Equal-width rows of buttons: use Inline uniform
- Two-dimensional layouts: use Grid
Examples
Empty state copy
A title and description stacked with the empty-state gap.
Your canvas is empty
Add a slide, or open a deck from your library.
Form fields
A deck's details as label and value pairs: the column keeps its rhythm as a note joins, and each pair keeps its tighter gap.
Spring launch
Wide, 1920 × 1080
Variants and states
Gap
The same three boxes at gaps 1, 2, 4 and 6 (4, 8, 16 and 24px).
Alignment
Stretch (the default), start, center and end.
Props
Stack
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "stretch" | "baseline" | "stretch" | Cross-axis (horizontal) alignment. Defaults to "stretch". |
justify | "start" | "center" | "end" | "between" | "start" | Main-axis (vertical) distribution. Defaults to "start". |
asChild | boolean | Render the single child element instead of a div, with Stack's props merged onto it. Give a ul or
ol laid out this way role="list" yourself, which drops its bullets and indent. | |
gap | SpaceValue | Space between children. | |
pad | SpaceValue | Padding on every side. | |
padX | SpaceValue | Left and right padding. Wins over pad on those sides. | |
padY | SpaceValue | Top and bottom padding. Wins over pad on those sides. |
It also takes ComponentPropsWithRef<"div">.
Accessibility
No role of its own; renders a div (or its child with asChild), so it adds nothing to the accessibility tree.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It's as wide as its parent and as tall as its children and the gaps between them. Children stretch across the column unless align says otherwise, and each can shrink below its content's width, so long text wraps or truncates instead of widening the column. It has no hit area of its own.
Related
Inline
When to use it
Use it for
- Icons next to labels, button groups, chips in a row
- Equal-width button pairs with uniform
Not for
- Toolbars, header bars and nav bars that might run out of room: use OverflowCluster
- Columns of content: use Stack or Grid
Examples
Equal buttons
A tour's step buttons sharing the width equally, whatever their labels.
Step 3 of 3: Share your deck
Icon and label
An icon and its label with the 6px icon gap.
Variants and states
Gap
Toolbar clusters use gap 1, button groups gap 2.
Uniform
Children share the width equally, regardless of their content.
Justify
Start (the default), center, end and between.
Props
Inline
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "stretch" | "baseline" | "center" | Cross-axis (vertical) alignment. Defaults to "center". |
justify | "start" | "center" | "end" | "between" | "start" | Main-axis (horizontal) distribution. Defaults to "start"; ignored when uniform. |
uniform | boolean | false | Give every child the same width, such as a dialog's back and forward buttons. Defaults to false. |
wrap | boolean | false | Let children wrap onto more lines. Defaults to false. Toolbars, header bars and nav bars never wrap:
use OverflowCluster for those. |
asChild | boolean | Render the single child element instead of a div, with Inline's props merged onto it. | |
gap | SpaceValue | Space between children. | |
pad | SpaceValue | Padding on every side. | |
padX | SpaceValue | Left and right padding. Wins over pad on those sides. | |
padY | SpaceValue | Top and bottom padding. Wins over pad on those sides. |
It also takes ComponentPropsWithRef<"div">.
Accessibility
No role of its own; reading order follows source order, so keep the visual and DOM order the same.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It's as wide as its parent and as tall as its tallest child, with its children in one row a gap apart, vertically centered unless align says otherwise. Children can shrink below their content's width, the row wraps only with wrap, and uniform gives every child the same width. It has no hit area of its own.
Related
Grid
When to use it
Use it for
- Cards in a grid, swatch grids, any set of equal peers
Not for
- One row of controls: use Inline
- Page layouts on the 12-column grid: use Page and Section
Examples
Review board
A review board's three columns of decks as equal cards with the 16px card gap: stacked when narrow, three across from 640px.
3 decks
1 deck
12 decks
Settings pairs
Two equal columns of label and value pairs.
Width
1080 pxHeight
1350 pxVariants and states
Columns
Equal columns that never widen for long content.
Props
Grid
| Prop | Type | Default | Description |
|---|---|---|---|
columns | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ResponsiveGridColumns | 1 | Number of equal columns, or columns that change with the container's width, such as
{ base: 1, md: 3 }. Defaults to 1. |
align | "start" | "center" | "end" | "stretch" | "baseline" | "stretch" | Vertical alignment of items within their row. Defaults to "stretch". |
asChild | boolean | Render the single child element instead of a div, with Grid's props merged onto it. | |
gap | SpaceValue | Space between children. | |
pad | SpaceValue | Padding on every side. | |
padX | SpaceValue | Left and right padding. Wins over pad on those sides. | |
padY | SpaceValue | Top and bottom padding. Wins over pad on those sides. |
It also takes ComponentPropsWithRef<"div">.
ResponsiveGridColumns
Columns that change with the space the grid has: `base` below 640px of its container, `md` from 640 and `lg` from 1024 (the page's breakpoints). A size left out keeps the one below it.
| Field | Type | Default | Description |
|---|---|---|---|
base | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 1 | Columns below 640px. Defaults to 1. |
md | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Columns from 640px. | |
lg | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Columns from 1024px. |
Accessibility
No role of its own; items are read row by row in source order.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It's as wide as its parent, split into equal columns that can each shrink to nothing, so one long item never widens its column. Given an object, the columns change at 640px and 1024px of the nearest size container. Each row is as tall as its tallest item, which the others stretch to unless align says otherwise. It has no hit area of its own.
Related
Divider
When to use it
Use it for
- Separating groups in a menu, toolbar or header
- Between rows that aren't already divided by their card
Not for
- Spacing groups apart: use the parent's gap
- Rows inside an inspector card: the card draws their dividers
Examples
Grouped list
A deck's slides and who it's shared with in one card, split into two groups by a divider.
Header separator
A header's title and save status, split by the 24px vertical divider, with the save action at the end.
Variants and states
Horizontal
Between groups in a column.
Vertical
Full height by default, or a length from the spacing scale.
Props
Divider
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | "horizontal" (the default) runs across a column; "vertical" separates items in a row. |
length | SpaceValue | Length of a vertical divider on the spacing scale, such as 6 for the 24px separator in a header.
Without it the divider spans the row's full height. | |
decorative | boolean | false | Hide the divider from screen readers when it's purely visual. Defaults to false, which announces it
as a separator. |
It also takes Omit<ComponentPropsWithRef<"div">, "children">.
Accessibility
Announced as a separator with its orientation; decorative dividers use role="none" and are skipped.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
A horizontal divider runs across its column's full width; a vertical one runs its row's full height, or the length you give it on the spacing scale, centered, such as length={6} for 24px. Either way it takes up only its 1px line, never growing or shrinking with the space around it, and it has no hit area.
Related
Related
- The layout contract
The 13 rules every buttery screen follows, so nothing overlaps, overflows or shifts, and how they're checked.
- Spacing and sizing
The 4px spacing scale, the control heights that change with density, and the widths pages and panels keep to.