# Framework v3.2.0

Released on 2026-07-30.

Framework 3.2 is a landmark release. It introduces themes and a JavaScript API that works like the class based system. And, most impactful of all..

## Open Source

The TRMNL Framework goes open source.

Since inception it lived inside the core product, wired into everything that runs TRMNL. In 3.2 we pulled it out into a repository of its own and made it public. The design system behind every TRMNL screen is now yours to read, fork, and build on.

It is also now yours to shape. We put the pieces in place for you to contribute:

- **Tests and CI.** Test suites across the stack (contract, RSpec, and Playwright runtime and visual) run in continuous integration on every pull request, so a change can be trusted before it lands.
- **Community scaffolding.** CONTRIBUTING, SECURITY, and CODE_OF_CONDUCT, with issue and pull-request templates that make the first step obvious.
- **Contributor guides.** New Open Source and Contributing guides that show where everything lives and how to ship a change.

We pledged to this because we believe that the TRMNL community deserves that people out there - smarter than us - build things we never would that makes all of our experiences better. That is why we opened it up: to make the most of this technology together.

## New Features

### Themes

Themes introduce the ability to personalize all and every TRMNL plugin out there. A theme recolors surfaces, has control over backgrounds, text, and borders, from a single stylesheet.

Under the hood, every meaningful property now maps to a named slot, and a theme simply reassigns them. That is what lets one small file restyle an entire ecosystem of plugins, keeping a coherent render across any device type.

3.2 ships three themes, Black and Yellow, White and Red, and Dark, ready to use on trmnl.com today. Dark is the dark color scheme as a plain theme, so a whole screen goes dark from one stylesheet link rather than from per-element classes. Run TRMNL yourself under BYOD and you can theme your own local plugins with the same system. Opening that up across the whole plugin library is where this is headed next.

### Inverse colors

Add `inverse` to any element to apply the opposite color scheme to that element and its descendants while siblings keep the screen scheme. Active cards and rows now reuse the framework's reversible color scale across bit depths, limited palettes, and themes instead of maintaining separate state colors. Slot values declared on the element and theme-specific inverse rules can still override the local defaults.

### TRMNLPaint, a JavaScript paint API

TRMNLPaint is the JavaScript counterpart to the framework's class system. Ask it for any framework color, border, type role, or numeric scale, and it returns exactly what the current screen resolves, with the device and every active mode already applied. Painting and numeric dimensions from JavaScript are now as device-aware as the markup around them, across TRMNL's growing catalog of devices.

### Adaptive charts

Charts are the first thing built on TRMNLPaint. `TRMNLCharts` wraps Highcharts with the new API and ships inside the runtime, so a chart now adapts to the device, scale, bit depth, dark mode, and themes instead of carrying its own fixed paint and dimensions.

### Adaptive icons

Monotone icons now adapt on their own. Add `image--adaptive` to a monochrome icon and the framework repaints it to match the active theme and light or dark mode, the same way it handles framework text.

### Rewritten color patterns

The color pattern system, the dithered fills and tiles behind every surface, was rebuilt from the ground up. Patterns are now inline vector graphics instead of downloaded image tiles.

Three things get better. Speed: a screen that used to fetch hundreds of pattern images now fetches none. Consistency: the same pattern renders identically in every browser, instead of drifting with each engine's image handling. Quality: as vector art, the patterns stay sharp on any device, at any size.

### Rebuilt border and stroke utilities

Border, Outline, Text Stroke, and Image Stroke were rebuilt from the ground up, the same way as the color patterns. Each one now adapts to the device and every mode it supports, bit depth, dark mode, and themes, instead of carrying its own fixed colors. Borders also paint from generated gradients instead of image tiles, so a screen full of them fetches no images and stays sharp at any size.

### Fluid Mashups

Fluid Mashups sit alongside the fixed layouts. The `mashup--3x3` layout divides the screen into a three by three grid, and dedicated `mashup-cell` column, row, and span modifiers create custom tilings without inline grid styles.

Every cell draws the mashup window frame and a compact title bar at any size, across 1-bit, 2-bit, and 4-bit screens, dark mode, and themes.

### Complete scale modifiers

Screen scale modifiers now compose with each device's native UI density and resize framework gaps, pixel-based utilities, image presets, component geometry, radii, and strokes. The new 66% xxsmall level fits dense dynamic mashups, and `TRMNLPaint.px()` gives numeric JavaScript dimensions the same live scale contract. Every modifier except regular also renders typography as Inter Variable with its tuned weights, so scaled screens draw crisp vector text instead of off-grid bitmap glyphs.

### Text Scale

