Progress
A progress bar for known and unknown amounts of work, with status tones.
Downloading pack
Installation
npx shadcn@latest add @runqora/craft-progressUsage
import { Progress } from "@/components/craft/progress";
<Progress label="Downloading pack" showValue value={40} />;Examples
Tones
Info
Primary
Warning
Destructive
Indeterminate
Pass null when you can't tell how much work is left. With reduced motion
turned on, the bar shows a steady half-strength fill instead of moving.
Connecting
Custom color and thin bar
size="sm" is the thin loading bar from the Ore UI reference.
Custom color
Props
Prop
Type
Accessibility
- Has the
progressbarrole, with its current value for screen readers. labelnames the bar. Without one, passaria-label.- Pair a tone with a label or message, since color alone doesn't say what happened.
Customization
color takes any CSS color, and a custom color replaces the highlights too. To
restyle a tone everywhere, edit its tokens, such as --craft-info and
--craft-info-hi.
Dependencies
@base-ui/react- shadcn's
utils, andcraft-theme
Source
"use client";
import { Progress as BaseProgress } from "@base-ui/react/progress";
import type { ComponentProps, CSSProperties, ReactNode } from "react";
import { cn } from "@/lib/utils";
const tones = {
destructive: "var(--craft-destructive)",
info: "var(--craft-info)",
primary: "var(--craft-primary)",
warning: "var(--craft-warning)",
};
const highlights = {
destructive: "var(--craft-destructive-hi)",
info: "var(--craft-info-hi)",
primary: "var(--craft-primary-hi)",
warning: "var(--craft-warning-hi)",
};
const shades = {
destructive: "var(--craft-destructive-edge)",
info: "var(--craft-info-lo)",
primary: "var(--craft-primary-edge)",
warning: "var(--craft-warning-lo)",
};
export interface ProgressProps
extends Omit<ComponentProps<typeof BaseProgress.Root>, "children" | "value"> {
/** Any CSS color for the fill. Takes priority over `tone`. */
color?: string;
/** Text above the bar. Without one, pass `aria-label`. */
label?: ReactNode;
/** Shows the value above the bar, on the right. */
showValue?: boolean;
/** The height of the bar: `sm` is the thin Ore UI loading bar. */
size?: "sm" | "md";
/** The color of the fill. */
tone?: keyof typeof tones;
/** The current value. `null` means the progress is unknown (indeterminate). */
value: number | null;
}
export function Progress({
className,
color,
label,
showValue,
size = "md",
style,
tone = "info",
...props
}: ProgressProps) {
const colors = {
"--progress-face": color ?? tones[tone],
"--progress-hi": color ?? highlights[tone],
"--progress-lo": color ?? shades[tone],
} as CSSProperties;
return (
<BaseProgress.Root
className={cn(
"font-craft-display text-craft-text flex w-full flex-col gap-2 text-xl",
className
)}
data-slot="progress"
style={{ ...colors, ...style }}
{...props}
>
{label || showValue ? (
<div className="flex items-baseline justify-between gap-4">
{label ? <BaseProgress.Label>{label}</BaseProgress.Label> : null}
{showValue ? (
<BaseProgress.Value className="ml-auto tabular-nums" />
) : null}
</div>
) : null}
<BaseProgress.Track
className={cn(
"bg-craft-sunken relative block w-full overflow-hidden",
size === "sm"
? "h-[calc(var(--craft-unit)*2)]"
: "border-craft-outline h-[calc(var(--craft-unit)*6)] border-(length:--craft-unit)"
)}
>
<BaseProgress.Indicator
className={cn(
"block h-full bg-(--progress-face)",
size === "md" &&
"shadow-[inset_0_var(--craft-unit)_0_var(--progress-hi),inset_0_calc(-1*var(--craft-unit))_0_var(--progress-lo)]",
"motion-safe:transition-[width] motion-safe:duration-200",
"data-indeterminate:w-1/4 motion-safe:data-indeterminate:animate-[craft-progress-slide_1.6s_steps(40)_infinite] motion-reduce:data-indeterminate:w-full motion-reduce:data-indeterminate:opacity-50"
)}
/>
</BaseProgress.Track>
</BaseProgress.Root>
);
}