Skip to main content

Breakpoints

Every example on this page is live, drawn by the library's Storybook, where you can also change its props and see the result.

Breakpoints let you lay your widget out with Tailwind classes that follow the size of the widget's own box, so one tree of elements rearranges itself for every shape a widget can take on a frame. They are container queries, not the sm: and md: prefixes you may know from web pages: those follow the page, while a widget has to follow its box, wherever it is shown.

There is nothing to import. Write a breakpoint in front of any Tailwind utility, and the utility applies only while the box is in that range:

BreakpointApplies while the box is...Preview Sizes it catches
@sm:too wide to be a slim columnSquare, Brick, Tall, Wide, Banner, Full
@md:at least half a frame wideWide, Banner, Full
@lg:wide enough for a block to one sideWide, Banner, Full
@max-sm: / @max-md: / @max-lg:narrower than thatColumn for @max-sm:, Square, Brick and Tall for @max-md:
short:too low for more than one row, and at least half a frame wideBanner
compact:too low for more than one row, and too short for a stripBrick
tall:high enough for a roomier layoutTall, Column, Full

No Preview Size lands between @md: and @lg:, but a widget can be dragged to any size in the Studio and placed in any slot on a frame, and @md: is there for the boxes in between: wide enough for two blocks side by side, not yet for a panel beside the main one.

The widths and heights these turn on are the same ones useLayoutShape uses to name a box's shape, so a class and the hook always agree:

ShapeIn classes
bandshort:
compactcompact:
narrow@max-sm: (a box at least as high as a square slot)
side@lg: (a box that is not also short)
stackeverything else, so the classes with no prefix
talltall:

A box that is too low for more than one row but too short to be a strip is compact, even when it is smaller than a square slot, and compact: applies to it rather than short:. Any other box smaller than a square slot in both directions is a stack, so neither short: nor compact: applies to it. It can still be @max-sm:, since that is a width alone: use @max-sm: for what a slim box needs whatever its height, such as stacking an icon over a figure, and leave the column's own arrangement to the plan from the hook.

Which one wins​

Write the classes for the smallest box first, with no prefix, and add the larger shapes in front of the utilities that change. A box can match a width and a height breakpoint at once: a Banner is both @lg: and short:. The height breakpoints come later in the stylesheet, so when both set the same property, the height one wins. That is usually what you want, since a strip has no room for the side-by-side layout a wide box gets. To ask for both at once, stack them (@lg:tall:gap-6), and to rule one out, negate it (not-short:flex-col).

Classes for the frame, the hook for the props​

Every widget starts from useLayoutShape: it names the box's shape, and the props a class cannot change follow from it, such as a component's size, a Divider's orientation or how many days a DayStrip shows. Breakpoints take the rest, everything CSS can set: which way a row runs, how blocks align, the gaps between them, how wide a side panel is, and what is hidden in a small box. You can also pick the whole frame in code from the shape and use the breakpoints only for the parts a class carries well, such as hiding a block in a small box; either way the two agree, since they turn on the same numbers. Here the frame is classes:

import { useLayoutShape } from "~lib/use-layout-shape";
import { Display, Heading } from "~lib/typography";
import { Icon } from "~lib/icon";
import { Divider } from "~lib/layout/divider";
import { KeyValue } from "~lib/data-display/key-value";
import { DayStrip } from "~lib/data-display/day-strip";

export const Widget = () => {
const { shape, tall } = useLayoutShape();
const beside = shape === "band" || shape === "side";
const forecast = shape !== "compact";

return (
<div className="flex size-full flex-col gap-3 overflow-hidden @lg:flex-row @lg:gap-6 short:flex-row short:items-center short:gap-3">
<div className="flex min-h-0 min-w-0 flex-1 flex-col justify-center gap-3 short:flex-none">
<Heading level={tall ? 2 : 4}>Today</Heading>
<div className="flex items-center gap-3 @max-sm:flex-col">
<Icon name="cloud-sun" size={tall ? 64 : 40} />
<Display size={tall ? "lg" : "sm"} unit="°C">
18
</Display>
</div>
<div className="hidden tall:block">
<KeyValue
orientation="horizontal"
size="sm"
data={[
{ key: "Feels like", value: "17°C" },
{ key: "Humidity", value: "64%" },
]}
/>
</div>
</div>
{forecast && (
<>
<Divider
orientation={beside ? "vertical" : "horizontal"}
spacing={beside ? "sm" : "none"}
/>
<div className="flex min-w-0 flex-col justify-center @lg:w-[42%] short:w-auto short:flex-1">
<DayStrip
direction={shape === "side" || tall ? "column" : "row"}
size={tall ? "md" : "sm"}
days={[
{ date: "2026-10-06", primary: "20° / 9°" },
{ date: "2026-10-07", primary: "15° / 10°" },
{ date: "2026-10-08", primary: "14° / 7°" },
]}
/>
</div>
</>
)}
</div>
);
};

Reading the root from left to right: a column with a small gap, which becomes a row with a larger gap once the box is wide enough for a side panel, and a tighter row centered on its height when the box is a strip. The main block takes the room left in either direction and gives it up in a strip, the details only appear in a tall box, the icon stacks above the figure in a column too narrow for both, and a compact box shows today alone. The divider and the forecast are left out in code rather than hidden with compact:hidden, since a Divider takes no classes.

Recipes​

  • The root is flex size-full flex-col gap-3 overflow-hidden, then @lg:flex-row for a wide box and short:flex-row short:items-center for a strip. Give it no padding: the person who installs the widget sets the space around it, so padding of your own would double up with theirs.
  • A side panel is @lg:w-[42%] on a block that is otherwise in the column's flow; the main block beside it is min-w-0 flex-1.
  • A block that takes the rest is min-h-0 min-w-0 flex-1, so it can shrink whichever way the row runs, and short:flex-none where a strip wants it sized by its content.
  • A header beside a strip's row is short:w-44 short:shrink-0; the row takes the rest.
  • Less in a small box is hidden tall:block on something only a tall box has room for, compact:hidden on what a compact box has no room for, and @max-sm:flex-col on a row that has to stack in a narrow column.
  • A grid that can take another column is grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3.

What to avoid​

  • sm:, md: and lg: without the @. They follow the page, not the widget. On a device a widget renders in a page of its own, so they seem to work in the Studio and then follow the wrong box in the widget catalog, where the same markup is shown inside the console.
  • Your own @container class. The widget's root is already the container the breakpoints measure; an element you make a container of its own becomes what its descendants measure instead, and its breakpoints stop following the widget's box.
  • Your own width and height checks in code. The breakpoints and useLayoutShape turn on the same numbers, which the Preview Sizes are tuned to; a threshold of your own would not be.

Every Preview Size​

The sample widget above, drawn at each Preview Size. In this storybook the classes carry the library's lib: prefix; in a widget they are written as in the code above.

Open Preview Sizes in Storybook

Any Size​

Open Custom Size in Storybook

See Also​

  • useLayoutShape - The box's shape and the room inside it, for the props a class cannot set
  • useResolution - The exact size of the widget's box
  • Card - A bordered block for a grid of items
  • Divider - A rule between blocks, horizontal or vertical