Display and sizing

The low-level half of layout: what box an element makes, how it aligns its children, how big it is allowed to get, and what happens when its content does not fit. Most pages want stack, cluster or grid instead — those are the same properties with the decisions already made. These are for the cases the primitives do not cover.

Display

.contents is the interesting one. It removes the element's own box while keeping its children in the parent's layout, which is how you get a server-rendered wrapper out of the way of a grid without changing the HTML. It also removes the box from the accessibility tree in some engines, so do not put it on anything with a role or a label.

Display utilities
Class Declares Resolves to Source
.hidden display: none — 09-utilities.css:10
.block display: block — 09-utilities.css:11
.inline-block display: inline-block — 09-utilities.css:12
.flex display: flex — 09-utilities.css:13
.inline-flex display: inline-flex — 09-utilities.css:14
.contents display: contents — 09-utilities.css:15

Flex direction and alignment

.grow sets flex: 1 1 auto and min-inline-size: 0 together. The second half is the fix for the commonest flex bug there is: a flex item will not shrink below its content, so a long unbroken string in a .grow item pushes the row wider than its container and the whole page picks up a horizontal scrollbar.

Flex direction and alignment utilities
Class Declares Resolves to Source
.flex-row flex-direction: row — 09-utilities.css:18
.flex-col flex-direction: column — 09-utilities.css:19
.wrap flex-wrap: wrap — 09-utilities.css:20
.nowrap flex-wrap: nowrap; white-space: nowrap — 09-utilities.css:21
.grow flex: 1 1 auto; min-inline-size: 0 — 09-utilities.css:32
.shrink-0 flex: 0 0 auto — 09-utilities.css:33
.items-start align-items: flex-start — 09-utilities.css:22
.items-center align-items: center — 09-utilities.css:23
.items-end align-items: flex-end — 09-utilities.css:24
.items-baseline align-items: baseline — 09-utilities.css:26
.items-stretch align-items: stretch — 09-utilities.css:25
.justify-start justify-content: flex-start — 09-utilities.css:27
.justify-center justify-content: center — 09-utilities.css:28
.justify-end justify-content: flex-end — 09-utilities.css:29
.justify-between justify-content: space-between — 09-utilities.css:30
.place-center place-items: center — 09-utilities.css:31

Size

All of these are logical: .w-full sets inline-size and .h-full sets block-size, so they follow the writing mode rather than the screen. .min-h-screen uses dvh rather than vh, which is what stops a full-height layout from being cropped by the address bar on a phone.

.min-is-0 is .grow's second half on its own, for grid items — a grid track's minimum is auto too, so a wide table inside one drags the whole track past the viewport unless the item is allowed to shrink.

Sizing utilities
Class Declares Resolves to Source
.w-full inline-size: 100% — 09-utilities.css:76
.w-auto inline-size: auto — 09-utilities.css:77
.h-full block-size: 100% — 09-utilities.css:78
.min-h-screen min-block-size: 100dvh — 09-utilities.css:79
.min-is-0 min-inline-size: 0 — 19-logical.css:74
.square aspect-ratio: 1 — 09-utilities.css:84
.max-w-sm max-inline-size: 24rem — 09-utilities.css:81
.max-w-md max-inline-size: 34rem — 09-utilities.css:82
.max-w-lg max-inline-size: 48rem — 09-utilities.css:83
.max-w-prose max-inline-size: var(--measure) max-inline-size: 68ch 09-utilities.css:80

Aspect ratio

Three ratios with object-fit: cover already set, for the media slot in a card or a gallery. Setting the ratio in CSS rather than leaving the image to size itself is what stops the page reflowing as images arrive.

Aspect ratio utilities
Class Declares Resolves to Source
.ratio-16x9 aspect-ratio: 16 / 9; object-fit: cover; inline-size: 100% — 04-layout.css:229
.ratio-4x3 aspect-ratio: 4 / 3; object-fit: cover; inline-size: 100% — 04-layout.css:230
.ratio-1x1 aspect-ratio: 1; object-fit: cover; inline-size: 100% — 04-layout.css:231
.square aspect-ratio: 1 — 09-utilities.css:84

Overflow

