A React component library for building pairwise comparison voting and ranking systems. Voters answer "which is better?" questions across multiple dimensions, and the library aggregates results into statistical rankings.
npm install bitvote-reactPeer dependencies: react >= 18.0.0 and react-dom >= 18.0.0
This library is a frontend component layer that requires the BitVote API backend to be running. See the BitVote API README for setup instructions. The apiUrl prop on <BitVoteProvider> should point to the running backend (e.g., http://localhost:3000 for local development).
The library includes built-in authentication components (AuthGate, LoginForm, RegisterForm) that handle user registration and login against the BitVote API. The auth flow works as follows:
- Registration — Users provide email, password, and display name. The API returns a JWT access token (15min expiry) and a refresh token (7 day expiry).
- Login — Returns the same token pair.
- Token management — The internal API client automatically includes the access token as a
Bearerheader on all requests. On401responses, it attempts to refresh the token using the stored refresh token. - Pre-authenticated users — If your app manages auth externally, pass a valid access token via the
tokenprop on<BitVoteProvider>to skip the login flow.
The composed components (BitVoteVoter, BitVoteOwner) wrap their content in AuthGate, so unauthenticated users are automatically shown a login/register form.
import { BitVoteProvider, BitVoteVoter, BitVoteOwner } from 'bitvote-react'
import 'bitvote-react/styles.css'
// Voter experience — participants compare items pairwise
function VoterApp() {
return (
<BitVoteProvider apiUrl="http://localhost:3000">
<BitVoteVoter bitvoteId="abc-123" />
</BitVoteProvider>
)
}
// Owner experience — create and manage polls
function OwnerApp() {
return (
<BitVoteProvider apiUrl="http://localhost:3000">
<BitVoteOwner />
</BitVoteProvider>
)
}Wraps your app and provides API client + auth context to all child components.
| Prop | Type | Required | Description |
|---|---|---|---|
apiUrl |
string |
Yes | Backend API base URL |
token |
string |
No | Pre-existing auth token |
| Component | Props | Description |
|---|---|---|
BitVoteVoter |
bitvoteId: string, className? |
Full voter flow: auth, voting booth, progress, results |
BitVoteOwner |
className? |
Full owner flow: auth, poll list, editor, items, results |
| Component | Props | Description |
|---|---|---|
AuthGate |
className? |
Renders children if authenticated, login form otherwise |
LoginForm |
className? |
Email/password login |
RegisterForm |
className? |
Email/password/display name registration |
| Component | Props | Description |
|---|---|---|
BitvoteList |
onSelect: (id: string) => void |
Lists user's bitvotes, create/delete |
BitvoteEditor |
bitvoteId: string, className? |
Edit name, description, toggle voting |
ItemManager |
bitvoteId: string, className? |
Add/remove items being compared |
DimensionManager |
bitvoteId: string, className? |
Add/remove comparison dimensions |
| Component | Props | Description |
|---|---|---|
VotingBooth |
bitvoteId: string, className? |
Side-by-side pairwise comparison UI |
ProgressBar |
bitvoteId: string, className? |
Completion progress per dimension |
| Component | Props | Description |
|---|---|---|
ResultsCards |
bitvoteId: string, className? |
Card-based results with rankings |
ResultsList |
bitvoteId: string, className? |
List-based results view |
All hooks require <BitVoteProvider> as an ancestor.
// Authentication
const { user, token, isAuthenticated, isLoading, error, login, register, logout } = useAuth()
// List & manage bitvotes
const { bitvotes, isLoading, error, create, update, remove, refetch } = useBitvotes()
// Single bitvote with items & dimensions
const { bitvote, items, dimensions, isLoading, error, refetch } = useBitvoteDetail(bitvoteId)
// Item CRUD
const { items, isLoading, error, addItem, updateItem, removeItem, refetch } = useItems(bitvoteId)
// Dimension CRUD
const { dimensions, isLoading, error, addDimension, updateDimension, removeDimension, refetch } = useDimensions(bitvoteId)
// Voting flow — get next question, submit answer
const { question, isLoading, error, submitAnswer, refetch } = useVoting(bitvoteId)
// Voting progress
const { progress, isComplete, isLoading, error, refetch } = useProgress(bitvoteId)
// Aggregated results
const { results, isLoading, error, refetch } = useResults(bitvoteId)interface Bitvote {
bitvoteId: string
ownerId: string
name: string
description: string | null
votingOpen: boolean
items: Item[]
dimensions: Dimension[]
createdAt: string
updatedAt: string
}
interface Item {
itemId: string
bitvoteId: string
name: string
description: string | null
sortOrder: number
}
interface Dimension {
dimensionId: string
bitvoteId: string
name: string
questionTemplate: string
}
interface VotingQuestion {
dimensionId: string
dimensionName: string
questionText: string
itemA: { itemId: string; name: string }
itemB: { itemId: string; name: string }
}
interface ProgressEntry {
dimensionId: string
dimensionName: string
answered: number
total: number
complete: boolean
}
interface ResultEntry {
dimensionId: string
dimensionName: string
itemId: string
itemName: string
meanRank: number
deviation: number
totalUsers: number
}
type ComparisonResult = 'A_BETTER' | 'B_BETTER'All CSS classes are prefixed with bitvote- to avoid conflicts with your app's styles. The library uses Tailwind CSS internally. Import the stylesheet:
import 'bitvote-react/styles.css'npm run dev # Dev server
npm run build # Production build
npm run test # Run tests
npm run test:watch # Watch mode| File | Format |
|---|---|
dist/bitvote-react.es.js |
ESM |
dist/bitvote-react.cjs.js |
CJS |
dist/bitvote-react.css |
CSS |
dist/index.d.ts |
Types |