Forced Colours Mode for SVG Charts

Permalink to "Forced Colours Mode for SVG Charts"

Forced colours mode — Windows High Contrast themes, and the equivalent settings in some other environments — replaces a page’s colours with a small user-chosen palette: typically a background, a text colour, a link colour, a highlight and a few more. It is used by many low-vision users and people with light sensitivity. Browsers apply it to HTML automatically. SVG is handled less gracefully: inline SVG fill and stroke from CSS are forced in Chromium, but presentation attributes and some gradients are not consistently, so charts can end up with invisible lines, text drawn in the background colour, or every series rendered in the same single colour.

This page shows what happens to charts under forced colours and how to adapt them. It belongs to chart colour & contrast.

Spec reference

Permalink to "Spec reference"

CSS Color Adjustment Level 1 defines:

  • The forced-colors: active media feature, true when a forced colour palette is in effect.
  • System colour keywords: Canvas, CanvasText, LinkText, VisitedText, ActiveText, ButtonFace, ButtonText, ButtonBorder, Field, FieldText, Highlight, HighlightText, SelectedItem, SelectedItemText, Mark, MarkText, GrayText, AccentColor, AccentColorText.
  • forced-color-adjust: auto | none | preserve-parent-color — none opts an element out of forcing, keeping author colours.

In forced colours, browsers override color, background-color, border-color, outline-color, fill and stroke (for SVG, in Chromium) with system colours, remove box-shadow and text-shadow, and remove background images in some cases. Contrast between system colours is guaranteed by the user’s chosen theme, not by you.

WCAG does not require forced-colours support directly, but content that disappears in it fails users in the same way as SC 1.4.1, 1.4.3 and 1.4.11 failures do, and several criteria’s understanding documents discuss it.

What happens to chart parts in forced colours Matrix of chart parts and what typically happens to each under forced colours mode in Chromium, with the recommended fix. What happens to chart parts in forced coloursChart partTypical resultFixSeries fill coloursAll forced to one colourPatterns or dashes per seriesLine strokes via CSSForced to CanvasTextDashes distinguish seriesText via CSS fillForced to CanvasTextNothing neededFocus ring via box-shadowRemovedUse outline or a strokeGradients and opacityUnpredictableSolid fills plus patterns
Fills collapse to one colour and shadows vanish — dashes, shapes, patterns and outlines survive.

When to preserve colours — and when to let them be forced

Permalink to "When to preserve colours — and when to let them be forced"

Let the browser force colours for almost everything: text, axes, gridlines, lines, focus indicators. The user chose their palette for a reason; overriding it defeats the mode’s purpose.

Consider forced-color-adjust: none only for data marks whose specific colours carry meaning that cannot be expressed otherwise — a heat map’s colour scale, a map’s choropleth — and then provide a legend and a data table, and ensure the marks have outlines in a system colour so they remain distinct from the background.

The misapplication to name is forced-color-adjust: none on the whole chart to “keep the brand palette”. Users in high contrast mode then get a low-contrast chart in a high-contrast page — exactly what they are trying to avoid.

Annotated code example

Permalink to "Annotated code example"
/* Normal mode: series by colour AND dash (see patterns guide) */
.series-1 { stroke: var(--series-1); }
.series-2 { stroke: var(--series-2); stroke-dasharray: 6 4; }
.series-3 { stroke: var(--series-3); stroke-dasharray: 2 3; }

