Progress
A horizontal or vertical fill bar with an optional numeric readout. One value or several, stacked in a single track. Best for quantities that genuinely represent completion or fill fraction.


Reach for it when you need:
- Batch progress
- Tank fill percentage
- Order completion
- A disk or capacity bar split into what is using it
Component name: Progress · Category: Display
In the palette: Progress / fill bar — horizontal or vertical, single- or multi-value, with a numeric readout
Properties
Any property can be bound to a tag, another component's property, or a query.
| Property | Type | Default | Description |
|---|---|---|---|
segments | array | [{"value":65,"color":"hsl(217 91% 60%)","label":"","show":tr… (truncated) | The bar's data — one coloured slice per item, stacked in a single track. ONE item = a classic single-value bar; several = a composition. Each item: { value (a MAGNITUDE, not a position on the scale), color, label, show }. Each item's 'value' is separately bindable to a tag. 1.0.1 |
stackMode | enum | absolute | absolute = each segment is a magnitude in min…max units; they stack and the sum clips at max (keeps 'how full is it', coloured by what's in it). proportional = segments are normalised against their own sum and always fill the track (share-of-total); min/max do not apply. Options: absolute · proportional. 1.0.1 |
min | number | 0 | — |
max | number | 100 | — |
orientation | enum | horizontal | Options: horizontal · vertical. |
reverse | boolean | false | Flip the edge the bar fills FROM — right→left when horizontal, top→bottom when vertical (default is left→right / bottom→top). |
thickness | number | 14 | Bar HEIGHT when horizontal, bar WIDTH when vertical (px). 0 = fill the widget box. |
radius | number | 999 | Corner radius (px). 999 = fully rounded ends; 0 = square. |
trackColor | color | hsl(var(--muted-foreground) / 0.16) | The background (unfilled) part. Segment colours live on the segments. |
gap | number | 0 | Hairline of track showing between segments (px). 1.0.1 |
readout | object | {"position":"end","mode":"percent","unit":"","decimals":0,"c… (truncated) | The numeric readout — where it sits and how it formats. Not the value itself. 1.0.1 |
label | object | {"show":true,"text":"Progress","color":"var(--widget-label)"… (truncated) | The caption above the bar. 1.0.1 |
legend | object | {"show":true,"position":"bottom","showValue":true,"color":"v… (truncated) | Per-segment key. Nothing is drawn for a bar with a single segment — there is nothing to tell apart. 1.0.1 |
style | style | {} | Custom CSS properties |
Events
Attach event actions to these in the Event Manager.
| Event | Label | Group | Payload |
|---|---|---|---|
click | Click | Mouse | The DOM event. |
dblclick | Double click | Mouse | The DOM event. |
contextmenu | Right click | Mouse | The DOM event. |
mouseenter | Mouse enter | Mouse | The DOM event. |
mouseleave | Mouse leave | Mouse | The DOM event. |
contextmenu fires your configured actions at runtime only — in LiveView and in preview. On the Designer canvas the right-click is captured to open the Event Manager, so testing it there will not run your actions. Test right-click in preview.
Notes
One bar, one or many values
The bar's data is segments — an array, one coloured slice per item, drawn in a single track. One item is an ordinary single-value bar; several are a composition. Each item is { value, color, label, show }, and each item's value is separately bindable, so a three-part bar can take its three numbers from three different tags.
There is no top-level value or fillColor. A single input is the whole point: with two, each one sat inert depending on the other — value drew the bar only while segments was empty, and fillColor painted only the slice that named no colour of its own — which is the "why does this control do nothing?" trap the rest of this schema is arranged to avoid.
absolute or proportional
stackMode decides what a segment's number means, and it is the setting people get wrong:
| Mode | A segment's value is | min/max | The track |
|---|---|---|---|
absolute (default) | a magnitude in min…max units | apply | fills as far as the segments sum to, clipping at max |
proportional | a share, normalised against the sum of the segments | ignored | always completely full |
Use absolute when "how full is it?" is the question and the colours only say what is in there — a 500 L tank holding 200 L of product and 50 L of water is half empty, and the bar should show that. Use proportional when the question is share-of-total and the parts are the whole of it — the split of today's output across three grades.
A segment carries a magnitude, not a position on the scale. Three segments of 30, 20 and 10 in absolute mode fill to 60 — they are not marks at 30, 20 and 10.
Example — a disk bar in two parts
stackMode: absolute
min / max: 0 / 500 (GB)
gap: 1
segments:
{ value: 180, color: "hsl(217 91% 60%)", label: "History", show: true }
{ value: 40, color: "hsl(43 96% 56%)", label: "Backups", show: true }
readout: { position: "end", mode: "value", unit: " GB", decimals: 0 }
The bar fills to 220 of 500, in two colours, with a key underneath naming each. Bind each value to its own tag and it tracks both independently.
Presentation is grouped
The three text elements are objects rather than a flat list of labelColor / valueSize properties:
label— the caption above the bar:{ show, text, color, size }.readout— the number itself:{ position, mode, unit, decimals, color, size }.modeispercent(progress throughmin…max) orvalue(the raw number plusunit).legend— the per-segment key:{ show, position, showValue, color, size }. Nothing is drawn for a single-segment bar, because there is nothing to tell apart.
Gotchas
show: falseremoves a segment from the bar, not just from the key. Hiding a slice changes the total inabsolutemode and re-normalises the rest inproportionalmode.gapis a hairline of track showing between segments, so it eats into the fill. Leave it at 0 for a single-value bar, where the gap has nothing to separate.thicknessis height when horizontal and width when vertical, and0means "fill the widget box" — which is usually what you want inside a tight container.