Bar
.bar is a flex row with align-items: center and a
.75rem gap, and it does
not wrap. Three declarations. Its value is entirely in what it does
not do: a bar stays one line, so a header does not suddenly become two rows tall when
somebody adds a button.
When to use it
Use it when the row is a single line by definition: a card header, a toolbar, a table's title row, a panel header with a control at the end. If wrapping would look like a bug rather than a feature, you want a bar.
<div class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<svg class="icon" aria-hidden="true"><use href="/assets/deck/deck-icons.svg#folder"></use></svg>
<strong>Q1 exports</strong>
<span class="badge">12 files</span>
</div>
<div class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<svg class="icon" aria-hidden="true"><use href="/assets/deck/deck-icons.svg#folder"></use></svg>
<strong>Q1 exports</strong>
<span class="badge">12 files</span>
</div>
.push
.push is margin-inline-start: auto. An auto margin in a
flex row absorbs all the free space, so the element it is on — and everything after
it — is shoved to the far end.
It is the better alternative to justify-content: space-between for two
reasons. It works with any number of children, and you choose where the split
happens instead of it always falling between the first item and the rest.
<div class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<strong>Q1 exports</strong>
<span class="badge">12 files</span>
<button class="btn btn-sm push">Share</button>
<button class="btn btn-sm btn-ghost btn-icon" aria-label="More">
<svg class="icon" aria-hidden="true"><use href="/assets/deck/deck-icons.svg#more-horizontal"></use></svg>
</button>
</div>
<div class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<strong>Q1 exports</strong>
<span class="badge">12 files</span>
<button class="btn btn-sm push">Share</button>
<button class="btn btn-sm btn-ghost btn-icon" aria-label="More">
<svg class="icon" aria-hidden="true"><use href="/assets/deck/deck-icons.svg#more-horizontal"></use></svg>
</button>
</div>
.push is a standalone utility in src/09-utilities.css, so
it works in any flex row, not only inside .bar.
.card-header, .menu-item and .sidebar-link
all rely on it, and the last two carry their own copy of the rule so the pushed
element also picks up a muted colour.
Invoice INV-2291
Paid.card-header is a flex row, so .push works there too.
<div class="card" style="max-inline-size:26rem">
<div class="card-header">
<h3 class="card-title">Invoice INV-2291</h3>
<span class="badge badge-good push">Paid</span>
</div>
<div class="card-body"><p class="text-muted">.card-header is a flex row, so .push works there too.</p></div>
</div>
<div class="card" style="max-inline-size:26rem">
<div class="card-header">
<h3 class="card-title">Invoice INV-2291</h3>
<span class="badge badge-good push">Paid</span>
</div>
<div class="card-body"><p class="text-muted">.card-header is a flex row, so .push works there too.</p></div>
</div>
What happens when it does not fit
A bar does not wrap, so when it runs out of room the items squash — and then, past a point, overflow. The point where squashing stops is the thing to know about: a flex item's minimum size is its content, so text with no break opportunity in it will not squash at all. It overflows, and takes the page's horizontal scrollbar with it.
The fix is to nominate the child that should give way, with
min-inline-size: 0 to allow the shrink and .truncate to
decide what happens to the text.
<div class="bar" style="max-inline-size:20rem;border:1px dashed var(--line);padding:var(--space-3)">
<span class="truncate" style="min-inline-size:0">a-very-long-export-filename-2026-03-11.csv</span>
<span class="badge push">2.4 MB</span>
</div>
<div class="bar" style="max-inline-size:20rem;border:1px dashed var(--line);padding:var(--space-3)">
<span class="truncate" style="min-inline-size:0">a-very-long-export-filename-2026-03-11.csv</span>
<span class="badge push">2.4 MB</span>
</div>
| Class | Out of room | Reach for it when |
|---|---|---|
.bar |
Squashes, then overflows | The row is one line by definition |
.cluster |
Wraps to the next line | The number of items varies |
.scroller |
Scrolls sideways, snapping | The items are cards and there are many |
Gap
.bar is the one layout primitive that does not read
--gap: it hard-codes gap: var(--space-3). So the
spacing scale classes have no effect on it, and
changing a bar's gap means setting gap directly.
That is an inconsistency rather than a decision — .stack,
.cluster, .grid, .split,
.scroller and .center all take --gap and
.bar does not. It is recorded in FINDINGS.md; the one-word
fix is to wrap the value in var(--gap, …) like its siblings.
<div class="bar" style="gap:var(--space-6);border:1px dashed var(--line);padding:var(--space-3)">
<strong>Wider gap</strong>
<span class="badge push">set with gap, not --gap</span>
</div>
<div class="bar" style="gap:var(--space-6);border:1px dashed var(--line);padding:var(--space-3)">
<strong>Wider gap</strong>
<span class="badge push">set with gap, not --gap</span>
</div>
Classes
.bar has one member rule — .bar > .push — and no
.bar-* variants, so the extractor does not treat it as a component root
and there is no completeness check on this page.
| Class | Layer | What it does | Source |
|---|---|---|---|
.bar |
deck.layout |
Push the last child away from the rest | 04-layout.css:94 |
.push |
deck.utilities |
Pushes this element and everything after it to the far end of a flex row. The one-class replacement for a spacer div. | 09-utilities.css:36 |
Tokens
| Token | Live | Declared value |
|---|---|---|
--space-3 |
.75rem |
|
--space-6 |
1.5rem |
Accessibility
-
No semantics. A bar is a
<div>withdisplay: flex. A toolbar that is genuinely a toolbar — a group of controls a reader arrows between — needsrole="toolbar"and its own key handling, which Deck does not provide. -
.pushdoes not reorder. It moves items without changing their DOM order, so the tab order still matches what is on screen. That is the reason to prefer it overorder, which does not. -
Not wrapping is a reflow risk. At 320px or 400% zoom a bar with
several items will squash and then overflow, and horizontal scrolling of the page
fails WCAG's reflow criterion. Nominate a child to truncate, move items into an
overflow menu, or use
.clusterand let it wrap. -
Truncated text needs to be available somewhere.
.truncateclips visually and leaves the full string in the DOM, so a screen reader still hears all of it — but a sighted reader cannot. Add atitleattribute, or make sure the full value appears elsewhere. -
Icon-only buttons in a bar need labels. Nothing about the layout
supplies an accessible name; each
.btn-iconneeds its ownaria-label.
Right to left
.push is margin-inline-start, so it pushes toward the end
edge in whichever direction the text runs. Nothing else about a bar is directional.
<div dir="rtl" class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<strong>تصدير الربع الأول</strong>
<span class="badge">١٢ ملفًا</span>
<button class="btn btn-sm push">مشاركة</button>
</div>
<div dir="rtl" class="bar" style="border:1px dashed var(--line);padding:var(--space-3)">
<strong>تصدير الربع الأول</strong>
<span class="badge">١٢ ملفًا</span>
<button class="btn btn-sm push">مشاركة</button>
</div>
Reduced motion
Nothing here animates or transitions, so
prefers-reduced-motion changes nothing.
Printing
No print rule, and none is needed: a non-wrapping flex row prints as it appears. The caveat is the same as on screen — if the bar is squashed or overflowing at the printed width, it prints that way. A bar that has to survive print should have a truncating child rather than relying on the viewport being wide.
Overriding it
@layer app.components {
/* Make it read --gap like every other primitive */
.bar { gap: var(--gap, var(--space-3)); }
/* Align on the first line of text rather than the middle */
.bar-baseline { align-items: baseline; }
/* Let it wrap after all, at small sizes only */
@media (max-width: 30rem) {
.bar { flex-wrap: wrap; }
}
}
That last one is worth thinking twice about. A bar that wraps on a phone is a
cluster; if that is what you want at every size, use
.cluster and skip the media query.
When not to use it
-
Not when the item count varies. A row built from user data — tags,
filters, collaborators — will eventually be too long. Use
.cluster. -
Not for many items. A bar with twelve chips squashes all of them.
Use
.scroller. -
Not as a navigation bar. Use
.navbar, which has the brand slot, the mobile behaviour and the right height..baris a layout primitive, not a chrome component. -
Not for a form's actions. Use
.form-actions, which wraps and has the sticky variant for long forms on a phone. -
Not with
.pushon more than one child. Two auto margins split the free space between them, which produces a three-way distribution that is hard to predict and harder to explain. If you want that, usejustify-content: space-betweenand say so.