Filter chip
canonOne filter field written out: icon, field name and value; the chip opens the field menu.
Rendered by FilterBar.tsx (ChipMenu); the menu frame is the canonical Popover. 14 Oct 2026 (acrm !5045): chip = field icon + value, field name in aria-label/title (RULES §24.3).
Used in 7 files · 7 areas · 18× — components/FilterBar, components/communication, components/segment, pages/PostaPage, pages/SetkaniPage, pages/TasksPage …
Default
export const Default = () => {
const [value, setValue] = useState('jednani');
const cur = STAGES.find((s) => s.key === value);
return (
<ChipWithMenu icon="target" name="Fáze" value={cur?.label}>
{(close) => <SingleMenu options={STAGES} value={value} onSet={setValue} close={close} />}
</ChipWithMenu>
);
};Variants
one value — check on the right, menu closes on pick
several values — checkbox on the right, menu stays open, the chip shows them joined
free text — search row first, Enter confirms; the row is the canonical .acrm-recordselect-search
grouped options — hairline separator between groups (.fb-sep)
export const Variants = () => {
const [stage, setStage] = useState('jednani');
const [tags, setTags] = useState<string[]>(['investor']);
const [text, setText] = useState('Atol');
const [draft, setDraft] = useState('Atol');
return (
<div style={canvas}>
<Labeled text="one value — check on the right, menu closes on pick">
<ChipWithMenu icon="target" name="Fáze" value={STAGES.find((s) => s.key === stage)?.label}>
{(close) => <SingleMenu options={STAGES} value={stage} onSet={setStage} close={close} />}
</ChipWithMenu>
</Labeled>
<Labeled text="several values — checkbox on the right, menu stays open, the chip shows them joined">
<ChipWithMenu
icon="tag"
name="Štítky"
value={tags.map((k) => TAGS.find((t) => t.key === k)?.label).join(', ') || undefined}
>
{(close) => <MultiMenu options={TAGS} selected={tags} onSet={setTags} close={close} />}
</ChipWithMenu>
</Labeled>
<Labeled text="free text — search row first, Enter confirms; the row is the canonical .acrm-recordselect-search">
<ChipWithMenu icon="search" name="Název" value={text || undefined}>
{(close) => (
<>
<label className="acrm-recordselect-search">
<Icon name="search" />
<input
value={draft}
placeholder="Zadej hodnotu…"
onChange={(e) => setDraft(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setText(draft);
close();
}
}}
/>
</label>
{text ? (
<Clear
onClear={() => {
setText('');
setDraft('');
close();
}}
/>
) : null}
</>
)}
</ChipWithMenu>
</Labeled>
<Labeled text="grouped options — hairline separator between groups (.fb-sep)">
<ChipWithMenu icon="user" name="Vlastník" value="Já">
{() => (
<>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="true">
Já
<span className="chk">
<Icon name="check" />
</span>
</button>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="false">
Bez vlastníka
<span className="chk off">
<Icon name="check" />
</span>
</button>
<div className="fb-sep" />
{['Štěpán Svoboda', 'Petr Smutný'].map((n) => (
<button
key={n}
type="button"
className="fb-opt"
role="menuitemradio"
aria-checked="false"
>
{n}
<span className="chk off">
<Icon name="check" />
</span>
</button>
))}
</>
)}
</ChipWithMenu>
</Labeled>
</div>
);
};States
option — picked and not picked
checkbox option — on and off
empty — nothing matches the search
loading — the same row, different sentence (there is no spinner in the menu)
with a value — the footer „Vymazat filtr“ appears and sticks to the bottom edge
export const States = () => (
<div style={{ display: 'flex', gap: 'var(--sp-6)', flexWrap: 'wrap', alignItems: 'flex-start' }}>
<Labeled text="option — picked and not picked">
<MenuBody>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="true">
Jednání
<span className="chk">
<Icon name="check" />
</span>
</button>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="false">
Nabídka
<span className="chk off">
<Icon name="check" />
</span>
</button>
</MenuBody>
</Labeled>
<Labeled text="checkbox option — on and off">
<MenuBody>
<button type="button" className="fb-opt" role="menuitemcheckbox" aria-checked="true">
Investoři
<span className="ck on">
<Icon name="check" />
</span>
</button>
<button type="button" className="fb-opt" role="menuitemcheckbox" aria-checked="false">
Dluhopisy
<span className="ck" />
</button>
</MenuBody>
</Labeled>
<Labeled text="empty — nothing matches the search">
<MenuBody>
<label className="acrm-recordselect-search">
<Icon name="search" />
<input defaultValue="xyz" placeholder="Hledat…" />
</label>
<div className="fb-empty">Nic nenalezeno</div>
</MenuBody>
</Labeled>
<Labeled text="loading — the same row, different sentence (there is no spinner in the menu)">
<MenuBody>
<div className="fb-empty">Načítám štítky…</div>
</MenuBody>
</Labeled>
<Labeled text="with a value — the footer „Vymazat filtr“ appears and sticks to the bottom edge">
<MenuBody>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="true">
Jednání
<span className="chk">
<Icon name="check" />
</span>
</button>
<div className="fb-foot">
<div className="fb-sep" />
<button type="button" className="fb-clear">
<Icon name="x" />
Vymazat filtr
</button>
</div>
</MenuBody>
</Labeled>
</div>
);With icon & kbd
menu row with a leading icon (.fb-opt .lead) — used where the option has an identity (view, type)
export const WithIconAndKbd = () => (
<div style={canvas}>
<Labeled text="menu row with a leading icon (.fb-opt .lead) — used where the option has an identity (view, type)">
<MenuBody>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="true">
<Icon name="layout-columns" className="lead" />
Kanban
<span className="chk">
<Icon name="check" />
</span>
</button>
<button type="button" className="fb-opt" role="menuitemradio" aria-checked="false">
<Icon name="table" className="lead" />
Tabulka
<span className="chk off">
<Icon name="check" />
</span>
</button>
</MenuBody>
</Labeled>
<span style={label}>
The chip and its menu carry no keyboard shortcut chip — Enter confirms a typed value, Escape
closes the menu.
</span>
</div>
);Composition
export const Composition = () => {
const [stage, setStage] = useState('jednani');
const [tags, setTags] = useState<string[]>(['investor']);
return (
<div className="fb">
<ChipWithMenu icon="target" name="Fáze" value={STAGES.find((s) => s.key === stage)?.label}>
{(close) => <SingleMenu options={STAGES} value={stage} onSet={setStage} close={close} />}
</ChipWithMenu>
<ChipWithMenu
icon="tag"
name="Štítky"
value={tags.map((k) => TAGS.find((t) => t.key === k)?.label).join(', ') || undefined}
>
{(close) => <MultiMenu options={TAGS} selected={tags} onSet={setTags} close={close} />}
</ChipWithMenu>
<Button variant="ghost" aria-haspopup="menu" aria-expanded={false}>
<Icon name="plus" />
Filtr
</Button>
</div>
);
};