ContentProgress

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>

Acceptance criteria3/4

<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 direct 0–100 value 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>
60%
<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:

displayOutput
fraction (default with value/max)12/20
percent60%
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>

Funding goal80%

<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="{&quot;sentiment&quot;:&quot;positive&quot;}" 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="{&quot;sentiment&quot;:&quot;caution&quot;}" 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="{&quot;sentiment&quot;:&quot;negative&quot;}" 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>

Default6/10

Positive9/10

Caution4/10

Negative2/10

<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

AttributeTypeRequiredDescription
valuenumberCompleted amount (paired with `max`).
maxnumberTotal amount (paired with `value`).
percentnumberDirect 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.

AttributeTypeRequiredDescription
bgstringBackground preset applied to this block
bg-fromstringGradient 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-tostringGradient end colour — a semantic token name
bg-viastringOptional 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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
reveal"none" | "fade" | "slide" | "scale" | "blur"Scroll-reveal entrance character (none|fade|slide|scale|blur); the theme owns the choreography
staggerbooleanCascade this block's items in as it reveals (no-op on single-child runes)
spacing

Block-level rhythm override.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
tintstringColor 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.

AttributeTypeRequiredDescription
width"compact" | "narrow" | "content" | "wide" | "full"Maximum width constraint for this block
Not availableWhy
dropcap, readingthis rune declares no prose body
framethis rune declares neither a `frameTarget` nor a media section
prominencethis 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 progress with a per-item template to show a bar per entity.