Skip to content
KONIGI

Dashboards / Color / Red/green direction coloring

4 of 6

Red/green direction coloring

Finance readers expect up-green and down-red, and a fifth of them can't tell the two apart.

Updated September 10, 2026

Problem

Every price, every delta, every tick is coloured green for up and red for down. The convention is so entrenched that violating it confuses everyone, and honouring it makes the display unreadable for a substantial share of the audience.

Solution

Keep the convention, and never let it carry the information alone.

The convention is genuinely strong. In Western markets green means up and red means down, and traders read it faster than they read the sign. Discarding it would cost more than it saves. But red-green is precisely the axis most colour vision deficiency runs along: deuteranomaly and protanomaly are the common forms, and around one in twelve men is affected. On a screen of nothing but red and green numbers, that person is reading unmarked values.

The redundancy that fixes it is cheap and mostly already available.

Sign and symbol. A plus or minus, an arrow, a triangle. Reading direction off a glyph is faster than reading it off a hue anyway.

Lightness as well as hue. Choosing a light green and a dark red—rather than two colours of equal lightness—means the pair survives greyscale, which is the same property that makes it survive most colour vision deficiency.

Position. In a ladder or a table, up-moves above and down-moves below is structural and needs no colour at all.

Two further complications are worth knowing. The convention inverts in several Asian markets, where red means up and is auspicious, so a platform serving both audiences needs the mapping to be a preference rather than a constant. And red is simultaneously doing duty as the status colour for “error”, so a page can end up with red meaning “down” in one panel and “broken” in the next.

Use when

The audience genuinely holds the convention and the display is dense enough that speed matters. Trading, treasury, portfolio and finance dashboards.

Don’t use when

The audience is general. Outside finance the red-green mapping carries less weight and costs the same accessibility, so a neutral encoding with explicit signs is usually better. And avoid it for anything where “up” is not obviously good or bad—up is good for revenue and bad for latency, and colouring both green because both rose is meaningless.

Trade-offs

The convention makes an entire class of display inaccessible when used alone, and the population affected rarely self-identifies at work. Redundant encoding costs space on displays already fighting for it. Palette inversion for different markets doubles testing. And the collision with status red means a viewer must infer from context whether red is a direction or a failure, which is exactly the ambiguity a semantic palette is supposed to eliminate.

Checklist

  • Is direction encoded by anything other than hue?
  • Do the two colours differ in lightness as well as hue?
  • Does the display survive being viewed in greyscale?
  • Is the up/down colour mapping a user preference for markets that invert it?
  • Does red mean “down” and “error” anywhere on the same screen?
  • Is the neutral or unchanged state visually distinct from both?
  • Are gains and losses distinguishable at the smallest text size used?
  • Has this been checked with a colour vision deficiency simulator?
  • Is “up” unambiguously good or bad for this metric, and does the colour reflect that rather than the sign?
  • Would an arrow or sign make the colour redundant enough to soften it?

Compare

Bloomberg and the terminal tradition established the convention and pair it with dense numeric detail, so the colour is a fast path rather than the only path for a trained reader. Crypto exchanges inherited the convention wholesale and generally add explicit signs and percentages, which makes them accidentally more accessible than the terminals they copied. Trading platforms serving multiple markets expose the colour mapping as a setting, which is the correct structural answer to a convention that is not universal. Grafana has no notion of direction colouring, so a finance dashboard built on it uses value mappings and thresholds to reconstruct it, which at least forces someone to declare the rule explicitly.

Semantic status color is the palette this convention collides with. Delta indicator is where the good-versus-bad decision belongs and is most often confused with up-versus-down. Order book is the display where this is most entrenched and most consequential. Categorical series palette shares the constraint from another angle. KPI tile is where a coloured delta reaches a general audience who may not hold the convention at all.

Red/green direction colouring anatomy The same six price moves twice. On the left, direction is carried by hue alone, so every row looks identical. On the right, the same rows carry a glyph, a signed number, a lightness difference and a position either side of zero. The same six moves, twice 1 2 3 Ticker Move AAPL 1.24 MSFT 0.86 NVDA 2.05 AMZN 0.12 META 0.44 TSLA 1.91 Hue only Ticker Move AAPL +1.24 MSFT −0.86 NVDA +2.05 AMZN −0.12 META +0.44 TSLA −1.91 Glyph, sign, lightness, position 1 GLYPH AND SIGN Reading direction off a triangle is faster than reading it off a hue anyway. 2 LIGHTNESS A light green against a dark red survives greyscale, and so survives most CVD. 3 POSITION Above and below a zero line is structural. It needs no colour at all. Red means up in several Asian markets, so the mapping is a preference, not a constant.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Up is not always good. Churn rising, latency rising, cost rising—a token called --positive would have to lie about all three. These are named for direction and the metric decides what it means.

Tokens
--card--card-foreground--muted-foreground--border--direction-up--direction-down--direction-flat
TickerMove
  • AAPL+1.24
  • MSFT−0.86
  • NVDA+2.05
  • AMZN−0.12
  • META+0.44
  • TSLA−1.91

direction.tsThe metric says whether up is good. The colour follows that, not the sign, and the market mapping is a parameter.

/**
 * Direction is arithmetic. Whether it is good news is a property of the metric.
 *
 * Revenue up is good, churn up is bad, and both are "+12%". A token named for
 * sentiment forces the caller to lie about one of them, so the tokens are named
 * --direction-up / --direction-down and the polarity below decides which one a
 * given metric should wear.
 */
export type Polarity = "higher-is-better" | "lower-is-better" | "neutral";

/**
 * Which hue "up" wears is a market convention, not a constant. Several Asian
 * markets read red for up, so a platform serving both audiences keeps this as
 * a user preference and passes it through.
 */
