Skip to content

Forms

Filter bar

Facet filters together, with active-filter chips, a result count and Clear all.

import { FilterBar, Chip } from '@jsonresume/ui'Source(opens in a new tab)

Examples

Job board filters
97 results
  • Remote
Show code
filter-bar/jobs.tsx
'use client';

import { Field, FilterBar, SearchField, Select, Switch } from '@jsonresume/ui';
import { useState } from 'react';

const labels: Record<string, string> = {
  fulltime: 'Full-time',
  contract: 'Contract',
  senior: 'Senior',
  lead: 'Lead',
};

export default function FilterBarJobs() {
  const [type, setType] = useState('');
  const [level, setLevel] = useState('');
  const [remote, setRemote] = useState(true);
  const active = [
    type && {
      key: 'type',
      label: labels[type] ?? type,
      onRemove: () => setType(''),
    },
    level && {
      key: 'level',
      label: labels[level] ?? level,
      onRemove: () => setLevel(''),
    },
    remote && {
      key: 'remote',
      label: 'Remote',
      onRemove: () => setRemote(false),
    },
  ].filter((f): f is { key: string; label: string; onRemove: () => void } =>
    Boolean(f)
  );

  return (
    <FilterBar
      active={active}
      resultCount={128 - active.length * 31}
      onClearAll={() => {
        setType('');
        setLevel('');
        setRemote(false);
      }}
    >
      <Field label="Search">
        <SearchField placeholder="Title or company" />
      </Field>
      <Field label="Type">
        <Select value={type} onChange={(e) => setType(e.target.value)}>
          <option value="">Any</option>
          <option value="fulltime">Full-time</option>
          <option value="contract">Contract</option>
        </Select>
      </Field>
      <Field label="Level">
        <Select value={level} onChange={(e) => setLevel(e.target.value)}>
          <option value="">Any</option>
          <option value="senior">Senior</option>
          <option value="lead">Lead</option>
        </Select>
      </Field>
      <Switch
        label="Remote only"
        checked={remote}
        onCheckedChange={setRemote}
      />
    </FilterBar>
  );
}

Usage

Do

  • Apply filters as they change; show the result count.
  • Name chips with the value, not the field ("Remote").

Don’t

  • Add an Apply button for instant filters.

Accessibility

  • A named region; the result count is announced politely.
  • Each chip has a "Remove <filter>" button.

Props

<FilterBar>

Also accepts the attributes of the element FilterBar renders.
PropTypeDefaultDescription
children requiredReactNode–The filter controls: Selects, SearchField, Switches.
activeActiveFilter[][]
labelstringFilters
onClearAll(() => void)–
resultCountnumber–Result count, announced politely when it changes.
resultLabel((count: number) => string)(n) => `${n} ${n === 1 ? 'result' : 'results'}`

<Chip>

Also accepts the attributes of the element Chip renders.
PropTypeDefaultDescription
labelstring–Plain-text name used in the remove button's label.
onRemove(() => void)–Shows a remove button labelled "Remove <label>".