Skip to content

feat: widget css tokens - #13

Open
dzole0311 wants to merge 11 commits into
mainfrom
feat-widget-css-tokens
Open

feat: widget css tokens#13
dzole0311 wants to merge 11 commits into
mainfrom
feat-widget-css-tokens

Conversation

@dzole0311

@dzole0311 dzole0311 commented Jul 29, 2026

Copy link
Copy Markdown
Member

Contributes to: #7

Changes:

Two parts, but happy to split part two into its own PR if that's easier to review.

Part 1: widgets use --mw-* tokens

Replaces hardcoded colors, fonts and sizes in each widget's CSS with the shared --mw-* tokens, so widgets pick up a theme instead of using fixed values. Touches button, toggle, slider, range slider, dropdown, number input, stat, number display, legend, text and chart.

Part 2: Theming for canvas-based widgets (eg. Chart.js)

Widgets like Chart.js draw on canvas so CSS tokens don't reach it on their own. This adds a small pattern for that.

Why a plain CSS read isn't enough? A canvas lib needs a plain value in JS (like "#0366d6"), not a CSS variable name. Normally you'd read the CSS variable with getComputedStyle, but that doesn't work here since our tokens often fall to other tokens (our color falls back to a JupyterLab color, which falls back to a plain hex code) and the browser won't resolve that chain when you read a custom prop directly.

The core doesn't know anything about Chart.js or any other lib. It only has the small helper functions (get a color, get a font size, get a palette). Each widget decides what fields it needs and writes its own file. Nothing to extend, no base class, just copy chart's file and change the names.

Screen.Recording.2026-08-03.at.17.21.27.mov

Proposed next steps:

  1. Add docs for widget authors (which tokens to use, when to add component-specific tokens and how users should create/apply themes) and/or extend the agent skill to be able to generate manywidget, also considering whether they are canvas/webgl based.

@dzole0311
dzole0311 marked this pull request as draft July 29, 2026 06:47
* One hidden probe per `(el, cssProperty)` is created once and reused,
* instead of inserted and removed on every call.
*/
function getProbe(el: HTMLElement, cssProperty: string): HTMLElement {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@batpad this is the slightly annoying/ugly part, eg Chart.js needs a real hex code and not a CSS var name. Problem is our tokens fall back through a whole chain (--mw-color-text to --myst-* to --jp-* to hex) which is good, but then if you just do getComputedStyle(el).getPropertyValue("--mw-color-text") the browser will give you back the unresolved var() expression, not the actual color. So the workaround is to add a hidden / throwaway <span>, assign the var() to a real CSS property on it (like color) and then read THAT back for the browser to resolve it.

return palette;
}

type ThemeFieldResolver<T> = (el: HTMLElement) => T;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only framework-ish bit, it just turns a { field: resolver } object into one (el) => theme function. So if you're adding theming to a new canvas/webgl widget, chart/src/theme.ts below is the file to copy and swap the fields for whatever your lib needs.

We need to document this better. But I think it'd be cool to extend the agent skill, so that if a new dev wants to generate a new widget, it will ask them a bunch of clarifying questions (eg. name of the widget, is it canvas/webgl based, etc) and based on that copy a template that can be based on chart/theme.ts. cc @batpad @wrynearson

fontWeightStrong: string;
}

export const readChartTheme = defineThemeReader<ChartTheme>({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the whole adapter file and here's the copy for Chart.js specifically, that just says "these are the fields Chart.js options need and here's how to get each one from our --mw-* tokens." The core doesn't know Chart.js exists and all the lib-specific knowledge lives right here.


function build(): void {
chart?.destroy();
const theme = readChartTheme(el);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is where we call readChartTheme on first render and again whenever theme_vars changes (eg light/dark toggle) the chart repaints with the new colors.

@dzole0311
dzole0311 marked this pull request as ready for review August 4, 2026 09:51
@dzole0311
dzole0311 requested review from batpad and wrynearson August 4, 2026 09:51
@batpad

batpad commented Aug 4, 2026

Copy link
Copy Markdown
Member

@dzole0311 at a quick glance, this looks really good to me - can spend a bit more time with it tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants