Skip to content
KONIGI Patterns

Dashboards / Key measures / KPI tile

‹ 2 of 7 ›

KPI tile

People need to check a key value and understand whether it needs attention.

Updated September 9, 2026

Problem

People need to quickly check a key measure and understand whether it needs attention.

Solution

Show one prominent value with a clear label and a useful piece of context. That context can be a comparison, a target, or a small trend chart.

Include units and make the time period clear. If color indicates a state, base it on a meaningful rule and include a text or icon cue.

Use a consistent arrangement across neighboring tiles so people can scan them together.

Use when

A key measure helps people decide what to do next and needs frequent checking.

Don’t use when

The number only matters in relation to a series. Monthly revenue with no comparison is a chart, not a tile. And don’t lead a page with tiles when the page’s job is analysis rather than monitoring; on an exploratory dashboard the big numbers are the least interesting thing on the screen.

Trade-offs

Each tile uses space to show a limited amount of information. Too many tiles weaken the page’s priorities. Rates can hide their underlying counts, and incorrect thresholds can make a value appear healthier than it is.

Checklist

  • Does the label say what the number is in the viewer’s words, not the metric name in the database?
  • Is there a unit, and is it the unit the viewer uses?
  • What time window does this number cover, and can the viewer tell?
  • What is the number compared against, and is that comparison honest (same window, same denominator)?
  • If the tile is colored, where are the thresholds defined, and who can change them?
  • What does the tile show when the data source is down, empty, or stale?
  • Can the viewer click it, and if so, where do they land?
  • Do all tiles in the row share the same anatomy: label position, number size, delta placement?
  • Are the digits tabular so the row doesn’t jitter as values change?
  • Is the number still readable from across the room if this ever goes on a wall?

Compare

Grafana’s stat panel is the most configurable version in the wild: value, sparkline background, threshold coloring, and unit formatting are all first-class, which means it’s also the easiest to misuse. Sentry puts a sparkline and an event count inline on every issue row rather than in a tile, which is the same pattern at list density. Stripe’s home tiles pair today’s value with a ghost line of the prior period, so the comparison is a shape, not a percentage. Plausible uses a strip of five tiles as the entire top of the page and switches the main chart when you click one, which turns the tile into a navigation control.

Delta indicator supplies a comparison. Sparkline adds recent history. Header KPI strip groups tiles into a summary row.

Semantic status color defines state cues. Freshness indicator shows how current the value is.

KPI tile anatomy A tile broken into four numbered parts: label, value, context and window. Below, the context slot filled three ways—a delta, a state colour, and a trend line. Anatomy Checkout success 1 99.4% 2 3 Last 24 hours 4 1 LABEL What the viewer calls it, not the column name. 2 VALUE One number, sized to be read across a room. 3 CONTEXT Delta, state colour, or trend. Without one of the three, the number is a decoration. 4 WINDOW How much time the number covers. The context slot, three ways All three answer the same question: is 99.4% fine? 0.3 pts Delta ABOVE TARGET State colour Trend Six tiles read as one status strip only when all six pick the same answer.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Composition rather than a component. A tile is shadcn's Card plus the three pieces this library already specifies—the delta with its window, the freshness of the data, and a status the viewer can read at a glance.

shadcn
npx shadcn@latest add card
Tokens
--foreground--muted--muted-foreground--border--status-nominal--chart-1--direction-up

Checkout success

99.4%

+0.3%Last 24 hours

KpiTile.tsxAssembles Delta and the freshness dot. The context slot is not optional.

import { Card } from "@/components/ui/card";
import { Delta } from "../delta-indicator/Delta";
import { Sparkline } from "../sparkline/Sparkline";
import { freshness, FRESHNESS_COLOR } from "../freshness-indicator/freshness";
import type { Polarity } from "../red-green-direction/direction";

/**
 * A tile is a composition, not a primitive.
 *
 * shadcn gives you the Card. What it cannot give you is the rule that a number
 * without context is a decoration: the viewer needs to know which way it moved,
 * against what, and how old it is. So `delta` and `lastArrival` are required
 * props rather than optional garnish, and a tile that cannot supply them should
 * be a plain figure instead of pretending to be a KPI.
 */
