Capability Matrix
<!-- generated by scripts/gen_capability_matrix.py --write; do not edit -->
Every styling question about XY has the same two halves: can I change this, and does the change survive where I need it. This page answers both from the registry the implementation is checked against.
- 11 mark style properties across 21 mark kinds, drawn by all three renderers.
- 29 stable chrome slots for CSS and Tailwind in the browser.
- 1 way to add a mark kind XY does not ship, without forking it.
Mark style properties
What a mark's style= accepts. Anything outside this raises while the chart is
built — one renderer never silently ignores what another draws.
Notes
opacity— Multiplies the mark's own alpha in every renderer.fill— A plain color compiles to the mark's paint; alinear-gradient(...)compiles to a gradient and is accepted only by area and rect kinds, which are the ones with a gradient program.fill-opacity— Independent ofopacity; the two multiply.stroke— The paint for line-like geometry, and the border for filled marks.stroke-width— CSS px; a bare number is px, matching the chrome style convention.stroke-dasharray— 2-8 positive px lengths, ornone. The WebGL client tracks arc length on the CPU so dashes stay continuous across segments and constant on screen through zoom.stroke-linecap— Line family only — a cap is open-path geometry. XY's default isround, not CSS'sbutt, because the native rasterizer has always drawn round and is the reference for static export. Verified per renderer: a Rust coverage test, a rasterized-ink test, and three Chromium screenshots that hash differently per cap.border-radius— Rect kinds only.corner_radius=(tip, base)rounds the two ends separately.wedge-gap— Gap between neighbouring polar wedges, in px. Rect kinds undercoords="polar"only; ignored elsewhere. Deliberately a LENGTH rather than an angle: an angular pad's seam isr * dthetawide, so it tapers to nothing at the hole and reads as uneven spacing. The angular inset therefore grows as the radius shrinks, which is the same construction as d3's padAngle/padRadius pair. An XY vocabulary name: CSS has no gap between two arcs.marker-shape— 17 shapes, drawn as analytic signed-distance fields in all three renderers. An XY vocabulary name: CSS has no shape keyword for a non-DOM point mark, and the CSS spelling andsymbol=compile to the same value.
Chrome slots
Stable data-xy-slot names that take class_names= and styles=. The native
raster and vector writers have no cascade, so per-slot styling is a browser
mechanism; put anything that must survive export in the chart-level style=
token bag or in mark and axis style=, which every renderer reads.
Notes
root(viachart style=) —styles={'root': ...}is browser-only, but the chart-levelstyle=token bag targets the same element and every renderer reads it (spec['dom']['style']). Prefer it for anything that must survive export.title(viastyles={'title': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.legend(viastyles={'legend': ...} / xy.legend(style=...) / --chart-legend-bg) — The frame box. Both spellings and the theme token now converge on one merged declaration block before the writers see it, so what agrees in the browser agrees in a PNG.background,boxShadow,borderRadius,--xy-legend-frame-alpha, andpadding/rowGapinemare honored; an explicit background paints opaque, as it does in the browser.legend_title(viastyles={'legend_title': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.legend_label(viastyles={'legend_label': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.colorbar(viastyles={'colorbar': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.colorbar_tick(viastyles={'colorbar_tick': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.colorbar_title(viastyles={'colorbar_title': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.tick_label(viastyles={'tick_label': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.axis_title(viastyles={'axis_title': ...}) — Vector (SVG, PDF) honors font-size, font-weight, font-style, font-family, letter-spacing, opacity and the text paint (fill, orcolor). The raster writer's glyph primitive takes a size and one RGBA paint and nothing else, so it honors font-size and the paint only — font-weight, font-style, font-family, letter-spacing and opacity are vector-only rather than silently approximated. Properties outside the subset stay browser-only.
Extension points
See Custom Marks for the worked example.
Where renderers still disagree
Differences in the defaults that no property selects. They are listed because an undocumented difference reads as a bug; a documented one is a contract.
For what is still alpha, see Limitations and Alpha Status.