ENG-10444: Add high-level theme presets to the declarative API#114
ENG-10444: Add high-level theme presets to the declarative API#114FarhanAliRaza wants to merge 11 commits into
Conversation
xy.theme() gains preset/color_scheme/palette/accent/contrast on top of the existing token escape hatches. Presets resolve Python-side into the implemented --chart-* vocabulary and a default mark palette threaded through Figure, so browser, standalone HTML, SVG, native PNG, and Chromium exports all agree by construction. color_scheme="system" ships both variants (dom.style + dom.styleDark) and the client swaps them on prefers-color-scheme; browser-free exports deterministically use light. Unknown names fail with actionable errors; xy.theme_presets() and xy.theme_palettes() cover discovery. Includes docs, a preset gallery page, and catalog/precedence/wire/export tests.
Merging this PR will not alter performance
Comparing Footnotes
|
…high-level-themes-to-the-declarative-xy-api
Scheme-pinned presets set the figure background inline but left --chart-bg, --chart-legend-bg, and --chart-modebar-bg inheritable, so a host page defining those tokens per color mode (the docs styling surface and app-level chart style both do) repainted a pinned chart's plot rect and legend/modebar chips to the page's mode — dark plot areas inside pinned-light cards and unreadable legend text. Every preset variant now pins all three surfaces with the engine's own scheme defaults (plot rect transparent so the figure background shows through), which changes nothing outside token-setting hosts. The SVG exporter treats fully transparent background paints as omitted so exports don't gain dead transparent rects.
Greptile SummaryThis PR adds high-level theme presets to the declarative API. The main changes are:
Confidence Score: 5/5This looks safe to merge. No blocking issue was identified, and accepted accent values retain alpha in solid emphasis tokens. None.
What T-Rex did
Important Files Changed
Reviews (6): Last reviewed commit: "docs: render a live example under each t..." | Re-trigger Greptile |
…high-level-themes-to-the-declarative-xy-api # Conflicts: # examples/demo-advance.ipynb # python/xy/_svg.py
_normalize_accent advertised #rrggbbaa in its error message and accepted it, then truncated to the opaque base — so accent="#8b5cf680" rendered selection, zoom, focus, and modebar-active at full opacity. The solid tokens now keep the caller's alpha; the faint fills still derive from the opaque base so appending the fixed fill alpha stays a valid 8-digit hex.
…high-level-themes-to-the-declarative-xy-api # Conflicts: # examples/demo-advance.ipynb # python/xy/marks.py # python/xy/static/index.js # python/xy/static/standalone.js
The #159 vite-minified build mangles internal declarations, so grepping the installed bundle for 'function renderStandalone(' has failed ever since when reflex is installed. Assert on the public export names instead — they are the stable identity of the real render client.
…high-level-themes-to-the-declarative-xy-api # Conflicts: # examples/demo-advance.ipynb # python/xy/_payload.py # python/xy/marks.py # python/xy/static/index.js # python/xy/static/standalone.js
| if theme_node.color_scheme is not None: | ||
| fig.color_scheme = theme_node.color_scheme | ||
| if theme_node.dark_style: | ||
| fig.dark_style.update(theme_node.dark_style) |
There was a problem hiding this comment.
Earlier Dark Tokens Still Survive
When a later system theme overrides a token, theme() removes that key from its dark_style, but this dict.update() cannot remove the value already stored by an earlier theme. For example, a later grid_color="#ff0000" applies in light mode but switches back to the earlier preset's grid color in dark mode, breaking component order and documented precedence.
Artifacts
Repro: Python declarative composition and serialized payload harness
- Evidence file captured while the check ran.
Repro: Playwright system color-scheme transition harness
- Evidence file captured while the check ran.
Repro: executed payload and browser transition output
- The full command output behind this check.
Repro Earlier Dark Tokens Dark
- What the screen looked like at this point in the check.
…high-level-themes-to-the-declarative-xy-api # Conflicts: # python/xy/static/index.js # python/xy/static/standalone.js
The preset gallery page authored only the all-presets comparison grid; the per-preset sections were headings with a one-line description and nothing to look at. Reuse the fence-shared preset_example helper via a small preset_pair() and render each preset's light/dark pair under its own heading.




Summary
Adds an ergonomic high-level theming layer to
xy.theme()while keeping the existing token-based escape hatches and export behavior intact:Design
python/xy/_themes.py: immutable preset catalog + resolver). Presets emit only the implemented--chart-*token vocabulary plus rootbackground, and a default mark palette threaded throughFigure.default_paletteinto mark building — so browser, standalone HTML, Python SVG, native PNG, and Chromium export all agree by construction. No new JS token vocabulary.color_scheme="system"ships both variants on the wire (dom.stylelight +dom.styleDark); the render client swaps them on the existingprefers-color-schemelistener. Browser-free exports (SVG, native PNG) and headless Chromium deterministically use the light variant — passcolor_scheme="dark"to export dark. Documented.docs/styling/themes-and-tokens.md: engine defaults → preset → color scheme → palette/accent/contrast → named low-level args →theme(style={})→ chartstyle={}→ explicit mark colors.ValueErrorlisting the valid options;xy.theme_presets()/xy.theme_palettes()enumerate the catalog. Matplotlib-compatible stylesheet names stay inxy.pyplot.style.backgroundin both schemes, preserving thedata-xy-own-bg/ VS Code white-card behavior.Docs
docs/styling/theme-presets.md) rendering all five presets in light and dark, wired into the docs nav.Testing
tests/test_themes.py: catalog integrity (implemented-tokens-only, hex palettes), resolver purity/immutability, actionable validation errors, backward compat, full precedence chain, system dual-variant wire spec, palette→marks/categorical with explicit colors winning.system.ruff check/formatclean;node js/build.mjsreproduces the committed bundles byte-identically;scripts/render_smoke_nonumpy.pypasses.systemtoggle with a headless-Chromium probe (stubbedmatchMedia): tokens flip light→dark→light on the change event with no errors and no MutationObserver feedback loop.Closes ENG-10444.