.overflow-clip and .overflow-hidden both cut the content off; the difference is that hidden makes the element a scroll container and clip does not. That matters more than it sounds: overflow: hidden on an ancestor is what breaks position: sticky, and swapping it for clip usually fixes a sticky header that mysteriously does not stick.

Overflow utilities
Class Declares Resolves to Source
.overflow-auto overflow: auto — 09-utilities.css:173
.overflow-clip overflow: clip — 09-utilities.css:174
.overflow-hidden overflow: hidden — 09-utilities.css:172

Visibility and input

.hidden removes an element from the page and from assistive technology. .sr-only does the opposite: it keeps the element in the accessibility tree and takes it off the screen, which is how you label an icon-only control. They are not interchangeable, and using .hidden for a label is how a button ends up announced as "button".

.touch-only and .mouse-only key off pointer: coarse and pointer: fine rather than width, so a touchscreen laptop is treated by what it has rather than by how wide it is. Neither is a substitute for making a control work with both.

Visibility and pointer utilities
Class Declares Resolves to Source
.sr-only position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0 — 09-utilities.css:182
.skip-link position: absolute; inset-block-start: -100%; inset-inline-start: var(--space-4); z-index: var(--z-toast); background: var(--brand-600); color: var(--text-on-brand); padding: var(--space-3) var(--space-4); border-radius: var(--r-sm) position: absolute; inset-block-start: -100%; inset-inline-start: 1rem; z-index: 500; background: oklch(51.6% var(--chroma-brand) var(--hue-brand)); color: oklch(99% .01 var(--hue-brand)); padding: .75rem 1rem; border-radius: 7px 09-utilities.css:193
.touch-only display: none — 09-utilities.css:238
.mouse-only display: none — 09-utilities.css:239
.cursor-pointer cursor: pointer — 09-utilities.css:179
.no-select user-select: none — 09-utilities.css:178
.pointer-none pointer-events: none — 09-utilities.css:177
.opacity-60 opacity: .6 — 09-utilities.css:176

Breakpoint prefixes

Deck has three viewport breakpoints — 40rem, 48rem and 64rem — and a small set of prefixed classes that turn on at each. They are mobile-first: the prefixed class applies at that width and above, so the unprefixed state is the phone.

<div class="stack stack-3 md:row md:items-center">
  <img class="ratio-1x1" src="…" alt="">
  <p>Stacked on a phone, side by side from 48rem up.</p>
</div>

The available combinations are fixed rather than generated, so this is not a system where any class can take any prefix. The rows below list what exists at each breakpoint, taken from the source.

The three rows below are named .sm, .md and .lg because that is how they land in the generated inventory: the extractor reads a class name up to the first character that cannot be in one, and in sm\:hidden that is the backslash escaping the colon. The classes you write are the full prefixed names. Recorded so the table is not silently wrong about it.

Viewport breakpoint prefixes
Class Declares Resolves to Source
.sm .sm\:hidden
@media (min-width: 40rem)
display: none
— 09-utilities.css:207
.md .md\:hidden
@media (min-width: 48rem)
display: none
— 09-utilities.css:215
.lg .lg\:hidden
@media (min-width: 64rem)
display: none
— 09-utilities.css:229
Which prefixed utilities exist at each viewport breakpoint
Breakpoint Applies from Classes
sm: 40rem hidden block flex row w-auto text-start
md: 48rem hidden block flex grid-d row col items-center justify-between w-auto text-center p-8 gap-6
lg: 64rem hidden block flex grid-d row gap-8

Container queries

A viewport breakpoint asks how wide the window is. A container query asks how wide the element's own column is, which is nearly always the question you actually meant: the same card should stack in a sidebar and spread out in a main column, and the window has no opinion about which of those it is in.

.cq marks an element as a container. The named variants exist so a deeply nested component can query a specific ancestor instead of the closest one — without a name, @container always resolves to the nearest container, which is rarely the one you meant once containers nest.

Container declaration utilities
Class Declares Resolves to Source
.cq container-type: inline-size — 04-layout.css:234
.cq-size container-type: size — 18-container.css:16
.cq-normal container-type: normal — 18-container.css:17
.cq-panel container: panel / inline-size — 18-container.css:19
.cq-pane container: pane / inline-size — 18-container.css:20
.cq-row container: row / inline-size — 18-container.css:21
.cq-shell container: shell / inline-size — 18-container.css:22
.cq-tone container-type: inline-size — 18-container.css:182

