Animated Tabs
motiq/animated-tabsFreeComponent
Radix Tabs with a Motion layoutId sliding active indicator and cross-faded, direction-aware content. Keyboard and roles preserved. Reduced-motion safe.
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/animated-tabs.jsonSource
1"use client";23import * as React from "react";4import {5 AnimatePresence,6 motion,7 useReducedMotion,8 type Transition,9} from "motion/react";10import * as TabsPrimitive from "@radix-ui/react-tabs";1112import { cn } from "@/lib/utils";1314type TransitionKind = "spring" | "tween";1516const SPRING: Transition = { type: "spring", stiffness: 520, damping: 42, mass: 1 };17const TWEEN: Transition = { duration: 0.24, ease: [0.2, 0, 0, 1] };1819interface TabsAnimationContextValue {20 /** Stable id so every List gets its own sliding indicator layout group. */21 layoutId: string;22 transition: TransitionKind;23 directionAware: boolean;24 value: string | undefined;25 reduce: boolean;26}2728const TabsAnimationContext =29 React.createContext<TabsAnimationContextValue | null>(null);3031function useTabsAnimation() {32 const ctx = React.useContext(TabsAnimationContext);33 if (!ctx) {34 throw new Error("AnimatedTabs.* must be rendered inside <AnimatedTabs>.");35 }36 return ctx;37}3839export interface AnimatedTabsProps40 extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {41 /** Slide content in the direction of travel between tabs. */42 directionAware?: boolean;43 /** Motion feel for the active indicator + content. */44 transition?: TransitionKind;45}4647/**48 * AnimatedTabs — Radix Tabs with a Motion `layoutId` sliding active indicator49 * and cross-faded content.50 *51 * Accessibility: Radix owns roles (`tablist`/`tab`/`tabpanel`), arrow-key52 * roving focus, and controlled/uncontrolled value — untouched here. The53 * indicator is purely decorative. Under `prefers-reduced-motion` the indicator54 * and content swap instantly. Content is keyed by active value inside55 * AnimatePresence with `mode="wait"`, so rapid switching never leaves a stale56 * panel mounted. Clean-room original.57 */58export function AnimatedTabs({59 directionAware = false,60 transition = "spring",61 value: valueProp,62 defaultValue,63 onValueChange,64 className,65 children,66 ...props67}: AnimatedTabsProps) {68 const reduce = useReducedMotion();69 const reactId = React.useId();70 const [value, setValue] = React.useState<string | undefined>(71 valueProp ?? defaultValue,72 );7374 React.useEffect(() => {75 if (valueProp !== undefined) setValue(valueProp);76 }, [valueProp]);7778 const handleValueChange = React.useCallback(79 (next: string) => {80 if (valueProp === undefined) setValue(next);81 onValueChange?.(next);82 },83 [valueProp, onValueChange],84 );8586// … 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 |
