Skip to content
UI

Filter Chip

Shows one applied filter, its field and value, with an optional button to remove it.

@zenoxai/ui/components/filter-chip Markdown
Severity:Critical
function FilterChipDemo() {
  return <FilterChip label="Severity" value="Critical" onRemove={() => {}} />
}

Usage

import { FilterChip } from "@zenoxai/ui/components/filter-chip"

export function SeverityFilter({ onRemove }: { onRemove: () => void }) {
  return <FilterChip label="Severity" value="Critical" onRemove={onRemove} />
}

Use filter chips above a table or list to show which filters are applied, one chip per field. label is the field and renders in secondary text followed by a colon; value renders in medium weight. Pass onRemove and the chip gets a remove button that calls it.

The chip is a <span> and is not clickable itself. To pick filters, use a Select, a Dropdown Menu or a Combobox; to switch a quick filter on and off, use a Toggle. For a status or a category that is not a filter, use a Badge.

When a field has several values, join them in one chip (“Critical, High”) rather than rendering a chip per value.

Examples

Variants

Status:OpenSeverity:HighTenant:Acme Corp
<>
  <FilterChip label="Status" value="Open" />
  <FilterChip variant="active" label="Severity" value="High" />
  <FilterChip variant="ghost" label="Tenant" value="Acme Corp" />
</>
Variant Use for
default Applied filters in a filter bar.
active A filter that differs from the view’s default, to draw attention to it.
ghost Filters inherited from context, such as the current tenant.

Sizes

sm (20px) fits inside table cells and card headers, default (24px) is for filter bars, and lg (28px) sits next to 28px buttons. default and lg use the 12.5px body text, sm the 10px caption size; the remove icon is 12px at sm and 14px otherwise.

Source:CT logsSource:CT logsSource:CT logs
<>
  <FilterChip
    size="sm"
    label="Source"
    value="CT logs"
    onRemove={() => {}}
  />
  <FilterChip label="Source" value="CT logs" onRemove={() => {}} />
  <FilterChip
    size="lg"
    label="Source"
    value="CT logs"
    onRemove={() => {}}
  />
</>

Filter bar

The applied filters of a list, each removable, with a link to clear them all. Keep the chips in the same order as the filter controls.

Severity:Critical, HighStatus:OpenAsset type:DomainFirst seen:Last 7 days
function FilterChipFilterBar() {
  const [filters, setFilters] = React.useState([
    { id: "severity", label: "Severity", value: "Critical, High" },
    { id: "status", label: "Status", value: "Open" },
    { id: "asset", label: "Asset type", value: "Domain" },
    { id: "age", label: "First seen", value: "Last 7 days" },
  ])

  if (filters.length === 0) {
    return (
      <p className="text-sm text-text-muted">No filters. Showing all alerts.</p>
    )
  }

  return (
    <div className="flex w-full max-w-lg flex-wrap items-center gap-2">
      {filters.map((filter) => (
        <FilterChip
          key={filter.id}
          label={filter.label}
          value={filter.value}
          onRemove={() =>
            setFilters((current) => current.filter((f) => f.id !== filter.id))
          }
        />
      ))}
      <Button variant="link" size="xs" onClick={() => setFilters([])}>
        Clear all
      </Button>
    </div>
  )
}

Custom content

Children render after the value, before the remove button. Use them for an icon, a count of hidden values, or a chip with no field name. Size icons yourself; the chip does not.

acme-corp.comTags:phishing+2
<>
  <FilterChip
    variant="active"
    onRemove={() => {}}
    removeAriaLabel="Remove domain filter"
  >
    <GlobeIcon className="size-3" />
    acme-corp.com
  </FilterChip>
  <FilterChip
    label="Tags"
    value="phishing"
    removeAriaLabel="Remove tags filter"
    onRemove={() => {}}
  >
    <span className="text-text-muted">+2</span>
  </FilterChip>
</>

Accessibility

  • The remove button is labelled “Remove filter label” when label is a string, and “Remove filter” otherwise. Set removeAriaLabel whenever label is not a string, so every button on the page has a distinct name.
  • Removing a chip removes the focused button with it. Move focus somewhere predictable after onRemove: the next chip’s button, or the filter control when none are left.
  • The remove button is 16px square, under the 24px target size of WCAG 2.2. It meets the spacing exception only while no other control sits within 4px of it, so keep at least gap-2 between chips and the controls around them.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

FilterChip

FilterChip shows one applied filter as "label: value", with an optional remove button, in the bar above a list or table. For a static tag or a count, use Badge.

PropTypeDefault
labelReactNode

Field/dimension being filtered (e.g. "Severity"). Renders before the colon.

-
onRemove(() => void)

When present, renders an inline X button that calls this on click.

-
removeAriaLabelstring

Accessible name of the remove button. Defaults to the English "Remove filter <label>" when `label` is a string, else "Remove filter"; pass a translated one.

-
size"default" | "sm" | "lg" | null

Height: `sm` 20px, `default` 24px, `lg` 28px. Text is the 12.5px body size, 10px at `sm`; the remove icon is 12px at `sm`, 14px otherwise.

-
valueReactNode

Filter value (e.g. "Critical"). Renders bold after the colon.

-
variant"default" | "ghost" | "active" | null

`default` is outlined; `active` is tinted with the primary color, to mark the chip in focus; `ghost` has a muted fill and no border.

-