Display
Rolling number
A number whose digits roll to the new value when it changes, like a mechanical counter. For totals, prices and stats.
Installation
npx sinaui add rolling-numberCopy src/lib/ui/RollingNumber.svelte and tokens.css. No dependencies.
Examples
A counter
Only the digits that change roll, each the shortest way round.
1,240 manuscripts copied
<script lang="ts">
import Button from '#lib/ui/Button.svelte';
import RollingNumber from '#lib/ui/RollingNumber.svelte';
let copies = $state(1_240);
</script>
<div class="stat">
<span class="n"><RollingNumber value={copies} /></span>
<span class="label">manuscripts copied</span>
<div class="row">
<Button variant="secondary" size="sm" onclick={() => (copies -= 17)}>−17</Button>
<Button size="sm" onclick={() => (copies += 83)}>+83</Button>
</div>
</div>
<style>
.stat {
display: grid;
justify-items: center;
gap: 0.25rem;
}
.n {
font: 600 2.5rem/1.1 var(--ui-font);
font-variant-numeric: tabular-nums;
}
.label {
color: var(--ui-muted);
font-size: 0.875rem;
}
.row {
display: flex;
gap: 0.5rem;
margin-block-start: 0.75rem;
}
</style>Prices and percentages
Give it Intl.NumberFormat options: currency, percent and units all roll.
$9 Save 0%
<script lang="ts">
import Segmented from '#lib/ui/Segmented.svelte';
import RollingNumber from '#lib/ui/RollingNumber.svelte';
// Any Intl.NumberFormat style rolls: currency, percent, units.
const plans = { month: { price: 9, saved: 0 }, year: { price: 90, saved: 0.17 } };
let plan = $state<'month' | 'year'>('month');
</script>
<div class="price">
<Segmented
label="Billing"
hideLabel
options={[
{ value: 'month', label: 'Monthly' },
{ value: 'year', label: 'Yearly' }
]}
bind:value={() => plan, (v) => (plan = v as typeof plan)}
/>
<span class="n">
<RollingNumber
value={plans[plan].price}
format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
/>
</span>
<span class="save">
Save <RollingNumber value={plans[plan].saved} format={{ style: 'percent' }} />
</span>
</div>
<style>
.price {
display: grid;
justify-items: center;
gap: 0.75rem;
}
.n {
font: 600 2.5rem/1 var(--ui-font);
}
.save {
color: var(--ui-muted);
font-size: 0.875rem;
}
</style>Props
| Prop | Type | Default |
|---|---|---|
value | number | string | required |
format | Intl.NumberFormatOptions | |
locales | Intl.LocalesArgument |
Use it for values that change now and then. A value that changes several times a second (a stopwatch) is better as plain text.
Accessibility
- Read as the whole number, not digit by digit.
- With reduced motion, the new value simply replaces the old.