Chrome Slots
Each public chart-chrome slot is attached to a DOM element as
data-xy-slot="<slot>". The same validated slot name works in class_names,
styles, component-local class/style props, and a plain CSS attribute
selector. A slot is a supported styling hook, not a promise that every painted
primitive or structural descendant is a separate DOM element.
Slot reference
Unknown slot names raise while the chart is built, before a typo can become a silently unstyled client element.
Tailwind capability by surface
The slot name tells you where a class lands; the surface type tells you what that class can control.
modebar styles the toolbar surface. modebar_button reaches both its
top-level controls and menu-item buttons. Tool groups, menus, separators,
indicators, and the drag handle are modebar substructure rather than additional
public slots; use --chart-modebar-* tokens or a descendant selector based on
their data-xy-modebar-* attributes. XY continues to own toolbar/menu
placement, fit visibility, opacity, and pointer-event state.
Some visible state is intentionally inline and conditional. Toggled or
hover-deemphasized legend rows receive client-owned opacity/filter;
tooltips, selections, and crosshairs receive live display and geometry; active
modebar controls expose .xy-active, aria-pressed, or aria-expanded.
Utilities still control their durable appearance, but overriding those inline
properties requires !important and also assumes responsibility for the
interaction state.
Classes and Tailwind in Reflex
In a Reflex app, enable its Tailwind plugin once in rxconfig.py:
darkMode="selector" makes Tailwind's dark: utilities follow the .dark
class used by Reflex's manual color-mode switch. Omit that option when the
application intentionally wants Tailwind's default OS
prefers-color-scheme behavior instead.
For a fixed xy.Chart or xy.Figure passed directly to reflex_xy.chart(...),
Reflex includes the chart's literal class strings in Tailwind's default scan
paths. The complete utility names below therefore work without adding the
original Python or Markdown file to Tailwind's source configuration.
Keep each utility name complete and literal, such as bg-zinc-950/90. Tailwind
cannot discover a name assembled at runtime from fragments such as
f"bg-{tone}-950"; map dynamic state to complete class strings instead.
For charts produced from a token or Var, pass every possible complete
utility through the adapter's build-time inventory:
tailwind_classes accepts one string or an ordered iterable of strings and
exists only for Tailwind's compile-time source scan.
It never becomes a DOM attribute; mappings and unordered sets raise instead of
making generated source depend on key iteration or hash order. Static
Chart/Figure sources still discover their own classes automatically, and an
explicit inventory is merged with those discovered classes.
List every complete class that a state-driven figure can emit, not just the classes in its initial state. When a live payload changes root or slot classes, XY rebuilds its DOM chrome so the new class set replaces the old one while the stable figure token remains mounted. The replacement preserves every named-axis range and silently rehydrates durable box/range/lasso geometry before refreshing the selection mask, so a theme swap does not replay callbacks or erase durable viewport/selection state. Transient view-local UI state—such as the selected drag tool, undo/redo history, legend toggles, and a manually moved modebar—belongs to the replaced chrome and resets.
Advanced Tailwind v4 candidates are preserved verbatim through the scan inventory, including quotes, backslashes, arbitrary properties, and Unicode content:
Tailwind interprets an underscore inside an arbitrary variant as a space. Slot
names such as legend_label contain a real underscore, so escape it as \_
and use a raw Python string when writing a descendant selector. Prefer
class_names={"legend_label": "font-semibold"} when styling one slot directly;
the arbitrary selector form is useful when one root class needs to target
descendants.
Without TailwindV4Plugin, XY still places the names in the DOM but no Tailwind
utilities are generated, so the chart renders without those styles. An XY
standalone HTML export likewise carries the names but does not bundle Tailwind;
inject already-compiled rules with custom_css or use ordinary CSS for a
portable file.
One tooltip, three styling approaches
All three examples target the tooltip container. The same mechanisms also
target tooltip_title, tooltip_row, tooltip_label, and tooltip_value.
Choose based on where the style originates; do not combine them unless you
intentionally want normal CSS cascade precedence.
Use class_names when the host already provides utilities or reusable classes:
Use styles for values computed in Python or when no stylesheet is involved:
Use a data-xy-slot selector when one host rule should style many charts or an
export needs raw author CSS:
An inline styles["tooltip"] declaration normally wins over a class or plain
author rule targeting the same property. Prefer one primary approach per slot
instead of escalating to !important.
Inline slot styles
Use styles when values are computed in Python or when no stylesheet is
appropriate:
Snake_case property aliases normalize to CSS kebab-case. Bare numbers on length properties become pixels; custom properties and unitless values pass through. Values are declaration-safety checked even though DOM styles accept a broader property set than rendered marks.
Plain CSS and exported documents
custom_css becomes an author <style> in the self-contained HTML document.
XY rejects strings that could break out of that style element. The same option
works for Chromium PNG capture; native PNG has no browser cascade and rejects
custom_css.
What survives which export
Slot styling is a browser mechanism, and the native writers have no cascade to apply it with. Rather than leave that to be discovered, it is a contract:
A per-slot styles= block reaches a file for the nine slots that name chrome a
file actually contains — title, axis_title, tick_label, the three legend
slots and the three colorbar slots — carrying font-size, font-weight,
font-style, font-family, letter-spacing, opacity and the text paint.
The rest are live-only chrome (tooltip*, modebar*, crosshair_*,
selection, badge*) with nothing in a file to paint. The native raster's
baked atlas is one face, so it honors a slot's size and paint and leaves the
typeface properties to the vector writers.
The class_names row is dropped rather than raising: raising would break every
native export of a chart that carries Tailwind classes for its live view, which
is the normal way to use both surfaces together — and a class name is the one
surface a file genuinely cannot honor, since it selects a rule out of a
stylesheet an export does not have. custom_css raises because there is no honest
partial application of an author stylesheet, and the error names
Engine.chromium as the fix.
A chart that must look identical on screen and in a PNG should carry its design
decisions in chart-level style= tokens and mark/axis style=, which every
renderer reads, and use slot classes only for things the browser alone shows —
tooltips, the modebar, hover chrome.
Cascade and structural layout
Built-in visual rules live in the low-priority base cascade layer and use
zero-specificity :where(...), so Tailwind's utility layer and ordinary
unlayered author selectors beat those visual defaults without !important.
That priority is not blanket: XY retains structural and conditional inline
styles for positioning, dimensions, visibility, z-index, and interaction
state. Avoid overriding those unless you intentionally take responsibility for
chart layout or behavior.
The chart root's default typography also lives in that base layer, so
font-*, text-*, and leading-* utilities on class_name work through the
normal cascade. An explicit chart style={"font_family": ...} or slot
styles={...} remains inline author intent and therefore wins over a normal
utility.
Responsive legend bounds and tooltip wrapping use the same layered, zero-specificity visual defaults; their anchors and live placement remain structural. Long legends become scrollable and edge tooltips wrap or flip inside the chart.
Responsive utilities on DOM chrome react to media queries normally. A narrower
case needs care: canvas paint samples --chart-bg, --chart-grid,
--chart-axis, and the canvas use of --chart-text into renderer state.
OS color-scheme changes and mutations to an ancestor's class, data-theme,
or style refresh that state, but crossing a Tailwind breakpoint alone does
not. Pair responsive canvas-token changes with such a state mutation or a
figure rebuild; CSS-only tokens used by DOM chrome update immediately.
The selection slot reaches box/range rectangles and the completed lasso's SVG
path and editable handles. Use box-oriented background/border utilities for
rectangles and SVG fill-* / stroke-* utilities for the lasso nodes. XY keeps
the lasso path non-interactive and the handles draggable even if a shared
selection class contains pointer-events-none.
The legend_swatch slot lands on the visible chip wrapper. Solid/bar swatches
consume background and size utilities there; scatter and line SVG handles
inherit fill-*, stroke-*, stroke-width, and dash utilities from the same
wrapper. Renderer paint and geometry are private base-layer fallbacks, so those
utilities remain defeatable.
Annotation labels use annotation_label; canvas-painted arrow shafts,
markers, rules, and zones do not. Style those through their annotation props as
described in
Customize Each Part.