Upvote button with live count
A toggle upvote button that updates the count instantly, confirms with the server and rolls back on failure, styled by aria-pressed.
Tailwind CSSFeedback and empty statesupvotebuttonoptimistic-uiaccessibility
Live preview
Code
How it works
What it solves
Voting feels broken when the number only changes after a network round trip. This button changes immediately, then reconciles with the server so the displayed count can never drift for long.
How it works
State lives in one attribute: aria-pressed. The same attribute that tells assistive technology the toggle is on also drives the look, because Tailwind's aria-pressed: variants restyle the border, background and colour. No extra class juggling is needed.
On click the handler stores the previous state, flips the attribute and adjusts the number, then disables the button while a POST (to vote) or DELETE (to withdraw) runs. A successful response replaces the optimistic number with the server's count, which accounts for other people voting in the meantime. If the request throws or returns an error status, the handler restores both the attribute and the old count. The finally block always re-enables the button, which also blocks double clicks mid-request.
Adapting it
- Point
data-vote-urlat your own route and return JSON containingcount. - Render the initial pressed state and count from the server for each idea.
- Add a CSRF header if your framework requires one on write requests.
Pitfalls
- Without authentication, ensure the server also de-duplicates votes; the client is not a gate.
- The
tabular-numsclass keeps the button from changing width as digits change. - Use a real
buttonelement, never a styleddiv, to get keyboard support for free.
How to use it
- Add the button markup to each idea row, with its own vote URL and starting count.
- Include the script once per page; it binds every element with
data-vote-url. - Implement the endpoint to accept POST and DELETE and return the new count as JSON.
- Render
aria-pressed="true"on load for ideas the current user has already voted on.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.