Skip to content
Template Empire
Browse components

Filter Chip Table

A free filterable table component for React and Next.js with animated chip filters.

6 of 6 rows shown

Wear Valley Railway fleet
LocomotiveWheelsShedBoiler ticket
76084 Kirkstone2-6-0BishopwoodTo Mar 2031
5643 Glyder0-6-2TFrosterleyRan out 2024
68011 Stanhope0-6-0STBishopwoodTo Nov 2028
42968 Rookhope2-6-0BishopwoodTo Jun 2030
31806 Wearmouth2-6-0FrosterleyRan out 2025
1054 Muggleswick0-4-0STFrosterleyRan out 2023

Customise

12px
0.28s
360
20px

Code

const filters = [
  { id: "in-traffic", label: "In traffic" },
  { id: "in-works", label: "In works" },
  { id: "stored", label: "Stored" },
];

const columns = [
  { key: "locomotive", label: "Locomotive" },
  { key: "shed", label: "Shed" },
  // Add up to three more stable-key columns.
];

const rows = [
  {
    id: "76084",
    filterIds: ["in-traffic"],
    cells: { locomotive: "76084 Kirkstone", shed: "Bishopwood" },
  },
  // Add more stable-ID rows.
];

const [value, setValue] = useState("all");

<TableFilterChips
  filters={filters}
  columns={columns}
  rows={rows}
  value={value}
  onValueChange={setValue}
  caption="Wear Valley Railway fleet"
/>

Filter Chip Table is a free filterable table component for React and Next.js, styled with Tailwind and animated with Motion. A row of native filter chips narrows a real HTML table to the rows that match, while a spring-driven shared indicator travels to the active chip and the matching rows fade and translate into place.

Filtering never mutates the dataset: rows are filtered, not changed, and their original order always holds. Every chip carries aria-pressed, the result count updates through a visible, polite live region after each selection, and an empty result set still renders one readable table row instead of collapsing to nothing. With reduced motion enabled the filtered set and the indicator both update instantly, with the same callback, DOM order and semantics.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/table-filter-chips

Usage

import { useState } from "react";
import { TableFilterChips } from "@/components/table-filter-chips";

const filters = [
  { id: "open", label: "Open" },
  { id: "resolved", label: "Resolved" },
];

const columns = [
  { key: "ticket", label: "Ticket" },
  { key: "customer", label: "Customer" },
];

const rows = [
  {
    id: "TCK-1",
    filterIds: ["open"],
    cells: { ticket: "TCK-1", customer: "Aurora Studio" },
  },
  {
    id: "TCK-2",
    filterIds: ["resolved"],
    cells: { ticket: "TCK-2", customer: "Northwind Labs" },
  },
];

export default function Page() {
  const [value, setValue] = useState("all");

  return (
    <TableFilterChips
      filters={filters}
      columns={columns}
      rows={rows}
      value={value}
      onValueChange={setValue}
      caption="Support tickets by status"
    />
  );
}

Props

PropTypeDefaultDescription
filters{ id: string; label: string }[]requiredTwo to eight filters with unique ids. The component prepends its own All option; do not include one.
columns{ key: string; label: string; align?: "left" | "right" }[]requiredTwo to six columns with unique keys, in display order.
rows{ id: string; filterIds: string[]; cells: Record<string, string | number> }[]requiredControlled row dataset with unique ids. Rows are filtered, never mutated, and original order is preserved.
valuestring"all"Controlled active filter id. An unrecognised id falls back to All visually without calling onValueChange.
onValueChange(id: string) => voidrequiredCalled with the selected filter id after an explicit chip selection.
captionstringrequiredRendered as a visible caption at the top of the table.
filterPlacement"top" | "side""top"Filter toolbar placement on wide screens. Narrow screens always stack the toolbar above the table.
rowOffsetnumber12Row enter/exit distance in CSS pixels, clamped to 0 through 28.
transitionDurationnumber0.28Row transition duration in seconds, clamped to 0 through 0.7.
density"comfortable" | "compact""comfortable"Chip and cell spacing density.
indicatorStiffnessnumber360Shared chip indicator spring stiffness, clamped to 140 through 560.
radiusnumber20Shell corner radius in CSS pixels, clamped to 0 through 40.
classNamestringundefinedAdditional classes merged onto the section.

Customisation

Filter Chip Table exposes four Tier 3 CSS variables: filter-table-accent for the active chip fill and shared indicator, filter-table-accent-text for inactive chip hover and focus text, filter-table-panel for the table and toolbar surface, and filter-table-divider for row and header borders. Each defaults to the shared semantic tokens, so retheme it from any ancestor without touching the component:

:root {
  --filter-table-accent: var(--ring);
  --filter-table-accent-text: var(--primary-accent);
  --filter-table-panel: var(--card, var(--surface));
  --filter-table-divider: var(--border);
}

<TableFilterChips
  filters={filters}
  columns={columns}
  rows={rows}
  value={value}
  onValueChange={setValue}
  caption="Support tickets by status"
  density="compact"
  radius={12}
/>

Frequently asked questions

Is Filter Chip Table free for commercial use?
Yes. Filter Chip Table is MIT licensed, so you can use it in personal and commercial projects without attribution.
Does filtering stay accessible and performant?
Yes. The filter toolbar is a labelled group of real buttons, each with aria-pressed, and Tab moves through it in ordinary document order with no tablist or arrow-key handling to learn. Row transitions animate only transform and opacity, an exiting row is marked aria-hidden for the length of its exit animation, and the result count renders as visible text inside a polite live region so screen reader users hear exactly what sighted users see. With reduced motion enabled the filtered set and indicator both update instantly with identical semantics.
Can I control the chip and row motion?
Yes. rowOffset and transitionDuration tune how far and how long rows travel on enter and exit, indicatorStiffness tunes the spring that carries the shared chip indicator, and density and radius adjust spacing and the shell's corner radius. filterPlacement moves the toolbar into a side column on wide screens instead of stacking it above the table.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates