Compiling the Framework
Compile the framework from source with Dart Sass: one entrypoint, one load path, plus a stylesheet per theme. The result is the same plugins.css the release pipeline produces. You only need a source build when a released one is not enough, for modified source or custom device profiles.
Toolchain
The framework compiles with Dart Sass; the module system rules out LibSass and node-sass. Releases pin
sass-embedded 1.93.3 so published artifacts
reproduce byte for byte, and any current Dart Sass produces equivalent CSS.
Compiling plugins.css
Create an entrypoint that loads the framework module, then compile it with the framework source on the
load path. bin/build in the repository runs
exactly this for the released artifacts.
// my-build.scss
@use 'framework';
sass --style=compressed --no-source-map \
--load-path=trmnl-framework/app/assets/stylesheets \
my-build.scss plugins.css
To bake your own device profiles into the build, configure the module at the same
@use site. See
Custom Devices
Custom Devices
Device profiles and the $custom-devices configuration for custom builds
Device profiles drive the compile: each entry in the device map becomes a screen--{name} class with its dimensions, density, and color depth baked in. Configure $custom-devices to add your own profiles without touching framework source.
.
The compiled stylesheet embeds every dither tile as data URIs, so it is large on disk. Serve it
compressed; the release pipeline ships a .gz
next to every artifact for the same reason.
Fonts
Every @font-face block in the framework points
at a root-relative /fonts/ URL, so a host has to
serve those files. Without them the pixel families fall back to the browser default. Nothing errors, the
type is simply wrong at every size.
Font URLs resolve against the host that serves the stylesheet, not the page. Link
plugins.css from the releases CDN and the fonts
load from the same host. Serve your own build and you serve
/fonts with it.
What the host serves
-
TRMNL12,TRMNL16, andTRMNL21in Regular and Bold, each as.woff2,.woff, and.ttf(18 files). -
NicoPups-Regular.ttf,NicoClean-Regular.ttf, andBlockKie.ttffor the Classic bundle (3 files). -
Inter.ttfandInter-Italic.ttf, which both bundles use on high-density displays (2 files).
Both bundles download as zips from
Releases, each with a
README of per-font credits and its license text. Extract the font files into the directory the host serves
at /fonts, and keep the credits and license
files with your build.
Font Family
Font Family
Switch between Classic and TRMNL font bundles per device
The Framework ships two pixel font bundles: Classic (NicoPups, NicoClean, BlockKie) and TRMNL (TRMNL12, TRMNL16, TRMNL21). Low-density displays use the selected bundle; high-density displays use Inter Variable for legibility.
covers which bundle renders what.
The framework release zip holds no fonts. They ship as their own bundles, so a stack assembled from that zip alone renders with no pixel fonts at all.
Palette tiles
The compiled stylesheet embeds every dither tile as a data URI, on grayscale screens and color palettes alike. Fonts are the only asset it loads by URL, so a custom stack has no pattern images to serve.
Themes
Themes are not bundled into plugins.css. Each
theme under framework/themes/ compiles to its
own stylesheet and loads as a second <link>
next to the framework. Usage is on
Themes
Themes
Opt-in stylesheets that re-theme screens while preserving device-capability rendering
Themes are standalone stylesheets that re-point semantic channels, component slots, and utility tokens at different palette tokens. A themed screen still renders through its device mode: dither patterns on 1-bit, palette images on limited color, solids on full color.
; the authoring contract is
on
Authoring Themes
.
sass --style=compressed --no-source-map \
--load-path=trmnl-framework/app/assets/stylesheets \
trmnl-framework/app/assets/stylesheets/framework/themes/black-and-yellow-theme.scss \
themes/black-and-yellow-theme.css
The JavaScript runtime
plugins.js is hand-authored and ships verbatim:
no bundler, no build step. Copy
app/javascript/plugin-render/plugins.js next to
your compiled CSS and serve both. It provides the render-time engines
(
Framework Runtime
Framework Runtime
How the runtime applies layout, clamping, overflow, and presentation adjustments at render time
Different devices have different, fixed amounts of screen space. The Framework Runtime fills that space when a plugin layout renders, doing the heavy, repetitive measuring and fitting for you. Expand the "Framework Runtime" panel under any example on this site to see the stats for that render.
) and TRMNLPaint
(
Paint API
Paint API
TRMNLPaint: read the live CSS cascade from JavaScript to resolve framework colors and tile patterns
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.
).
Releases and minification
The repository's release task compiles, minifies, and precompresses (gzip and brotli) every published version reproducibly; those artifacts are what the releases CDN serves. If you don't need a modified build, serve a released version directly and skip the toolchain entirely.
Minifying your own build is optional. If you do, keep custom properties intact: the framework's JavaScript
reads public --* variables from the live
cascade.