WebberUI

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.

Loading preview…
npx shadcn@latest add https://webberui.com/r/sieve-filter-grid.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

3
<SieveFilterGrid />

Installation

npx shadcn@latest add https://webberui.com/r/sieve-filter-grid.json

Or, 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

PropTypeDefaultDescription
itemsSieveFilterGridItem[]Grid items, each shaped { id, category, content }
categoriesstring[]derived automaticallyList of filter categories; when unspecified, derived from the items' categories, deduplicated in order of first appearance
columnsnumber3Number of grid columns
allLabelstring"全部"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) => voidFires when the filter changes, receiving null when showing all
classNamestringAppended 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, getBoundingClientRect measures 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" plus aria-label), marking their selected state with aria-pressed
  • The chips can be focused in order with Tab, showing an outline on focus-visible

On this page