Ai Loader
smoothui-registry/ai-loaderFreeComponent
Family of AI waiting indicators — dots, sweep bar and pixel grid — sharing one cycle length, with an optional elapsed counter.
Install it
npx shadcn@latest add https://www.smoothui.dev/r/ai-loader.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { motion, useAnimationFrame, useReducedMotion } from "motion/react";5import { useRef, useState } from "react";67/**8 * One cycle length for every variant.9 *10 * Two loaders on the same screen running at different tempos read as two11 * unrelated things loading. Sharing the cycle makes them feel like one system12 * working, which is the whole point of a loader family.13 */14export const AI_LOADER_CYCLE_SECONDS = 1.2;1516const DOT_COUNT = 3;17const GRID_SIZE = 3;18const GRID_CELLS = GRID_SIZE * GRID_SIZE;19/** Diagonal wave order, so the grid fills like a sweep rather than at random. */20const GRID_DELAYS = [0, 1, 2, 1, 2, 3, 2, 3, 4];21const EASE_IN_OUT = [0.645, 0.045, 0.355, 1] as const;22const MS_PER_SECOND = 1000;23const ELAPSED_DECIMALS = 1;2425export type AILoaderVariant = "dots" | "bar" | "grid";2627export type AILoaderProps = {28 className?: string;29 /** Optional text before the indicator, e.g. "Thinking". */30 label?: string;31 /** Appends a live elapsed counter. Long waits need a sign of progress. */32 showElapsed?: boolean;33 variant?: AILoaderVariant;34};3536const Dots = ({ reduced }: { reduced: boolean }) => (37 <span className="flex items-center gap-1">38 {Array.from({ length: DOT_COUNT }, (_, index) => (39 <motion.span40 animate={reduced ? { opacity: 0.5 } : { opacity: [0.25, 1, 0.25] }}41 className="size-1.5 rounded-full bg-current"42 // biome-ignore lint/suspicious/noArrayIndexKey: dots are positional43 key={index}44 transition={45 reduced46 ? { duration: 0 }47 : {48 delay: (index * AI_LOADER_CYCLE_SECONDS) / (DOT_COUNT * 2),49 duration: AI_LOADER_CYCLE_SECONDS,50 ease: EASE_IN_OUT,51 repeat: Number.POSITIVE_INFINITY,52 }53 }54 />55 ))}56 </span>57);5859const Bar = ({ reduced }: { reduced: boolean }) => (60 <span className="relative block h-1 w-24 overflow-hidden rounded-full bg-current/15">61 {/* A sweep, not a percentage. Faking determinate progress for an unknown62 wait is the one thing a loader must never do. */}63 <motion.span64 animate={reduced ? { x: "0%" } : { x: ["-100%", "200%"] }}65 className="absolute inset-y-0 w-1/3 rounded-full bg-current"66 transition={67 reduced68 ? { duration: 0 }69 : {70 duration: AI_LOADER_CYCLE_SECONDS * 1.4,71 ease: EASE_IN_OUT,72// … truncated
What it pulls in
npm packages
Files it writes
More from SmoothUI Registry
All 178 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Agent Avataragent-avatar | SmoothUI Registry | Components | Free | no deps | |
| Ai Approvalai-approval | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Artifactai-artifact | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Branchai-branch | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Citationai-citation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Context Meterai-context-meter | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Conversationai-conversation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Coreai-core | SmoothUI Registry | Components | Free | 1 dep |
