shadcn.io is not affiliated with official shadcn/ui
Shadcn Circular Progress for React and Tailwind
Shows task progress with a circular indicator.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/circular-progress.jsonpnpm dlx shadcn@latest add https://kit.dev/r/circular-progress.jsonnpx shadcn@latest add https://kit.dev/r/circular-progress.jsonyarn shadcn@latest add https://kit.dev/r/circular-progress.jsonInstall the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import {
Progress as ArkProgress,
useProgress as useArkProgress,
useProgressContext as useArkProgressContext,
} from "@ark-ui/react/progress";
import type React from "react";
import { cn } from "@/lib/utils";
export const useCircularProgress = useArkProgress;
export const useCircularProgressContext = useArkProgressContext;
export const CircularProgressContext = ArkProgress.Context;
const DEFAULT_SIZE = 32;
const DEFAULT_THICKNESS = 4;
interface CircularProgressSizeProps {
/**
* Width and height of the circle, in pixels.
*
* @default 32
*/
size?: number;
/**
* Stroke width of the circle, in pixels.
*
* @default 4
*/
thickness?: number;
}
const circularProgressRootClassName = cn(
"group/circular-progress",
"relative",
"inline-flex flex-col items-center justify-center"
);
const sizeStyle = (size: number, thickness: number) =>
({
"--size": `${size}px`,
"--thickness": `${thickness}px`,
}) as React.CSSProperties;
export const CircularProgress = (
props: React.ComponentProps<typeof ArkProgress.Root> &
CircularProgressSizeProps & {
/**
* Shows indeterminate progress (`value={null}`).
*
* @default false
*/
indeterminate?: boolean;
}
) => {
const {
value,
indeterminate = false,
size = DEFAULT_SIZE,
thickness = DEFAULT_THICKNESS,
className,
style,
children,
...rest
} = props;
return (
<ArkProgress.Root
className={cn(circularProgressRootClassName, className)}
data-slot="circular-progress"
style={{ ...sizeStyle(size, thickness), ...style }}
value={indeterminate ? null : value}
{...rest}
>
{children}
<CircularProgressTrack size={size} thickness={thickness} />
</ArkProgress.Root>
);
};
export const CircularProgressRootProvider = (
props: React.ComponentProps<typeof ArkProgress.RootProvider> &
CircularProgressSizeProps
) => {
const {
size = DEFAULT_SIZE,
thickness = DEFAULT_THICKNESS,
className,
style,
children,
...rest
} = props;
return (
<ArkProgress.RootProvider
className={cn(circularProgressRootClassName, className)}
data-slot="circular-progress"
style={{ ...sizeStyle(size, thickness), ...style }}
{...rest}
>
{children}
<CircularProgressTrack size={size} thickness={thickness} />
</ArkProgress.RootProvider>
);
};
export const CircularProgressTrack = (
props: React.ComponentProps<typeof ArkProgress.Circle> &
CircularProgressSizeProps
) => {
const {
size = DEFAULT_SIZE,
thickness = DEFAULT_THICKNESS,
className,
style,
...rest
} = props;
return (
<ArkProgress.Circle
className={cn(
"block",
"pointer-events-none",
"overflow-visible",
"motion-reduce:animate-none!",
"group-data-[state=indeterminate]/circular-progress:animate-spin!",
className
)}
data-slot="circular-progress-circle"
style={{ ...sizeStyle(size, thickness), ...style }}
{...rest}
>
<ArkProgress.CircleTrack
className="stroke-input"
data-slot="circular-progress-track"
/>
<ArkProgress.CircleRange
className={cn(
"stroke-primary",
"[stroke-linecap:round]",
"transition-[stroke-dashoffset] duration-300 ease-out",
"motion-reduce:transition-none!",
"data-[state=indeterminate]:[stroke-dasharray:calc(var(--circumference)*0.3)_var(--circumference)]"
)}
data-slot="circular-progress-range"
/>
</ArkProgress.Circle>
);
};
export const CircularProgressValue = (
props: React.ComponentProps<typeof ArkProgress.ValueText>
) => {
const { className, ...rest } = props;
return (
<ArkProgress.ValueText
className={cn(
"absolute bottom-0",
"flex size-(--size) items-center justify-center",
"font-medium text-xs tabular-nums",
"pointer-events-none",
className
)}
data-slot="circular-progress-value"
{...rest}
/>
);
};
export const CircularProgressLabel = (
props: React.ComponentProps<typeof ArkProgress.Label>
) => {
const { className, ...rest } = props;
return (
<ArkProgress.Label
className={cn("font-medium text-sm", className)}
data-slot="circular-progress-label"
{...rest}
/>
);
};
export const CircularProgressView = (
props: React.ComponentProps<typeof ArkProgress.View>
) => {
const { className, ...rest } = props;
return (
<ArkProgress.View
className={cn("text-muted-foreground text-sm", className)}
data-slot="circular-progress-view"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
CircularProgress
├── CircularProgressLabel
├── CircularProgressTrack
├── CircularProgressRange
├── CircularProgressValue
├── CircularProgressView
└── CircularProgressTrack
├── CircularProgressCircleTrack
└── CircularProgressCircleRangeUsage
import { useEffect, useState } from "react";
import { CircularProgress } from "@/components/ui/circular-progress";const Example = () => {
const [progress, setProgress] = useState(24);
useEffect(() => {
const timer = setTimeout(() => {
setProgress(72);
}, 500);
return () => {
clearTimeout(timer);
};
}, []);
return <CircularProgress value={progress} />;
};Demos
Controlled
Indeterminate
Min Max
Root Provider
Size
Thickness
View
With Label
With Value
API Reference
CircularProgress
div
PropType
AttributeType
CircularProgressLabel
span
PropType
AttributeType
CircularProgressValue
span
PropType
AttributeType
CSS variableType
CircularProgressView
span
PropType
AttributeType
CircularProgressTrack
svg
PropType
AttributeType
CSS variableType
CircularProgressCircleTrack
circle
PropType
AttributeType
CSS variableType
CircularProgressCircleRange
circle
PropType
AttributeType
CSS variableType
CircularProgressRootProvider
div
PropType
AttributeType