Styling

/

Themes and Export

Themes and Export

Use this page when a visual system must travel beyond one chart. Start with xy.theme() and reusable palette tokens, add light/dark host values, then pick an export path based on whether the output can run a browser CSS cascade.

Use this page to:

If you are still choosing a finished visual, start with Examples. To adjust a single mark, axis, legend, tooltip, or annotation, use Customize Each Part. For density, uncertainty, facets, and renderer edge cases, continue to the Advanced Gallery.

Start with the theme component

Use xy.theme() when the value belongs to the chart rather than one specific mark. Its named arguments are readable aliases for the standard tokens: for example, plot_background writes --chart-bg, while grid_color writes --chart-grid.

The preview separates two kinds of values:

  • plot_background, grid_color, axis_color, and text_color configure standard XY chrome through xy.theme().
  • The chart class supplies neutral light and dark hex values for the demo surface and chrome. The area keeps one saturated rose stroke and matching 30%-to-transparent fade so the theme choices stay visually distinct.
  • Axis components decide which chrome is drawn: both axes omit labels and tick marks, the x grid is disabled, and the y axis keeps only horizontal guides.

Keep a mark's stroke and fade stops on the same vivid hue:

Build a reusable palette

Application tokens are useful when several marks should move together. This grouped column chart maps three semantic names to three colors; swapping the values in style= restyles the whole preview without touching the marks. Explicit numeric offsets and narrow columns leave a visible gap inside every group.

Style resolution without surprises

Think from the narrowest styling surface to the broadest:

  1. Typed mark or axis props describe rendered geometry and paint.
  2. Component-local style= customizes one legend, tooltip, modebar, or annotation label.
  3. Chart styles={slot: ...} and class_names={slot: ...} apply a shared rule to that chrome slot.
  4. xy.theme() and chart-root style= define chart-wide tokens and root appearance.
  5. Host CSS supplies inherited defaults, application tokens, and color-mode values in a browser.

That list is a scope ladder, not one universal cascade. Marks and axes are painted geometry, while legends and tooltips are DOM chrome. When two declarations target the same property, use these concrete winner rules:

TargetStrongest to weakest
Mark paintMark style={"fill"/"stroke": ...} → typed paint prop such as fill=, color=, or line_width= → resolved token or palette default
Axis paintAxis style={...} → chart-local theme token → token inherited from host CSS → built-in default
One DOM chrome componentComponent style= → chart styles={slot: ...} → ordinary class/host rule → inherited theme token
Chart-root tokenChart style= → last xy.theme() → earlier xy.theme() → token inherited from a host ancestor → built-in default

Component style= and chart styles= become inline DOM styles. The component is merged last, so it wins for the same property. Classes and normal host CSS remain ideal for shared or stateful styling, but they do not override an inline declaration unless the host deliberately uses !important. A CSS variable is resolved where it is used: color="var(--series-primary)" does not make host CSS stronger than a chart-local --series-primary declaration.

For example, the mark is violet, the legend is compact, and the chart-root token is amber:

Use typed props for readable geometry, component styles for one-off chrome, chart slots for reusable chrome rules, and theme tokens for values that should move together across the whole chart.

Where should a token be set?

GoalRecommended location
Keep browser, SVG, and PNG exports consistentxy.theme(...) or chart-root style={...}
Share a palette across many browser chartsCSS variables on a common host ancestor
Let Reflex state choose a paletteResolve the state to a chart class or style mapping
Change only one markUse the mark's color, fill, stroke, or style directly

Token reference

TokenSurfaceDefault behavior
--chart-bgPlot backgroundtransparent
--chart-textTitles, ticks, legend, labelsinherited text color
--chart-gridCanvas grid linescurrent color at low opacity
--chart-axisAxis lines and tickscurrent color at medium opacity
--chart-tooltip-bg / --chart-tooltip-textTooltipdark translucent / white
--chart-legend-bgLegend backgroundfaint neutral fill
--chart-badge-bg / --chart-badge-textReduction badgeslight / dark
--chart-tick-label-max-widthMaximum browser width of categorical y-axis tick labelsavailable edge space
--chart-modebar-bg / --chart-modebar-activeToolbar and active buttonscheme-aware white/dark surface / #edf1f6 light, #121417 dark
--chart-modebar-focusToolbar keyboard focus ring--chart-focus, else #1b212a light, #e2e5e9 dark
--chart-selection / --chart-selection-fillSelection outline/fillneutral grey matching the toolbar (follows a .dark root class)
--chart-zoom-selection / --chart-zoom-selection-fillBox-zoom outline/fillsame neutral grey as selection
--chart-crosshairCrosshair linestranslucent dark
--chart-annotation-textAnnotation labelsfalls back to --chart-text
--chart-cursor / --chart-cursor-panPlot cursorscrosshair / grab
--chart-focusKeyboard focus ring on the plot canvas, and on toolbar buttons unless --chart-modebar-focus is set#aa99ec

You can define application-specific variables such as --chart-accent and use them from mark styles. XY validates the var(...) shape, then the browser resolves it against the chart root on each render.

Cascading from host CSS

