Filter Chip Table
A free filterable table component for React and Next.js with animated chip filters.
6 of 6 rows shown
| Locomotive | Wheels | Shed | Boiler ticket |
|---|---|---|---|
| 76084 Kirkstone | 2-6-0 | Bishopwood | To Mar 2031 |
| 5643 Glyder | 0-6-2T | Frosterley | Ran out 2024 |
| 68011 Stanhope | 0-6-0ST | Bishopwood | To Nov 2028 |
| 42968 Rookhope | 2-6-0 | Bishopwood | To Jun 2030 |
| 31806 Wearmouth | 2-6-0 | Frosterley | Ran out 2025 |
| 1054 Muggleswick | 0-4-0ST | Frosterley | Ran out 2023 |
Customise
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-chipsUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
| filters | { id: string; label: string }[] | required | Two 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" }[] | required | Two to six columns with unique keys, in display order. |
| rows | { id: string; filterIds: string[]; cells: Record<string, string | number> }[] | required | Controlled row dataset with unique ids. Rows are filtered, never mutated, and original order is preserved. |
| value | string | "all" | Controlled active filter id. An unrecognised id falls back to All visually without calling onValueChange. |
| onValueChange | (id: string) => void | required | Called with the selected filter id after an explicit chip selection. |
| caption | string | required | Rendered 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. |
| rowOffset | number | 12 | Row enter/exit distance in CSS pixels, clamped to 0 through 28. |
| transitionDuration | number | 0.28 | Row transition duration in seconds, clamped to 0 through 0.7. |
| density | "comfortable" | "compact" | "comfortable" | Chip and cell spacing density. |
| indicatorStiffness | number | 360 | Shared chip indicator spring stiffness, clamped to 140 through 560. |
| radius | number | 20 | Shell corner radius in CSS pixels, clamped to 0 through 40. |
| className | string | undefined | Additional 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.