number-ticker
v3.0.2
Published
Lightweight TypeScript library for animating numbers with smooth column-scroll transitions. Zero dependencies, framework-agnostic counter animation component for React, Vue, Angular, Web Components, and Vanilla JS.
Downloads
189
Maintainers
Keywords
Readme
number-ticker
Animate numbers elegantly — framework agnostic, zero dependencies.
A lightweight TypeScript library that animates digit changes with smooth column-scroll transitions. Works natively in Vanilla JS, React, Angular, Vue, and as a Web Component.
Preview

Table of Contents
- Installation
- Quick Start
- Options Reference
- Public API
- Styles
- Framework Usage
- Examples
- Browser Support
- Contributing
- License
Installation
npm install number-tickeryarn add number-tickerpnpm add number-tickerQuick Start
import { NumberTicker } from "number-ticker";
import "number-ticker/dist/number-ticker.css";
const el = document.getElementById("my-ticker");
const ticker = new NumberTicker(el, {
prefix: "$",
digitsAfterDecimal: 2,
currencyFormat: "en-US",
});
ticker.update(1234.56); // animates to $1,234.56Options Reference
Pass these as the second argument to the constructor. All options are optional.
| Option | Type | Default | Description |
| --------------------- | --------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| prefix | string | '' | Static text rendered before the number (e.g. '$', '€', '#'). |
| autoWidth | boolean | true | Animates column width as digit count changes (e.g. 9 → 10). Disable for fixed-width layouts. |
| digitsAfterDecimal | number | 2 | Number of decimal places to display. Set to 0 for integers only. |
| currencyFormat | string | '' | A BCP 47 locale string (e.g. 'en-US', 'de-DE') to format the integer part with locale-specific thousand separators. Leave empty to skip. |
| numberOnInit | number | 0 | The number shown when the ticker first mounts — appears instantly with no animation. |
| timingFunction | string | 'ease' | CSS easing for the digit scroll. Accepts any valid transition-timing-function value. |
| transitionSpeed | number | 3 | Controls animation duration. Duration in seconds = 1 / transitionSpeed. Higher = faster. |
| transitionDelay | number | 6 | Stagger delay between digit columns. Each digit's delay = sequence / transitionDelay. Higher = less stagger. |
| colorOnNumberChange | object | { colorOnIncrease: '', colorOnDecrease: '' } | Temporarily tints digits during animation based on direction of change. Pass any CSS color string. |
colorOnNumberChange Detail
colorOnNumberChange: {
colorOnIncrease: 'springgreen', // applied when new > old
colorOnDecrease: 'tomato', // applied when new < old
}The color is applied at transitionstart and removed at transitionend automatically.
Public API
constructor(element, options?)
Initializes the ticker on the given DOM element.
new NumberTicker(element: HTMLElement | null, options?: NumberTickerOptions)| Parameter | Type | Description |
| --------- | --------------------- | ------------------------------------------------------------------------------ |
| element | HTMLElement \| null | The container element. A null value logs an error and skips init gracefully. |
| options | NumberTickerOptions | Optional configuration object (see Options Reference). |
update(number: number)
Animates the ticker from its current value to a new value.
ticker.update(9999.99);- Triggers column scroll animations with staggered delays.
- Adds or removes digit columns automatically as digit count changes.
- Applies
colorOnNumberChangecolors if configured.
destroy()
Removes all ticker DOM nodes and resets internal state. Call this on component unmount to prevent memory leaks.
ticker.destroy();Styles
Import the compiled CSS (or the SCSS source if your bundler supports it):
// Compiled CSS (recommended)
import "number-ticker/dist/number-ticker.css";// SCSS source (requires a SCSS-capable bundler)
@import "number-ticker/src/NumberTicker.scss";Customization
The ticker inherits font-size, font-family, font-weight, and color from the host element:
#my-ticker {
font-size: 3rem;
font-weight: 700;
font-family: "Tabular", monospace;
}Key Class Reference
| Class | Role |
| ------------------------ | ------------------------------------------------------------------------ |
| .native-ticker | Added to the host element by the library. |
| .number-ticker-wrapper | Flex container wrapping the entire ticker. |
| .ticker-columns | Row of digit column groups. Gets .auto-width when autoWidth: true. |
| .splited-column | Holds columns for the integer part (index 0) and decimal part (index 1). |
| .column-wrapper | Clips each digit column via overflow: hidden. |
| .column | Scrolls vertically — each child <span> is one character. |
| .decimal | The . separator between integer and decimal columns. |
| .prefix | Prefix text span. |
Framework Usage
Vanilla JS / TypeScript
import { NumberTicker } from "number-ticker";
import "number-ticker/dist/number-ticker.css";
const ticker = new NumberTicker(document.getElementById("price-ticker"), {
prefix: "$",
digitsAfterDecimal: 2,
currencyFormat: "en-US",
transitionSpeed: 4,
colorOnNumberChange: {
colorOnIncrease: "springgreen",
colorOnDecrease: "tomato",
},
});
ticker.update(4299.0);
// Cleanup
ticker.destroy();<span id="price-ticker"></span>React
// hooks/useNumberTicker.ts
import { useEffect, useRef } from "react";
import { NumberTicker, NumberTickerOptions } from "number-ticker";
export function useNumberTicker(options?: NumberTickerOptions) {
const ref = useRef<HTMLSpanElement>(null);
const ticker = useRef<NumberTicker | null>(null);
useEffect(() => {
if (ref.current) {
ticker.current = new NumberTicker(ref.current, options);
}
return () => ticker.current?.destroy();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const update = (n: number) => ticker.current?.update(n);
return { ref, update };
}// PriceBadge.tsx
import "number-ticker/dist/number-ticker.css";
import { useNumberTicker } from "./hooks/useNumberTicker";
function PriceBadge() {
const { ref, update } = useNumberTicker({
prefix: "$",
digitsAfterDecimal: 2,
currencyFormat: "en-US",
});
return (
<div>
<span ref={ref} />
<button onClick={() => update(1999.99)}>Update Price</button>
</div>
);
}Note:
destroy()is called automatically on component unmount.
Angular
// number-ticker.directive.ts
import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core";
import { NumberTicker, NumberTickerOptions } from "number-ticker";
@Directive({ selector: "[numberTicker]", standalone: true })
export class NumberTickerDirective implements OnInit, OnDestroy {
@Input() tickerOptions: NumberTickerOptions = {};
private ticker!: NumberTicker;
constructor(private el: ElementRef<HTMLElement>) {}
ngOnInit() {
this.ticker = new NumberTicker(this.el.nativeElement, this.tickerOptions);
}
update(value: number) {
this.ticker.update(value);
}
ngOnDestroy() {
this.ticker.destroy();
}
}<!-- app.component.html -->
<span
numberTicker
[tickerOptions]="{ prefix: '€', digitsAfterDecimal: 2, currencyFormat: 'de-DE' }"
#myTicker="numberTicker"
></span>
<button (click)="myTicker.update(3500)">Update</button>// app.component.ts
import { Component, ViewChild } from "@angular/core";
import { NumberTickerDirective } from "./number-ticker.directive";
@Component({
imports: [NumberTickerDirective],
templateUrl: "./app.component.html",
})
export class AppComponent {
@ViewChild("myTicker") ticker!: NumberTickerDirective;
updatePrice() {
this.ticker.update(2499.99);
}
}Note: Import
NumberTickerDirectivein yourimportsarray (standalone API) ordeclarations(NgModule). Import the CSS in your globalstyles.scss.
Vue 3
// composables/useNumberTicker.ts
import { onMounted, onUnmounted, ref, Ref } from "vue";
import { NumberTicker, NumberTickerOptions } from "number-ticker";
export function useNumberTicker(options?: NumberTickerOptions) {
const elRef: Ref<HTMLElement | null> = ref(null);
let ticker: NumberTicker | null = null;
onMounted(() => {
if (elRef.value) ticker = new NumberTicker(elRef.value, options);
});
onUnmounted(() => ticker?.destroy());
const update = (n: number) => ticker?.update(n);
return { elRef, update };
}<script setup lang="ts">
import "number-ticker/dist/number-ticker.css";
import { useNumberTicker } from "@/composables/useNumberTicker";
const { elRef, update } = useNumberTicker({
prefix: "€",
digitsAfterDecimal: 2,
currencyFormat: "fr-FR",
colorOnNumberChange: {
colorOnIncrease: "#22c55e",
colorOnDecrease: "#ef4444",
},
});
</script>
<template>
<div>
<span ref="elRef" />
<button @click="update(8800)">Animate</button>
</div>
</template>Web Component (Zero Framework)
Register the custom element once, then use it in any HTML page — no framework required.
// Register once in your entry file
import "number-ticker/web-component";
import "number-ticker/dist/number-ticker.css";<!-- Basic integer -->
<number-ticker value="0"></number-ticker>
<!-- Currency with prefix -->
<number-ticker
value="1234"
prefix="$"
digits="2"
currency="en-US"
></number-ticker>
<!-- Animate via JavaScript -->
<number-ticker id="live-count" value="0" digits="0"></number-ticker>
<script>
const el = document.getElementById("live-count");
setInterval(() => {
el.setAttribute("value", String(Math.floor(Math.random() * 10000)));
}, 2000);
</script>Supported HTML attributes:
| Attribute | Maps to option | Example |
| ---------- | --------------------------- | ------------------ |
| value | numberOnInit / update() | value="1234" |
| prefix | prefix | prefix="$" |
| digits | digitsAfterDecimal | digits="2" |
| currency | currencyFormat | currency="en-US" |
| speed | transitionSpeed | speed="4" |
Changing
valueviasetAttribute()triggers animation automatically.
Examples
Stock Price Ticker
import { NumberTicker } from "number-ticker";
const ticker = new NumberTicker(document.getElementById("stock"), {
prefix: "$",
digitsAfterDecimal: 2,
currencyFormat: "en-US",
transitionSpeed: 5,
transitionDelay: 4,
colorOnNumberChange: {
colorOnIncrease: "#22c55e",
colorOnDecrease: "#ef4444",
},
});
setInterval(() => ticker.update(Math.random() * 500 + 100), 3000);Animated Counter (Integer Only)
const ticker = new NumberTicker(document.getElementById("counter"), {
digitsAfterDecimal: 0,
autoWidth: true,
transitionSpeed: 2,
});
ticker.update(1000000);Fixed-Width Score Display
const ticker = new NumberTicker(document.getElementById("score"), {
digitsAfterDecimal: 0,
autoWidth: false,
numberOnInit: 0,
transitionSpeed: 6,
timingFunction: "cubic-bezier(0.34, 1.56, 0.64, 1)",
});Browser Support
| Browser | Version | | ------- | ------- | | Chrome | ≥ 79 | | Firefox | ≥ 75 | | Safari | ≥ 13.1 | | Edge | ≥ 79 |
Web Component usage requires browsers with Custom Elements v1 support.
Contributing
# Clone the repo
git clone https://github.com/your-username/number-ticker.git
cd number-ticker
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run devPull requests are welcome. For major changes, please open an issue first.
License
MIT © Harsh Rana
