Metric Cluster
Compact stack of related metrics pinned to a canvas object's corner. Install Metric Cluster from the VLLNT UI registry with the shadcn CLI.
Compact stack of related metrics pinned to a canvas object's corner. Part of the data family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.
Preview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
Add Metric Cluster to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/metric-cluster.jsonSource
"use client";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import { cn } from "../../lib/utils";
/**
* Tone of a single metric line — drives the dot color.
*
* @public
*/
export type MetricClusterTone = "danger" | "neutral" | "success" | "warn";
const TONE_DOT: Record<MetricClusterTone, string> = {
danger: "bg-red-500",
neutral: "bg-muted-foreground",
success: "bg-emerald-500",
warn: "bg-amber-500",
};
/**
* Anchor corner relative to the canvas object the cluster attaches to.
*
* @public
*/
export type MetricClusterAnchor =
| "bottom-left"
| "bottom-right"
| "top-left"
| "top-right";
const ANCHOR_TRANSFORM: Record<MetricClusterAnchor, string> = {
"bottom-left": "translate(-100%, 100%)",
"bottom-right": "translate(0%, 100%)",
"top-left": "translate(-100%, -100%)",
"top-right": "translate(0%, -100%)",
};
/**
* One metric in the cluster.
*
* @public
*/
export type MetricClusterEntry = {
/** Stable identifier — used as the React key + analytics hook. */
id: string;
/** Short label (e.g. `"errs/min"`). */
label: ReactNode;
/** Optional tone for the leading dot. Defaults to `"neutral"`. */
tone?: MetricClusterTone;
/** Display value (already formatted by host). */
value: ReactNode;
};
/**
* Localizable strings.
*
* @public
*/
export type MetricClusterLabels = {
/** Aria-label override. Defaults to `"Metric cluster"`. */
region?: string;
};
const DEFAULT_LABELS = {
region: "Metric cluster",
} as const satisfies Required<MetricClusterLabels>;
/**
* Props for {@link MetricCluster}.
*
* @public
*/
export type MetricClusterProps = {
/** Anchor corner. Defaults to `"top-right"`. */
anchor?: MetricClusterAnchor;
/** Localizable strings. */
labels?: MetricClusterLabels;
/** Metric entries in render order. */
metrics: MetricClusterEntry[];
/** Optional cluster title rendered above the rows. */
title?: ReactNode;
/** Anchor X in canvas pixels. */
x: number;
/** Anchor Y in canvas pixels. */
y: number;
} & ComponentPropsWithoutRef<"div">;
const Row = (props: { entry: MetricClusterEntry }): React.ReactElement => {
const { entry } = props;
const tone = entry.tone ?? "neutral";
return (
<li
className="flex items-center justify-between gap-2 text-[11px]"
data-metric-cluster-row={entry.id}
data-metric-cluster-tone={tone}
>
<span className="flex items-center gap-1.5 text-muted-foreground">
<span
aria-hidden="true"
className={cn("size-1.5 rounded-full", TONE_DOT[tone])}
/>
{entry.label}
</span>
<span className="font-semibold text-foreground">{entry.value}</span>
</li>
);
};
/**
* Compact stack of related metrics pinned to a canvas object's corner.
* Use when a single `StickyMetric` doesn't carry enough signal — for
* runs whose throughput, latency, and error rate must all be visible
* at once. Pure presentation; the host supplies the anchor coords + the
* metric list.
*
* The wrapper is `pointer-events: none` — host gestures pass through.
*
* @example
* ```tsx
* <MetricCluster
* x={420} y={180}
* anchor="top-right"
* title="research-2025"
* metrics={[
* { id: "qps", label: "qps", value: "240", tone: "success" },
* { id: "errs", label: "errs", value: "14", tone: "danger" },
* { id: "p95", label: "p95", value: "180ms" },
* ]}
* />
* ```
*
* @public
*/
export const MetricCluster = ({
ref,
...props
}: MetricClusterProps & { ref?: React.Ref<HTMLDivElement> }) => {
const {
anchor = "top-right",
className,
labels,
metrics,
title,
x,
y,
...rest
} = props;
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
return (
<div
aria-label={resolvedLabels.region}
className={cn(
"pointer-events-none absolute z-20 flex flex-col gap-1 rounded-md border border-border bg-background/90 p-2 shadow-sm backdrop-blur",
className,
)}
data-metric-cluster
data-metric-cluster-anchor={anchor}
ref={ref}
role="status"
style={{
left: x,
top: y,
transform: ANCHOR_TRANSFORM[anchor],
}}
{...rest}
>
{title ? (
<header
className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground"
data-metric-cluster-title
>
{title}
</header>
) : null}
<ul className="space-y-0.5">
{metrics.map((entry) => (
<Row entry={entry} key={entry.id} />
))}
</ul>
</div>
);
};
MetricCluster.displayName = "MetricCluster";
Stories
Explore every variant and state in the interactive Storybook:
Preview
Switch between light and dark to inspect the embedded Storybook preview.