Text Scale lets one screen modifier adjust every framework font size and pixel line height from 80% to 150%. It composes with Scale, so plugins can make the interface denser while keeping text readable and text strokes tied to the interface scale. Every factor except regular resolves to the vector face as well, while the active font bundle keeps its sizes and line heights.

## Fixes and Improvements

- **Scale token functions with fallbacks.** `content-scaled()`, `ui-scaled()`, and the new `text-ui-scaled()` accept space-separated shorthand values and emit a `1` fallback, so plugin stylesheets referencing the scale variables keep rendering against framework bundles that predate them.
- **Crisp fit values on WebKit.** Fixed WebKit keeping stale flex heights after a fitted value shrinks its text: the text shrank but its container kept the pre-shrink height. The runtime now forces a layout recalculation of every screen when rendering finishes.
- **Themed mashup backdrops.** The `screen--backdrop` mashup backdrop now adapts to themes and dark mode.
- **`.hidden` hides layout elements.** Fixed `.hidden` failing to hide `.flex`, `.grid`, and `.block` elements.
- **Stable repeated renders.** Re-rendering a screen no longer drops overflow items, sticks values at a shrunken size, or drifts column gaps.
- **Phantom label classes removed.** Duplicated overflow group headers now carry the real `label--base` class instead of the nonexistent `label--medium`, and the dead `label--underline-2` selectors are gone from the stylesheet.
- **Corrected TRMNL pixel fonts.** The TRMNL12, TRMNL16, and TRMNL21 families are updated to v1.002, following two community reports. Every advance, outline point, and kern value now lands on the pixel grid, so TRMNL16 no longer renders random words heavier on device, and each weight carries its own name records instead of the placeholder "." style that collided in font managers. The files now also embed their SIL OFL 1.1 license terms, and the full per-change log ships as `public/fonts/CHANGELOG.md`.
- **Dogicapixel removed.** The two Dogicapixel `@font-face` declarations are gone from the stylesheet and the font files no longer ship. The family rode along declared in every bundle since 0.0.2 but was never applied by a framework rule, never part of a font bundle, and never documented.
- **Distinct grayscale ramps for chromatic shades.** On grayscale screens every hue now renders a monotone ramp with distinct steps while keeping its true lightness: dark blues no longer collapse into a single near-black band on 16-gray screens, and the light yellows spread across distinct grays instead of nine identical whites.
- **Orientation variants for table sizes.** Table modifiers now take orientation the way every other family does, so `lg:portrait:table--xlarge` raises the row height only on large portrait screens. Only `sm:`, `md:`, and `lg:` compiled before, so an orientation prefix on a table did nothing at all.
- **Aspect Ratio out of beta.** The aspect utilities ship with the full size and orientation variant set, so `lg:landscape:aspect--16/9` reshapes a tile only on large landscape screens. Stacked size prefixes also resolve mobile-first now, so `md:aspect--16/9 lg:aspect--4/3` lands the ratio the screen calls for instead of whichever class the stylesheet wrote last.
- **Responsive out of beta.** The responsive system graduates: size, orientation, bit-depth, and dark variants compile for every family the docs list, and combined gates like `md:portrait:2bit:label--filled` target exactly the screens they name instead of depending on which selectors happened to emit. The Responsive and Responsive Test pages drop their beta tags.
- **Responsive visibility across breakpoints.** Display classes now resolve mobile-first across size prefixes, so `md:hidden lg:visible` hides an element on medium screens and shows it again on large ones. The stylesheet wrote every `hidden` rule after every other display rule, so a narrower `md:hidden` beat any wider display class and the element stayed hidden.
- **Landscape variants without an explicit screen class.** Compound landscape variants such as `md:landscape:gap--large`, and every size, bit-depth, and dark pairing, now follow the framework's landscape default the same way the bare `landscape:` prefix already did. They required a `.screen--landscape` class that nothing sets, so they never matched real markup.
- **Flex alignment through variant prefixes.** `md:flex--center` and its eight sibling alignment modifiers now compile. Each was emitted as an escaped compound selector no markup could target, so a prefixed alignment class had no effect on any screen.
- **Label variants restored.** `label--inverted` and `label--gray-out` now carry the full variant set. Each shared a stylesheet rule with a sibling modifier and only the sibling received variants, so `md:label--inverted` did nothing.
- **Filled labels inside Items keep their pill.** `label--filled`, `label--inverted`, and the semantic color labels keep their background inside an Item on 2-bit and 4-bit screens. The Item's label dimming repainted them as plain text, so the pill vanished and the label's light text sat directly on the screen.
- **Correct type weight on bare orientation variants.** `portrait:value--large` and `landscape:content--base` now render at the device weight on 2-bit and 4-bit screens. The device override reached the size and size-with-orientation forms but not the bare orientation ones, so those rendered the right size at the wrong weight.
- **Phantom rich text variants removed.** `sm:content` and its prefixed forms no longer ship. The rich text container has no responsive variants of its own, so those classes carried a font weight and nothing else.
- **Flex, layout, and grid alignment through variant prefixes.** `md:flex--center`, `md:layout--center-y`, `portrait:gap--auto` and their siblings now compile. Each modifier sits on a compound selector next to a direction or container class, and the whole compound was escaped into a single class name no markup could target, so a prefixed alignment class had no effect on any screen.

