Progress
{% progress %} is a generic, presentational completion bar. You give it numbers — a value/max pair or a direct percent — and it renders a labelled bar. It computes nothing itself: the data is always supplied, either inline or fed from an aggregate (see Feeding from data).
{% progress value=3 max=4 %}Acceptance criteria{% /progress %}<p>
<div data-rune="progress" role="progressbar" aria-valuemin="0" aria-valuenow="3" aria-valuemax="4" aria-label="Acceptance criteria" style="--rf-progress: 75%">
<span data-name="label">Acceptance criteria</span>
<span data-name="value">3/4</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p><p>
<div class="rf-progress" role="progressbar" aria-valuemin="0" aria-valuenow="3" aria-valuemax="4" aria-label="Acceptance criteria" style="--rf-progress: 75%" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Acceptance criteria</span>
<span data-name="value" class="rf-progress__value">3/4</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>Input
Two ways to express the ratio:
value+max(primary) — a count, e.g.value=12 max=20. Yields a "12/20" readout.percent(alternative) — a direct0–100value when there's no count.
If both are given, value/max wins. The percentage is clamped to 0–100; a max of 0 (or absent) renders an empty bar with no numeric readout — never NaN.
{% progress percent=60 /%}<div data-rune="progress" role="progressbar" aria-valuemin="0" aria-valuenow="60" aria-valuemax="100" style="--rf-progress: 60%">
<span data-name="value">60%</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div><div class="rf-progress" role="progressbar" aria-valuemin="0" aria-valuenow="60" aria-valuemax="100" style="--rf-progress: 60%" data-rune="progress" data-density="full">
<span data-name="value" class="rf-progress__value">60%</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>Readout — display
display controls the text beside the bar:
display | Output |
|---|---|
fraction (default with value/max) | 12/20 |
percent | 60% |
none | (no readout) |
Label
An optional body is the label — it may hold inline markup, and its text also becomes the bar's accessible name (aria-label):
{% progress percent=80 %}Funding goal{% /progress %}<p>
<div data-rune="progress" role="progressbar" aria-valuemin="0" aria-valuenow="80" aria-valuemax="100" aria-label="Funding goal" style="--rf-progress: 80%">
<span data-name="label">Funding goal</span>
<span data-name="value">80%</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p><p>
<div class="rf-progress" role="progressbar" aria-valuemin="0" aria-valuenow="80" aria-valuemax="100" aria-label="Funding goal" style="--rf-progress: 80%" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Funding goal</span>
<span data-name="value" class="rf-progress__value">80%</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>Sentiment
sentiment tints the fill (positive / caution / negative), reusing the theme's sentiment colors. When it's absent the bar uses the neutral primary fill. There is no automatic threshold coloring — the sentiment is always explicit.
{% progress value=6 max=10 %}Default{% /progress %}
{% progress value=9 max=10 sentiment="positive" %}Positive{% /progress %}
{% progress value=4 max=10 sentiment="caution" %}Caution{% /progress %}
{% progress value=2 max=10 sentiment="negative" %}Negative{% /progress %}<p>
<div data-rune="progress" role="progressbar" aria-valuemin="0" aria-valuenow="6" aria-valuemax="10" aria-label="Default" style="--rf-progress: 60%">
<span data-name="label">Default</span>
<span data-name="value">6/10</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p>
<p>
<div data-rune="progress" data-rune-fields="{"sentiment":"positive"}" role="progressbar" aria-valuemin="0" aria-valuenow="9" aria-valuemax="10" aria-label="Positive" style="--rf-progress: 90%">
<span data-name="label">Positive</span>
<span data-name="value">9/10</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p>
<p>
<div data-rune="progress" data-rune-fields="{"sentiment":"caution"}" role="progressbar" aria-valuemin="0" aria-valuenow="4" aria-valuemax="10" aria-label="Caution" style="--rf-progress: 40%">
<span data-name="label">Caution</span>
<span data-name="value">4/10</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p>
<p>
<div data-rune="progress" data-rune-fields="{"sentiment":"negative"}" role="progressbar" aria-valuemin="0" aria-valuenow="2" aria-valuemax="10" aria-label="Negative" style="--rf-progress: 20%">
<span data-name="label">Negative</span>
<span data-name="value">2/10</span>
<span data-name="track">
<span data-name="fill"></span>
</span>
</div>
</p><p>
<div class="rf-progress" role="progressbar" aria-valuemin="0" aria-valuenow="6" aria-valuemax="10" aria-label="Default" style="--rf-progress: 60%" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Default</span>
<span data-name="value" class="rf-progress__value">6/10</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>
<p>
<div class="rf-progress rf-progress--positive" role="progressbar" aria-valuemin="0" aria-valuenow="9" aria-valuemax="10" aria-label="Positive" style="--rf-progress: 90%" data-sentiment="positive" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Positive</span>
<span data-name="value" class="rf-progress__value">9/10</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>
<p>
<div class="rf-progress rf-progress--caution" role="progressbar" aria-valuemin="0" aria-valuenow="4" aria-valuemax="10" aria-label="Caution" style="--rf-progress: 40%" data-sentiment="caution" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Caution</span>
<span data-name="value" class="rf-progress__value">4/10</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>
<p>
<div class="rf-progress rf-progress--negative" role="progressbar" aria-valuemin="0" aria-valuenow="2" aria-valuemax="10" aria-label="Negative" style="--rf-progress: 20%" data-sentiment="negative" data-rune="progress" data-density="full">
<span data-name="label" class="rf-progress__label">Negative</span>
<span data-name="value" class="rf-progress__value">2/10</span>
<span data-name="track" class="rf-progress__track">
<span data-name="fill" class="rf-progress__fill"></span>
</span>
</div>
</p>Feeding from data
progress reads only what you pass it, so a live value comes from a variable — typically an aggregate a plugin writes onto an entity. For example, a milestone page fed its completion rollup:
{% progress value=$item.data.progressDone max=$item.data.progressTotal %}Completion{% /progress %}
The rune stays generic; computing progressDone/progressTotal is the data layer's job.
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
value | number | — | Completed amount (paired with `max`). |
max | number | — | Total amount (paired with `value`). |
percent | number | — | Direct percentage 0–100, when there is no count. |
display | "fraction" | "percent" | "none" | — | Readout: fraction (default with value/max), percent, or none. |
sentiment | "positive" | "caution" | "negative" | — | Tone cue: positive / caution / negative. Absent → the neutral primary fill. |
Universal attributes
bg
The background layer (SPEC-088): image, video, gradient, flat overlay wash and legibility scrim, in a single injected layer behind the rune's content.
| Attribute | Type | Required | Description |
|---|---|---|---|
bg | string | — | Background preset applied to this block |
bg-from | string | — | Gradient start colour — a semantic token name (→ var(--rf-color-*)) |
bg-gradient | "to-t" | "to-b" | "to-l" | "to-r" | "to-tr" | "to-br" | "to-bl" | "to-tl" | — | Gradient direction (bounded named set) |
bg-gradient-type | "linear" | "radial" | "conic" | — | Gradient type |
bg-to | string | — | Gradient end colour — a semantic token name |
bg-via | string | — | Optional middle gradient stop — a semantic token name |
scrim | "top" | "bottom" | "left" | "right" | "none" | — | Scrim direction (heaviest edge); presence turns the scrim on, "none" opts out of the default cover scrim |
scrim-blur | "none" | "sm" | "md" | "lg" | — | Frost scrim blur amount |
scrim-strength | "sm" | "md" | "lg" | — | Gradient scrim strength |
scrim-tone | "dark" | "light" | — | Whether the scrim darkens (for light text) or lightens (for dark text) |
scrim-type | "gradient" | "frost" | — | Scrim treatment: gradient (default) or frost (backdrop blur) |
elevation
The chrome/depth ladder (SPEC-107). The skin maps each rung to a chrome bundle by attribute, so there is no BEM class.
| Attribute | Type | Required | Description |
|---|---|---|---|
elevation | "sunken" | "flush" | "flat" | "raised" | "floating" | "overlay" | "none" | "sm" | "md" | "lg" | — | Surface depth on the SPEC-107 ladder (sunken→overlay); none/sm/md/lg are deprecated aliases |
inset
Internal padding override.
| Attribute | Type | Required | Description |
|---|---|---|---|
inset | "flush" | "tight" | "default" | "loose" | "breathe" | — | Inner padding of this block |
motion
Scroll-reveal entrance (SPEC-105). The author declares the character, the theme owns the choreography, a behaviour owns the timing.
| Attribute | Type | Required | Description |
|---|---|---|---|
reveal | "none" | "fade" | "slide" | "scale" | "blur" | — | Scroll-reveal entrance character (none|fade|slide|scale|blur); the theme owns the choreography |
stagger | boolean | — | Cascade this block's items in as it reveals (no-op on single-child runes) |
spacing
Block-level rhythm override.
| Attribute | Type | Required | Description |
|---|---|---|---|
spacing | "flush" | "tight" | "default" | "loose" | "breathe" | — | Vertical spacing above and below this block |
substrate
Generated pattern fills (SPEC-087). Markers only — the engine sets the attributes and cell/opacity custom properties, CSS draws the pattern.
| Attribute | Type | Required | Description |
|---|---|---|---|
substrate | "dots" | "grid" | "lines" | "cross" | "checker" | "none" | — | Generated surface pattern |
substrate-fill | "inherit" | "inset" | — | Surface fill the pattern sits on (full colour stays with tint) |
substrate-opacity | "sm" | "md" | "lg" | — | Pattern ink strength |
substrate-size | "sm" | "md" | "lg" | — | Pattern cell size |
substrate-target | "self" | "media" | — | Which surface the pattern fills (overrides the rune/theme default) |
tint
Per-rune colour override (SPEC-053): a named tint from the theme registry, with inline per-token overrides layered on top.
| Attribute | Type | Required | Description |
|---|---|---|---|
tint | string | — | Color tint preset applied to this block |
tint-mode | "auto" | "dark" | "light" | — | Whether the tint adapts to auto, dark, or light mode |
width
The track a block rune occupies.
| Attribute | Type | Required | Description |
|---|---|---|---|
width | "compact" | "narrow" | "content" | "wide" | "full" | — | Maximum width constraint for this block |
| Not available | Why |
|---|---|
| dropcap, reading | this rune declares no prose body |
| frame | this rune declares neither a `frameTarget` nor a media section |
| prominence | this rune has no page-section header |
Output contract
<div class="rf-progress" data-rune="progress"
role="progressbar" aria-valuenow="3" aria-valuemin="0" aria-valuemax="4"
aria-label="Acceptance criteria" style="--rf-progress: 75%">
<span class="rf-progress__label">Acceptance criteria</span>
<span class="rf-progress__value">3/4</span>
<span class="rf-progress__track"><span class="rf-progress__fill"></span></span>
</div>
The fill width is driven by the --rf-progress custom property, so themes restyle the bar without touching the markup.
See also
- collection — pair
progresswith a per-item template to show a bar per entity.