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 totalnever 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>
)
}