Yugen UI

Data visualization

A chart is read by people and rendered by machines. Choose the form first, assign color by task, and check the palette with a tool instead of by eye before you draw.

Choose a chart form

Color comes last. First ask what the reader needs to do with the data: compare magnitudes, tell series apart, see positive and negative values, or read a single number?

TaskDefault formJob of colorIn Yugen
One current value (+ trend)A stat tile, not a one-column chart—StatTile
Compare magnitudesColumns; horizontal bars for many categories / long namesOne color (slot 1)BarChart
Trend over timeLine; area if there is only one seriesOne color or categoricalLineChart AreaChart
Tell several series apartGrouped / stacked columns, several linesCategoricalBarChart LineChart
One series is the focusHighlight: one color, the rest gray1 color + grayhighlight
Above / below zeroDiverging columnsDiverging (2 poles + gray midpoint)diverging
Part of a wholeStacked columns; donut with ≤ 6 slices for a quick readCategoricalstacked DonutChart
Relationship between two measuresScatter; bubbles when there is a third dimensionOne color; ≤ 3 categorical groupsScatterChart
Magnitude on a 2D grid (hour × day, cohorts)HeatmapSequential; diverging when there is a midpointHeatmap
More than ~7 equally important groupsTable (or table + chart)—Table

Series count: with 1–3 series, color alone is enough; 4 series require direct labels; 5–6 is the soft limit (legend or small multiples); 7–8 is the token limit. A 9th series never gets a new color: merge it into “Other”, split into small multiples, or combine encodings.

The answer is one number

Orders today
1,284
Increase+6.4%vs. yesterday

The story is one series

Palette & tokens

Each color does one job: categorical (identity), sequential (magnitude), diverging (sign), status (good/bad, reserved). Yugen's categorical palette starts with Sakura in a fixed order, and that order is what keeps it safe for color-blind readers.

Light · surface #FFFFFF

  1. 1Sakura 桜#C2347A5.16:1
  2. 2Ai 藍 · indigo#2F6FD04.88:1
  3. 3Kohaku 琥珀 · amber#B783003.35:1
  4. 4Fuji 藤 · wisteria#7A5AD64.93:1
  5. 5Matcha 抹茶 · jade#0E9F8A3.31:1
  6. 6Kaki 柿 · persimmon#D65A223.93:1
  7. 7Sora 空 · sky#0A8FB83.73:1
  8. 8Koke 苔 · moss#4E8A1E4.22:1

Dark · surface #292929

  1. 1Sakura 桜#E24A923.90:1
  2. 2Ai 藍 · indigo#4A8EEB4.41:1
  3. 3Kohaku 琥珀 · amber#C084004.53:1
  4. 4Fuji 藤 · wisteria#9079E64.20:1
  5. 5Matcha 抹茶 · jade#14A08A4.45:1
  6. 6Kaki 柿 · persimmon#E0662E4.23:1
  7. 7Sora 空 · sky#22A4C64.98:1
  8. 8Koke 苔 · moss#5FA0324.55:1

Dark is not an automatic inversion: the same 8 hues each get their own step for dark surfaces (OKLCH L 0.48–0.67) and are checked again on #292929. In the High contrast theme every mark turns white, and identity moves to 45° / 135° hatching (columns, slices) and dashes (lines).

Check (validate_palette.js)Light · #FFFFFFDark · #292929
OKLCH lightness bandPASS: all 8 within 0.43–0.77PASS: all 8 within 0.48–0.67
Chroma ≥ 0.10PASSPASS
Red–green color blindness, adjacent pairs (ΔE ≥ 8)PASS: worst 12.9 (Kaki ↔ Matcha, deutan)PASS: worst 12.8 (Kaki ↔ Matcha, protan)
Normal vision, adjacent pairs (ΔE ≥ 15)PASS: worst 19.8 (Koke ↔ Sora)PASS: worst 19.0 (Koke ↔ Sora)
Contrast with the surface ≥ 3:1PASS: 3.31 … 5.16PASS: 3.90 … 4.98
All pairs (scatter, maps)First 3 colors: ΔE 14.4, so 3 series at mostFirst 3 colors: ΔE 13.8, so 3 series at most
# From the dataviz skill folder: run once per mode, on the chart's actual surface
node scripts/validate_palette.js "#c2347a,#2f6fd0,#b78300,#7a5ad6,#0e9f8a,#d65a22,#0a8fb8,#4e8a1e" --mode light --surface "#ffffff"
node scripts/validate_palette.js "#e24a92,#4a8eeb,#c08400,#9079e6,#14a08a,#e0662e,#22a4c6,#5fa032" --mode dark --surface "#292929"

# "All pairs" forms (scatter, maps, small multiples): first 3 colors only
node scripts/validate_palette.js "#c2347a,#2f6fd0,#b78300" --mode light --surface "#ffffff" --pairs all

Light

Sequential · low → high
Diverging · negative · midpoint · positive
Other / background

Dark (reversed anchor)

Sequential · low → high
Diverging · negative · midpoint · positive
Other / background

