Vote Tally
Composable upvote list for React and shadcn/ui with accessible vote buttons, sorting by count, and controlled or uncontrolled state.
Updated
- API AccessPublic API for third-party integrations
- Mobile AppNative iOS and Android applications
- Dark ModeAdd system-wide dark mode support with automatic detection
- Keyboard ShortcutsCustomizable keyboard shortcuts for power users
- Export to PDFExport documents and reports as PDF files
- Inline editing
- Batch operations
- Auto-save drafts
Vote Tally is a composable React component for shadcn/ui. Use it for feature request boards, polls, roadmap feedback, or community idea lists.
Installation
pnpm dlx shadcn@latest add @cult-ui/vote-tally
Usage
Use VoteTally.Root with VoteTally.Item for each item; each item can include Trigger, Title, Description, and Count. Supports controlled or uncontrolled vote counts and voted-items state, and optional sorting via VoteTally.Group.
Basic list
import { VoteTally } from "@/registry/default/ui/vote-tally";
export default function Example() {
return (
<VoteTally.Root>
<VoteTally.Item value="dark-mode">
<VoteTally.Title>Dark mode</VoteTally.Title>
<VoteTally.Description>System-aware dark theme</VoteTally.Description>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
<VoteTally.Item value="keyboard-shortcuts">
<VoteTally.Title>Keyboard shortcuts</VoteTally.Title>
<VoteTally.Description>Customizable hotkeys</VoteTally.Description>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
</VoteTally.Root>
);
}Controlled votes and voted items
Wire value / onValueChange and votedItems / onVotedItemsChange to sync with your backend or state.
import { useState } from "react";
import { VoteTally } from "@/registry/default/ui/vote-tally";
export default function Example() {
const [votes, setVotes] = useState({
"dark-mode": 10,
"keyboard-shortcuts": 5,
});
const [voted, setVoted] = useState(new Set<string>());
return (
<VoteTally.Root
value={votes}
onValueChange={setVotes}
votedItems={voted}
onVotedItemsChange={setVoted}
>
<VoteTally.Item value="dark-mode">
<VoteTally.Title>Dark mode</VoteTally.Title>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
<VoteTally.Item value="keyboard-shortcuts">
<VoteTally.Title>Keyboard shortcuts</VoteTally.Title>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
</VoteTally.Root>
);
}With sorting by votes
Wrap items in VoteTally.Group with sortBy="votes-desc" or sortBy="votes-asc" to reorder by vote count.
import { VoteTally } from "@/registry/default/ui/vote-tally";
export default function Example() {
return (
<VoteTally.Root defaultValue={{ a: 2, b: 5, c: 1 }}>
<VoteTally.Group sortBy="votes-desc">
<VoteTally.Item value="a">
<VoteTally.Title>Option A</VoteTally.Title>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
<VoteTally.Item value="b">
<VoteTally.Title>Option B</VoteTally.Title>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
<VoteTally.Item value="c">
<VoteTally.Title>Option C</VoteTally.Title>
<VoteTally.Count />
<VoteTally.Trigger>Vote</VoteTally.Trigger>
</VoteTally.Item>
</VoteTally.Group>
</VoteTally.Root>
);
}Features
- Vote and unvote per item via Trigger; one vote per user per item (tracked by
votedItems) - Controlled or uncontrolled:
value/defaultValueandonValueChangefor vote counts;votedItems/defaultVotedItemsandonVotedItemsChangefor which items the user voted for - Optional sorting:
VoteTally.GroupwithsortBy="votes-asc"orsortBy="votes-desc" - Compound API: Root, Group, Item, Trigger, Count, Title, Description
useVoteTally()hook for external access to vote state and actions