Progress Bar
A gradient progress bar for boosts, uploads and other long-running tasks.
A gradient progress bar for boosts, uploads and other long-running tasks. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
newt-progressimport { Progress, ProgressLabel } from "@/registry/bases/newt/ui/progress"
export default function ProgressDemo() {
return (
<div className="w-full max-w-sm">
<ProgressLabel>
<span>Label</span>
<span>50%</span>
</ProgressLabel>
<Progress value={50} />
</div>
)
}<script setup lang="ts">
import { Progress, ProgressLabel } from "~~/registry/bases/newt/ui/progress"
</script>
<template>
<div class="w-full max-w-sm">
<ProgressLabel>
<span>Label</span>
<span>50%</span>
</ProgressLabel>
<Progress :value="50" />
</div>
</template>Installation
pnpm dlx newtui@latest add progressnpx newtui@latest add progressyarn dlx newtui@latest add progressbunx newtui@latest add progressInstall the dependencies
pnpm dlx --yes class-variance-authoritynpx --yes class-variance-authorityyarn dlx --yes class-variance-authoritybunx --yes class-variance-authorityCopy the source into your project
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { cn } from "@/lib/utils"
const progressVariants = cva(
"h-full rounded-full transition-[width] duration-base ease-newt",
{
variants: {
variant: {
default: "bg-[linear-gradient(90deg,var(--newt-brand),#8c54ff)]",
success: "bg-[linear-gradient(90deg,var(--newt-online),#45c178)]",
danger: "bg-[linear-gradient(90deg,var(--newt-dnd),#f4878a)]",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface ProgressProps
extends
React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof progressVariants> {
/** Current value, 0–max */
value?: number
max?: number
indicatorClassName?: string
}
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
(
{ className, indicatorClassName, value = 0, max = 100, variant, ...props },
ref
) => {
const clamped = Math.min(Math.max(value, 0), max)
const percent = max > 0 ? (clamped / max) * 100 : 0
return (
<div
ref={ref}
role="progressbar"
aria-valuemin={0}
aria-valuemax={max}
aria-valuenow={clamped}
className={cn(
"h-2 w-full overflow-hidden rounded-full bg-newt-bg-base",
className
)}
{...props}
>
<div
className={cn(progressVariants({ variant }), indicatorClassName)}
style={{ width: `${percent}%` }}
/>
</div>
)
}
)
Progress.displayName = "Progress"
const ProgressLabel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"mb-1.5 flex justify-between text-xs text-newt-text-muted",
className
)}
{...props}
/>
))
ProgressLabel.displayName = "ProgressLabel"
export { Progress, ProgressLabel, progressVariants }<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed } from "vue"
import { cn } from "@/lib/utils"
import { type ProgressVariants, progressVariants } from "."
const props = withDefaults(
defineProps<{
value?: number
max?: number
variant?: ProgressVariants["variant"]
indicatorClass?: HTMLAttributes["class"]
class?: HTMLAttributes["class"]
}>(),
{ value: 0, max: 100 }
)
const clamped = computed(() => Math.min(Math.max(props.value, 0), props.max))
const percent = computed(() =>
props.max > 0 ? (clamped.value / props.max) * 100 : 0
)
</script>
<template>
<div
role="progressbar"
:aria-valuemin="0"
:aria-valuemax="props.max"
:aria-valuenow="clamped"
:class="
cn('h-2 w-full overflow-hidden rounded-full bg-newt-bg-base', props.class)
"
>
<div
:class="
cn(progressVariants({ variant: props.variant }), props.indicatorClass)
"
:style="{ width: `${percent}%` }"
/>
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="
cn(
'mb-1.5 flex justify-between text-xs text-newt-text-muted',
props.class
)
"
>
<slot />
</div>
</template>import { cva, type VariantProps } from "class-variance-authority"
export { default as Progress } from "./Progress.vue"
export { default as ProgressLabel } from "./ProgressLabel.vue"
export const progressVariants = cva(
"h-full rounded-full transition-[width] duration-base ease-newt",
{
variants: {
variant: {
default: "bg-[linear-gradient(90deg,var(--newt-brand),#8c54ff)]",
success: "bg-[linear-gradient(90deg,var(--newt-online),#45c178)]",
danger: "bg-[linear-gradient(90deg,var(--newt-dnd),#f4878a)]",
},
},
defaultVariants: {
variant: "default",
},
}
)
export type ProgressVariants = VariantProps<typeof progressVariants>Update the import paths
The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.
Usage
import { Progress } from "@/components/ui/progress"
export function Example() {
return <Progress />
}<script setup lang="ts">
import { Progress, ProgressLabel } from "@/components/ui/progress"
</script>
<template>
<Progress />
</template>API reference
Progress
value?number | undefined
Current value, 0–max
number | undefinedmax?number | undefined
Optional max value.
number | undefinedindicatorClassName?string | undefined
Optional indicatorClassName value.
string | undefinedThe registry exports Progress, ProgressLabel. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.
Tokens
Progress Bar reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-progress.