Facetcomponents

Progress

A track and a fill, where the fill's width is the value — and this file names neither number.

Terminal window
npx facet-rbxts add progress

Copies ui/progress.tsx, plus lib/utils.ts. Needs @facet-ui/react-variants, @lattice-ui/react-runtime@^0.8.0 and @lattice-ui/react-progress@^0.8.0.

import { Progress } from "../shared/ui/progress";
<Progress value={72} />
<Progress indeterminate />
Two determinate bars and the indeterminate sweep. The sweep is motion, not a class.

Renders a Frame. Unknown props forward onto it and are type-checked against it, so a prop Frame does not accept is a compile error. The primitive owns Size on the indicator, which it animates from the value, so values you pass for it are ignored.

Props

PropTypeDescription
valuenumberHow far along. Read against max.
maxnumberThe end. Defaults to 100.
indeterminatebooleanSweep the indicator back and forth instead of mapping value — for work with no known end.
classNameClassNameThreaded into the root recipe's className slot inside the component. A class written at a Vela-compiled call site never reaches it — see Overriding from the call site.

There is no Text, no label and no percentage readout. A progress bar in this registry is two rectangles; the words above it are a Label you place yourself.

The primitive draws nothing either

<ProgressPrimitive.Root indeterminate={props.indeterminate} max={props.max} value={props.value}>
<frame className={cn(progressVariants.root({ className: props.className }))} …>
<ProgressPrimitive.Indicator className={cn(progressVariants.indicator())} />
</frame>
</ProgressPrimitive.Root>

Progress.Root renders no instance — it turns value and max into a ratio and nothing else — so the track is a plain <frame> in the copied file, with the primitive wrapped around it. Same inversion as Avatar.

The indicator has no width

export const progressVariants = {
root: fv("h-2 w-full rounded-full bg-secondary overflow-hidden"),
indicator: fv("rounded-full bg-primary"),
};

Two classes on the indicator, and neither is a size. Its Size is the value: Lattice’s motion owns the property, animates it as the number moves, and sweeps it end to end when indeterminate.

overflow-hidden on the root is what keeps the fill’s square end inside the rounded track: ClipsDescendants clips to the rectangle, and the indicator’s own rounded-full does the corners.

indeterminate is not a value

<Progress indeterminate /> ignores value entirely. Use it for work whose end you cannot measure — a server round trip, an asset load with no progress signal — rather than passing a fake number that never advances.