Components
ProgressBar
BetaA determinate or indeterminate progress indicator used for multi-step booking flows, search loading states, and upload or processing feedback.
Purpose
ProgressBar communicates where a user or system stands in a process that takes measurable time or discrete steps. In a travel booking context this appears in two primary scenarios:
Determinate — the system knows the total duration or step count: "Step 2 of 4 — Passenger details", "Uploading passport scan (78%)", "Payment authorisation (45 s remaining)". The fill moves incrementally as the process advances.
Indeterminate — the system is working but cannot estimate completion: "Searching 2,400 holidays", "Fetching live pricing", "Confirming with supplier". An animated sliding bar communicates activity without implying a known duration.
ProgressBar is a passive indicator — it informs the user, it does not accept input. For stepped form navigation with explicit back/next controls, pair it with a StepIndicator component rather than replacing it.
Usage
Use ProgressBar whenever a process has a measurable duration or step count that the user is waiting on — booking steps, uploads, live search. For a compact, non-labelled loading indicator with no meaningful progress value (e.g. a small inline spinner while a button submits), use Spinner instead.
ProgressBar typically appears at the top of a multi-step checkout form, inside a loading state for search results, or alongside an upload or processing indicator.
Anatomy
- 1Label — A visible text label above the bar describing what is progressing. Always provide a label — the track alone does not communicate context.
- 2Value text — Optional percentage shown to the right of the label row when showValue is true. Rounds to the nearest whole number.
- 3Track — The full-width pill-shaped container in bg-grey-100. Height varies by size prop: 4px (sm), 8px (md, default), 12px (lg).
- 4Fill — The coloured portion of the track representing current progress. Colour is determined by the variant prop. For indeterminate, this fills approximately 30% of the track width and slides end-to-end on a 1.5s loop.
Variants
Semantic fill colours
| Variant | Fill colour | Use for |
|---|---|---|
default | bg-teal | Standard progress — booking steps, uploads |
success | bg-success | Completed step or full confirmation |
warning | bg-warning | Partial completion, pending external action |
danger | bg-danger | Failure state, retry in progress |
Sizes
| Size | Track height | Use for |
|---|---|---|
sm | 4 px | Compact layouts, card footers, inline status |
md | 8 px | Default — checkout flows, step indicators |
lg | 12 px | Hero onboarding, standalone progress screens |
Indeterminate
Use indeterminate when the system is working but cannot report a percentage. The fill element animates from left to right in a continuous 1.5 s loop using animate-[slide_1.5s_ease-in-out_infinite].
When indeterminate is true, the value prop is ignored for display purposes but is still passed to aria-valuenow as undefined, which is correct per the ARIA spec for indeterminate progress.
Booking step flow
A common pattern on Kuoni checkout — a determinate bar with a descriptive label that updates as the user moves through steps.
States
| State | Description |
|---|---|
| Determinate | Fill width = (value / max) * 100%. Value clamped between 0 and max. |
| Indeterminate | Fill animates at fixed width (~30% of track). value prop has no visual effect. |
| Complete | value === max. Switch variant to 'success' to confirm completion visually. |
| Error | Switch variant to 'danger' to signal a failed operation. Update label to describe the error. |
Best practices
Always provide a descriptive label that explains what is progressing — not just 'Loading' or 'Please wait'.
A label like 'Step 2 of 4 — Passenger details' tells the traveller exactly where they are and what comes next.
Render a ProgressBar without a label prop or a visible heading nearby that explains the process.
A bare progress bar with no label forces the user to infer context from surrounding elements they may not see.
Switch from indeterminate to determinate as soon as the system can report a real percentage.
Use indeterminate only when the system genuinely cannot compute a percentage — it signals 'working' not 'almost done'.
Display a determinate bar when the value is estimated, fabricated, or does not reflect real progress.
A progress bar stuck at 0% or jumping non-linearly from 10% to 90% destroys user trust.
Use variant='success' when the process completes to give a positive completion signal.
Switching to success variant at 100% provides a distinct visual confirmation without requiring a separate UI element.
Leave the variant as 'default' when the process completes if a success state is available.
A teal bar at 100% looks identical to one at 50% — the user has to read the label to know it is done.
Show the value percentage for longer-running processes (over 3 s) where the exact number is reassuring.
showValue adds a precise percentage for processes where the traveller is waiting and wants to know how close to done they are.
Show the percentage value when the underlying data changes infrequently or unpredictably.
A percentage that jumps or increments in large blocks is more alarming than reassuring.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | 0 | Current progress value. Must be between 0 and max. Ignored visually when indeterminate is true, but still passed to aria-valuenow. |
| max | number | 100 | Maximum value of the progress range. The fill percentage is calculated as (value / max) * 100. |
| label | string | — | Visible label text shown above the track. Always provide this for context. Also used as the accessible label via aria-label when no external label element is present. |
| showValue | boolean | false | When true, renders the percentage as text to the right of the label row. Rounds to the nearest whole number. Has no effect when indeterminate is true. |
| size | 'sm' | 'md' | 'lg' | 'md' | Track height. sm=4px, md=8px, lg=12px. The fill element matches the track height. |
| variant | 'default' | 'success' | 'warning' | 'danger' | 'default' | Fill colour. Use success at 100% completion, warning for pending external action, and danger for error or failure states. |
| indeterminate | boolean | false | When true, the fill animates in a continuous slide loop. Use when progress cannot be measured — e.g. searching, fetching live prices. Overrides value for display. |
| className | string | — | Additional Tailwind classes merged onto the root wrapper element. |
Accessibility
ARIA role and attributes
The track element renders as role="progressbar" with the following attributes:
<div
role="progressbar"
aria-valuenow={indeterminate ? undefined : value}
aria-valuemin={0}
aria-valuemax={max}
aria-label={label}
/>
For indeterminate bars, aria-valuenow is omitted entirely (not set to 0). The ARIA spec states that omitting aria-valuenow on a progressbar signals an indeterminate state to assistive technology.
Live announcements
ProgressBar does not include an aria-live region by default — the progressbar role is sufficient for screen readers to announce state changes. If you need periodic spoken updates (e.g. "75% complete" announced at 25%, 50%, 75%), add an aria-live="polite" region outside the component and update it at the appropriate thresholds in your consuming code.
Example pattern for announced milestones:
const [liveText, setLiveText] = useState('');
// In your progress update handler:
if (value === 50) setLiveText('Search half complete');
if (value === 100) setLiveText('Search complete. 47 holidays found.');
return (
<>
<ProgressBar label="Searching holidays" value={value} showValue />
<span className="sr-only" aria-live="polite">{liveText}</span>
</>
);
Reduced motion
The indeterminate slide animation respects the prefers-reduced-motion media query. When the user has requested reduced motion, the animation is paused and the fill bar is displayed as a static half-width block. This still communicates 'working' without triggering vestibular discomfort.
Colour contrast
The track and fill colours meet WCAG 2.1 AA for UI components (3:1 ratio against the page background).
| Variant | Fill colour | Track colour | Against white | WCAG |
|---|---|---|---|---|
default | Teal (#005B55) | #E5E5E5 | 7.4:1 | AA |
success | Green (#2E7D32) | #E5E5E5 | 6.1:1 | AA |
warning | Amber (#B45309) | #E5E5E5 | 4.5:1 | AA |
danger | Red (#B91C1C) | #E5E5E5 | 5.1:1 | AA |
Related components
- Spinner — a compact, indeterminate loading indicator without a measurable progress value; use for short waits where a full labelled bar is unnecessary.