Anatomy of the GodUI Progress
Why the fill is always full width, how a new value glides there on a spring, and how the indeterminate bar sweeps without touching layout.
↓Scroll to step through it
The bar never resizes
The obvious way to draw progress is a bar whose width is the percentage.
Animate that and the browser runs layout on every frame of the tween.
shadcn already avoids it: the indicator is always the full width of the track
and slides left by whatever is left to do, translateX(-(100 - value)%). The
track is overflow-hidden, so the part that's slid out is clipped. At 66% the
bar sits 34% to the left.
shadcn then transitions it with transition-all, which also tweens any color
or size that happens to change. GodUI names transform only and moves it on
ease-spring-smooth over 380ms, so each new value lands with a soft settle
instead of a linear crawl.
Indeterminate
When there's nothing to measure yet, leave out value. Radix then marks the
indicator data-state="indeterminate", and GodUI turns it into a sweep.
The indicator snaps to 40% of the track, once, when it becomes indeterminate.
The keyframe then moves it from one full bar-width left of the track
(-100%) to two and a half bar-widths right (250%, which is exactly the
track's far edge, since 2.5 × 40% = 100%). A CSS animation outranks the inline
transform, so the value-driven slide stays out of the way. The easing is a
symmetric ease-in-out, cubic-bezier(0.65, 0, 0.35, 1): the bar eases in from
off the left edge, is at full speed while it crosses the track, and eases out
past the right edge. Some of the bar is on the track for almost 90% of each
loop; it's empty for under 200ms between one sweep and the next.
The result
Press Next step and Back to move the fill. The lower bar has no value, so it keeps sweeping.
<ProgressPrimitive.Indicatordata-slot="progress-indicator"className="h-full w-full flex-1 bg-primary transition-[transform] duration-(--godui-duration-slow) ease-spring-smooth motion-reduce:transition-none …"style={{ transform: `translateX(-${100 - (value || 0)}%)` }}/>Anatomy of the GodUI Progress
Why the fill is always full width, how a new value glides there on a spring, and how the indeterminate bar sweeps without touching layout.
<ProgressPrimitive.Indicatordata-slot="progress-indicator"className="h-full w-full flex-1 bg-primary transition-[transform] duration-(--godui-duration-slow) ease-spring-smooth motion-reduce:transition-none …"style={{ transform: `translateX(-${100 - (value || 0)}%)` }}/>The bar never resizes
The obvious way to draw progress is a bar whose width is the percentage.
Animate that and the browser runs layout on every frame of the tween.
shadcn already avoids it: the indicator is always the full width of the track
and slides left by whatever is left to do, translateX(-(100 - value)%). The
track is overflow-hidden, so the part that's slid out is clipped. At 66% the
bar sits 34% to the left.
shadcn then transitions it with transition-all, which also tweens any color
or size that happens to change. GodUI names transform only and moves it on
ease-spring-smooth over 380ms, so each new value lands with a soft settle
instead of a linear crawl.
"… data-[state=indeterminate]:w-2/5 data-[state=indeterminate]:animate-godui-progress-indeterminate …"@keyframes godui-progress-indeterminate {from { transform: translateX(-100%); }to { transform: translateX(250%); }}/* 1.4s, cubic-bezier(0.65, 0, 0.35, 1), infinite */Indeterminate
When there's nothing to measure yet, leave out value. Radix then marks the
indicator data-state="indeterminate", and GodUI turns it into a sweep.
The indicator snaps to 40% of the track, once, when it becomes indeterminate.
The keyframe then moves it from one full bar-width left of the track
(-100%) to two and a half bar-widths right (250%, which is exactly the
track's far edge, since 2.5 × 40% = 100%). A CSS animation outranks the inline
transform, so the value-driven slide stays out of the way. The easing is a
symmetric ease-in-out, cubic-bezier(0.65, 0, 0.35, 1): the bar eases in from
off the left edge, is at full speed while it crosses the track, and eases out
past the right edge. Some of the bar is on the track for almost 90% of each
loop; it's empty for under 200ms between one sweep and the next.
The result
Press Next step and Back to move the fill. The lower bar has no value, so it keeps sweeping.
What's animated
| Interaction | Keyframe / mechanism | Properties | Easing | Duration |
|---|---|---|---|---|
| Value change | transition | transform | ease-spring-smooth | 380ms |
| Indeterminate | godui-progress-indeterminate | transform | cubic-bezier(0.65, 0, 0.35, 1) (ease-in-out) | 1.4s loop |
Why GPU-only
Both animations move transform. The fill never changes size; the
indeterminate bar changes its width once, as a snap, and then only slides.
shadcn's transition-all is gone, so nothing else on the bar can tween.
Reduced motion
motion-reduce:transition-none makes a new value snap into place. An
indeterminate bar stops sweeping: it fills the whole track and pulses its
opacity, so it still reads as busy without moving.
Replacing shadcn
npx shadcn add @godui/progress overwrites components/ui/progress.tsx.
Exports, props and data-slot attributes match shadcn/ui new-york-v4, so
existing imports keep working. It also installs godui-motion (easings,
keyframes, the FLIP hook) into your project, plus the
godui-progress-indeterminate keyframe.
GodUI also fixes a shadcn bug: shadcn reads value but never hands it to Radix, so its
bar has no aria-valuenow and always reports data-state="indeterminate".
GodUI passes value through, so screen readers hear the percentage and only a
missing value counts as indeterminate.