In Reflex, resolve reactive theme choices into ordinary classes, styles, or CSS variables. XY does not duplicate Reflex conditions or application state.

What survives each output

Browser-backed output can run a CSS cascade; browser-free output cannot. Put export-critical paint and tokens on the chart itself, then use host CSS for browser-only adaptation.

OutputTyped mark/axis paintChart theme and root styleComponent and slot chromeHost CSSDark mode
Live browser / ReflexFullFullFull component styles, slot styles, and classesFull host cascadeAdaptive while the page is open
Standalone HTMLFullFullFull serialized component and slot stylingOnly CSS included with custom_cssAdaptive through included class or media rules
Python to_svg()Full validated static surfaceChart-local tokens and var() fallbacksSupported static chrome fields; no general slot-class cascadeNoOne resolved state
Native PNGFull validated static surfaceChart-local tokens and var() fallbacksSupported static chrome fields; no general slot-class cascadeNoOne resolved state
Chromium PNGFullFullFull serialized component and slot stylingOnly CSS included with custom_cssEvaluated at capture time, then frozen

“Supported static chrome fields” means options the SVG/native renderer owns, such as axes and the built-in legend. It does not mean arbitrary DOM CSS or Tailwind classes are evaluated without a browser. Browser-only color expressions may also need a concrete fallback for SVG or native PNG: var(--accent, #8e51ff).

The toolbar’s client PNG and SVG actions are a separate browser snapshot path: they read the live chart’s computed tokens and font styles, so inherited host styling is captured at download time. Python to_svg() and native to_png() never inspect the host page. Chromium to_png() renders standalone HTML, so pass any required author rules explicitly with custom_css.

For exporter selection, engine arguments, and complete code examples, see Display and Export.

Custom fonts and export limitations

Browser charts inherit fonts from the chart root. Load the font in the host application first, then set font_family through chart style= or apply a class that defines font-family. The live example uses a system serif so it does not depend on a network font; replace that stack with the family your host loads.

For a downloaded or self-hosted font, define @font-face in the Reflex application's global stylesheet and use the same family on the chart:

Tailwind users can apply a configured font utility through class_name in the same way. XY does not download, register, or rewrite font files itself.

Standalone HTML and Chromium PNG accept the font declaration through custom_css. Embed the font as a data URL when the HTML file must remain fully portable; an ordinary URL still depends on that resource being reachable.

OutputCustom-font behavior
Live browser / ReflexUses a font already loaded by the host page.
Toolbar PNGCaptures the browser-rendered font into pixels.
Toolbar SVGPreserves the computed family and font styles, but does not embed the font file.
Standalone HTMLSupports @font-face and root font-family through custom_css.
Chromium PNGSupports the same browser CSS through custom_css.
Native PNGUses XY's baked bitmap font; custom fonts are not supported.
Python to_svg()Uses XY's fixed system font stack and cannot embed a custom font.

Automatic dark mode for the toolbar

The interactive toolbar (modebar) has no colored default a page can show through, so it reads the light/dark state straight from your page: when a .dark class is present on the chart root or any ancestor — the convention Reflex (next-themes), Radix Themes, and Tailwind all set on the root <html> element — its background, border, and shadow switch to a dark palette. Icon color already follows the inherited text color, so the toolbar stays readable in both modes with no configuration. An explicit .light class (or no class at all) keeps the light palette.

These neutral colors are built into every toolbar; an app does not need to map them to its own secondary palette. A --chart-modebar-bg, --chart-modebar-active, or --chart-modebar-focus value you set through theme(), chart style=, or a host stylesheet still wins in either mode.

The toolbar's focus ring resolves in three steps: --chart-modebar-focus first, then --chart-focus, then the built-in scheme-aware neutral. So an app that themes focus once with --chart-focus gets one ring color across the plot canvas and the toolbar, and --chart-modebar-focus exists to break that tie when the toolbar needs its own.

Dark mode in a standalone export

Standalone HTML and Chromium PNG use the browser cascade. Native PNG and SVG can resolve variables declared on the chart's own style/theme plus var(--name, fallback) values, but they cannot see an external host page's stylesheet. Put export-critical tokens on the chart itself when output must be identical outside the browser host.

API Reference

xy.theme

Configure chart theme tokens.

Props

PropTypeDescription
styleOptional[dict[str, StyleValue]]

Base chart style overrides.

backgroundOptional[StyleValue]

Figure background color — paints the whole chart card including margins, title, and tick labels (matplotlib's ``figure.facecolor``). The plot rect shows through unless ``plot_background`` sets it separately.

plot_backgroundOptional[StyleValue]

Plot-area background color — the data rect only (matplotlib's ``axes.facecolor``).

grid_colorOptional[StyleValue]

Grid-line color.

axis_colorOptional[StyleValue]

Axis-line and tick color.

text_colorOptional[StyleValue]

Default chart text color.

crosshair_colorOptional[StyleValue]

Hover crosshair color.

selection_colorOptional[StyleValue]

Selection-outline color.

selection_fillOptional[StyleValue]

Selection-region fill color.

**tokensStyleValue

Additional supported theme tokens.

Built with Reflex