@media (forced-colors: active) {
  /* Let the palette win, but make roles explicit with system colours */
  .chart-bg      { fill: Canvas; }
  .chart-axis,
  .chart-grid    { stroke: GrayText; }
  .chart text    { fill: CanvasText; }
  .series-line   { stroke: CanvasText; }           /* dashes now carry identity */
  .bar           { fill: Canvas; stroke: CanvasText; stroke-width: 2; }   /* outlined bars */
  .bar.series-2  { fill: url(#hatch-forced); }     /* pattern drawn in CanvasText */

  /* Focus and selection: use the user's highlight colours */
  .point:focus-visible,
  .point.is-active { stroke: Highlight; stroke-width: 3; }
  .point.is-selected { fill: SelectedItem; }

  /* box-shadow focus rings disappear in forced colours; use outline instead */
  .chart:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
}

/* A heat map genuinely needs its scale: keep colours, add system-colour cell borders */
@media (forced-colors: active) {
  .heatmap-cell { forced-color-adjust: none; stroke: CanvasText; stroke-width: 1; }
}

The chart’s SVG should apply colours through classes and CSS rather than presentation attributes (fill="#155c38"). CSS rules are reliably forced and can be overridden in the media query; attribute colours are handled inconsistently across browsers.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"

Forced colours affects visibility, not semantics; the checks are visual, performed with the keyboard.

Check Expected in forced colours Failure indicator
Tab to the chart Visible outline in Highlight No visible focus (box-shadow removed)
Arrow through points Active point ringed in Highlight Ring invisible or same as line
Series distinction Dashes, shapes or patterns differ All series identical
Tooltip Text in CanvasText on Canvas with a border Borderless box merges with the plot
Selected points SelectedItem fill No difference from unselected
Opting out of forced colours, wholesale versus targeted Comparison of applying forced-color-adjust none to a whole chart against letting colours be forced and opting out only for colour-scale marks with system-colour borders. Opting out of forced colours, wholesale versus targeted✗ Whole chart opted outBrand palette on a high contrast pageLow-contrast lines for users who need highText and axes ignore the user's themeDefeats the purpose of the mode✓ Targeted opt-outText, axes, lines use system coloursSeries distinguished by dashes and patternsOnly colour-scale cells keep author coloursThose cells outlined in CanvasText
Respect the user's palette; preserve colour only where the colour is the data.

Integration context

Permalink to "Integration context"

Forced colours is where a chart’s second channel proves itself: if series are distinguished only by colour, they become identical. The encodings are covered in patterns and shapes for colour-independent charts. The audit procedure — emulation plus a real Windows contrast theme — is in Windows High Contrast audits for data UIs.

Inline SVG diagrams across a site face the same issue; theme-aware SVG styling through classes and CSS custom properties, rather than hard-coded attributes, makes both dark mode and forced colours easier.

Testing a chart in forced colours Four steps to test a chart under forced colours: emulate in DevTools, check series distinction, check focus and selection, and confirm on a real Windows contrast theme. Testing a chart in forced coloursEmulateDevTools Rendering: forced-colors activeChromiumSeriescan every series be told apart?dashes, shapes, patternsFocus and selectionTab and arrow; is the active point visible?Highlight, SelectedItemReal themeWindows Contrast theme, e.g. Night sky or DesertEdge and Chrome
Emulation catches most problems; one pass on real Windows catches the rest.

Gotchas

Permalink to "Gotchas"

Presentation attributes. fill="#..." on SVG elements is not always forced. Move colours to CSS classes.

Images of charts. A chart exported as PNG is never adapted. Prefer inline SVG, and give images a text alternative and data table.

currentColor tricks. Icons and marks drawn with currentColor adapt automatically — often the easiest route for axes and simple marks.

Design system notes

Permalink to "Design system notes"

Chart components should style every mark through classes mapped to tokens, include a forced-colours stylesheet that maps roles (background, text, axis, series line, focus, selection) to system colours, and forbid presentation-attribute colours in chart code via lint rules. Colour-scale charts get a documented, targeted opt-out with outlines.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
What happens to SVG charts in Windows High Contrast mode?

The browser replaces CSS colours with the user’s system palette, so series fills often become the same colour, shadows disappear, and anything relying on colour alone is lost. Dashes, shapes, patterns and outlines survive.

Should charts use forced-color-adjust: none?

Only for marks whose colour is the data, such as heat map cells, and then with system-colour borders and a legend. Opting a whole chart out ignores the palette the user chose.

How do I keep chart focus indicators visible in forced colours?

Use outline or SVG strokes rather than box-shadow, which is removed in forced colours, and colour them with the Highlight system colour.

How do I test forced colours without Windows?

Use the forced-colors emulation in Chromium DevTools’ Rendering panel for most checks, and confirm on a real Windows contrast theme before release.

Permalink to "Related"

← Back to Chart Colour & Contrast