Filter Result Transition
motiq/filter-result-transitionFreeComponent
A transition layer for filtered/searched collections your app drives: cards enter, leave, and reorder with layout continuity, the result count morphs, and empty/loading/error states cross-fade. If the focused item is filtered out, onFocusFallback fires so focus never drops to the page root. Capped entrance stagger; results are never delayed for animation; reduced motion is an instant swap. Not a data table or a search library.
Live preview
live · rendered by the registry
Rendered by motiq on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://www.motiq.dev/r/filter-result-transition.jsonSource
1"use client";23import * as React from "react";4import { motion, AnimatePresence, LayoutGroup } from "motion/react";56import { cn } from "@/lib/utils";7import { useReducedMotion, useAnimatedNumber, formatNumber } from "@/lib/motiq";89/* -------------------------------------------------------------------------- */10/* Types */11/* -------------------------------------------------------------------------- */1213export type FilterLayout = "cards" | "list" | "grid";14export type FilterResultState = "idle" | "loading" | "error";1516/**17 * One active filter/facet the host applied. `id` is the stable removal key; the18 * host removes it in `onRemoveFilter`. `group` labels the facet (e.g. "Status")19 * so the chip and its accessible name stay unambiguous.20 */21export interface ActiveFilter {22 /** Stable id passed back to `onRemoveFilter` (required). */23 id: string;24 /** Visible chip text (e.g. "Design"). */25 label: string;26 /** Optional facet name shown before the value (e.g. "Category"). */27 group?: string;28}2930/** Context handed to `renderItem` for each row. */31export interface RenderItemContext {32 /** Position in the current (post-filter) result set. */33 index: number;34 /** Active layout, in case the card adapts. */35 layout: FilterLayout;36 /** Whether this item matches `focusedItemId`. */37 focused: boolean;38}3940/** Context handed to `onFocusFallback` when the focused item disappears. */41export interface FocusFallbackContext<T> {42 /** The id that was focused and is now gone. */43 previousId: string;44 /** The stable results-region anchor (already `tabIndex=-1`). */45 anchor: HTMLElement | null;46 /** The surviving items after the change, for "focus the first result" logic. */47 items: T[];48}4950export interface FilterResultTransitionProps<T> {51 /** The already-filtered/sorted items to show — the host owns filtering. */52 items: T[];53 /** Stable identity per item — the key to continuity + focus preservation. */54 getItemId: (item: T) => string;55 /** Render one result. Keep interactive controls inside the card, not nested. */56 renderItem: (item: T, ctx: RenderItemContext) => React.ReactNode;5758 /** Card arrangement. `grid` reflows responsively; `list`/`cards` stack. */59 layout?: FilterLayout;6061 /** Full override for the count line; receives the *morphing* integer. */62 resultLabel?: (count: number) => React.ReactNode;63 /** Plain-text count for the polite announcement (no nodes). */64// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from motiq
All 100 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Activity Streamactivity-stream | motiq | Components | Free | 1 dep | |
| Adaptive Safe-Zone Gridadaptive-safe-zone-grid | motiq | Components | Free | no deps | |
| Agent Run Timelineagent-run-timeline | motiq | Components | Free | 1 dep | |
| AI Response Streamai-response-stream | motiq | Components | Free | 1 dep | |
| Animated Accordionanimated-accordion | motiq | Components | Free | 2 deps | |
| Animated Buttonanimated-button | motiq | Components | Free | 1 dep | |
| Animated Dialoganimated-dialog | motiq | Components | Free | 2 deps | |
| Animated Gridanimated-grid | motiq | Components | Free | 2 deps |
