Paint API
TRMNLPaint is the framework's public JavaScript paint API. It reads the live CSS cascade (bit depth, dark mode, theme, limited palette, and tiles all resolved) and hands back a canonical Fill, so token mappings are never duplicated in JavaScript. Charts are just one consumer; any plugin can resolve framework colors from JS for any purpose.
How it works
The framework paints every surface through per-mode CSS custom properties on the
.screen element, and a theme is CSS that
re-points those properties. TRMNLPaint is a
reader of that live cascade, never a second definition of the color system.
Each resolver appends a hidden probe element inside the target element, applies the framework's own
utility class (bg--<token>,
text--<token>, ...), and reads back the
browser-resolved computed style. Bit depth, dark mode, inverse subtrees that flip the screen scheme, themes, slot overrides, and
limited palettes are all honoured automatically, with zero token mappings duplicated in JavaScript.
Every function is total: a missing .screen or
an unknown token returns a Fill with null fields instead of throwing. Plugins render on a screenshot
service, and a thrown error would mean a blank device screen.
The paint domains
The resolvers are documented by what they paint. Every resolver takes an optional
{ el }: the id or element whose local cascade
supplies the paint, with device settings from its nearest
.screen. Omit it on a single-screen plugin
and the first screen on the page is used.
Painting Colors
Resolve background, text, stroke, and semantic tokens as Fills, and paint them onto nodes.
bg(),text(),stroke()semantic(),textColor()apply()
Painting Charts
Series colors from the screen's chart ramp, and the Highcharts fill adapter.
series(),ramp()toHighcharts()
Painting Borders
Border and divider rails as BorderFills, for custom rails and Highcharts axes.
border(),divider()applyBorder()toHighchartsAxis(),applyHighchartsAxisPaint()
Painting Typography
Text roles and stroke rings as TypeSpecs, for custom text and chart labels.
type(),strokeSpec()applyType()toHighchartsText()
Scale values
Resolve scale-aware JavaScript dimensions through TRMNLPaint. It reads the same custom properties as the CSS framework, so device density, Scale, and Text Scale stay in one source of truth.
-
scale({ el }): returns{ name, device, modifier, ui, content, textName, textModifier, textUi }for the target screen. -
px(value, { el, kind }): scales one number or an array of numbers. It uses content scale by default; passkind: "ui"for framework geometry orkind: "text"for framework typography.
Use px() for numeric chart options, canvas dimensions, and other library configuration. Percentages, data values, zero-width rails, and physical one-pixel strokes should remain unchanged.
var scale = TRMNLPaint.scale({ el: "my-chart" });
var height = TRMNLPaint.px(260, { el: "my-chart" });
var spacing = TRMNLPaint.px([10, 10, 5, 10], { el: "my-chart" });
// Framework-owned component geometry also includes device density.
var componentInset = TRMNLPaint.px(6, { el: "my-chart", kind: "ui" });
// Framework typography includes device density, Scale, and Text Scale.
var fontSize = TRMNLPaint.px(16, { el: "my-chart", kind: "text" });
The Fill type
Every resolver returns a canonical Fill, a
plain object describing paint independently of any charting library. A Fill with a
url and
size is a dither tile pattern; a Fill with only
a color is a solid. Library-specific shaping
lives in adapters, never in the Fill.
Fill = {
color: string | null, // resolved "rgb(...)" flat / field color
image: string | null, // full resolved background-image; null when "none"
url: string | null, // first url(...) from image; null for gradients / solids
size: number | null, // tile size in px (falls back to --dither-bg-size)
}
// url && size => a dither tile pattern
// color only => a solid
Borders and typography have richer shapes: BorderFill
is documented on
Painting Borders
Painting Borders
Read border rails as BorderFill objects for custom rails and Highcharts axes
The border resolvers read the framework border rails as BorderFill objects. Apply them to custom rails, or convert them for Highcharts axes and grid lines.
and
TypeSpec on
Painting Typography
Painting Typography
Read text roles as TypeSpec objects for custom text and chart labels
The typography resolver reads a text role or utility class as a TypeSpec: font, size, weight, paint, and optional stroke. Apply it to custom text, or convert it for Highcharts labels.
.
Painting and reactivity
-
apply(node, fill): paints a node's background from a Fill, compositing the field color under the tile image with the same two-layer CSS the rest of the screen uses. -
watch(el, onChange, { immediate }): runsonChangenow (unlessimmediate: false) and again whenever the mode, dark or theme classes change on the screen or on a wrapper above it. Returns astop()function. -
screen(el): the nearest.screenfor a target element, or the first screen on the page.
applyBorder() and
applyType() write border and typography
longhands the same way; they are documented with their resolvers on
Painting Borders
Painting Borders
Read border rails as BorderFill objects for custom rails and Highcharts axes
The border resolvers read the framework border rails as BorderFill objects. Apply them to custom rails, or convert them for Highcharts axes and grid lines.
and
Painting Typography
Painting Typography
Read text roles as TypeSpec objects for custom text and chart labels
The typography resolver reads a text role or utility class as a TypeSpec: font, size, weight, paint, and optional stroke. Apply it to custom text, or convert it for Highcharts labels.
.
Extending a theme from JavaScript
Themes never ship JavaScript. A theme that needs to expose extra values to plugin code (a brand accent, a
custom ramp) publishes additional public
--* custom properties on the screen, and JS
reads them back with cssVar(). No framework
release and no JS bundle per theme: just CSS that both worlds read.
cssVar(name, { el }) returns the trimmed
computed value of any public custom property on the screen. It is the documented escape hatch for exactly
these theme-published values.
Read public var families only. The CSS minifier renames private variables
(--_*,
--framework-internal-*,
--tile-*,
--bline-*, most of
--border-* and
--tn-*) in the released
plugins.min.css; the readable
plugins.css keeps the source names. Never
read those families from JS.
cssVar("--border-step-40-h-color")
answers in the dev build and returns an empty string against every release. See
CSS Variables
CSS Variables
The CSS variable contract: which families are public, which are internal, and who reads, re-points, and generates them
Every framework CSS variable is either public contract or private implementation. This page states the contract: which families are public, which are internal, and how the Paint API reads them, themes re-point them, and the Sass source generates them.
for the families that survive.
// A theme publishes a public var on .screen--theme-my-brand:
// --my-brand-accent: var(--red-50);
// Read it back from JS, resolved for the active mode/theme:
var accent = TRMNLPaint.cssVar("--my-brand-accent", { el: "my-chart" });
Where This Applies
These pages document the surfaces this API programs.