shadcn.io is an independent project built on shadcn/ui
Buttons
ProCounter Button for Svelte
A button whose number rolls to its next value on each press, for likes, votes and carts. For Svelte with Ark UI, when a count should feel like it moved.
Each press adds incrementBy to the number beside the label, and every digit that changes rolls to its new value like an odometer, while the others stay still.
Use it for likes, upvotes, add-to-cart and guest counts: small numbers someone nudges with a press. For a number people type or set precisely, use a number field instead. It wraps the Button, so variant and size work as usual. Each digit is a column of 0 to 9 behind a one-digit window, slid by a CSS transition, with no animation library; with reduced motion the digits change in place without rolling.
Installation
Usage
<script lang="ts">
import { CounterButton } from "@/components/ui/counter-button";
</script>
<CounterButton defaultCount={128}>Like</CounterButton>
Demos
Step
incrementBy is what one press adds, and the default is 1. A box of 5 adds 5 at a time; a negative step counts down, as a downvote does. Here min stops the downvotes at 0.
Controlled
Keep the number in your own state: pass it as count and update it from onCountChange. Here a Clear button sets it back to 0 and the digits roll down to it, and max stops the guest list at 6.
Accessibility
The rolling digits are hidden from assistive tech. The button carries the plain number instead, in a polite status inside it, so it's announced as its label followed by the count, such as Like 128, and each new count is read out after a press. It presses with Enter and Space like any button.
- Enter
- Add one step to the count.
- Space
- Add one step to the count.
FAQ
How do I start from an existing count?
Pass defaultCount, such as the number of likes you loaded. It's 0 by default, and the button keeps counting from there on its own. To hold the number yourself, pass count and onCountChange instead, as in the Controlled demo; the digits roll whenever count changes.
How do I save the new number?
Use onCountChange: it gets the new number after each press that changes it.
Can it count down?
Yes. Give incrementBy a negative number, such as -1 for a downvote, and min={0} to stop at zero. Each digit rolls to its new value whichever way the count goes.
How do I stop it at a maximum?
Pass max, and min for a floor. A press that would go past either stops at it, and a press at the limit does nothing. To show that, disable the button once your stored count reaches the limit.
How do I put a heart next to the label?
Pass it as icon: it goes before the label, and the number after. The preview at the top of this page uses a heart.
API Reference
CounterButton
data-slot="counter-button-count"
count?number
The number it shows, when you hold it yourself. Pair it with
onCountChange.
defaultCount?number
The number it starts from when you don't pass count.
- Default
0
onCountChange?(count: number) โ void
Called with the new number after each press that changes it.
- Parameters
countโ number- Returns
void
children?Snippet
The label before the count.
clickEffect?boolean
Apply a click effect to the button.
- Default
true
icon?Snippet
An icon before the label, such as a heart or a cart.
incrementBy?number
What one press adds. A negative step counts down.
- Default
1
isLoading?boolean
Show a loading indicator.
- Default
false
max?number
The highest number a press can reach; a press past it stops there.
min?number
The lowest number a press can reach; a press past it stops there.
asChild?Snippet<[PropsFn]>
Render your own element: a snippet that gets the part's props to spread on it.
- Type
Snippet<[PropsFn]>
class?string
Merged with the part's classes.
--counter-button-digitstring
The digit a column shows, 0 to 9 (set per digit).




