# PivotFilter
URL: /docs/web/components/pivot-filter
Horizontal or vertical strip of count-tagged pills used to quickly slice a list by one dimension — status, user, date bucket, or any other categorical value.
**Demo:**
```tsx
'use client';
import { useState } from 'react';
import {
PivotFilter,
type PivotFilterItem
} from '@docyrus/ui/components/pivot-filter';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@docyrus/ui/primitives/ui/select';
import { Switch } from '@docyrus/ui/primitives/ui/switch';
import { cn } from '@docyrus/ui/primitives/lib/utils';
const STATUS_ITEMS: Array
} />
```
The popover open state is internal by default; pass `settingsOpen` + `onSettingsOpenChange` if you need to control it.
### Loading state
Pass `loading` while items are being fetched. The component renders two skeleton pills while keeping the "All" pill mounted so the selection is not lost.
```tsx
```
## Colors
Pills accent on selection. The accent color is derived from each item's `color` field through this pipeline:
1. If `color` already includes a Tailwind shade (e.g. `'emerald-600'`, `'rose-300'`), use it as-is.
2. Otherwise treat it as a family name and append `-500` (e.g. `'sky'` → `'sky-500'`).
3. Resolve the resulting token to a hex value through `resolveColorHex` (it also accepts hex / rgb / hsl / CSS variables as-is).
4. When `color` is omitted, the pill falls back to `sky-500`.
This means status / select / radio-group options that already carry a `color` value from Docyrus enum metadata work without any extra mapping.
## Empty items
Items flagged `isEmpty: true` are treated as the "Not Set" bucket:
- they render with a muted (`opacity-60`) style,
- they are **automatically hidden** when their `stat` is `0`, regardless of `hideZeroValues`,
- they are still emitted via `onSelect` when the user taps them.
## Hiding zero-count items
Set `hideZeroValues` to hide every item whose `stat === 0`, except the currently selected one (so the user can always reset). Use this when buckets are dynamically derived (e.g. user list, date buckets) and you don't want empty pills crowding the strip.
## API Reference
### ``
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | `Array` | — | Items rendered in the strip. |
| `selectedItemId` | `string \| null` | — | Currently selected item id, or `null` when the "All" pill is active. |
| `onSelect` | `(item: PivotFilterItem \| null) => void` | — | Fired when an item is selected. The "All" pill fires with `null`. |
| `total` | `number` | — | Aggregate stat displayed on the "All" pill. |
| `totalLabel` | `string` | translated `"All"` | Label for the "All" pill. |
| `loading` | `boolean` | `false` | Renders two skeleton pills instead of items. |
| `vertical` | `boolean` | `false` | Vertical column layout. |
| `compact` | `boolean` | `false` | Collapse name + stat onto a single row inside each pill. |
| `hideZeroValues` | `boolean` | `false` | Hide items whose `stat === 0` (except the currently selected one). |
| `hideAllPill` | `boolean` | `false` | Hide the "All" pill — use when a filter is required. |
| `onRefresh` | `() => void` | — | Refresh button click handler. Pass `undefined` to hide the refresh button. |
| `settingsContent` | `ReactNode` | — | Content rendered inside the settings popover. When `undefined`, the gear button is hidden. |
| `settingsOpen` | `boolean` | uncontrolled | Controlled open state for the settings popover. |
| `onSettingsOpenChange` | `(open: boolean) => void` | — | Called when the popover open state changes. |
| `emptyState` | `ReactNode` | built-in | Custom placeholder when there are no items and the "All" pill is hidden. |
| `className` | `string` | — | Wrapper class name. |
### `PivotFilterItem`
| Field | Type | Description |
|------|------|-------------|
| `id` | `string` | Stable identifier. Used to match `selectedItemId`. |
| `name` | `string` | Display label rendered in the pill. |
| `stat` | `number` | Numeric value shown on the right side of the pill (count, sum, etc.). |
| `secondary` | `string` | Secondary text (e.g. `"to 14"` for hours, `"01.01 - 07.01"` for week ranges). |
| `color` | `string` | Tailwind color token (e.g. `'sky'`, `'emerald-500'`) or hex. Drives selection accents. |
| `icon` | `string` | Docyrus icon identifier (e.g. `'huge building-06'`, `'fal star'`). Rendered via `DocyrusIcon`. |
| `user` | `PivotFilterUser` | When present, an avatar is rendered instead of an icon. |
| `isEmpty` | `boolean` | Marks the synthetic "Not Set" bucket — muted style, auto-hidden at `stat === 0`. |
| `meta` | `Record` | Arbitrary payload returned to `onSelect`. Not read by the component. |
### `PivotFilterUser`
| Field | Type | Description |
|------|------|-------------|
| `id` | `string` | User id. |
| `name` | `string` | Display name. Initial is used as the avatar fallback. |
| `picturePath` | `string \| null` | Avatar URL. Falls back to initials when absent. |
## Components
| Component | Description |
|-----------|-------------|
| `PivotFilter` | Main strip — orchestrates toolbar, "All" pill, item list, loading state. |
| `PivotFilterPill` | Internal pill renderer — exported for advanced custom layouts. |
## Type Exports
| Type | Description |
|------|-------------|
| `PivotFilterItem` | Item shape — id, name, stat, plus optional icon / user / color / secondary / isEmpty / meta. |
| `PivotFilterProps` | Component prop shape. |
| `PivotFilterUser` | `{ id, name, picturePath? }` for the user-rendered variant. |
## i18n keys
The component reads these keys via `useUiTranslation()`. English fallbacks are bundled, so no provider is required.
| Key | Default |
|------|---------|
| `ui.pivotFilter.all` | `All` |
| `ui.pivotFilter.refresh` | `Refresh` |
| `ui.pivotFilter.settings` | `Settings` |
| `ui.pivotFilter.empty` | `No data` |
## When to reach for the hook
If you want the same UI bound to a Docyrus data source — auto-running a pivot query with `COUNT_OF_id` (or any other aggregate), reacting to active filters, and emitting a query rule when the user picks a bucket — use [`useDocyrusPivotFilter`](/docs/web/hooks/use-docyrus-pivot-filter) instead. It produces the `items` / `total` / `loading` / `onRefresh` / `settingsContent` props for you.