Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

The layout contract

The 13 rules every buttery screen follows, so nothing overlaps, overflows or shifts, and how they're checked.

The layout contract is the set of rules buttery's components follow and its checks enforce: things sit in flow with gaps between them, take their sizes from tokens, and never overlap, overflow or move after a press. Follow it in your own screens, and the same checks keep them right at every width.

The 13 rules

  1. Flow layout

    Siblings are laid out by Stack, Inline or Grid, with a gap from the spacing scale. A component never sets its own outer margin, and nothing uses negative margins.

  2. Out-of-flow is owned

    absolute, fixed and sticky belong to the layer primitives (Layer, Popover, Scrim, Sticky and FloatingLanes) and to a component's own parts, such as a slider's thumb. App code uses the primitives.

  3. Floating UI goes in lanes

    Toasts and notices stack 8px apart in a FloatingLanes lane, never wider than it and clear of sticky bars and toolbars, so they never cover each other.

  4. Overlays avoid collisions

    Popovers and menus open 8px from their trigger without covering it, stay 8px inside the window, and scroll inside when they're taller than the room.

  5. Sizes come from tokens

    Controls take their heights from the height tokens and fixed widths from the width tokens. Everything else takes the width its container gives it.

  6. Children can shrink

    Every flex and grid child in buttery's primitives has min-width: 0, so long content shrinks instead of pushing its neighbors off the screen.

  7. Text never overflows

    One-line control text truncates with an ellipsis and keeps the whole text in a tooltip, as Text truncate does. Body text wraps at 60 characters a line and display headings at 22, and numbers that change use tabular figures so they don't push their neighbors.

  8. Media keeps its shape

    Images and canvases keep their aspect ratio, and FitBox fits content of a given aspect into the space it has, as large as it can and centered.

  9. Containers, not the viewport

    Components respond to the room they're given, through container queries, so the same component works in a 340px inspector and on a 1200px page.

  10. Overflow by priority, not by wrapping

    A row that runs out of room moves its least important items into More with OverflowCluster. Applied filter pills are the exception: FilterBar wraps them, so each one stays in sight to remove.

  11. Scrolling stays inside

    The page never scrolls sideways, and a panel scrolls on its own without taking the page with it.

  12. Sticky things reserve space

    Sticky bars publish their height as --bt-sticky-top, so a link to a heading lands below them instead of under them.

  13. Nothing moves after a press

    Loading states, skeletons and labels that change, such as Continue becoming Finish, keep their exact size, and fonts load without moving text. Only what the press is about moves.

Do and don't

The first rule in code: the parent sets the space between its children.

Do

tsx

import type { ReactElement } from "react";
import { Stack, Text } from "@butteryui/core";
import { Button } from "@butteryui/react";

export function Notice(): ReactElement {
  return (
    <Stack gap={3}>
      <Text variant="title-sm">Payouts are paused</Text>
      <Button size="sm">Add a bank account</Button>
    </Stack>
  );
}

The Stack owns the gap, so the button sits right wherever the notice is used.

Don't

css

.notice-button {
  margin-top: 12px;
}

A margin on a child moves it wherever it's reused, and adds to whatever gap its next parent sets.

How it's checked

  • While you work

    LayoutGuard outlines anything that overlaps, overflows, crowds the next line, breaks a word, sits out of line or drifts off the scale. ⌥⇧L hides the outlines and ⌥⇧G shows the grid.

  • In your tests

    expectButtery(page) from @butteryui/test runs the same checks in Playwright, at every width, theme and density you ask for, with a fix for each problem.

  • In your styles

    buttery's stylelint config fails raw colors, lengths off the scale, z-index values outside a layer band, negative margins and transition: all.

  • Stack, Inline and Grid

    The three flow primitives that lay out everything in buttery, and the divider between their children.

  • Responsive

    Why buttery components respond to their container rather than the window, and how to do the same.

  • Check your layout

    Catching overlaps, overflow and layout shift while you work and in your tests.