Data display

Bar list

Ranked list of values as proportional bars.

Preview
Live demo coming - the component is in the registry below.
Install
npx shadcn@latest add https://ui.thjsecurity.tech/r/bar-list.json

Pulls the component into components/ui/bar-list.tsx and its token dependencies. The app owns the copy from then on.

Source
import * as React from "react"

import { cn } from "@/lib/utils"

export interface BarListItem {
  /** Left-aligned label (truncated if long). */
  label: React.ReactNode
  /** Numeric magnitude that sizes the bar. */
  value: number
  /** Optional muted hint shown right of the label. */
  hint?: React.ReactNode
  /** Bar color (CSS color or hsl(var(--…))). Defaults to primary. */
  color?: string
  /** Optional click target for the whole row. */
  href?: string
}

interface BarListProps {
  items: BarListItem[]
  /** Override the denominator; defaults to the max value in items. */
  max?: number
  /** Cap rendered rows. */
  limit?: number
  className?: string
}

/**
 * Scannable horizontal bar-list (PD-style): label over a subtle track with a
 * filled bar, right-aligned count. Pure CSS - no chart library.
 */
function BarList({ items, max, limit, className }: BarListProps) {
  const rows = limit ? items.slice(0, limit) : items
  const denom = Math.max(1, max ?? Math.max(...rows.map((r) => r.value), 1))

  if (rows.length === 0) {
    return (
      <p className="py-8 text-center text-sm text-muted-foreground">
        No data available
      </p>
    )
  }

  return (
    <div className={cn("space-y-2.5", className)}>
      {rows.map((row, i) => {
        const pct = Math.max(2, Math.round((row.value / denom) * 100))
        return (
          <div key={i} className="group">
            <div className="mb-1 flex items-center justify-between gap-3 text-sm">
              <div className="flex min-w-0 items-center gap-2">
                <span className="truncate font-medium">{row.label}</span>
                {row.hint != null && (
                  <span className="shrink-0 text-xs text-muted-foreground">
                    {row.hint}
                  </span>
                )}
              </div>
              <span className="shrink-0 tabular-nums text-muted-foreground">
                {row.value.toLocaleString()}
              </span>
            </div>
            <div className="h-2 w-full overflow-hidden rounded-full bg-muted">
              <div
                className="h-full rounded-full transition-all"
                style={{
                  width: `${pct}%`,
                  backgroundColor: row.color ?? "hsl(var(--sev-low))",
                }}
              />
            </div>
          </div>
        )
      })}
    </div>
  )
}

export { BarList }