Sequential is a single Sakura hue from light to dark (in dark the anchor is reversed, so small values sink into the background). For discrete ranks (funnels, rankings), use only steps 3–7 so the lightest step stays at ≥ 2:1 (checked with --ordinal). Diverging runs Sakura ↔ Ai with a neutral gray midpoint, never a hue. Status colors (success / warning / danger) are not part of the categorical palette and always come with an icon and text.

/* src/styles/charts.css — use in SVG via style={{ fill: "var(--…)" }} */
--colorChartCategorical1 … 8   /* series identity, fixed order */
--colorChartSequential1 … 7    /* magnitude, a single Sakura hue (ordinal: steps 3–7 only) */
--colorChartDivergingNeg / Mid / Pos
--colorChartOther              /* "Other", background series */
--colorChartSurface  --colorChartGrid  --colorChartAxis  --colorChartCrosshair
--colorChartLabel  --colorChartLabelStrong
--chartAreaOpacity  --chartDimOpacity

Marks & spacing

The quiet, deliberate look comes from a few fixed settings and two kinds of white space. Only the data gets to be loud.

MarkSpec
Column / bar≤ 24px (doesn't fill the slot), 4px rounding at the data end, square at the baseline, grows from a single baseline
Line2px, round caps and joins
Dot / end point≥ 8px, series color, 2px border in the surface color
AreaSeries color at ~10% (dark 14%): a light wash, not a solid block
Grid / axisThin 1px, solid (never dashed), one step off the surface
Surface gap2px between all marks that touch: stacked segments, adjacent columns, donut slices. No outline around marks
LabelsSelective: end points, extremes, the focus series. Text always uses text tokens, never the series color
Revenue (million ₫)
Two series, so there is a legend; thin columns, a 2px gap between the two columns in a group

Interaction

HTML/SVG charts are interactive by default. The tooltip adds detail; it is never the only way to read a value.

  • Line / area: a vertical crosshair snaps to the nearest X value; one tooltip lists every series at that point.
  • Column / slice: the mark itself is the hit area (wider than the mark, gap included); the selected mark stands out and the others dim.
  • In the tooltip, the value is bold and comes first; the series name is secondary, keyed by a short stroke in the series color (not a filled square). The legend mirrors the mark: a square for columns / areas, a stroke for lines.
  • On multi-series charts, legend items are toggle buttons; the remaining series keep their colors (color follows the entity, not its rank).
  • Filters (date range, dimension) sit in a row above all the charts they affect, not inside a chart card.
KeyBehavior
TabEnter the chart (a single tab stop); the tooltip shows for the focused point.
←→↑↓Previous / next point (the plot area is always LTR, so → moves forward, even on RTL pages).
HomeEndFirst / last point.
EscHide the tooltip.
Visits
Hover, or Tab into the chart and use the arrow keys

Accessibility

Identity never relies on color alone: there is always a legend with ≥ 2 series, direct labels with ≤ 4, and every chart has an equivalent data table.

  • The frame is a figure named by its title; the SVG plot area has an aria-label that summarizes the data; each focusable data point is role="img" labelled “category, series: value”.
  • The Bảng dữ liệu (Data table) button switches to a table with column and row headers, the WCAG-conformant equivalent of the chart.
  • The High contrast theme and forced-colors turn on hatching / dashes automatically; the texture prop turns them on manually (print, complete color blindness).
  • Numbers and dates are formatted with Intl using locale (default vi-VN): compact axis labels (1 N / 2,5 Tr in vi-VN, 1K / 2.5M in en-US), full values in tooltips and the table.
  • The plot area is always dir="ltr" (the time axis does not flip); the title, legend and tooltip follow the page's text direction.
  • Entry animations (columns growing from the baseline, lines drawing in) are turned off entirely with prefers-reduced-motion.
Table mode
defaultShowTable opens the table first; press “Bảng dữ liệu” (Data table) to go back to the chart
Column chart: 4 categories, 2 series (Online, In store). Use the arrow keys to move between values.
MonthOnlineIn store
Jul510325
Aug488340
Sep560352
Oct615360

Common mistakes

If a chart matches an item on the right, it is wrong. Fix it before you ship.

Do
  • Two measures on different scales → two charts, small multiples, or both indexed to a common base on one axis.
  • Color by entity: when some series are filtered out, the rest keep their colors.
  • The story is one number → StatTile; one series → highlight it and gray out the rest.
  • Thin 1px solid grid lines and axes that stay in the background; thin columns with room around them.
  • A label that doesn't fit inside the mark goes past the end of the column or into the tooltip; the value is still in the table.
  • Run validate_palette.js on every new palette, in both light and dark.
Don't
  • Dual Y-axis charts. They suggest correlations that don't exist.
  • Generating a 9th color, or cycling colors past 8 series.
  • A rainbow for sequential data; a hue at the diverging midpoint; two cool-toned poles.
  • Status colors used for “series 4”, or series colors used to signal status.
  • Donuts to compare close values, 2-slice pies, single-column charts.
  • Numbers on every point, dashed grid lines, outlines around marks, a tooltip as the only way to read values.