export function KpiTile({
  label,
  value,
  delta,
  window,
  polarity = "higher-is-better",
  lastArrival,
  expectedEveryMs,
  now,
  trend,
}: {
  label: string;
  value: string;
  delta: number;
  window: string;
  polarity?: Polarity;
  lastArrival: Date;
  expectedEveryMs: number;
  /** The clock to age against. Defaults to now; pass one to render on a server. */
  now?: Date;
  /** The shape that got here: the third context signal, beside the delta. */
  trend?: number[];
}) {
  const { state } = freshness(lastArrival, expectedEveryMs, now);
  return (
    <Card className="p-4">
      <div className="flex items-start justify-between gap-2">
        <p className="text-xs text-muted-foreground">{label}</p>
        <span
          className="mt-1 size-2 shrink-0 rounded-full"
          style={{ background: FRESHNESS_COLOR[state] }}
          title={`data is ${state}`}
        />
      </div>
      <p className="mt-1 text-2xl font-semibold tabular-nums text-card-foreground">{value}</p>
      {trend && (
        <div className="mt-2 text-chart-1">
          <Sparkline values={trend} width={130} height={26} />
        </div>
      )}
      <Delta value={delta} window={window} polarity={polarity} />
    </Card>
  );
}

demo.tsxHow it is called: label, value, delta, window, and when the data last arrived.

import { KpiTile } from "./KpiTile";

/**
 * One tile with its context signals: the delta, the trend, the window, and
 * the freshness dot. The clock is fixed so the dot renders the same on the
 * server and in the browser.
 */
const NOW = new Date("2026-09-15T09:00:00Z");

export default function Demo() {
  return (
    <div className="w-[272px]">
      <KpiTile
        label="Checkout success"
        value="99.4%"
        delta={0.3}
        window="Last 24 hours"
        trend={[98.6, 98.9, 98.5, 99.2, 99.0, 99.4, 99.3, 99.4]}
        lastArrival={new Date(NOW.getTime() - 40_000)}
        expectedEveryMs={60_000}
        now={NOW}
      />
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

Screenshots are grouped by product and include the capture date. Dashed boxes highlight this pattern. Notes appear below each screenshot and when you hover over a box.

GitHub

The contribution calendar shows daily activity using a small set of color levels.

Profile / contribution graph September 11, 2026 github.com, signed out (public profile) medium · light · desktop-web
GitHub arranges daily contributions by week and weekday, with a yearly total above the grid. The five color levels make the calendar compact but limit precise comparisons between days. In this active profile, many cells have similar shades, making variation harder to see. The weekday rows still provide a useful way to look for recurring gaps or patterns.
Show 2 more examples Hide the rest

Grafana

Examples of panel grids, shared selectors, stat tiles, and chart configuration.

Examples / Polystat Visualization Showcase September 10, 2026 Grafana Play (signed out; no version string exposed) medium · dark · desktop-web
This showcase displays the same threshold-colored cells in several shapes. Hexagons touch along their edges, while circles leave gaps, changing how adjacent cells form visible groups. The choice matters when scanning many items together. Other panels demonstrate named states, sorting, and label-only cells, each emphasizing a different part of the reading.
  • Host map Touching hexagons make adjacent cells read as a group.
  • Host map Circles leave gaps between neighboring cells.
  • Semantic status color Cell fills represent OK, warning, and critical states.
  • Ranked list Descending order brings the highest values first.
  • KPI tile Label-only cells show a name and color without a numeric value.
Examples / Stats September 10, 2026 Grafana Play (signed out; no version string exposed) dense · dark · desktop-web
Grafana’s stat showcase compares value colors, background fills, layouts, and text options. Most examples emphasize presentation without adding a comparison value or clear reference point. The No text panel uses color alone, leaving cells without labels or numbers. These are useful demonstrations of settings, but a working dashboard needs enough context to explain each value and state.
  • KPI tile A label, value, unit, and sparkline appear without an explicit comparison.
  • Dark-first data palette The dark theme makes these value colors more prominent than in the light capture.
  • Sparkline Sparklines use individual scales, so row-to-row comparison needs care.
  • Semantic status color Whole-tile color strongly emphasizes every value in this group.
  • Grayscale with alerts Multiple strong colors compete for attention.
  • KPI tile Values appear without labels explaining what they measure.
  • Semantic status color The cells have no text, leaving color as the only visible cue.
  • Header KPI strip Consistent label and value positions make the tile row easier to scan.