export type Mapping = "western" | "inverted";

export function directionColor(delta: number, polarity: Polarity, mapping: Mapping = "western"): string {
  if (delta === 0 || polarity === "neutral") return "hsl(var(--direction-flat))";
  const good = polarity === "higher-is-better" ? delta > 0 : delta < 0;
  const up = mapping === "inverted" ? !good : good;
  return up ? "hsl(var(--direction-up))" : "hsl(var(--direction-down))";
}

/**
 * Colour is never the only carrier.
 *
 * The glyph goes with the sign, always, so the reading survives greyscale and
 * the roughly one man in twelve who cannot separate the two hues. In markets
 * that invert the convention this mapping is a user preference, not a constant.
 */
export const directionGlyph = (delta: number) => (delta === 0 ? "→" : delta > 0 ? "▲" : "▼");

MoveTable.tsxGlyph, sign, a bar either side of zero, and hue last. Every row carries direction three ways before colour gets a say.

import { directionColor, directionGlyph, type Mapping, type Polarity } from "./direction";

export type Move = { ticker: string; move: number };

type Props = {
  rows: Move[];
  /** For a price, higher is better from the holder's side. A cost ladder
   *  would pass lower-is-better and the same rows would swap colour. */
  polarity?: Polarity;
  /** The market convention, threaded through from user settings. */
  mapping?: Mapping;
};

/** Signed always, with a real minus. The sign is the cheapest carrier there is. */
const signed = (n: number) => `${n > 0 ? "+" : n < 0 ? "−" : ""}${Math.abs(n).toFixed(2)}`;

/**
 * Four carriers per row and hue is the fourth. The glyph and the sign go
 * with the arithmetic; the bar sits left or right of a zero line, which is
 * structural and needs no colour at all; the tokens differ in lightness, so
 * the hue survives greyscale when it is all that is left.
 */
export function MoveTable({ rows, polarity = "higher-is-better", mapping = "western" }: Props) {
  const max = Math.max(...rows.map((r) => Math.abs(r.move)), 0.01);
  return (
    <div className="rounded-lg border bg-card p-4">
      <div className="flex items-baseline border-b pb-2 text-[11px] uppercase tracking-wide text-muted-foreground">
        <span className="w-14">Ticker</span>
        <span className="w-[4.25rem] text-right">Move</span>
      </div>
      <ul className="mt-1 text-xs tabular-nums">
        {rows.map((r) => {
          const color = directionColor(r.move, polarity, mapping);
          const half = (Math.abs(r.move) / max) * 50;
          return (
            <li key={r.ticker} className="flex items-center gap-2 py-1.5">
              <span className="w-14 text-card-foreground">{r.ticker}</span>
              <span className="w-2.5 text-center text-[9px] leading-none" style={{ color }} aria-hidden="true">
                {directionGlyph(r.move)}
              </span>
              <span className="w-12 text-right" style={{ color }}>{signed(r.move)}</span>
              <span className="relative ml-2 h-2.5 flex-1" aria-hidden="true">
                <span className="absolute inset-y-[-2px] left-1/2 w-px bg-border" />
                <span
                  className="absolute h-full"
                  style={{ background: color, width: `${half}%`, [r.move >= 0 ? "left" : "right"]: "50%" }}
                />
              </span>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

demo.tsxHow it is called: six moves, three each way, in the western mapping.

import { MoveTable } from "./MoveTable";

/** Six price moves, three each way, in the western mapping. */
export default function Demo() {
  return (
    <div className="w-[320px]">
      <MoveTable
        rows={[
          { ticker: "AAPL", move: 1.24 },
          { ticker: "MSFT", move: -0.86 },
          { ticker: "NVDA", move: 2.05 },
          { ticker: "AMZN", move: -0.12 },
          { ticker: "META", move: 0.44 },
          { ticker: "TSLA", move: -1.91 },
        ]}
      />
    </div>
  );
}
What it renders. Identical markup in all three panes, with only the token values changing.

Examples

Captures whose hotspots reference this pattern, grouped by product and dated. The dashed boxes are this pattern; hover any box for the note.

Kraken Pro

The full order book and depth ladder are public with no account, which makes it the only display here updating several times a second that anyone can go and watch.

Trade / BTC-USD September 11, 2026 Kraken Pro, signed out (public market data) dense · dark · desktop-web
The red-green-direction entry argues that the convention should never carry the information alone, and this ladder shows the redundancy arriving for free. Asks are red and bids are green, but asks are also above the spread and bids below it, so the side is encoded twice—once in a hue that a colourblind reader may not separate, and once in a position that everyone can. Convert this panel to greyscale and it still works. The spread row between them is the other good decision: it reads "0.1 (0.0001%)", the absolute and the ratio side by side, so the number means something whether you are trading one Bitcoin or a hundred. Two details worth noticing. The quantity column runs to eight decimal places, which is correct for the asset and unreadable at a glance, and the depth bars behind each row are doing most of the actual communicating. And the "0.10" control at the top of the panel is the price increment the book is aggregated into—change it and the number of levels changes underneath you.
  • Order book and depth Two ladders sharing a price axis, with the spread labelled between them and a depth bar behind every row.
  • Red/green direction coloring Asks in red, and also above the spread. Position carries the side on its own, so greyscale survives.
  • Ratio and rate The spread as 0.1 and as 0.0001% together, which is the absolute and the normalised form on one line.
  • Delta indicator −197.0 USD and −0.26% together, over a named 24-hour base. All three of the decisions, made and stated.
  • Time series Candles rather than a line, so each interval carries open, high, low and close instead of one sampled value.
  • Order book and depth The price increment the book is aggregated into. Change it and the level count changes under the eye.