> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes.heygen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Number Wheel

> A rolling digit counter primitive for metrics, prices, counts, and scoreboards

export const InstallCommand = ({command}) => {
  const [copied, setCopied] = React.useState(false);
  const copy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(command);
      } else {
        const previous = document.activeElement;
        const scratch = document.createElement("textarea");
        scratch.value = command;
        scratch.setAttribute("readonly", "");
        scratch.style.position = "fixed";
        scratch.style.opacity = "0";
        document.body.appendChild(scratch);
        scratch.select();
        document.execCommand("copy");
        document.body.removeChild(scratch);
        previous?.focus?.();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="hf-install-command not-prose my-4 flex items-stretch overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900">
      <code className="flex-1 overflow-x-auto whitespace-nowrap border-r border-zinc-200 px-4 py-3 font-mono text-sm text-zinc-800 dark:border-zinc-800 dark:text-zinc-100">
        {command}
      </code>
      <button type="button" onClick={copy} data-copied={copied ? "true" : "false"} aria-label={`Copy ${command} to the clipboard`} className="hf-install-copy">
        <svg className="hf-install-copy-clipboard" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" />
          <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" />
        </svg>
        <svg className="hf-install-copy-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M2.75 9.5L6.5 13.25L15.25 4.5" />
        </svg>
      </button>
      <span className="hf-install-copy-status" role="status" aria-live="polite">
        {copied ? "Copied" : ""}
      </span>
    </div>;
};

<iframe className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800" title="number-wheel preview" loading="lazy" srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/components/number-wheel.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/number-wheel.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add number-wheel" />

That writes one file: `compositions/components/number-wheel.html`.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable  | Default    | Accepts                             | What it does                                                                                                                  |
| --------- | ---------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `spin`    | `single`   | `single`, `double`, `triple`        | How many full 0 to 9 revolutions each column adds before it lands, which is how far the reel travels in the same time.        |
| `spacing` | `standard` | `tight`, `standard`, `wide`         | Gap between the digit columns.                                                                                                |
| `tone`    | `inherit`  | `inherit`, `ink`, `paper`, `accent` | Digit colour: inherit keeps the surrounding text colour, ink suits light frames, paper suits dark ones, accent rides --brand. |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap theme={null}
<div
  data-composition-id="number-wheel"
  data-composition-src="compositions/components/number-wheel.html"
  data-variable-values='{"spin":"single","spacing":"standard","tone":"inherit"}'
