Design Library

Cost composition - radial layer breakdown

cost-composition-radial/ ยท 2 files

componentskill: design-taste-frontendchartdashboardcomparisoninteractive

Brief

The prompt that produced this. Re-run it to generate more in the same taste.

Aesthetic
analytical instrument - concentric, quiet, tabular-adjacent, hover-led, restrained colour, precise
Reference
stackbill cost composition, radial layer + line-item breakdown
Intent
show where a monthly bill actually goes at two depths at once - by layer, and by the line items inside each layer - without either ring winning
Guardrails
always keep the running total legible in the centre at all times, give the outer ring the same angular scale as the inner one, name the hovered segment and its share, not just its value, degrade to an explicit empty state at zero total
never imply precision the estimate does not have, colour by anything but layer, hide small line items rather than let them read as thin, animate on every state change

Install

pnpm dlx shadcn@latest add http://localhost:3040/r/cost-composition-radial.json

Source

Meta block and library type import stripped - this is what pastes cleanly elsewhere.

'use client'

import { RadialCostGraph, type RadialItem, type RadialLayer } from './radial-cost-graph'

/**
 * Colours arrive as props rather than CSS variables so the design carries its
 * own palette - the app resolves them from its theme instead.
 */
const layers: RadialLayer[] = [
  { key: 'infra', label: 'Infrastructure', value: 4_180, color: '#3f6f8f' },
  { key: 'saas', label: 'Third-party SaaS', value: 2_640, color: '#7a9a6b' },
  { key: 'seat', label: 'Per-seat / licensing', value: 1_920, color: '#c08b4a' },
  { key: 'ai', label: 'AI / LLM', value: 3_310, color: '#8b6fa8' },
]

const items: RadialItem[] = [
  { key: 'compute', label: 'Compute', value: 2_400, layer: 'infra' },
  { key: 'storage', label: 'Object storage', value: 980, layer: 'infra' },
  { key: 'egress', label: 'Egress', value: 800, layer: 'infra' },
  { key: 'observability', label: 'Observability', value: 1_240, layer: 'saas' },
  { key: 'ci', label: 'CI minutes', value: 760, layer: 'saas' },
  { key: 'errors', label: 'Error tracking', value: 640, layer: 'saas' },
  { key: 'design', label: 'Design seats', value: 1_120, layer: 'seat' },
  { key: 'support', label: 'Support desk', value: 800, layer: 'seat' },
  { key: 'inference', label: 'Inference', value: 2_450, layer: 'ai' },
  { key: 'embeddings', label: 'Embeddings', value: 860, layer: 'ai' },
]

const total = layers.reduce((sum, l) => sum + l.value, 0)

const usd = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  maximumFractionDigits: 0,
})

export default function CostCompositionRadial() {
  return (
    <main className="grid min-h-[100dvh] place-items-center bg-background p-8 text-foreground">
      <div className="w-full max-w-md">
        <header className="mb-6 text-center">
          <p className="font-mono text-[11px] text-muted-foreground uppercase tracking-widest">
            Monthly run rate
          </p>
          <h1 className="mt-1 font-semibold text-2xl tracking-[-0.02em]">Cost composition</h1>
        </header>

        <RadialCostGraph
          layers={layers}
          items={items}
          total={total}
          format={(v) => usd.format(v)}
        />

        <ul className="mt-6 grid grid-cols-2 gap-x-6 gap-y-2 border-border border-t pt-4 text-sm">
          {layers.map((l) => (
            <li key={l.key} className="flex items-center gap-2">
              <span
                aria-hidden
                className="size-2.5 flex-none rounded-full"
                style={{ backgroundColor: l.color }}
              />
              <span className="min-w-0 flex-1 truncate text-muted-foreground">{l.label}</span>
              <span className="tabular-nums">{usd.format(l.value)}</span>
            </li>
          ))}
        </ul>
      </div>
    </main>
  )
}