Container breakpoint prefixes

The same idea as md:, keyed to the container at 24rem, 32rem and 44rem. These only do anything inside an element carrying .cq, and the same extractor caveat applies to their names.

Container breakpoint prefixes
Class Declares Resolves to Source
.cq-sm .cq-sm\:row
@container (min-width: 24rem)
flex-direction: row
— 18-container.css:153
.cq-md .cq-md\:row
@container (min-width: 32rem)
flex-direction: row
— 18-container.css:160
.cq-lg .cq-lg\:row
@container (min-width: 44rem)
flex-direction: row
— 18-container.css:170

Container-driven patterns

Five ready-made ones. .metarow is a list row that reveals metadata as space allows: .row-meta appears at 26rem of container width and .row-meta-wide at 38rem, so the same row is terse in a narrow pane and complete in a wide one with no JavaScript measuring anything.

Container-driven layout patterns
Class Declares Resolves to Source
.actions-cq display: flex; flex-direction: column; gap: var(--space-2) display: flex; flex-direction: column; gap: .5rem 18-container.css:89
.metarow display: flex; align-items: center; gap: var(--space-3) display: flex; align-items: center; gap: .75rem 18-container.css:70
.row-meta .metarow .row-meta
display: none
— 18-container.css:71
.row-meta-wide .metarow .row-meta-wide
@container (min-width: 38rem)
display: block
— 18-container.css:76
.stat-cq display: flex; flex-direction: column; gap: var(--space-1) display: flex; flex-direction: column; gap: .25rem 18-container.css:55

Mirroring

Deck is written in logical properties, so a right-to-left page mirrors on its own. These two are for the exceptions. .mirror-rtl flips an element horizontally when the direction is RTL — for a directional glyph in an icon set that does not have a mirrored variant. .no-flip pins something that would otherwise be flipped by a rule higher up: a logo, a play button, a chart axis.

RTL mirroring utilities
Class Declares Resolves to Source
.mirror-rtl [dir="rtl"] .mirror-rtl
scale: -1 1
— 19-logical.css:133
.no-flip [dir="rtl"] .no-flip
scale: 1 1
— 19-logical.css:150

Containment

Six classes that tell the engine it may skip work. .defer sets content-visibility: auto, so an offscreen section is not laid out or painted until it approaches the viewport; .virtual applies the same idea per child, which is what makes a very long list scroll smoothly with no virtualisation library.

The catch with .defer is that skipped content is skipped by in-page find as well in some engines, and the contain-intrinsic-size estimate is what keeps the scrollbar from jumping as sections resolve. Keep the estimate close to the real height.

Containment and deferral utilities
Class Declares Resolves to Source
.contain contain: layout style — 26-perf.css:66
.contain-paint contain: layout style paint — 26-perf.css:67
.defer content-visibility: auto; contain-intrinsic-size: auto 600px — 26-perf.css:48
.virtual .virtual > *
content-visibility: auto; contain-intrinsic-size: auto var(--item-size, 120px)
— 26-perf.css:40
.virtual-sm .virtual-sm > *
--item-size: 64px
— 26-perf.css:44
.virtual-lg .virtual-lg > *
--item-size: 220px
— 26-perf.css:45

When not to use these

  • Not to rebuild a primitive. .flex .flex-col .gap-4 is .stack written out. .flex .wrap .items-center .gap-3 is .cluster. Use the name; it is one class and it carries the default.
  • Not .hidden for a visual-only hide. It takes the element out of the accessibility tree too. If it should be read but not seen, that is .sr-only.
  • Not a breakpoint prefix where a container query fits. A component that reflows on window width is wrong the moment somebody puts it in a sidebar. .cq plus cq-md: asks the right question.
  • Not .cursor-pointer on a <div> to make it a button. The cursor is the least of what a button is. Use a <button> and get the focus ring, the keyboard activation and the role for free.
  • Not .defer on everything. It costs a layout pass when the element comes into view. On short content it is slower than doing nothing.