Componentdata1.13 KB gzipZero dependencies
Poll Results
Animated percentage result bars.
Preview
Live Preview
Best curtain time?
301 total votes
19:30 Evening47%
142 votes · WINNER
21:00 Late33%
98 votes
Matinee 14:0020%
61 votes
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add poll-resultsOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { PollResults } from "@/components/ui/poll-results";
<PollResults />Usage guidance
Use Poll Results when a ticket stub surface needs Animated percentage result bars. Keep props minimal and prefer composition with sibling stubs.
Avoid Poll Results for long form flows or dense data grids where a dedicated layout block fits better.
Source code
poll-results.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface PollResultOption {
id: string;
label: string;
votes: number;
mine?: boolean;
}
export interface PollResultsProps extends React.HTMLAttributes<HTMLDivElement> {
question?: string;
options?: PollResultOption[];
}
/** Poll results: read-only animated percentage bars with winner stamp. */
export function PollResults({
question = "Best curtain time?",
options = [
{ id: "p1", label: "19:30 Evening", votes: 142, mine: true },
{ id: "p2", label: "21:00 Late", votes: 98 },
{ id: "p3", label: "Matinee 14:00", votes: 61 },
],
className,
...props
}: PollResultsProps) {
const total = options.reduce((s, o) => s + o.votes, 0);
const winner = options.reduce((a, b) => (b.votes > a.votes ? b : a), options[0]);
return (
<div className={cn("w-full max-w-sm rounded-lg border-2 border-foreground bg-card p-4", className)} {...props}>
<style>{`@keyframes pollGrow { from { width: 0; } }`}</style>
<h3 className="text-sm font-black">{question}</h3>
<p className="mt-0.5 font-mono text-[10px] uppercase tracking-widest text-muted-foreground">{total} total votes</p>
<div className="mt-3 space-y-2">
{options.map((o) => {
const pct = total > 0 ? Math.round((o.votes / total) * 100) : 0;
const isWin = winner && o.id === winner.id;
return (
<div key={o.id} className={cn("rounded-md border px-3 py-2", isWin ? "border-accent" : "border-dashed border-border")}>
<div className="flex items-center justify-between gap-2">
<span className="text-xs font-bold">
{o.mine && <span className="mr-1 text-accent" aria-hidden="true">✓</span>}
{o.label}
</span>
<span className="font-mono text-xs font-bold tabular-nums">{pct}%</span>
</div>
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-secondary" role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} aria-label={o.label}>
<div
className={cn("h-full rounded-full", isWin ? "bg-accent" : "bg-foreground/60")}
style={{ width: `${pct}%`, animation: "pollGrow 0.6s cubic-bezier(0.16,1,0.3,1) both" }}
/>
</div>
<p className="mt-1 font-mono text-[10px] text-muted-foreground">
{o.votes} votes{isWin ? " · WINNER" : ""}
</p>
</div>
);
})}
</div>
</div>
);
}