Skip to content

FilterPopover

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/filter-popover.json
See it liveA heading with a funnel - tick an owner and watch it fill and the rows narrow; Clear puts it down.→

A heading carries a funnel, shown when the pointer is over the heading and staying on, filled, while that column is narrowing the list. Press it and a small panel opens for that column — a text box, a handful of checkboxes — with one way to clear it. The shell only: what goes in the panel is yours, since a stage is ticked and a title is typed and the popover has no opinion.

<TableHeader className="group">
<span className="flex items-center gap-1">
Owner
<FilterPopover
title={t('columns.owner')}
label={t('table.filterBy', { column: t('columns.owner') })}
active={chosen.length > 0}
clearLabel={t('table.clear')}
onClear={() => setChosen([])}
className="opacity-0 group-hover:opacity-100 focus-visible:opacity-100 data-[active]:opacity-100 data-[popup-open]:opacity-100"
>
{owners.map((owner) => (
<Checkbox key={owner} checked={chosen.includes(owner)} onCheckedChange={…}>
{owner}
</Checkbox>
))}
</FilterPopover>
</span>
</TableHeader>

The funnel says whether the column is narrowing, and that is all it says. A column with a filter on it has to say so from the header, or a table narrowed by a funnel opened last week looks like a table with fewer rows in it. active fills the funnel and sets data-active on the trigger.

Whether it shows at rest is your rule. A header with five funnels always showing is a header nobody can read, but hiding the active one would hide the one thing that matters — so the classes above show it on hover, on focus, while active, and while open, and data-active is there for that rule to key off.

Clear is inside the panel. Unticking three boxes one by one is the failure this exists to prevent, and the panel is where a person is already looking. It is disabled while there is nothing to clear.

The panel reads in sentence case even under a header set in uppercase: the header is scanned, the panel is read.

Built on Popover and Button.

Props

Read from the types in ui/filter-popover.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
titlerequiredstringThe heading inside the panel: the column's name.
labelrequiredstringWhat the funnel says to assistive technology and on hover.
activerequiredbooleanWhether the column's filter holds anything; fills the funnel.
clearLabelrequiredstringThe words of the button inside the panel that empties the filter.
onClearrequired() => voidCalled when that button is pressed; the button is disabled while active is false.
childrenrequiredReactNodeThe controls: a text box, checkboxes, whatever narrows this column.
classNamestringOn the trigger, for showing it on hover or always.
aligncenter | start | endWhich edge of the funnel the panel lines up with. Defaults to start.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 18 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/filter-popover.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.40.0
    • refactor say state on the element, and measure the palette's anchor apart 8d9b4e1
  3. v0.26.0
    • feat a header edge to drag, rows that reorder, a funnel with a panel 8310586