# <motion-counter>

Animated number counter. Springs from `from` to `to` once the element
scrolls into view. Use `prefix`/`suffix` for currency or units, `decimals`
for fractional precision.

**Use it for:** key figures and statistics, such as a year, a percentage or
a price, that count up once when they scroll into view.

**Avoid it for:** values that change while the page is open. Setting a new
`to` does not update the number that is shown, and `replay()` counts again
from `from`.

**Accessibility:** the number is plain text with no `aria-live`. Screen
readers read the value shown at that moment, which can be `from` or a value
in between. If the final value matters, also state it in nearby text.

**Reduced motion:** the number shows `to` at once when the element scrolls
into view, with no counting. Before that, it shows `from`.

**Common mistakes:** expecting thousands separators. The number is formatted
with `toFixed` only, so `10000` shows as "10000", not "10,000". Setting
`decimals` lower than the decimals in `to` rounds the final value, so
`to="4.87"` with `decimals="0"` ends at "5".

Docs: https://www.motion-components.dev/docs/text/motion-counter/

## Install

```js
import 'motion-components/motion-counter'
```

Or without a build step:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-counter.js/+esm"></script>
```

## Example

```html
<motion-counter from="0" to="2024" duration="2"></motion-counter>
<motion-counter to="99" suffix="%" decimals="0"></motion-counter>
```

## Attributes

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `from` | `number` | `0` | Starting value of the counter. |
| `to` | `number` | `100` | Target value to count up (or down) to. |
| `duration` | `number` | `1.5` | Spring duration of the count animation, in seconds. |
| `decimals` | `number` | `0` | Number of decimal places to render. |
| `prefix` | `string` | `''` | Text rendered before the number (e.g. `"$"`). |
| `suffix` | `string` | `''` | Text rendered after the number (e.g. `"%"`). |
| `once` | `boolean` | `true` | When `true`, only count the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. |
| `loop` | `boolean` | `false` | Count to `to`, hold, snap back to `from`, wait, then count again on repeat. |
| `hold` | `number` | `1.6` | With `loop`, seconds the counter stays at `to` before it snaps back to `from`. |
| `gap` | `number` | `0.5` | With `loop`, seconds the counter stays at `from` before it counts again. |
| `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the counter. |

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. |
| `finished` | `Promise<void>` | Resolves when the current run settles. A new run replaces the promise. |

## Methods

| Method | Description |
| --- | --- |
| `replay()` | Resets the counter to `from` and re-runs the count animation. |
| `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. |
| `pause()` | Holds the animation at its current position. No-op unless running. |
| `finish()` | Jumps to the final state and settles. Fires `motion-finish`. |
| `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. |

## Events

The `motion-*` events bubble and are composed.

| Event | Description |
| --- | --- |
| `motion-start` | When a run starts. |
| `motion-finish` | When a run finishes, or `finish()` jumps to the end. |
| `motion-cancel` | When `cancel()` stops a run and resets it. |
