Componentcharts0.93 KB gzipZero dependencies
Bullet Chart
Target versus actual progress meter with qualitative ranges and goal marker.
Preview
Live Preview
Ticket Sales Velocity780 stubs / 1,000 stubs target
220 stubs to target
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add bullet-chartOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { BulletChart } from "@/components/ui/bullet-chart";
<BulletChart label="Ticket Sales" value={780} target={1000} max={1200} unit=" stubs" />Usage guidance
Use Bullet Chart when a ticket stub surface needs Target versus actual progress meter with qualitative ranges and goal marker. Keep props minimal and prefer composition with sibling stubs.
Avoid Bullet Chart for long form flows or dense data grids where a dedicated layout block fits better.
Source code
bullet-chart.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 BulletChartProps extends React.HTMLAttributes<HTMLDivElement> {
label: string;
/** Actual value */
value: number;
/** Target value */
target: number;
max: number;
unit?: string;
}
/** Bullet chart: actual value bar vs target indicator marker. */
export function BulletChart({ label, value, target, max, unit = "", className, ...props }: BulletChartProps) {
const vpct = Math.min(100, (value / max) * 100);
const tpct = Math.min(100, (target / max) * 100);
const hit = value >= target;
return (
<div className={cn("w-full max-w-md", className)} {...props}>
<style>{`@keyframes bltGrow { from { width: 0; } }`}</style>
<div className="flex items-baseline justify-between">
<span className="font-mono text-[10px] uppercase tracking-[0.15em] text-muted-foreground">{label}</span>
<span className="font-mono text-xs tabular-nums">
<span className={hit ? "text-success" : "text-warning"}>{value.toLocaleString("en-US")}{unit}</span>
<span className="text-muted-foreground"> / {target.toLocaleString("en-US")}{unit} target</span>
</span>
</div>
<div className="relative mt-2 h-4 overflow-hidden rounded-sm border border-border bg-secondary/50">
{/* Range bars */}
<div className="absolute inset-y-0 left-0 w-1/3 bg-secondary/60" aria-hidden="true" />
<div className="absolute inset-y-0 left-1/3 w-1/3 bg-secondary/40" aria-hidden="true" />
{/* Value bar */}
<div
className={cn("absolute inset-y-0 left-0 rounded-r-sm transition-all duration-700 ease-out motion-reduce:transition-none", hit ? "bg-success/70" : "bg-accent/70")}
style={{ width: `${vpct}%`, animation: "bltGrow 0.7s cubic-bezier(0.16,1,0.3,1) both" }}
/>
{/* Target marker */}
<div className="absolute inset-y-0 w-0.5 bg-foreground" style={{ left: `${tpct}%` }} aria-hidden="true" />
</div>
{!hit && (
<p className="mt-1 font-mono text-[9px] text-warning">
{(target - value).toLocaleString("en-US")}{unit} to target
</p>
)}
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| label | string | Label of the metric |
| value | number | Current actual value |
| target | number | Target goal value |
| max | number | Maximum scale value |
| unit | string | Unit suffix (e.g. " stubs") |