Skip to content
Nizivo UI

Motion

CountUp: a number that counts up to its value on the client, while the server renders the final value in NumberValue's element. Tabular digits that keep their width, 0 ms under reduced motion, and nothing announced while it counts. Also useCountUp, for an element of your own.

Tier: own

Import

import { CountUp } from '@nizivo/react/motion';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/number-value.css
import '@nizivo/react/css/motion.css';

Static or interactive

Client. CountUp is a client module (count-up.client.tsx), the one client part of the dashboard set, so it always ships its code. Its server HTML is the final formatted value in NumberValue's <data> element, so the number is right before hydration and without JavaScript; the client only animates it. For a number that does not count, use NumberValue, which ships nothing. How to choose.

When it counts

It counts with requestAnimationFrame, only while the element is in the viewport. An element mounted on the client counts from from (default 0). An element that was already on screen when the server HTML hydrated keeps its number: its reader has seen it. An element off screen counts when it first comes into view. A new value counts from the number on screen when the element is in view, and shows at once when it is not.

The duration is --nz-duration-count-up (900 ms), read from the element; a duration prop in milliseconds replaces it. Under prefers-reduced-motion: reduce or data-nz-motion="reduce" the token is 0 ms and the value shows at once, whatever duration says (Motion). The curve is cubic-bezier(0, 0, 0, 1), the same curve as --nz-easing-enter, which Figma 04 proposed for count-up; it is written into the code and the token is not read, so overriding the token does not change it. Figma defines no easing for count-up.

Accessibility

The counting digits are aria-hidden; a visually hidden copy beside them holds the final value, which is what assistive technology reads. There is no live region, so nothing is announced while it counts or when value changes. While counting, the digits keep the final string's width, so nothing around them moves.

The Figma 04 notes proposed a separate @nizivo/motion package for this; the plan put it at the @nizivo/react/motion subpath instead (ARCHITECTURE.md §13.4).

Examples

Count Up

The server renders each final value; the client counts it up once it is in view.

$48,2901,28412.4%12.500
import type { JSX } from 'react';
import { CountUp } from '@nizivo/react/motion';

export function CountUpExample(): JSX.Element {
  return (
    <div>
      <CountUp
        value={48290}
        format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
      />
      <CountUp value={1284} />
      <CountUp value={0.124} format={{ style: 'percent', maximumFractionDigits: 1 }} />
      {/* Starts at 1,000 instead of 0, in German grouping */}
      <CountUp value={12500} from={1000} locale="de-DE" />
    </div>
  );
}

Props

NumberValue's <data> with the final value, counted up on the client. A client leaf: under RSC it is the one dashboard part that ships JavaScript (§17.2).

Client component Renders data-slot="count-up" with the class nz-count-up. Props: CountUpProps. Source: src/motion/count-up.client.tsx.

NameTypeDefaultDescription
durationnumber–

Milliseconds. The default reads --nz-duration-count-up from the element (900 ms). Under reduced motion that token is 0 ms and the value is shown at once, whatever this says.

From CountUpOptions.

formatIntl.NumberFormatOptions{}

Intl.NumberFormat options, as NumberValue takes them.

From CountUpOptions.

fromnumber0

Where the first count starts. Later counts start from the number on screen.

From CountUpOptions.

localestring'en-US'

BCP 47 tag for the number.

From CountUpOptions.

renderRenderProp<CountUpState>–
valuerequirednumber–
  • Also takes every attribute of <data> except children, value. (Omit<ComponentPropsWithRef<"data">, "children" | "value">)

render takes an element, or, in client code, a function of the props and this state (CountUpState):

NameTypeDefaultDescription
textrequiredstring–

The final formatted text, which the server renders and assistive tech reads.

valuerequirednumber–

Other exports

CountUpOptions type

import type { CountUpOptions } from '@nizivo/react/motion'

NameTypeDefaultDescription
durationnumber–

Milliseconds. The default reads --nz-duration-count-up from the element (900 ms). Under reduced motion that token is 0 ms and the value is shown at once, whatever this says.

formatIntl.NumberFormatOptions{}

Intl.NumberFormat options, as NumberValue takes them.

fromnumber0

Where the first count starts. Later counts start from the number on screen.

localestring'en-US'

BCP 47 tag for the number.

CountUpProps type

import type { CountUpProps } from '@nizivo/react/motion'

The props of CountUp, listed above.

CountUpState type

import type { CountUpState } from '@nizivo/react/motion'

NumberValue's state: the final value and its formatted text.

The render state of CountUp, listed above.

useCountUp hook

import { useCountUp } from '@nizivo/react/motion'

Counts the text of ref's element up to value, formatted with formatNumber (so every frame reads like the server's text). Render the final formatted value in that element yourself; the hook only writes frames and gives the element the final string's width while it counts (make it inline-block). Keep it aria-hidden and put the final value in visually hidden text beside it.

function useCountUp(ref: RefObject<HTMLElement | null>, value: number, options?: CountUpOptions): void

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
count-up
.nz-count-up
CountUp
root (takes render)
none
count-up-value
.nz-count-up-value
CountUp
inner element (CSS only)
none

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

This stylesheet reads no public hook.

Layers: nz.components. Classes: .nz-count-up-label, .nz-count-up-value.

Accessibility

Nothing in this component takes focus or keyboard input.

Keyboard

None.

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

  • Canonical: none
  • Ghost masters: none

On this page