Media & time
Animated counter
A number that counts up to its value the first time it is scrolled into view.
Import
import { AnimatedCounter } from '@the_viveksingh/vivek-ui'Count up on scroll
Starts when it enters the viewport. Under prefers-reduced-motion it renders the final number immediately rather than animating.
83
Counts up when it scrolls into view. With reduced motion requested it renders the final number immediately instead of animating.
<Heading level={3} size="hero">
<AnimatedCounter value={83} duration={1200} />
</Heading>Formatted
format takes Intl.NumberFormat options, or your own function. Currency, percentages and fixed decimals all come from the platform rather than string maths.
₹4,280
99.98%
~1,268 tests
<AnimatedCounter
value={4280}
format={{ style: 'currency', currency: 'INR', maximumFractionDigits: 0 }}
locale="en-IN"
/>
<AnimatedCounter value={99.98} suffix="%" format={{ minimumFractionDigits: 2 }} />
<AnimatedCounter value={1268} prefix="~" suffix=" tests" />Props
Generated from the package's own type declarations, so this table cannot drift from the code.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | The number to land on. Defaults to `0`. |
from | number | — | The number to count from. Defaults to `0`. |
duration | number | — | Milliseconds. Defaults to `1600`. `0` disables the animation entirely. |
format | CounterFormat | — | `Intl.NumberFormat` options, or a function. |
locale | string | string[] | — | Locale for `Intl.NumberFormat`. Pass it if the page is server-rendered: the runtime default differs between Node and the browser, and that difference IS a hydration mismatch, because the final value is what both sides render. |
prefix | string | — | — |
suffix | string | — | — |
startOnView | boolean | — | Wait until the counter is scrolled into view. Defaults to `true`. |
Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.
Rendering
AnimatedCounter declares 'use client' because it needs state, effects or event handlers. Importing it into a Server Component creates a client boundary at this component — everything above it stays on the server.