Animated Icons
motiq/animated-iconsFreeComponent
Two inline-SVG animated icons — AnimatedArrow (nudge) and AnimatedCopy (copy-to-check swap) — triggered on hover, tap, focus, or mount. Decorative and inherit their label from a parent control. 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-icons.jsonSource
1"use client";23import * as React from "react";4import { AnimatePresence, motion, useReducedMotion } from "motion/react";56import { cn } from "@/lib/utils";78type TriggerOn = "hover" | "tap" | "focus" | "mount" | "none";910interface BaseIconProps11 extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {12 /** Icon size in px (width & height). */13 size?: number;14 /** What kicks off the animation. */15 triggerOn?: TriggerOn;16}1718/**19 * Shared decorative-icon wrapper. Icons are `aria-hidden` and never focusable —20 * they inherit their meaning from the containing button/link. The wrapper is a21 * plain `<span>` so it does not disturb focus order.22 */23function useTrigger(triggerOn: TriggerOn) {24 const [active, setActive] = React.useState(triggerOn === "mount");2526 React.useEffect(() => {27 if (triggerOn === "mount") setActive(true);28 }, [triggerOn]);2930 const handlers: React.HTMLAttributes<HTMLSpanElement> = {};31 if (triggerOn === "hover") {32 handlers.onPointerEnter = () => setActive(true);33 handlers.onPointerLeave = () => setActive(false);34 } else if (triggerOn === "tap") {35 handlers.onPointerDown = () => setActive((a) => !a);36 } else if (triggerOn === "focus") {37 // The parent button owns focus; mirror its focus via focus-within on span.38 handlers.onFocusCapture = () => setActive(true);39 handlers.onBlurCapture = () => setActive(false);40 }4142 return { active, handlers };43}4445const EASE = [0.2, 0, 0, 1] as const;4647/**48 * AnimatedArrow — a right-pointing arrow that nudges on trigger.49 *50 * Accessibility: decorative only (`aria-hidden`, `focusable="false"`, not51 * tabbable); a labelled parent control conveys meaning. Under52 * `prefers-reduced-motion` it renders static. Clean-room original.53 */54export const AnimatedArrow = React.forwardRef<HTMLSpanElement, BaseIconProps>(55 ({ size = 18, triggerOn = "hover", className, ...props }, ref) => {56 const reduce = useReducedMotion();57 const { active, handlers } = useTrigger(triggerOn);58 const nudge = reduce ? 0 : active && triggerOn !== "none" ? 3 : 0;5960 return (61 <span62 ref={ref}63 aria-hidden="true"64 className={cn("inline-flex", className)}65 {...handlers}66 {...props}67 >68 <svg69 viewBox="0 0 24 24"70 width={size}71 height={size}72 fill="none"73 stroke="currentColor"74 strokeWidth={2}75 strokeLinecap="round"76 strokeLinejoin="round"77 focusable="false"78// … 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 |
