Progress
Overview
The Progress is a visual indicator element used to show the completion status of a task or process. It is composed of two recipe parts: useProgressRecipe() for the track container and useProgressBarRecipe() for the fill bar. Each composable creates a fully configured recipe with color, size, and orientation options — plus compound variants that handle the color-orientation combinations automatically.
The Progress recipes integrate directly with the default design tokens preset and generate type-safe utility classes at build time with zero runtime CSS.
Why use the Progress recipe?
The Progress recipe helps you:
- Ship faster with sensible defaults: Get 9 bar colors, 5 sizes, 2 orientations, and 4 indeterminate animations out of the box with two composable calls.
- Compose structured layouts: Two coordinated recipes (track and bar) share size and orientation axes, so the track and fill stay internally consistent.
- Animate without extra CSS: Indeterminate animation keyframes are registered automatically when you use the bar recipe — no manual
@keyframesdefinition needed. - Maintain consistency: Compound variants ensure every color combination follows the same design rules, including dark mode overrides.
- Customize without forking: Override base styles, default variants, or filter out options you don't need — all through the options API.
- Stay type-safe: Full TypeScript support means your editor catches invalid color, size, orientation, or animation values at compile time.
- Integrate with your tokens: Every value references the design tokens preset, so theme changes propagate automatically.
- Support dark mode: Background colors adapt automatically between light and dark color schemes.
Usage
Register the recipes
Add the Progress recipes to a local Styleframe instance. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipes themselves:
import { styleframe } from 'virtual:styleframe';
import { useProgressRecipe, useProgressBarRecipe } from '@styleframe/theme';
const s = styleframe();
const progress = useProgressRecipe(s);
const progressBar = useProgressBarRecipe(s);
export default s;
Build the component
Import the progress and progressBar runtime functions from the virtual module and pass variant props to compute class names:
import { progress, progressBar } from "virtual:styleframe";
interface ProgressProps {
color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "light" | "dark" | "neutral";
trackColor?: "light" | "dark" | "neutral";
size?: "xs" | "sm" | "md" | "lg" | "xl";
orientation?: "horizontal" | "vertical";
inverted?: boolean;
animation?: "none" | "carousel" | "carousel-inverse" | "swing" | "elastic";
value?: number | null;
}
export function Progress({
color = "primary",
trackColor = "neutral",
size = "md",
orientation = "horizontal",
inverted = false,
animation = "none",
value = 0,
}: ProgressProps) {
const isIndeterminate = value == null;
const clampedValue = isIndeterminate ? 50 : Math.min(100, Math.max(0, value));
const fillProp = orientation === "vertical" ? "height" : "width";
return (
<div
className={progress({ color: trackColor, size, orientation })}
role="progressbar"
aria-valuenow={value ?? undefined}
aria-valuemin={0}
aria-valuemax={100}
aria-orientation={orientation}
>
<div
className={progressBar({
color,
size,
orientation,
inverted: String(inverted),
animation: isIndeterminate && animation !== "none" ? animation : "none",
})}
style={{ [fillProp]: `${clampedValue}%` }}
/>
</div>
);
}
<script setup lang="ts">
import { computed } from "vue";
import { progress, progressBar } from "virtual:styleframe";
const props = withDefaults(
defineProps<{
color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "light" | "dark" | "neutral";
trackColor?: "light" | "dark" | "neutral";
size?: "xs" | "sm" | "md" | "lg" | "xl";
orientation?: "horizontal" | "vertical";
inverted?: boolean;
animation?: "none" | "carousel" | "carousel-inverse" | "swing" | "elastic";
value?: number | null;
}>(),
{ color: "primary", trackColor: "neutral", value: 0, animation: "none" },
);
const isIndeterminate = computed(() => props.value == null);
const trackClasses = computed(() =>
progress({ color: props.trackColor, size: props.size, orientation: props.orientation }),
);
const barClasses = computed(() =>
progressBar({
color: props.color,
size: props.size,
orientation: props.orientation,
inverted: props.inverted ? "true" : "false",
animation: isIndeterminate.value && props.animation !== "none" ? props.animation : "none",
}),
);
const fillStyle = computed(() => {
const clamped = isIndeterminate.value ? 50 : Math.min(100, Math.max(0, props.value!));
const prop = props.orientation === "vertical" ? "height" : "width";
return { [prop]: `${clamped}%` };
});
</script>
<template>
<div
:class="trackClasses"
role="progressbar"
:aria-valuenow="value ?? undefined"
:aria-valuemin="0"
:aria-valuemax="100"
:aria-orientation="orientation"
>
<div :class="barClasses" :style="fillStyle" />
</div>
</template>
See it in action
Colors
The Progress bar recipe includes 9 color variants: the 6 semantic colors (primary, secondary, success, info, warning, error) plus 3 neutral-spectrum colors (light, dark, neutral). The track recipe uses only the 3 neutral-spectrum colors (light, dark, neutral) to provide a subtle background container.
Each color is applied through compound variants with automatic dark mode overrides. The neutral color adapts between light and dark mode automatically.
Color Reference
Bar colors:
| Color | Token | Use Case |
|---|---|---|
primary | @color.primary | Default. Primary brand indicator |
secondary | @color.secondary | Secondary or supporting progress |
success | @color.success | Successful completions, positive progress |
info | @color.info | Informational progress, downloads |
warning | @color.warning | Caution states, approaching limits |
error | @color.error | Error states, critical progress |
light | @color.gray-400 | Light-themed fill, fixed across color schemes |
dark | @color.gray-600 | Dark-themed fill, fixed across color schemes |
neutral | Adaptive | Light fill in light mode, dark fill in dark mode |
Track colors:
| Color | Token | Use Case |
|---|---|---|
light | @color.gray-200 | Light track, fixed across color schemes |
dark | @color.gray-800 | Dark track, fixed across color schemes |
neutral | Adaptive | Default. Light track in light mode, dark track in dark mode |
trackColor separately from the bar color. The track should be a neutral background, while the bar communicates the semantic meaning through color.Sizes
Five size variants from xs to xl control the height (or width in vertical orientation) and border radius of both the track and bar.
Size Reference
| Size | Height Token | Border Radius | Use Case |
|---|---|---|---|
xs | @0.25 | @border-radius.sm | Thin indicators, inline progress |
sm | @0.375 | @border-radius.sm | Compact progress bars |
md | @0.5 | @border-radius.md | Default. Standard progress bars |
lg | @0.75 | @border-radius.md | Prominent progress indicators |
xl | @1 | @border-radius.lg | Large, highly visible progress |
size prop must be passed to both the track and bar recipes. In vertical orientation, the size controls the width instead of the height.Orientation
The orientation variant controls the layout direction of the progress bar. Two options are available: horizontal (default) and vertical.
Horizontal
The bar fills from left to right. The track stretches to width: 100% of its container.
Vertical
The bar fills from bottom to top. The track stretches to height: 100% of its container. The parent container must have an explicit height for the vertical progress bar to be visible.
| Orientation | Fill Direction | Track Sizing | Use Case |
|---|---|---|---|
horizontal | Left to right | width: 100% | Default. Standard horizontal progress bars |
vertical | Bottom to top | height: 100% | Vertical meters, level indicators, volume bars |
Inverted
The inverted variant reverses the fill direction of the progress bar. In horizontal orientation, the bar fills from right to left. In vertical orientation, it fills from top to bottom.
| Orientation | Normal | Inverted |
|---|---|---|
horizontal | Left → right | Right → left |
vertical | Bottom → top | Top → bottom |
Indeterminate
When the progress value is null, the bar enters an indeterminate state. The bar displays at 50% width (or height in vertical) and can animate to indicate ongoing work without a known completion percentage.
Four animation styles are available for indeterminate progress bars. The keyframes are registered automatically when the bar recipe is used — no manual @keyframes definition needed.
Animation Reference
| Animation | Duration | Timing | Description |
|---|---|---|---|
none | — | — | Default. No animation, static bar at 50% |
carousel | 1.5s | linear | Slides in the fill direction continuously |
carousel-inverse | 1.5s | linear | Slides against the fill direction continuously |
swing | 2s | ease-in-out | Oscillates back and forth |
elastic | 2s | ease-in-out | Stretches and contracts while oscillating |
Vertical Indeterminate
Animations adapt automatically for vertical orientation. Horizontal translateX transforms are swapped to translateY, and scaleX to scaleY.
Anatomy
The Progress recipe is composed of two independent recipes that work together:
| Part | Recipe | Role |
|---|---|---|
| Track | useProgressRecipe() | Outer container with background color, border radius, and overflow hidden |
| Bar | useProgressBarRecipe() | Inner fill element with color, transition, orientation, inversion, and animation |
The track provides the neutral background container. The bar is a child element whose width (or height) is controlled by an inline style based on the current value. Both recipes share the size and orientation axes and should receive the same values.
<!-- Both parts working together -->
<div class="progress(...)">
<div class="progressBar(...)" style="width: 65%"></div>
</div>
Accessibility
- Use the
progressbarrole. Applyrole="progressbar"to the track element. This tells assistive technology that the element represents a progress indicator.
<div role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" class="...">
<div class="..." style="width: 65%"></div>
</div>
- Set
aria-valuenow,aria-valuemin, andaria-valuemax. These attributes communicate the current progress value to screen readers. For indeterminate progress, omitaria-valuenowentirely.
<!-- Determinate -->
<div role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100">...</div>
<!-- Indeterminate -->
<div role="progressbar" aria-valuemin="0" aria-valuemax="100">...</div>
- Set
aria-orientationfor vertical progress bars. This tells assistive technology the orientation of the progress indicator.
<div role="progressbar" aria-orientation="vertical" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100">...</div>
- Add a label. Use
aria-labeloraria-labelledbyto provide a descriptive name for the progress bar, especially when multiple progress bars appear on the same page.
<label id="upload-label">Upload progress</label>
<div role="progressbar" aria-labelledby="upload-label" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" class="...">
<div class="..." style="width: 65%"></div>
</div>
Customization
Overriding Defaults
Each progress composable accepts an optional second argument to override any part of the recipe configuration. Overrides are deep-merged with the defaults, so you only need to specify the properties you want to change:
import { styleframe } from 'virtual:styleframe';
import { useProgressRecipe, useProgressBarRecipe } from '@styleframe/theme';
const s = styleframe();
const progress = useProgressRecipe(s, {
base: {
borderRadius: '@border-radius.lg',
},
defaultVariants: {
color: 'neutral',
orientation: 'horizontal',
size: 'lg',
},
});
const progressBar = useProgressBarRecipe(s, {
defaultVariants: {
color: 'primary',
orientation: 'horizontal',
inverted: 'false',
animation: 'none',
size: 'lg',
},
});
export default s;
Filtering Variants
If you only need a subset of the available variants, use the filter option to limit which values are generated. This reduces the output CSS and keeps your component API focused:
import { styleframe } from 'virtual:styleframe';
import { useProgressRecipe, useProgressBarRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate primary and success bar colors
const progress = useProgressRecipe(s);
const progressBar = useProgressBarRecipe(s, {
filter: {
color: ['primary', 'success'],
},
});
export default s;
API Reference
useProgressRecipe(s, options?)
Creates the progress track recipe with background color, border radius, and orientation support.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles for the track |
options.variants | Variants | Custom variant definitions for the recipe |
options.defaultVariants | Record<keyof Variants, string> | Default variant values for the recipe |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
color | light, dark, neutral | neutral |
orientation | horizontal, vertical | horizontal |
size | xs, sm, md, lg, xl | md |
useProgressBarRecipe(s, options?)
Creates the progress bar fill recipe with color, orientation, inversion, and indeterminate animation support. Registers 8 keyframes (4 horizontal + 4 vertical) automatically.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles for the bar |
options.variants | Variants | Custom variant definitions for the recipe |
options.defaultVariants | Record<keyof Variants, string> | Default variant values for the recipe |
options.compoundVariants | CompoundVariant[] | Custom compound variant definitions for the recipe |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
color | primary, secondary, success, info, warning, error, light, dark, neutral | primary |
orientation | horizontal, vertical | horizontal |
inverted | true, false | false |
animation | none, carousel, carousel-inverse, swing, elastic | none |
size | xs, sm, md, lg, xl | md |
Best Practices
- Pass
sizeandorientationto both recipes: The track and bar must share the samesizeandorientationvalues so that border radii and layout directions match. - Use semantic bar colors for meaning: Use
successfor completions,errorfor failures, andprimaryfor general progress — this keeps your UI consistent when tokens change. - Use
neutralfor the track: The track should be a subtle background. Save color for the bar where it communicates progress status. - Omit
aria-valuenowfor indeterminate progress: Whenvalueisnull, screen readers should not announce a specific percentage. Omit the attribute entirely. - Set the fill with inline styles: The recipe handles visual styling, but the bar's width (or height) percentage must be set via an inline style based on the current value.
- Provide an explicit height for vertical progress: The vertical track uses
height: 100%, so the parent container must have an explicit height for the progress bar to be visible. - Filter what you don't need: If your component only uses a few colors, pass a
filteroption to reduce generated CSS. - Override defaults at the recipe level: Set your most common variant combination as
defaultVariantsso component consumers write less code.
FAQ
size and orientation axes.animation variant is set to a value other than none, the bar applies the corresponding animationName, animationDuration, animationTimingFunction, and animationIterationCount properties. In vertical orientation, compound variants automatically swap the animation to the vertical keyframe version. The keyframes are registered automatically when you call useProgressBarRecipe() — no manual @keyframes definition is needed.Set value to null and choose an animation variant. The bar displays at 50% width (or height) and animates continuously:
<div class="progress(...)">
<div class="progressBar({ animation: 'carousel' })" style="width: 50%"></div>
</div>
light always uses the same gray tones regardless of the color scheme. dark always uses darker gray tones. neutral adapts to the current color scheme: it appears light in light mode and dark in dark mode. Use neutral when you want the progress bar to blend naturally with the surrounding interface.inverted variant reverses the fill direction using CSS auto margins. In horizontal mode, marginLeft: auto pushes the bar to the right so it fills right-to-left. In vertical mode, marginBottom: auto pushes the bar to the top of the column-reverse container so it fills top-to-bottom.Yes. The track is a flex container, so you can place multiple bar elements inside it. Each bar independently controls its own color and percentage width. This creates a segmented progress indicator:
<div class="progress(...)">
<div class="progressBar({ color: 'success' })" style="width: 40%"></div>
<div class="progressBar({ color: 'warning' })" style="width: 20%"></div>
<div class="progressBar({ color: 'error' })" style="width: 10%"></div>
</div>
inverted variant uses "true" and "false" as string keys internally, but your component can accept a boolean prop and convert it with String(inverted) when passing it to the recipe function.@color.primary, @border-radius.md, and @easing.ease-in-out through string refs. These tokens need to be defined in your Styleframe instance for the recipe to generate valid CSS. The easiest way is to use useDesignTokensPreset(s), but you can also define the required tokens manually.Placeholder
A visual placeholder container with a dashed border and hatch pattern for layout prototyping, wireframing, and empty states. Uses the recipe system with dark mode support.
Popover
A floating container component with structured sections and a directional arrow for contextual content. Supports multiple colors, visual styles, and sizes through the recipe system.