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.flat drops 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 primaryAction is 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); danger rows are red; phoneOnly actions 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 (NavSelect chip — „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 phoneOnly secondary actions.
  • The primary becomes the Fab bottom-right (round only for kind: 'create', otherwise extended with its label).
  • Desktop is unchanged: secondary actions are buttons before the primary, menuOnly actions sit in ⋮, closeAction stays 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 .tbar when 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.
  • 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 overview components--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 ⋯ (RecordActionsMenu leadItems): 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