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-chart

Or install the full npm package:

npm install bigbullui
import { 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.tsx

Zero 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

PropTypeDescription
labelstringLabel of the metric
valuenumberCurrent actual value
targetnumberTarget goal value
maxnumberMaximum scale value
unitstringUnit suffix (e.g. " stubs")