Sieve Filter Grid
A sieve filter grid — filtered-out items shrink and fly into the filter chip that triggered them, the count badge pulses in sync, and the surviving items close ranks with a FLIP reflow.
npx shadcn@latest add https://webberui.com/r/sieve-filter-grid.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<SieveFilterGrid />
Installation
npx shadcn@latest add https://webberui.com/r/sieve-filter-grid.jsonOr, once registries are configured in components.json, install it as @webberui/sieve-filter-grid.
Usage
import { SieveFilterGrid } from "@/components/ui/sieve-filter-grid";
<SieveFilterGrid
columns={3}
items={projects.map((project) => ({
id: project.id,
category: project.type,
content: <ProjectCard project={project} />,
}))}
onFilterChange={(category) => console.log(category)}
/>Each item needs a unique id (used to pair items across enter and exit animations) and the category it belongs to; when categories is not specified, the filter chips are derived from the items automatically, each carrying its own count badge.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | SieveFilterGridItem[] | — | Grid items, each shaped { id, category, content } |
categories | string[] | derived automatically | List of filter categories; when unspecified, derived from the items' categories, deduplicated in order of first appearance |
columns | number | 3 | Number of grid columns |
allLabel | string | "全部" | Text of the "show all" chip (the built-in default is Traditional Chinese — pass this prop to word it in your own language) |
onFilterChange | (category: string | null) => void | — | Fires when the filter changes, receiving null when showing all |
className | string | — | Appended to the outermost container's className |
How it works
- Causal animation: ordinary filtering just fades items out and in, and you cannot tell "who caused it". Here, clicking a chip makes the filtered-out items shrink and fly toward that chip — the animation's destination is the action's cause, answering "where did the items go, and why" visually and directly
- Flight path: at the moment of the click,
getBoundingClientRectmeasures the center offset between each filtered-out item and the target chip, and the exit animation then animates to that offset while shrinking to 0.2× and fading out, as if being drawn into the chip - Badge pulse: when items fly in, that chip's count badge gives a scale pulse as they arrive, reinforcing the sense of "being caught"
- Remembering where they went: each filtered-out item remembers which chip caught it, so when it comes back it scales in from that same chip's direction, keeping the round trip causally consistent
- FLIP closing ranks: exiting items leave the document flow first through
AnimatePresence mode="popLayout", and the surviving items immediately close ranks with a smooth layout FLIP, so both animations run at once - The selected chip's pill slides between chips through
layoutId
Accessibility
- When the user has "reduce motion" enabled at the system level, the flight, the FLIP, and the badge pulse are all disabled and filtering switches the display directly (the DOM structure matches SSR)
- The filter chips are a group of native
<button>s (role="group"plusaria-label), marking their selected state witharia-pressed - The chips can be focused in order with Tab, showing an outline on
focus-visible
Editorial Interleave Grid
An editorial interleave grid — a product grid that automatically inserts a full-width image, pull quote, or video section every N cards, giving it a magazine-like rhythm.
Shove Reflow Grid
A shove reflow grid — switching category makes incoming cards squeeze into the lineup, shoving their neighbors aside before they spring back, while outgoing cards get pushed past the edge and tumble away.