Skip to content
KONIGI Patterns

Dashboards / Color / Semantic status color

‹ 5 of 6 ›

Semantic status color

People need consistent status cues to spot items requiring attention.

Updated September 10, 2026

Problem

People scanning many items need to find those requiring attention before reading every value.

Solution

Define a small set of status colors and use each one consistently. Keep those colors distinct from decorative accents or category identities where confusion is likely.

Pair every status with a word, icon, shape, or position so color is not the only cue. Check contrast and legibility in every supported theme.

Keep the rules behind the statuses visible and maintained. Show unknown or missing data separately from a healthy result.

Use when

People need to prioritize items by their current condition.

Don’t use when

The quantity is continuous and the magnitude matters. Reducing latency to amber loses the difference between 210ms and 900ms. Use colour for state and let a number or a bar carry the amount.

Trade-offs

Threshold colors can make nearly identical values appear categorically different. Familiar color meanings may vary between products or audiences. Persistent warnings lose prominence, especially when their underlying rules are outdated.

Checklist

  • How many states, and is the set closed and written down?
  • Does any of these colours also appear decoratively or as a series colour on the same page?
  • Is there a non-colour signal for every state—icon, shape, label, position?
  • Do the colours meet contrast requirements against both light and dark backgrounds?
  • Has the palette been checked against the common colour vision deficiencies?
  • Does saturation or weight distinguish severity, or are warning and critical equally loud?
  • Where are the thresholds defined, and who can change them?
  • What does “unknown” or “no data” look like, and is it distinct from healthy?
  • Does the same colour mean the same thing across every page in this product?
  • Is anything permanently coloured, and has anyone noticed?

Compare

Grafana attaches colour to thresholds as a field property, so status colour is derived from a rule rather than set by hand, and the same thresholds drive the stat panel, the gauge, the table cell and the state timeline. That consistency is the strongest argument for defining status colour at the data layer rather than the panel layer. Sentry encodes severity levels on the event rather than on the display, so colour follows the data into lists, alerts and digests. Datadog ties colour to monitor state, meaning what you see is what would page someone, which keeps the page and the alerting honest with each other. Public status pages typically run three states and always pair colour with a word, because their audience is unknown and cannot be assumed to share any convention.

Grayscale with alerts limits competing color. Threshold line shows a status boundary. Status history applies states over time.

Red/green direction coloring shows movement instead of health. Header KPI strip uses status to guide the first scan.

Semantic status colour anatomy Four status values, each a swatch paired with its own shape, its own word and the threshold that fires it. Beside them, the same swatch doing three different jobs on one page: a status, a brand accent and the third series in a chart. A closed set of four State Fires when OK p95 under 300ms WARNING 300ms to 800ms CRITICAL over 800ms UNKNOWN no sample for 5m Four values, defined once HEALTHY a state a button series 3 The same swatch, three jobs 1 2 1 PAIRED, ALWAYS Every value gets a shape and a word as well as a swatch. A red dot and a green dot that differ only in hue are a coin flip for one man in twelve. 2 HOW IT GETS LOST One reasonable exception at a time. The moment the status green is also the brand green it stops being a signal and becomes a coincidence. Every colour is a claim that a rule fired. A stale rule is confidently wrong.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

shadcn ships a Badge with four variants and none of them is a status. It has --destructive, which means a destructive action, not a system on fire. This is what you add to close that gap.

shadcn
npx shadcn@latest add badge
npm
class-variance-authority clsx tailwind-merge lucide-react
Tokens
--card--card-foreground--muted-foreground--border--status-critical--status-warn--status-nominal--status-unknown
StateFires when
  • Nominalp95 under 300ms
  • Warning300ms to 800ms
  • Criticalover 800ms
  • No datano sample for 5m

status.tsThe closed set. A fifth status cannot be invented at a call site.

import type { LucideIcon } from "lucide-react";
import { AlertTriangle, CheckCircle2, CircleHelp, OctagonAlert } from "lucide-react";

/**
 * The closed set.
 *
 * Adding a state means changing this union, which is the whole point: the
 * compiler refuses anything that is not one of these four, so a fifth status
 * cannot quietly appear at a call site six months from now. "How many states,
 * and is the set closed and written down?" stops being a review question.
 */
export const STATUSES = ["nominal", "warn", "critical", "unknown"] as const;
export type Status = (typeof STATUSES)[number];

/**
 * Every state carries a non-colour signal.
 *
 * This makes "is there a non-colour signal for every state" structural rather
 * than advisory—there is no way to render a status without also rendering its
 * icon and its label, because the component reads both from here.
 *
 * `unknown` is deliberately not a quiet success. No data and healthy are
 * different answers and they must not look alike.
 */
export const STATUS_META: Record<Status, { label: string; Icon: LucideIcon }> = {
  nominal: { label: "Nominal", Icon: CheckCircle2 },
  warn: { label: "Warning", Icon: AlertTriangle },
  critical: { label: "Critical", Icon: OctagonAlert },
  unknown: { label: "No data", Icon: CircleHelp },
};

StatusBadge.tsxWraps shadcn's Badge. The icon is structural, not decoration.

import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { STATUS_META, type Status } from "./status";

/**
 * Severity is carried by three things at once: hue, lightness, and an icon.
 * Hue alone fails in greyscale and fails for the common colour vision
 * deficiencies, both of which are checklist items on this pattern.
 *
 * The tokens are --status-*, never --destructive. shadcn's --destructive means
 * "this button deletes things"; a critical alarm is a state of the world. They
 * often land on similar reds and they are not the same semantic.
 */
