Progress
A progress bar built on radix-ui - 6 tones × 3 sizes, with a fill that animates using the ai2 motion tokens.
import { Progress } from "@/components/ui/progress"
export default function ProgressDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<Progress value={60} tone="brand" />
<Progress value={92} tone="success" size="lg" />
</div>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/progressDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install class-variance-authority@^0.7.1 radix-ui@^1.6.1Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/progress.tsx"use client"
import type * as React from "react"
import { Progress as ProgressPrimitive } from "@/components/ui/primitives"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const progressVariants = cva("relative w-full overflow-hidden rounded-full bg-surface-3", {
variants: {
size: {
sm: "h-1.5",
md: "h-2",
lg: "h-3",
},
},
defaultVariants: { size: "md" },
})
const indicatorVariants = cva(
"h-full w-full flex-1 rounded-full transition-transform duration-(--motion-slow) ease-(--motion-ease)",
{
variants: {
tone: {
neutral: "bg-primary",
brand: "bg-brand",
success: "bg-success",
warning: "bg-warning",
danger: "bg-danger",
info: "bg-info",
},
},
defaultVariants: { tone: "neutral" },
}
)
interface ProgressProps
extends React.ComponentProps<typeof ProgressPrimitive.Root>,
VariantProps<typeof progressVariants>,
VariantProps<typeof indicatorVariants> {}
function Progress({ className, value, size, tone, ...props }: ProgressProps) {
return (
<ProgressPrimitive.Root
data-slot="progress"
data-tone={tone ?? "neutral"}
className={cn(progressVariants({ size, className }))}
value={value}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className={cn(indicatorVariants({ tone }))}
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
/>
</ProgressPrimitive.Root>
)
}
export { Progress, progressVariants, type ProgressProps }Manual installs skip the @ai2/tokens theme - add the token CSS from the theming guide or the tone colors will be missing.
Usage
import { Progress } from "@/components/ui/progress"
<Progress value={60} tone="brand" />Pass a value between 0 and 100. When the value changes, the fill animates to its new position using the ai2 motion tokens.
Examples
Tones
Use the tone to convey status - success for complete, danger for over-quota.
Sizes
With label
Props
Progress also accepts every prop of the radix Progress.Root primitive.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | - | Current progress from 0 to 100. Missing values render as an empty track. |
tone | "neutral" | "brand" | "success" | "warning" | "danger" | "info" | "neutral" | Color of the fill indicator, mapped to theme tokens. Conveys status: success for complete, danger for over-quota. |
size | "sm" | "md" | "lg" | "md" | Height of the track. |
ai2 Progress: a token-driven progress bar for React
The ai2 Progress is a shadcn-compatible progress bar component for React, built on the radix-ui Progress primitive and styled with Tailwind CSS v4. It renders a rounded track with a fill indicator that carries one of 6 tones and 3 sizes, and every value change animates the fill to its new position using the shared ai2 motion tokens.
Because it ships through the shadcn registry format, you install it with one CLI command, an MCP agent, or a copy-paste, and the source lands in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Progress?
It is a single exported component, Progress, that composes the radix Progress.Root track and Progress.Indicator fill internally. The anatomy matches shadcn/ui exactly, so existing snippets, muscle memory and AI agents keep working without changes.
Two cva axes cover the visual range: tone (neutral, brand, success, warning, danger, info) colors the fill from theme tokens, and size (sm, md, lg) sets the track height. Pass a determinate value from 0 to 100 and the fill takes care of itself.
Why use it
- Accessible by construction: The radix-ui Progress primitive renders a proper progressbar role with the current, minimum and maximum values exposed to assistive technology.
- Status through tone: Six tones map straight to theme tokens: neutral for generic progress, brand for uploads, success for complete, warning near a quota, danger when over it, info for background syncs and notices.
- Motion from tokens: The fill animates with a transform transition that reads --motion-slow and --motion-ease from the shared token file, so progress updates glide instead of jumping.
- Transform-based fill: The indicator moves with translateX instead of animating width, so updates never trigger layout and stay smooth even with many bars on screen.
- Agent-readable metadata: The registry item describes its tone and size axes in plain words, so an MCP agent can find, inspect and install it without guessing.
Features
- shadcn registry install: One command adds the component, its dependencies and the @ai2/tokens theme to your project.
- 6 tones x 3 sizes: Eighteen combinations from two props, all resolved through cva with neutral and md as defaults.
- Token-driven track and fill: The track uses the surface-3 token and each tone maps to a theme token, so the bar follows your palette in both light and dark mode.
- Empty-track fallback: A missing value renders as an empty track instead of crashing or showing a stale fill, so loading states before the first measurement stay clean.
- Data attributes for styling: The root exposes data-slot and data-tone and the fill exposes data-slot, so you can restyle any tone from CSS without forking the component.
- TypeScript source with exported variants: The file is typed end to end and also exports progressVariants, so you can reuse the track styling for custom meters.
Production tips
- Always label the bar: A bar alone is ambiguous to screen readers. Add aria-label or pair it with a visible label and value text, like the storage example above.
- Keep values in the 0 to 100 range: The fill position is computed from the value as a percentage. Clamp computed values before passing them so an overshoot does not render a broken bar.
- Switch tone with state: Drive the tone from the same state as the value: brand while running, success at 100, danger when a quota check fails. The color change reads as status without extra text.
- Let the motion smooth the steps: Chunked updates (10, 35, 80) look fine because the transform transition interpolates between them. There is no need to tick the value every frame.
- Do not fake progress: For work with unknown duration, show an ai2 Spinner or Skeleton instead of a bar creeping to 90 percent. Reserve Progress for genuinely measurable work.
Works with the rest of ai2
The progress bar composes naturally with the rest of the registry. Place it inside an ai2 Card for usage and quota panels, pair it with an ai2 Badge showing the percentage, or announce completion with ai2 Sonner toasts.
For the states around it, use ai2 Skeleton while the first measurement loads and ai2 Spinner for work you cannot measure. Everything shares one token source, so combinations stay visually consistent in both modes.