Skip to content
Español

Navigate

Type to search. Press Escape to close.

    Components · js

    Countup

    A number that rises to itself. The figure in the markup is the final value — prefix, suffix, decimals and all — and the component counts from from up to it once the element reaches the viewport, then restores the served text character for character. Nothing is computed, nothing is invented: the only thing JavaScript adds is the climb.

    CSS: .iv-count in css/text.css (in ivolt.css). JS: @intervolutions/ivolt/countup or auto.js. Added in v0.7.

    The fixture below counts the measurements of the package itself. Scroll it into view and the four cards climb once; reload with JavaScript off and the same four numbers are simply there.

    Measured figuresfixtures/countup/basic.htmlOpen alone — Measured figures
    <p class="iv-u-m-0 iv-u-mb-6">Four figures measured by <code>npm run sizes</code> and <code>npm test</code> on the
    <code>0.6.0-beta.0</code> milestone (commit <code>34e69c1</code>): they are the real numbers of the package, not a
    demonstration set. Each one is served complete, so the page reads the same without JavaScript, and counts up once when it
    reaches the viewport.</p>
    
    <div class="iv-stat-group">
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Core stylesheet</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup">2.60</strong>
        <span class="iv-stat__hint">KiB min + gzip, budget 8</span>
      </div>
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Full stylesheet</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup">25.64</strong>
        <span class="iv-stat__hint">KiB min + gzip, budget 40</span>
      </div>
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Bundled JavaScript</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup">39.04</strong>
        <span class="iv-stat__hint">KiB min + gzip, budget 48</span>
      </div>
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Unit tests</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup" data-iv-duration="1400">641</strong>
        <span class="iv-stat__hint">27 files, all passing</span>
      </div>
    </div>
    
    <p class="iv-u-text-sm iv-u-text-muted iv-u-mt-6">With <code>prefers-reduced-motion: reduce</code>, without an
    <code>IntersectionObserver</code> or without JavaScript, nothing moves: the served figure is already the answer.</p>
    // data-iv-component="countup"
    {
      duration: 1400,
    }

    Formats, prefixes and locales

    The format comes from the served text and from the language of the element. A figure written 12.480,50 € inside lang="es" counts in Spanish grouping and keeps its suffix; € 1,240.50 in English keeps its prefix and its comma. decimals is deduced from what was served, and a non-breaking space keeps a currency sign from falling to the next line.

    Formats and localesfixtures/countup/formats.htmlOpen alone — Formats and locales
    <p class="iv-u-m-0 iv-u-mb-6">Prefixes, suffixes and separators come from the served text and from the language of the
    element: the component never invents a format. The three money figures below are <strong>made up for this
    fixture</strong>; the test count is the real one of the <code>0.6.0-beta.0</code> milestone.</p>
    
    <div class="iv-stat-group" style="--iv-stat-group-min: 16rem">
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Invoiced (made-up figure)</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup">€ 1,240.50</strong>
        <span class="iv-stat__hint">English grouping, prefix kept</span>
      </div>
      <div class="iv-stat iv-stat--card" lang="es">
        <span class="iv-stat__label" lang="en">Invoiced, Spanish locale (made-up figure)</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup">12.480,50 €</strong>
        <span class="iv-stat__hint" lang="en">Spanish grouping and decimal comma, suffix kept</span>
      </div>
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Coverage of the contract matrix</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup" data-iv-autostart="true">98 %</strong>
        <span class="iv-stat__hint">Starts at <code>init</code>, not in view</span>
      </div>
      <div class="iv-stat iv-stat--card">
        <span class="iv-stat__label">Browser tests, three engines</span>
        <strong class="iv-stat__value iv-count" data-iv-component="countup" data-iv-from="900" data-iv-duration="1800" data-iv-grouping="false">1068</strong>
        <span class="iv-stat__hint">From 900, ungrouped while counting</span>
      </div>
    </div>
    
    <p class="iv-u-mt-6">A figure also works inside a sentence: the last full gate
    passed <strong class="iv-count" data-iv-component="countup" data-iv-duration="1200" data-iv-grouping="false">1068</strong> browser tests and
    skipped <strong class="iv-count" data-iv-component="countup" data-iv-duration="1200">114</strong> of them, the visual
    ones outside Chromium.</p>
    .iv-root {
      --iv-stat-group-min: 16rem;
    }
    // data-iv-component="countup"
    {
      autostart: true,
      from: 900,
      duration: 1800,
      grouping: false,
    }

    Options

    OptionAttributeDefaultEffect
    fromdata-iv-from0Where the climb starts. A figure that only moves in its last digits reads faster from close by.
    durationdata-iv-duration900Milliseconds of the climb, eased out so it settles rather than stops.
    decimalsdata-iv-decimals-1 (auto)Decimal places held during the climb. -1 is the sentinel for “take them from the served figure”, because the attribute grammar has no null.
    groupingdata-iv-groupingtrueThousands separators while counting. Off, the digits stop shifting sideways.
    oncedata-iv-oncetrueCount the first time only. false counts again on every entry.
    autostartdata-iv-autostartfalseStart at init instead of waiting for the viewport.

    Methods and events

    start(), reset(), destroy(); read value and done. value is the number currently shown, and done means “there is nothing left to count” — which is also true immediately under reduced motion or without an IntersectionObserver. destroy() puts back the nodes that were served, not just their text, so the DOM returns identical.

    EventWhendetail
    iv:countthe climb begins; cancelablefrom, to
    iv:countedthe served figure is back on screenfrom, to

    Where nothing counts, nothing is announced: neither event fires under reduced motion or without an observer.

    Keyboard and assistive technology

    A figure is text, not a control: there is nothing to focus and nothing to operate. It is deliberately not a live region — a number ticking into a screen reader forty times a second would be unusable — so assistive technology reads the final value, which is the one that was served and the one that is there again a second later. Give the figure a label in its own markup (.iv-stat__label does it inside a stat) and never let the number stand alone as a claim.

    Without JavaScript

    The page is already correct. The value is in the HTML, the styling is CSS, and .iv-count only adds font-variant-numeric: tabular-nums so the digits sit in fixed columns and the figure does not jitter while it climbs. Under prefers-reduced-motion: reduce, or where IntersectionObserver is missing, the component reads the figure and stops there.

    Tokens and locals

    Countup has no locals of its own: it borrows the typography of whatever holds it. Inside a stat that is --iv-stat-size, --iv-tracking-tight and the tabular figures of .iv-count; inside a sentence it is the surrounding text. The one rule the class enforces is the tabular numbers, and it is there for a visual reason, not a decorative one.

    Common mistakes

    • Serving 0 and expecting the script to fill in the real number. Without JavaScript the page would then be a lie; serve the answer.
    • Counting a figure nobody measured. A number that climbs looks authoritative — say where it comes from, or mark it as invented.
    • A long duration on a figure above the fold: the reader arrives before the number does.
    • Grouping on while counting a four-digit number: the separator appears and disappears and the line jumps.
    • Wrapping the figure in a live region to “make it accessible”. It makes it unreadable.