## Upgrade Notes

Framework 3.2 is backward compatible with 3.0 and 3.1: documented classes and attributes keep their meaning. The notes below cover the corrected fonts, two deprecations, the served asset change, and a small set of fixed behaviors that render differently.

- **TRMNL16 text lays out differently.** The corrected word space is exactly 6 pixels instead of 6.125, so line breaks can fall in new places and the randomly emboldened words disappear. This is the fix landing, not a regression.
- **Remove locally installed v1.000 TRMNL fonts.** If you installed the original TTFs on your system, uninstall them before installing v1.002. The PostScript names changed, and a stale v1.000 install can shadow or duplicate the new fonts in Font Book or Windows Fonts.
- **`Dogicapixel` no longer resolves.** The family was never part of a font bundle and never documented. If custom markup sets `font-family: Dogicapixel`, it now falls back like any unknown family.
- **Numbered border levels are deprecated.** `border--h-1` through `border--h-7` (and the vertical forms) still render, but new markup should use the `border--h-10` to `border--h-75` shade-step rail. The numbered classes are scheduled for removal in 4.0.
- **`table-overflow-counter` is now `data-table-overflow-counter`.** New markup should use the `data-` prefixed form for valid HTML. The old attribute still works.
- **Responsive variants of arbitrary gaps no longer ship.** `md:gap--[25px]` and other prefixed arbitrary gaps compiled in 3.1 but were never documented; the Gap page always stated arbitrary gaps carry no responsive variants. The tokenized gap scale keeps every variant, and a leftover prefixed spelling now does nothing instead of zeroing the gap.
- **Compound landscape variants now apply.** `md:landscape:gap--large` and every other pairing with `landscape:` required a wrapper class the platform never emits, so they never matched. They now activate whenever the screen is not portrait, matching how the portrait side always worked.
- **Invalid spacing tokens now no-op.** A typo like `gap--8` or `p--999` used to erase the value a lower layer had set. It now leaves the container's own value in place.
- **The served bundles are the processed output.** `plugins.css` and `plugins.js` now ship minified, with private implementation variables shortened and duplicate rules folded; `plugins.min.css` and `plugins.min.js` remain as byte-identical aliases, so every pinned URL keeps working. Brotli siblings ship next to the gzip files. Class names and every public, runtime, and theme variable are unchanged.

## Documentation Changes

- Added the 3.2 guide track (v3 Overview, Enhancement Guide, and Upgrade Guide) and froze the 3.0 and 3.1 guides into their own version track. 3.2 is now the default docs version.
- Added Themes and Paint API documentation pages, and reworked the Chart, Image, Border, Outline, Text Stroke, and Image Stroke pages for the new adaptive behavior.
- Documented Fluid Mashups on the Mashup page: a 3x3 grid, custom tilings, and title bars inside cells.
- Updated the Scale and Screen pages with the xxsmall level and complete pixel-based scaling behavior, and added the Text Scale reference.
- Documented the vector font fallback on the Font Family, Text Size, Text Scale, and Scale pages, and moved the Text Scale custom CSS example to the `var(--text-ui-scale, 1)` fallback form.
- Regrouped the render-time documentation under a Runtime section.
- Added a Style selector to the docs screen picker for previewing any example under a theme.
- Added a Text Scale selector to the docs screen picker, and rebuilt the Text Scale interactive preview around a sizing slider.
- Corrected docs that taught nonexistent modifier classes, wrong size ranges, and responsive variants that do not compile.
- Corrected the Responsive page to describe breakpoints as the size class each device carries rather than a minimum pixel width. The published widths did not match the device map: Kindle 2024 and TRMNL OG share a resolution across two breakpoints, and reMarkable Paper 2 is narrower than several `md` devices while resolving to `lg`. The prefix table now lists which size classes each prefix applies on. Added a Table row to the component support matrix.
- The Colors page palette grids now sit directly on the docs background in light and dark mode instead of a white screen panel, so the swatches read against the page like every other docs figure.
- Added license terms to the Font Family page and the bundle READMEs: every font now lists its license and copyright, and each bundle download carries OFL.txt with the full license text.
