Components › Forms & input › Filter › Filter bar

Filter bar

canon

Row of filter chips with add, reset and save controls.

React implementation lives in the app (apps/web/src/components/FilterBar.tsx), not in @acrm/ui. Phone (owner 2026-09-28, acrm !4088): with onOpenChange the bar is the bottom sheet „Filtry" (search, conditions with ⋮ Odebrat, Přidat filtr, Vymazat vše, Hotovo); the Filtr toggle shows a neutral CountBadge, Řadit (SegmentScreen) a sort sheet; „Uložit pro všechny" moves to the bar's ⋮ (phoneOnly). „Vymazat filtry" = Button ghost tone muted. Desktop unchanged. 14 Oct 2026 (owner "24c approved", proto/lista-vypisu@a16fe17a7, acrm !5045): no word labels — Filtr = icon (+ dot), Seskupit/Řadit/Zobrazení = icon + chosen value, field name only in aria-label/tooltip; open row: chip = field icon + value, „+" and „×" icons; closed by default, „Jen moje" is a chip inside Filtr. Dev check 14 Oct on /tasks /pipeline /objednavky /setkani /lide, 1440 + 390: 0 label words, no overflow. Registry repaired 17 Oct 2026 (review F2): removed classes with no CSS definition and no TSX use on main: .fb-add, .fb-reset, .fb-save.

Used in 23 files · 21 areas · 48× — components/communication, components/datamart, pages/AccountingInvoicesPage, pages/AccountingReceivedInvoicesPage, pages/AsistentAuditPage, pages/DokumentyPage …

Keyboard

  • Every control in the bar is a real <button>: Tab reaches the chips in reading order, then + Filtr, then Vymazat filtry and Uložit pro všechny. Enter and Space activate them.
  • A chip carries aria-haspopup="menu" and aria-expanded; Enter opens its menu, Escape closes it (handled by Popover).
  • The inline search .fb-search is a plain <input> inside a <label>: Tab reaches it, typing filters, there is no shortcut to jump to it.
  • The conjunction is a button — Enter switches a zároveň ↔ nebo.
  • Missing: the menu does not take focus when it opens, and it is portalled to the end of <body>, so Tab from the chip walks the rest of the page instead of entering the menu. Arrow-key movement between menu rows does not exist either (see Popover — known limits; the same defect, one implementation).

ARIA

  • Chip: aria-haspopup="menu" + aria-expanded; the accessible name is the field name and the value read together ("Fáze Jednání").
  • Toggle chip (+ Filtr in the Section top bar) uses aria-pressed, not aria-expanded — it switches a region on and off rather than opening a layer.
  • Conjunction: aria-label says what the click will do ("Spojka a zároveň, změnit na nebo") — the bare word "nebo" would be read as text, not as a control.
  • The bar itself is a plain <div> with no landmark role; it is announced through the controls it contains.
  • The group .fb-group is decorative: the tint carries no semantics, the conjunction buttons do.
  • Phone (below 720 px, with onOpenChange; acrm !4088, 28 Sep 2026): the toggle's visible label is hidden and only the neutral count stays, so its accessible name carries both: „Filtr" without active conditions, „Filtr, aktivní podmínky: N" with them. The Řadit icon button is named „Řadit".
  • The Filtry sheet is the canonical BottomSheet: role="dialog" titled „Filtry", grip, scrim that closes on tap, and one full-width „Hotovo" (<Button touch>) — Escape or a click outside is never the only way out on a phone. Each condition's ⋮ is named „Podmínka <pole>" and opens a menu with „Odebrat filtr" (danger).

Focus

  • No focus style is declared in filter-bar.css. All the controls are <button>, so they take the foundation ring from ds.css: outline: 2px solid var(--accent); outline-offset: 1px on :focus-visible.
  • The only element that opts out of the ring is the <input> inside .fb-search (outline: 0); the wrapper answers with :focus-within — accent border plus --surface-100 fill. The field is therefore visibly focused, but through the box, not a ring.
  • Opening and closing the bar does not move focus; the toggle chip keeps it.

Contrast

Computed from packages/ui/src/tokens/tokens.css (WCAG 2.x relative luminance):

  • Chip value .v --ink-900 on --surface-100 = 17.59:1 light, 14.53:1 dark.
  • Chip field name .k --fg-muted on --surface-100 = 2.85:1 light, 3.63:1 dark — below AA 4.5:1 for text. It is the muted half of a pair whose value carries the meaning, but it is a genuine limit.
  • Chip border --border-strong on --surface-100 = 1.38:1 light, 1.55:1 dark — hairline, below the 3:1 non-text minimum; the chip is discoverable by its fill and shadow.
  • Ghost actions .fb-add / .fb-reset --fg-muted on --bg-canvas = 2.85:1 light, 3.63:1 dark — below AA; on hover the text goes to --ink-700 (12.99:1).
  • Save .fb-save --accent-ink on --accent-tint = 4.58:1 light, 5.37:1 dark — passes AA.
  • Conjunction --fg-muted on canvas = 2.85:1 light; active nebo --ink-900 on --surface-100 = 17.59:1.
  • Focus ring --accent on --surface-100 = 4.44:1 light, 3.83:1 dark — passes the 3:1 non-text minimum in both themes.

Known limits

  • Muted text (.k, .fb-add, .fb-reset, .fb-conj) sits at 2.85:1 in light theme — below AA for text. It is the DS-wide --fg-muted limit, not a bar-specific choice.
  • Keyboard users cannot reach a chip menu by Tab (portal + no focus move) and cannot move through its rows with arrows.
  • The disabled look of a toggle chip comes from a different sheet (.lst-toggle:disabled in ds/list-view.css); a chip used outside a listing has no disabled style of its own.
  • Nothing announces that the listing has been re-filtered — there is no live region; the change is only visible.

Source: docs/filter/filter-bar.a11y.md · GitLab