const statusBadge = cva(
  "inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium",
  {
    variants: {
      status: {
        nominal: "border-status-nominal/30 bg-status-nominal/10 text-status-nominal",
        warn: "border-status-warn/30 bg-status-warn/10 text-status-warn",
        critical: "border-status-critical/40 bg-status-critical/10 text-status-critical",
        unknown: "border-status-unknown/30 bg-status-unknown/10 text-status-unknown",
      },
    },
    defaultVariants: { status: "unknown" },
  },
);

export function StatusBadge({ status, className }: { status: Status; className?: string }) {
  const { label, Icon } = STATUS_META[status];
  return (
    <span className={cn(statusBadge({ status }), className)}>
      <Icon aria-hidden="true" className="size-3.5 shrink-0" />
      {label}
    </span>
  );
}

demo.tsxHow it is called: the four values from STATUSES, each beside the rule that fires it.

import { STATUSES } from "./status";
import { StatusBadge } from "./StatusBadge";

/** The closed set, each value beside the rule that fires it. */
const FIRES_WHEN = {
  nominal: "p95 under 300ms",
  warn: "300ms to 800ms",
  critical: "over 800ms",
  unknown: "no sample for 5m",
} as const;

export default function Demo() {
  return (
    <div className="w-[360px] rounded-lg border bg-card p-4">
      <div className="flex items-baseline justify-between border-b pb-2 text-[11px] uppercase tracking-wide text-muted-foreground">
        <span>State</span><span>Fires when</span>
      </div>
      <ul className="mt-1">
        {STATUSES.map((s) => (
          <li key={s} className="flex items-center justify-between gap-3 py-2 text-xs">
            <StatusBadge status={s} />
            <span className="tabular-nums text-muted-foreground">{FIRES_WHEN[s]}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}
What it renders. Identical markup in all three 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.

Ignition

A process-monitoring demo uses simulated plant data and offers simple or realistic equipment rendering.

Water Treatment / Overview September 11, 2026 Ignition Perspective public demo, Water Treatment (signed out) dense · dark · desktop-web
The demo uses simulated plant data in a process diagram arranged from incoming water through treatment and storage. Values appear beside the relevant equipment. Green means running, while stopped filters are gray and explicitly labeled. This makes normal operation colorful, unlike a display that reserves color for warnings. Repeated filter panels and clear process connections help people compare equipment.
Show 10 more examples Hide the rest

Grafana

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

Examples / Alert List September 10, 2026 Grafana Play (signed out; no version string exposed) medium · dark · desktop-web
This list shows seven alerts with their states and durations. Several have been firing for days, which raises a question about whether the rules still prompt a useful response. The names use inconsistent conventions and often do not describe the affected system clearly. Pending and firing states are labeled separately, and each row links back to its rule.
Where The Cool Dashboards Live / Chicago L Trains September 10, 2026 Grafana Play (signed out; no version string exposed) sparse · dark · wall
The departure board gives the next train more space than later arrivals and uses large text without hover-only details. Route colors connect the display to the train lines people already know. Green also appears in the On Time status, so the same hue has two meanings in one row. The status label helps distinguish them. Countdown values are useful, but the board also needs a clear indication of feed age.
  • Wallboard mode The next departure receives the largest text and panel.
  • Panel grid Later departures use smaller rows, making their lower priority visible.
  • Semantic status color Green identifies both a route and an on-time status; the words distinguish the meanings.
  • Freshness indicator A countdown shows time until arrival but does not state the feed’s age.
  • Data table The raw feed includes the color value for each route.
Examples / Gauge September 10, 2026 Grafana Play (signed out; no version string exposed) medium · dark · desktop-web
The showcase includes several gauges with similar disk values. Comparing them requires reading the numbers, while each arc uses substantial space. A compact set of bars would make those values easier to compare. Elsewhere, different values share the same red treatment, making the threshold definition important to understanding the result. The examples also mix color ramps, sparklines, and threshold bands.
Examples / Node graph panel September 10, 2026 Grafana Play (signed out; no version string exposed) sparse · dark · desktop-web
Several service nodes extend beyond the panel’s right edge, so the full graph is not visible at once. Each node contains two values and a colored ring, with a legend explaining the measures. This adds detail but makes the map harder to scan for a single overall state. The zoom control helps reach the clipped nodes, though a clearer initial fit would make the relationships easier to inspect.
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, light theme September 10, 2026 Grafana Play (signed out; ?theme=light) dense · light · desktop-web
This light-theme capture shows the same stat-panel options as the dark version. Several value colors become pale against white, especially pink and orange. Whole-tile fills retain a different relationship between text and background, while the faint sparkline areas are harder to see. The pair is useful for checking each treatment on both backgrounds rather than assuming a theme switch preserves readability.
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.
Examples / State timeline and Status history September 10, 2026 Grafana Play (signed out; no version string exposed) medium · dark · desktop-web
This page places a state timeline above a time-series view of the same query. The timeline converts numeric ranges into named states, making the periods easier to scan. The line chart keeps the numeric variation available for closer reading. Labels inside the colored regions explain their meaning, reducing reliance on color alone.
Examples / Table September 10, 2026 Grafana Play (signed out; no version string exposed) dense · dark · desktop-web
The tables demonstrate colored cells, gradients, images, bars, pills, and richer text. Some treatments become less useful in narrow columns: a bar is squeezed into a thin strip and nearby text is cut off. Another table shows many pages of results alongside sort controls. The interface needs to make clear whether sorting applies to all returned records or a smaller subset.

Home Assistant

Room-based card layouts combine household device states with controls.

Demo dashboard September 10, 2026 Home Assistant public demo (signed out) medium · light · desktop-web
Cards are grouped by rooms such as Living room, Kitchen, and Study. States are written in words, with icon color providing a second cue. Section headers add useful room summaries, such as temperature or meeting status. The page also contains controls, including lighting sliders and thermostat buttons, so people can act in the same place they check the state.