VisorVisor
ComponentsAdmin

Filter Bar

Admin filter bar compound — search input, filter controls slot, removable active-filter chips, results count, and a clear-all affordance above a data-table. Built with CSS Modules and container queries — copy it into your project and own it completely.

Basic

With a Select Filter

With Active Filter Chips

Dense

Installation

npx visor add filter-bar

This copies two files into your project:

  • components/ui/filter-bar/filter-bar.tsx — the component
  • components/ui/filter-bar/filter-bar.module.css — the styles

The registry also pulls in search-input, badge, and button as dependencies.

Usage

import { useState } from 'react';
import { FilterBar } from '@/components/ui/filter-bar/filter-bar';
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from '@/components/ui/select/select';

export default function Example() {
  const [search, setSearch] = useState('');
  const [role, setRole] = useState<string | null>(null);

  const activeFilters = role
    ? [{ id: 'role', label: `Role: ${role}`, onRemove: () => setRole(null) }]
    : [];

  return (
    <FilterBar
      searchValue={search}
      onSearchChange={setSearch}
      searchPlaceholder="Search users"
      activeFilters={activeFilters}
      onClearAll={() => {
        setSearch('');
        setRole(null);
      }}
      resultsCount="42 results"
    >
      <Select value={role ?? ''} onValueChange={setRole}>
        <SelectTrigger>
          <SelectValue placeholder="Role" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="Admin">Admin</SelectItem>
          <SelectItem value="Editor">Editor</SelectItem>
          <SelectItem value="Viewer">Viewer</SelectItem>
        </SelectContent>
      </Select>
    </FilterBar>
  );
}

API Reference

FilterBarProps

PropTypeDefaultDescription
searchValuestringControlled value for the search input.
onSearchChange(value: string) => voidHandler for search input changes. When omitted, the search input is not rendered.
searchPlaceholderstring'Search...'Placeholder text for the search input.
childrenReact.ReactNodeFilter control slot — typically Select or Combobox instances rendered horizontally after the search.
activeFiltersFilterBarChip[]Active filters rendered as removable Badge chips on a secondary row. Each chip has { id, label, onRemove }.
onClearAll() => voidHandler for the clear-all button. The button only renders when this is provided AND at least one filter is active or a search value is present.
clearLabelReact.ReactNode'Clear all'Label for the clear-all button.
resultsCountReact.ReactNodeMeta text rendered on the far right, e.g. "42 results". Wrapped in aria-live="polite" so filter changes are announced.
densebooleanfalseTighter padding for dense admin layouts.
classNamestringAdditional CSS class names to merge onto the root element.
...propsReact.HTMLAttributes<HTMLDivElement>All standard HTML attributes are forwarded to the root div.

The component also accepts all standard HTML attributes for the root <div>.

FilterBarChip

interface FilterBarChip {
  id: string;
  label: React.ReactNode;
  onRemove: () => void;
}

Source Files

After running npx visor add filter-bar, you'll have:

filter-bar.tsx

A forwardRef client component that composes SearchInput, a children slot for filter controls, Badge variant="secondary" chips with labeled remove buttons, and a ghost Button for clear-all. The root is a <div role="search"> so assistive tech treats the bar as a search region, and the results count is wrapped in aria-live="polite" so filter changes are announced.

filter-bar.module.css

All values use CSS custom properties from @loworbitstudio/visor-core. The component uses container-type: inline-size with a @container query so the bar stacks vertically inside narrow containers without viewport media queries.

Customization

After copying the component, you own it completely. Common customizations:

  • Add a leading icon slot next to the search for domain iconography.
  • Replace chips with a dropdown "Active filters" menu when the list gets long.
  • Add a "Save view" affordance next to clear-all for persisted filter sets.
  • Swap role="search" for role="region" with an aria-labelledby when nesting inside a larger search landmark.