Customize Each Part
Start with the part you want to change. Data marks and axis geometry use XY's validated renderer-neutral style vocabulary. Colorbars, legends, tooltips, controls, and annotation labels are DOM chrome and also accept component or slot styles.
Fill, stroke, opacity, and gradients
Marks are rendered geometry rather than DOM nodes. Use typed props for common
choices, or the mark's validated style= mapping when CSS-shaped paint is
clearer. Unsupported properties raise while the chart is built instead of
silently disappearing in one renderer.
Use fill, fill-opacity, stroke, stroke-width, stroke-opacity, and
opacity only on mark families that support them. Lines use stroke properties;
areas, points, bars, and columns also support fill properties. Bar-like marks
add border-radius. line, step, stairs, and ecdf add
stroke-dasharray and stroke-linecap; area adds stroke-dasharray only,
because a cap is open-path geometry. scatter adds marker-shape.
stroke-linecap (butt, round, square) carries its standard SVG meaning,
and XY defaults it to round rather than to the CSS initial value — the native
rasterizer has always drawn round caps and it is the reference for static
export. marker-shape is the CSS spelling of symbol= and takes any of the 17
built-in marker names.
Axes, grid, and ticks
Treat the x and y axes independently. Grid lines are owned by the axis they cross: the y axis draws horizontal guides and the x axis draws vertical ones. Axis baselines, tick marks, tick text, and titles are separate style properties, so hiding one never requires hiding the others.
Customize horizontal grid lines
Keep the y-axis grid and turn off the x-axis grid for a calm reporting chart.
grid_color, grid_width, grid_dash, and grid_opacity style the guides;
the remaining zero-width and transparent properties remove the baseline and
ticks without removing the horizontal grid.
Use "solid", "dashed", "dotted", or "dashdot" for grid_dash.
Setting grid_opacity=0 is the direct way to disable one direction of grid.
Customize the axis line, ticks, and tick text
This column chart keeps a deliberate bottom axis. The baseline uses
axis_color and axis_width; tick marks use tick_color, tick_width, and
tick_length; the month text uses tick_label_color. The y axis remains
visually quiet while still drawing horizontal guides.
Add label="Month" and label_color only when the axis title adds information
that the surrounding heading does not already provide. tick_label_strategy
controls collisions independently: use "rotate", "stagger", "hide", or
"none" for dense categorical axes.
Clean dashboard axes
For compact dashboard charts, remove both baselines, ticks, and tick text; disable the x grid; and retain only the y grid. This is the same quiet axis treatment used by the product-ready examples, shown here with two vivid series and a compact stroke-shaped legend.
The reusable recipe is: tick_label_strategy="none", transparent axis and
tick colors with zero widths on both axes, grid_opacity=0 on the x axis, and
a subtle grid_color on the y axis. Keep extra top padding when a legend sits
inside the plot.
Hide axis chrome with a switch
Because hiding axis parts is subtraction, each axis takes plain switches
instead of transparent colors: show, line, ticks, grid, and text
(tick labels plus the axis title). show sets the other four, and each of them
overrides show, so the reporting look above is two lines.
An explicit style= property still wins, so you can switch a part off and then
bring one property back — xy.x_axis(show=False, style={"grid_color": "#eee", "grid_opacity": 1}) — without writing the other six.
Color scales and colorbars
Set a continuous mark's colormap= and domain= together when colors must
have a stable meaning across charts. Add xy.colorbar(...) to explain that
scale, then style its container, gradient, ticks, and title through the
colorbar, colorbar_bar, colorbar_tick, colorbar_title,
colorbar_extension, colorbar_line, and colorbar_minor_tick slots.
Build a colormap from your own colors
colormap= takes any of XY's twenty built-in names, and also a custom ramp
built from the colors your design system already defines:
A gradient takes up to 8 stops, the list forms up to 256. Colormap stops are
opaque: use the mark's opacity/fill-opacity for transparency rather than a
translucent stop, which XY refuses instead of silently flattening to black.
The ramp is resolved once, in Python, so the browser, to_svg(), and
to_png() paint identical marks, and the colorbar follows it in every renderer.
In the browser the legend row for a continuous encoding also becomes a gradient
swatch of the ramp; the static legend draws a solid handle, as it always has.
One rule follows from that: colormap stops must be colors XY can resolve
without a browser — hex, rgb(), hsl(), or a named color. var(--brand),
oklch(...), and color-mix(...) are fine on an individual color=, stroke,
or fill, but not as a colormap stop or an xy.theme(palette=...) entry. Both
are indexed color lookups used by static renderers that have no cascade to
resolve browser-only colors. Resolve the token to a literal in Python and pass
that.
Recolor categories with a chart palette
A categorical color= channel and unnamed series both draw from the chart's
palette. xy.theme(palette=[...]) replaces it for the whole chart, so a set of
brand swatches colors every series and every category at once:
Series take the palette in order — one slot per series, whatever its trace
count, so a box plot (four traces) or a stem (two) advances the cycle by one
like everything else. An explicit color= on a mark still wins, and takes no
slot, so the next series that needs a color still gets the palette's first.
A palette shorter than the number of series or categories repeats, and says so
with a warning rather than quietly reusing a color.
To pin specific categories to specific colors, pass a mapping instead of a list:
A list can only say "the first category is blue", so the same category changes color whenever the set of categories does — most visibly across facet panels, where a panel that happens to be missing one category silently recolors the rest. A mapping keys on the label, so a category keeps its color everywhere. Categories the mapping does not name take the next default color it has not already used, and say so with a warning.
Palette entries follow the same rule as colormap stops: literal colors only —
hex, rgb(), hsl(), or a named color. var(--brand-500) is fine on an
individual color=, but not in a palette, because a palette is indexed and XY
has to resolve each entry itself for density surfaces and for to_svg() /
to_png(). Several var() entries would land on one fallback and merge
distinct categories into a single color, so XY refuses them and says why.
Resolve your token to a literal in Python and pass that. Named colors,
rgb(), and hsl() are all fine — XY normalizes them to hex for you.
Use a legend instead when colors identify discrete categories. Built-in
colorbars are available in browser, SVG, native PNG, and Chromium output; host
framework components passed through render= are not part of standalone XY
exports. See Colorbars for supported marks,
orientation, inferred scales, and custom-component boundaries.
Legend
Configure legend content and layout with xy.legend(...). Style the component
directly, or use the chart's legend, legend_title, legend_item,
legend_swatch, and legend_label slots when one rule should cover several
charts. Complete literal Tailwind utilities work through class_name /
class_names when the host enables Reflex's TailwindV4Plugin.
A short, rounded swatch makes an area-series legend read like its visible
stroke instead of a generic color chip. A genuinely custom host legend is
ordinary Reflex UI: hide the built-in legend
with xy.legend(show=False), keep the chart in state, and render the controls
beside it. Host-owned UI is not included in standalone XY exports.
Tooltip
Tooltip fields must already be resident in a rendered data channel. Use named
data columns for stable lookup, map them to readable display text with
labels=, and format them with format=. Style the built-in container
directly or target tooltip_title, tooltip_row, tooltip_label, and
tooltip_value independently.
For a host-owned tooltip, set xy.tooltip(show=False), handle
on_point_hover, and render ordinary framework UI from the received row. The
built-in tooltip remains the right choice when it must track the pointer or
survive a standalone export.
Annotations
Annotation geometry is painted with the chart; annotation text is DOM chrome.
Use geometry props such as color, width, and opacity, then use the
annotation's style or the chart's annotation_label slot for its label.
threshold(...) and threshold_zone(...) are semantic aliases for reference
lines and bands. Arrow shafts, markers, rules, and zones remain painted
geometry; only their labels respond to DOM slot styles.
Interaction chrome
Crosshairs, selections, and the modebar are configured independently from data
marks. Interaction colors belong in xy.theme(...); DOM pieces can also use
the crosshair_x, crosshair_y, selection, modebar, and modebar_button
slots. The remaining modebar_* slots reach the draggable grip, control group,
separators, icons, zoom value, indicators, selection icon, menus, menu content,
and history controls independently.
Move across the plot to inspect the crosshair. Open the modebar selection menu,
or Shift-drag, to inspect the styled selection rectangle. Framework callbacks
such as on_select_end can turn the result into filters or related views.