></div>
```

## Source

<Accordion title={`number-wheel.html`}>
  ```html theme={null}
  <!--
    Number Wheel - rolling digit counter.

    Markup:
      <span class="hf-number-wheel" data-value="30222"></span>

    Paste this snippet once. It builds one column per digit. Animate the generated
    .hf-number-wheel-strip elements with the data-target-y custom property.

    Variables. The script reads each one, falls back to the declared default on
    anything missing or unrecognised, and either builds a longer strip or writes a
    custom property the CSS above consumes. Every strip still starts on 0 and
    still lands on its own digit, so only the run in between changes.

      - spin (single | double | triple, default single): how many full 0 to 9
        revolutions each column adds before it lands. Extra revolutions lengthen
        the strip and push --hf-number-target-y further, so the reel covers more
        ground in the same time. Nothing moves at rest, so this one only shows
        once the timeline runs.
      - spacing (tight | standard | wide, default standard): the gap between digit
        columns.
      - tone (inherit | ink | paper | accent, default inherit): digit colour.
        inherit leaves the surrounding text colour alone, ink is near-black for
        light frames, paper near-white for dark ones, accent rides --brand.

    On every default the result is identical to the original: one column per
    digit, each rolling 0 up to its own digit in the inherited text colour.
  -->

  <style
    data-composition-variables='[
      { "id": "spin", "type": "enum", "role": "motion", "label": "Spin", "description": "How many full 0 to 9 revolutions each column adds before it lands, which is how far the reel travels in the same time.", "default": "single", "options": [{ "value": "single", "label": "Single" }, { "value": "double", "label": "Double" }, { "value": "triple", "label": "Triple" }] },
      { "id": "spacing", "type": "enum", "role": "layout", "label": "Spacing", "description": "Gap between the digit columns.", "default": "standard", "options": [{ "value": "tight", "label": "Tight" }, { "value": "standard", "label": "Standard" }, { "value": "wide", "label": "Wide" }] },
      { "id": "tone", "type": "enum", "role": "style", "label": "Tone", "description": "Digit colour: inherit keeps the surrounding text colour, ink suits light frames, paper suits dark ones, accent rides --brand.", "default": "inherit", "options": [{ "value": "inherit", "label": "Inherit" }, { "value": "ink", "label": "Ink" }, { "value": "paper", "label": "Paper" }, { "value": "accent", "label": "Accent" }] }
    ]'
  >
    .hf-number-wheel {
      display: inline-flex;
      align-items: baseline;
      gap: var(--hf-wheel-gap, 0.02em);
      color: var(--hf-wheel-color, inherit);
      font-variant-numeric: tabular-nums;
    }
    .hf-number-wheel-digit {
      display: inline-block;
      height: 1em;
      overflow: hidden;
    }
    .hf-number-wheel-strip {
      display: grid;
      transform: translateY(0em);
      will-change: transform;
    }
    .hf-number-wheel-strip span {
      height: 1em;
      line-height: 1em;
    }
  </style>

  <script>
    (function () {
      const vars =
        window.__hyperframes && window.__hyperframes.getVariables
          ? window.__hyperframes.getVariables()
          : {};

      // Unrecognised overrides return to their declared defaults.
      const spins = { single: 0, double: 1, triple: 2 };
      const gaps = { tight: "0em", standard: "0.02em", wide: "0.14em" };
      const tones = {
        inherit: "inherit",
        ink: "#18181b",
        paper: "#fafafa",
        accent: "var(--brand, #34d399)",
      };
      const pick = (table, value, fallback) =>
        Object.prototype.hasOwnProperty.call(table, value) ? table[value] : table[fallback];

      const cycles = pick(spins, vars.spin, "single");
      const gap = pick(gaps, vars.spacing, "standard");
      const tone = pick(tones, vars.tone, "inherit");

      document.querySelectorAll(".hf-number-wheel").forEach((el) => {
        if (el.dataset.hfNumberWheelReady === "1") return;
        el.style.setProperty("--hf-wheel-gap", gap);
        el.style.setProperty("--hf-wheel-color", tone);
        const value = el.dataset.value || "0";
        el.textContent = "";
        for (const char of value) {
          if (!/\d/.test(char)) {
            el.append(document.createTextNode(char));
            continue;
          }
          const stops = cycles * 10 + Number(char);
          const frame = document.createElement("span");
          frame.className = "hf-number-wheel-digit";
          const strip = document.createElement("span");
          strip.className = "hf-number-wheel-strip";
          strip.style.setProperty("--hf-number-target-y", `${-stops}em`);
          for (let i = 0; i <= stops; i++) {
            const step = document.createElement("span");
            step.textContent = String(i % 10);
            strip.appendChild(step);
          }
          frame.appendChild(strip);
          el.appendChild(frame);
        }
        el.dataset.hfNumberWheelReady = "1";
      });
    })();
  </script>

  <!--
    Timeline integration:

    tl.to(".hf-number-wheel-strip", {
      y: (_, strip) => strip.style.getPropertyValue("--hf-number-target-y"),
      duration: 1.1,
      ease: "power3.out",
      stagger: 0.05,
    }, startTime);
  -->
  ```
</Accordion>

## Usage

Open `compositions/components/number-wheel.html` and paste its contents into your composition. See the comment header in the file for detailed instructions.

Tagged `video-primitive` `motion-primitive` `number` `counter` `data`.

## Related topics

* [Browse the complete Catalog](/catalog)
* [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
* [Build a richer composition](/go-further)


## Related topics

- [Color grade images and footage](/guides/color-grading.md)
- [Color grading implementation](/reference/color-grading.md)
- [Number Pop In](/catalog/components/number-pop-in.md)
