Section top bar
One bar above every list and every record. It answers, in this order: what am I looking at (identity), what changes what I see (view, filter, search), and what can I do (quiet actions, at most one primary). packages/ui/src/components/SectionTopBar.tsx — SectionTopBar, SectionTopBarChip, SectionTopBarSearch, SectionTopBarDivider, SectionTopBarSpacer; classes .tbar*, .tchip, .vspace in apps/web/src/ds.css. Simple list screens use the PageHeader wrapper.
The bar is opinionated on purpose, and the component warns in development when its rules are broken.
When to use
- Above any list, any record, any section screen — one screen, one bar.
- To host the screen's view switch (
NavSelect), its filter and sort chips, its search and its actions.
When not to use
- As the application frame — that is Top bar (
.as-top), which never changes with the screen. - As a tab row — that is Tab strip, which goes under this bar.
- As a place for a count, a breadcrumb, or a status sentence.
Anatomy
- Bar
.tbar: one row, hairline underneath (.tbar.flatdrops it), slots in fixed order — identity · view/filter controls ·SectionTopBarSpacer(.vspace) · actions ·primaryAction. - Identity:
SectionTopBarTitle— exactly one of icon, tile+tone, avatar, heading-dropdown, inline rename (see its own page). - Chip
.tchip:height: var(--control-h)(30px),padding: 0 10px,gap: 7px,radius-md,var(--small), icon--ico-xs; muted key.k, bold value.v, chevron.chev, open state.tchip.open. - Search
.tsearch: quiet box that paints its border accent on:focus-within; the input itself carries no ring. - Divider
.tdiv(1×20px hairline), spacer.vspace(flex: 1). - Actions are quiet (icon + verb); the single
primaryActionis the only filled button.
Identity by colour — dot (2026-10-20)
SectionTopBarTitle dot="var(--gc-…)" draws a 12 px colour circle inside the tile frame, with no glyph. Use it when the screen's identity is a colour the user chose (a mail label), not a type. Needs no icon; do not combine with tileIcon, kind or avatar.
Phone (below 720 px) — Attio pattern (2026-09-28, acrm !4088)
Only for a bar whose actions are data (primaryAction as { label, icon, onClick, kind? }, secondaryActions, closeAction; .tbar.has-acts):
- Row 1: title (ellipsis) + ⋮ „Další akce" +
closeAction(×). No text buttons. - ⋮ opens one bottom sheet with all secondary actions (
forceMenu: even a single item opens the sheet);dangerrows are red;phoneOnlyactions appear only here (e.g. „Uložit pro všechny"). - Row 2 (view row, owner-approved 28 Sep 2026, acrm !4114): one view picker on the left (
NavSelectchip — „Zobrazení ⌄" on Úkoly with Kompaktní / Plné instead of the segmented switch, „Schránka ⌄" on Pošta), icon buttons on the right. Each opens a bottom sheet with a check at the chosen option. - Filtr carries the neutral count of conditions (
FilterToggleChip count). - Seskupit and Řadit carry an accent dot, not a count, when they are off their default (
.fb-chip.lst-toggle.tb-dot.set::after, inside the icon's corner so the scrolling tools row does not clip it). A count would widen the icon and the row would overflow at 390 px. The chosen option lives in the accessible name and the tooltip („Řadit: Termín", „Seskupit: Podle stavu"). - Search is an icon on the right; a tap expands it over the whole row (
.tsearch-fill) with × to close; with a typed query it stays open. The view picker steps aside while search is open. - Quiet desktop tool icons that do not fit (Pošta: Synchronizovat, Spravovat složky) move into ⋮ as
phoneOnlysecondary actions. - The primary becomes the
Fabbottom-right (round only forkind: 'create', otherwise extended with its label). - Desktop is unchanged: secondary actions are buttons before the primary,
menuOnlyactions sit in ⋮,closeActionstays last. - A bar still passing a JSX primary keeps it in row 1 until it is converted.
Best practices
Do
- Give the bar exactly one identity mark — it never starts with bare text.
- Put everything that changes the view before the spacer, and everything that acts after it.
- Keep at most one primary action, and let it carry the verb of the screen (
Nový obchod). - Let the bar scroll sideways on a narrow screen (≤720px) rather than squeezing its controls; the primary action sticks to the right edge.
Don't
- A count or "N z M" in the bar — that belongs to the listing footer or a stat tile. (Record navigation arrows are the one documented exception.)
- A breadcrumb inside the identity.
- Coloured or filled action buttons other than the one primary.
- Capitals, and no second bar under this one except the Filter bar or a Tab strip.
- A hand-built
.tbarwhen the component would do; the escape hatch exists (status star, logo trigger, colour swatch) but it skips every guardrail.
Content
- Identity = the name of the thing, sentence case, no type prefix.
- Chips read
key value(Řadit Vytvořeno,Filtr 2). - Actions are verbs; the primary action names what will be created.
Related
title-with-entity-tile— the identity of this bar.filter-bar— the second row under it.tab-strip,record-tabs— the tab row under a record's bar.top-bar— the application frame, not this.- Stories:
components--section-top-bar--default,--variants,--states,--with-icon-and-kbd,--composition; legacy overviewcomponents--section-top-bar.
Record header on the phone (owner approved 1. 10. 2026, acrm !4362, verified on dev)
- ≤720 px the record header (person, company, deal, task, project…) is ONE row of the normal bar height: ‹ back (44 px, „Zpět na výpis“) · mark + name (truncates) · ⋯ (44 px). The ✕ is not drawn on the phone — ‹ has the same target, Esc too. Desktop unchanged.
- The view switch (Aktivity / Profil) and reservation actions (Rezervovat / Uvolnit / Převzít) are the first items of ⋯ (
RecordActionsMenuleadItems): choices with a tick on the active one, then a hairline, then the record actions. This is an approved exception to the MenuButton rule „choices all or none“ — the hairline separates the two groups (Linear does the same). - Reservation state lives as a row „Rezervace“ in Detail záznamu, not in the header.
- Pattern source: Attio person (one row ‹ · avatar + name · ⋮) and Linear issue (‹ · ✎ · ⋯). Two-row header was rejected (takes too much space).
- The header stays on top while the card scrolls (sticky, acrm !4369).
- Extended to meeting, task, project (acrm !4394, verified on dev 1. 10. 2026): same one row ‹ · mark + name · ⋯ on the phone, sticky; meeting ⋯ starts with „Zeptat se AI nad touto schůzkou“; task has no ⋯ (no header actions). Team member in Settings: ✕ hidden on the phone, back is the Settings bar „← Nastavení“ only.
- Menu items that cannot act yet are hidden, not disabled (no tooltip on touch): meeting exports appear only when minutes / a finished transcript exist.
Source: docs/section-top-bar/section-top-bar.usage.md · GitLab