Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Responsive

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

A responsive buttery component changes its layout by the width of the container it's in, through CSS container queries, not by the width of the window. Build your own components the same way, so one component works in a 340px inspector, a sheet on a phone and a 1200px page.

Containers

Page, AppPage, AppShell's column and the patterns that change their own layout, such as SplitHero or Bento, are size containers; Container, Surface and Stack aren't. Grid's columns and the display type step at 640px and 1024px of the nearest one, so a responsive grid needs one above it, and development builds warn when it has none.

Your own component can query them too:

Do

css

.order-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--bt-space-4);
}

@container (width >= 640px) {
  .order-summary {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }
}

The summary takes two columns once its container is 640px wide, whether that's a page or a wide panel.

Don't

css

@media (width >= 640px) {
  .order-summary {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }
}

A viewport query is wrong as soon as the component sits in a panel.

When things don't fit

  • A row runs out of room

    OverflowCluster moves the row's least important items into a "More" menu, rather than wrapping the row or shrinking its text.

  • Text runs long

    Text truncate cuts a line that doesn't fit short with an ellipsis, and keeps the whole text in a tooltip.

  • Content has a fixed aspect

    FitBox scales content of a fixed aspect, such as a canvas, into whatever space it has.

Media queries in code

Lay out with container queries in CSS first. When code has to know about the device itself, such as whether it has a fine pointer, useMediaQuery(query) returns whether a media query matches, and stays current as that changes.

tsx

import type { ReactElement } from "react";
import { Text } from "@butteryui/core";
import { useMediaQuery } from "@butteryui/react";

export function DragHint(): ReactElement {
  const finePointer = useMediaQuery("(pointer: fine)");
  return (
    <Text variant="caption">
      {finePointer
        ? "Drag a card to reorder it"
        : "Press and hold a card to reorder it"}
    </Text>
  );
}

On a server. useMediaQuery returns false when it's rendered on a server.

Checking it

expectButtery checks a page at the widths you give it, from a 390px phone to a 1728px desktop, so a layout that only works at one width fails its test. Check your layout shows how to run it.

  • The layout contract

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

  • Page grid and containers

    The page grid, the content column, sections and centered content, for marketing and content pages.