# V3.4 Overview Framework 3.4 adds custom theme fonts and fixes clipped card borders. It keeps the adaptive maps, position utilities, and themes introduced in 3.3. Existing plugin markup continues to work. ### Open Source The design system behind every TRMNL screen is now yours to read, fork, and build on. It lived inside the core product since inception; 3.2 pulled it out into its own public repository at [github.com/usetrmnl/trmnl-framework](https://github.com/usetrmnl/trmnl-framework). It is yours to shape too. Test suites and CI run on every pull request, and the [Open Source](/framework/docs/3.4/open_source) and [Contributing](/framework/docs/3.4/contributing) guides show where everything lives and how to ship a change. ### The v3 Color Foundation 3.3 builds on the color system introduced in v3.0: 10 hues with 14 lightness steps, semantic roles (primary, success, error, warning), and a 14-step grayscale, listed in full on [Colors](/framework/docs/3.4/colors) . Every color adapts to the device's palette and bit depth on its own, with no per-device markup. ### Independent Text Scale Text Scale resizes all framework text without touching the rest of the interface. It stacks with Scale, so a dense layout can keep its text large instead of shrinking both together. See [Text Scale](/framework/docs/3.4/text_scale) . ### Themes Recolor a whole screen by loading one extra stylesheet and adding `screen--theme-`. The theme picks the colors; each device renders them with the inks it has. 3.2 ships three themes: Black and Yellow, White and Red, and Dark. - Author your own theme with the `theme-slots` mixins and validate it with `rake framework:themes:lint`. - Preview any docs example with a theme via the Style selector in the screen picker. Full reference: [Themes](/framework/docs/3.4/themes) . ### Extended Theming 3.3 widens what a theme can reach. The title bar's inks split into slots of their own, item cards gain a fill, a paired ink, padding and a border art, and dividers gain a slot, so a theme restyles those surfaces instead of leaving them on the screen's defaults. Structure moves through unitless factors over the unthemed geometry, where 1 is always the unthemed screen: whitespace, corners, title bar height and progress. A signed weight shift moves every vector-face role, and text modifiers set case and tracking per text role. Size and line height stay out of it, because those belong to the device and the reader's own text scale. 3.4 lets a theme or a plugin bring its own font: [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . Every slot and factor: [Theme Slots](/framework/docs/3.4/theme_slots) . ### TRMNLPaint: the JavaScript Paint API Read any framework color, border, text style, or size from JavaScript. `TRMNLPaint` returns what CSS would paint right now, with the device, scale, and theme already applied, so your JavaScript never hardcodes a color. - Resolvers read one value each: `bg()`, `text()`, `semantic()`, `series()`, `border()`, `type()`, and more. - Painters: `apply()`, `applyBorder()`, `applyType()` write resolved paint onto nodes. - `watch()` re-runs your build function whenever the screen's device, scale, mode, dark mode, or theme changes. - `cssVar()` reads any public `--*` variable, which is how a theme hands values to plugin code. - `slot()` returns what any component slot paints with, and `toMapLibre()` shapes a Fill for MapLibre GL JS. - `scale()` reads the current scale factor, and `px()` converts pixel numbers with it. Full reference: [Paint API](/framework/docs/3.4/paint_api) . ### Generated Paint Assets Every dither pattern is now generated at build time as an inline SVG instead of shipping as a PNG image, so a screen paints with zero image fetches. - CSS and JavaScript share the same generated patterns: `TRMNLPaint` reads back exactly what CSS paints. - Every screen mode, Raw/Preview included, paints from the same generated set, so pattern shapes never change between modes. ### Adaptive Charts The runtime now bundles `TRMNLCharts`, a Highcharts adapter built on TRMNLPaint. Series colors, grid lines, and chart text all come from the framework, so a chart adapts with the rest of the screen instead of carrying its own fixed colors. - `options()` and `merge()` give you adaptive Highcharts defaults under your own settings. - `series(i, n)` picks each series color; `applySwatches()` paints matching legend markers. - `watch()` rebuilds the chart on device, scale, mode, dark mode, or theme changes. - `TRMNLPaint.px()` scales numeric chart dimensions from the same live CSS contract. Usage and live examples: [Chart](/framework/docs/3.4/chart) . For charting beyond Highcharts, use [Paint API](/framework/docs/3.4/paint_api) directly. ### Adaptive Maps Maps join charts on TRMNLPaint. The runtime bundles `TRMNLMaps`, a MapLibre GL JS adapter that composes the map style from the framework's map slots over OpenStreetMap vector tiles, so a plotted map takes dither tiles on 1-bit, solids on 4-bit, hues on a color panel, and a theme's own tokens. Maps render as one still frame, with no interaction and no animation. A map with no source of its own fetches OpenStreetMap's public Shortbread tiles directly. A plugin names its own source and key instead, and a host can hand one to a single plugin instance, so a key never sits in the markup. Usage and live examples, including a Strava activity: [Map](/framework/docs/3.4/map) . The resolvers and adapter behind it: [Painting Maps](/framework/docs/3.4/paint_maps) . ### Positioned Content `relative` and `absolute` put one element over another instead of beside it, so a full-bleed panel can take the whole layout and carry a compact card in one corner of it. Offsets run on the spacing scale, and a short stacking scale settles which of two overlapping elements is drawn on top. An element out of flow is not measured by the fitting engines, so it carries its own size and clamps. The classes, the scale, and what an overlay needs to stay readable on 1-bit: [Position](/framework/docs/3.4/position) . ### Adaptive Icons Add `image--adaptive` to a monochrome icon and the framework recolors it the way it colors text: it follows the device, Raw/Preview, and the active theme. One set of icons works everywhere, as long as they are same-origin or CORS-readable. See [Image](/framework/docs/3.4/image) . ### Rebuilt Borders, Outlines, and Strokes The Border, Outline, Text Stroke, and Image Stroke utilities were rebuilt the same way as the color system and now follow the device and themes on their own. - **Borders:** `border--h-{step}` and `border--v-{step}` now use the same 10 to 75 shade scale as backgrounds, plus `border--h-black` and `border--h-white`. Lines render as generated gradients instead of PNG tiles, so a bordered screen fetches no images. The numbered levels `border--h-1` through `border--h-7` still work but are deprecated. See [Border](/framework/docs/3.4/border) . - **Outline:** draws a pixel-perfect dotted rounded border on 1-bit, and a solid rounded border on 2-bit and 4-bit. 3.3 rounds the element itself on the same 8px curve, so a card with its own background ends where the outline does, and adds `outline--muted` for an edge in a mid gray that still prints on 1-bit. See [Outline](/framework/docs/3.4/outline) . - **Text Stroke:** renders as stacked drop shadows instead of native `-webkit-text-stroke`, so the stroke sits behind the letters in every browser, even when the text is filled with a pattern. Sizes run `text-stroke--small` to `text-stroke--xlarge`. See [Text Stroke](/framework/docs/3.4/text_stroke) . - **Image Stroke:** outlines transparent images with the same size and color variants. See [Image Stroke](/framework/docs/3.4/image_stroke) . ### Mashup Backdrop The `screen--backdrop` modifier now follows themes like every other surface. See [Mashup](/framework/docs/3.4/mashup) . ### Fluid Mashups Fluid Mashups sit alongside the fixed layouts: place views on a three by three grid with `mashup--3x3` and the `mashup-cell` modifiers, no inline styles needed. Every cell keeps its frame and a compact title bar at any size, on every device and under themes. See [Mashup](/framework/docs/3.4/mashup) . ### Complete Scale Modifiers Scale modifiers now resize everything: gaps, pixel utilities, image presets, component geometry, radii, and strokes, on top of each device's own density. The new `screen--scale-xxsmall` level adds a 66% scale for dense mashups, and `TRMNLPaint.px()` lets JavaScript convert pixel numbers the same way. See [Scale](/framework/docs/3.4/scale) . ### Under the Hood - **Smaller stylesheets:** the responsive and device rules were refactored onto `:is()` selectors. ### Start Here - Upgrading an existing plugin? → [V3.4 Upgrade Guide](/framework/docs/3.4/v3_upgrade_guide) . - Want to adopt themes, adaptive charts, and adaptive icons? → [V3.4 Enhancement Guide](/framework/docs/3.4/v3_enhancement_guide) . Next [ ## V3.4 Upgrade Guide Compatibility notes for upgrading plugins to Framework 3.4 ](/framework/docs/3.4/v3_upgrade_guide) --- # V3.4 Upgrade Guide Framework 3.4 is fully backward compatible with 3.0 through 3.3. Existing class names render unchanged, and every new feature (themes, the paint API, adaptive charts, maps, and icons) is opt-in. This guide lists the few things worth reviewing when you upgrade. ### From Framework 3.0 or 3.1 Your plugin looks the same until you opt into new features. Two things are worth reviewing. - **Numbered border levels are deprecated:** `border--h-1` through `border--h-7` (and vertical counterparts) still render, but will be removed in Framework 4.0. Prefer the shade steps (`border--h-10` to `border--h-75`) in new markup. See [Border](/framework/docs/3.4/border) . - **Themed screens ignore dark mode:** if you add a `screen--theme-` class, `screen--dark-mode` no longer applies. A theme already decides every color. See [Themes](/framework/docs/3.4/themes) . ### From Framework v2 v3 is backward compatible with v2: your class names still render. One visual change needs review. v3 rebuilt the 1-bit grayscale dither patterns on a 14-step linear scale, so most shade names look lighter or darker than they did in v2. The full shade-by-shade migration table lives in the [Framework 3.1 upgrade guide](/framework/docs/3.1/v3_upgrade_guide). It lists each shade's v2 and v3 lightness and which v3 shade restores the original look. ### Verifying the Upgrade Use the Raw / Preview toggle in the screen picker (top right) to compare the raw colors against what the panel will actually show. To check themes, pick a Style in the same picker and confirm your plugin stays legible under each one. ### Next Steps Head to the [V3.4 Enhancement Guide](/framework/docs/3.4/v3_enhancement_guide) to make your plugin theme-ready and to adopt adaptive charts and icons. Previous [ ## V3.4 Overview What's new in Framework 3.4: custom theme fonts and complete card borders ](/framework/docs/3.4/v3_overview) Next [ ## V3.4 Enhancement Guide Make your plugin theme-ready and adopt adaptive charts, icons, and JS paint ](/framework/docs/3.4/v3_enhancement_guide) --- # V3.4 Enhancement Guide Framework 3.4 lets an existing plugin follow themes and device modes everywhere: in markup, in charts, in icons, and in outlined text and images. This guide walks through the enhancements one at a time; adopt them in any order. ### 1. Make Your Plugin Theme-Ready A theme recolors the whole screen. Your plugin does not load themes itself; it renders inside a screen that may carry one. A plugin is theme-ready when everything it draws uses framework classes and colors. - Style with framework utilities (`bg--`, `text--`, `border--`) and elements (`label`, `value`, `title`). Themes recolor all of them. - Avoid hardcoded hex colors and inline styles; a theme cannot recolor them. - Test with the Style selector in the docs screen picker. It applies a theme to every example on the page. To theme a screen you control, include the theme stylesheet and add the theme class. See [Themes](/framework/docs/3.4/themes) . ```
...
``` ### 2. Migrate Charts to TRMNLCharts Use `TRMNLCharts`, the Highcharts adapter bundled with the runtime, for your charts. They become device-responsive, adapting to each panel's capabilities, and follow the active theme, with no hardcoded colors. - Build your chart inside `TRMNLCharts.watch()` so it rebuilds when the device, scale, mode, dark mode, or theme changes. - Start from `TRMNLCharts.options()` merged under your own settings. - Color each series with `TRMNLCharts.series(i, n)` instead of a literal color. - Convert numeric chart dimensions with `TRMNLPaint.px()`. ``` var el = document.getElementById("my-chart"); TRMNLCharts.watch(el, function () { var px = function (value) { return TRMNLPaint.px(value, { el: el }); }; Highcharts.chart(el, TRMNLCharts.merge(TRMNLCharts.options({ el: el }), { chart: { height: px(260) }, plotOptions: { series: { lineWidth: px(4) } }, series: [ { data: incoming, color: TRMNLCharts.series(0, 2, { el: el }) }, { data: outgoing, color: TRMNLCharts.series(1, 2, { el: el }) } ] })); // Paint legend markers tagged data-chart-series="i" with matching series colors. TRMNLCharts.applySwatches({ el: el }); }); ``` Full examples for line, multi-series, and bar charts are on the [Chart](/framework/docs/3.4/chart) page. ### 3. Mark Monochrome Icons Adaptive Add `image--adaptive` to monochrome silhouette icons. The framework takes the icon's shape and repaints it in the screen's icon color, following the device, Raw/Preview, and the active theme. ``` ``` - Silhouettes only: never use it on photos or multi-color logos. Use [Image Stroke](/framework/docs/3.4/image_stroke) to keep those legible instead. - Icons must be same-origin or served from a CORS-enabled host, or the framework leaves them unpainted. See [Image](/framework/docs/3.4/image) . ### 4. Resolve Paint from JavaScript Drawing something yourself, on canvas, in SVG, or with another library? Ask `TRMNLPaint` for the colors. It returns what CSS would paint right now, with the device and the active theme already applied. ``` // A background token: solid where the panel can print it, dithered down to its inks where it cannot. var fill = TRMNLPaint.bg("gray-40", { el: "my-visual" }); // The effective one-color value of a text utility for SVG or canvas text. var ink = TRMNLPaint.textColor("default", { el: "my-visual" }); // Rebuild whenever the screen device, scale, mode, dark mode, or theme changes. TRMNLPaint.watch("my-visual", function () { draw(); }); ``` The full resolver and painter surface is documented on [Paint API](/framework/docs/3.4/paint_api) . ### 5. Move Borders to Shade Steps Replace the numbered border levels with shade steps; the numbered classes still render, but they are deprecated. Steps use the same 10 to 75 scale as backgrounds, and themes recolor them. Borders now render as generated gradients instead of PNG tiles, so a bordered screen fetches no images. ```
...
...
...
``` See [Border](/framework/docs/3.4/border) for the full step scale and how themes recolor it. ### 6. Keep Overlaid Text and Images Legible Text and images placed over a shaded or patterned surface can lose contrast. The Text Stroke and Image Stroke utilities outline them, and both were rebuilt to follow the device and themes like the rest of the screen. - Add `text-stroke` to framework text over a busy background, and size it with `text-stroke--small` through `text-stroke--xlarge`. The stroke stays behind the letters in every browser, even when the text is filled with a pattern. - Add `image-stroke` to a transparent or vector image for the same effect, with the matching `--small` through `--xlarge` sizes. - Leave the color off to stroke with the default contrast ink, or set one with a color variant (`text-stroke--black`, `image-stroke--white`, or any palette token). Color variants follow the active theme. ``` 64% ``` Full size and color scales are on the [Text Stroke](/framework/docs/3.4/text_stroke) and [Image Stroke](/framework/docs/3.4/image_stroke) pages. Previous [ ## V3.4 Upgrade Guide Compatibility notes for upgrading plugins to Framework 3.4 ](/framework/docs/3.4/v3_upgrade_guide) Next [ ## TRMNL X Guide Framework changes for TRMNL X compatibility ](/framework/docs/3.4/trmnl_x_guide) --- # TRMNL X Guide TRMNL X is a larger, 4-bit ePaper display. This guide covers what changed in the Framework to support it: new size modifiers, container query units, responsive overflow columns, and layout improvements. Existing plugins keep working; adopt these features to use the larger screen, portrait orientation, and expanded grayscale. ### The --base Modifier Every element and component now supports an explicit `--base` size modifier. It produces the exact same result as using the class without any size modifier, but it lets you **reset to the default size at a specific breakpoint**. Previously, if you set a smaller size for compact screens, there was no way to undo it at a larger breakpoint. Now you can: ``` Dashboard 48,206
...
``` The `--base` modifier is available on: - **Typography:** [Title](/framework/docs/3.4/title) (`title--base`), [Value](/framework/docs/3.4/value) (`value--base`), [Label](/framework/docs/3.4/label) (`label--base`), [Description](/framework/docs/3.4/description) (`description--base`) - **Rich Text:** [Rich Text](/framework/docs/3.4/rich_text) (`content--base`) - **Components:** [Table](/framework/docs/3.4/table) (`table--base`), [Progress](/framework/docs/3.4/progress) (`progress-bar--base`, `progress-dots--base`) - **Utilities:** [Gap](/framework/docs/3.4/gap) (`gap--base`), [Rounded](/framework/docs/3.4/rounded) (`rounded--base`), [Text Stroke](/framework/docs/3.4/text_stroke) (`text-stroke--base`), [Image Stroke](/framework/docs/3.4/image_stroke) (`image-stroke--base`) ### New Larger Typography Sizes To take full advantage of TRMNL X's larger display, new size tiers have been added across all typography elements. All new sizes support responsive prefixes and render correctly on 2-bit and 4-bit screens. #### Value Four new sizes for hero-style numbers and display text. See the [Value](/framework/docs/3.4/value) docs for full details. ``` 42 42 42 42 42 ``` #### Title Three new large sizes for headings. See the [Title](/framework/docs/3.4/title) docs. ``` Heading Heading Heading ``` #### Label, Description, Rich Text New large sizes for [Label](/framework/docs/3.4/label) (`label--large`, `label--xlarge`, `label--xxlarge`), [Description](/framework/docs/3.4/description) (`description--large`, `description--xlarge`, `description--xxlarge`), and [Rich Text](/framework/docs/3.4/rich_text) content (`content--xlarge`, `content--xxlarge`, `content--xxxlarge`). ### Container Query Units The [Layout](/framework/docs/3.4/layout) element now establishes a CSS Container Query context, enabling a new set of container-relative sizing utilities. Unlike viewport units, these are relative to the layout's dimensions, so they work correctly inside mashup slots (half vertical, quadrant, etc.) where the available space is a fraction of the full screen. ```
Half the layout width
80% of layout height
Constrained width
Full width on small, half on large
``` Available utilities: `w--[Ncqw]`, `h--[Ncqh]` (0 to 100), plus `w--min-[Ncqw]`, `w--max-[Ncqw]`, `h--min-[Ncqh]`, `h--max-[Ncqh]`. All support responsive variants. See the [Size](/framework/docs/3.4/size) docs for full details. ### Responsive Arbitrary Sizes The `w--[Npx]` and `h--[Npx]` utilities now support responsive prefixes, where they used to be static-only. Pixel values range from 0 to 128. See the [Size](/framework/docs/3.4/size) docs. ```
Responsive arbitrary width
Orientation-aware height
``` ### Responsive Overflow Columns The [Overflow](/framework/docs/3.4/overflow) engine now reads responsive data attributes to determine how many columns to generate, based on screen size and orientation. Resolution order (most specific wins): size + portrait, then size, then portrait, then base. ```
...
...
``` Supported suffixes: `-sm`, `-md`, `-lg`, `-portrait`, `-sm-portrait`, `-md-portrait`, `-lg-portrait`. The same pattern works for `data-overflow-cols` (fixed column count). ### Layout Improvements #### Axis-Correct stretch-x / stretch-y `stretch-x` and `stretch-y` now behave correctly relative to the [Layout](/framework/docs/3.4/layout) direction. In `layout--row`, `stretch-x` grows along the horizontal axis and `stretch-y` stretches vertically, and in `layout--col` the axes are swapped. These utilities also now include `min-width: 0` / `min-height: 0` to prevent flex children from overflowing their containers. #### Responsive Grid Column Spans The [Grid](/framework/docs/3.4/grid)`col--span-*` classes now work with all responsive prefixes. Responsive parent variants also work: if the grid itself has a responsive prefix (e.g., `portrait:grid`), nested children resolve correctly. ```
Full width on small, half on large
Full width on small, half on large
...
``` #### Item Component The [Item](/framework/docs/3.4/item) component now gives `.icon` elements the same flex styling as `.content`. Items in [Flex](/framework/docs/3.4/flex) row containers automatically stretch to match the tallest sibling, and items in [Grid](/framework/docs/3.4/grid) containers stretch to fill their cell height. ### Gap and Rounded Utilities #### Gap New [Gap](/framework/docs/3.4/gap) classes: `gap--base` (explicit base, useful for responsive reset), `gap--auto` (distributes space evenly around items), and `gap--distribute` (first item at start, last at end, space between). Arbitrary gaps now start from `gap--[0px]` (previously 5px). `gap--space-between` remains as a legacy alias. #### Rounded New [Rounded](/framework/docs/3.4/rounded) class: `rounded--base` (explicit base, 10px). Arbitrary values now start from `rounded--[0px]` (previously 1px). ### Rich Text Improvements The [Rich Text](/framework/docs/3.4/rich_text) component has several improvements: - `content--center` now correctly centers text on 4-bit screens. - Rich text content max-width is now size-aware and adjusts automatically per screen size (small: 380px, medium: 640px, large: 780px). In portrait orientation, it uses the full screen width. - All content size variants support responsive prefixes. ```

Small on compact devices, default on large screens

``` ### Responsive Clamp The [Clamp](/framework/docs/3.4/clamp) system now supports the `lg` breakpoint. ``` Long label text... ``` Available attributes: `data-clamp`, `data-clamp-sm`, `data-clamp-md`, `data-clamp-lg`, `data-clamp-portrait`, `data-clamp-sm-portrait`, `data-clamp-md-portrait`, `data-clamp-lg-portrait`. ### Landscape Orientation Default `landscape:` prefixed classes need no class of their own on the screen element. Landscape is the default orientation, so `landscape:` prefixes activate whenever `.screen--portrait` is not present. See the [Responsive](/framework/docs/3.4/responsive) docs. ### Bug Fixes - **Title bar on 4-bit screens:** Fixed an issue where the background image could bleed through on 4-bit displays. The title bar now explicitly clears the background image before applying the background color. - **Half horizontal layout height:** The layout height inside half horizontal views was incorrectly using quadrant dimensions. It now correctly uses half horizontal dimensions. - **Available height computation:** Improved height calculation for elements inside flex column layouts. The engine now sums sibling heights and gaps instead of relying on `getBoundingClientRect()`, which can be inaccurate before layout settles. ### Internal Changes The following changes are under-the-hood improvements to how the Framework generates its CSS. They don't introduce new classes or markup patterns, but they make the responsive system more robust and maintainable. - Responsive mixin system refactored to use a shared `parse-selector-components` helper, fixing edge cases with nested selectors. - New `with-responsive-parent-child-variants` mixin powers responsive grid column spans and flex child utilities. - New device-specific responsive generators (`responsive-for-bit-depth`, `responsive-orientation-for-bit-depth`) for scoped bit-depth overrides. - Value and rich text device overrides refactored from manual per-class rules to generated loops. - `screen--sm`, `screen--md`, and `screen--lg` now apply size-specific CSS variable overrides (previously empty). Previous [ ## V3.4 Enhancement Guide Make your plugin theme-ready and adopt adaptive charts, icons, and JS paint ](/framework/docs/3.4/v3_enhancement_guide) Next [ ## Open Source What this repository is, how it fits together, and the paint mandate that shapes it ](/framework/docs/3.4/open_source) --- # Open Source The TRMNL Framework is open source as of version 3.2. It is the design system TRMNL plugin screens are built with, tuned for 1-bit, 2-bit, 4-bit, and limited-color ePaper displays. This repository holds the CSS, the JavaScript runtime, the design tokens, and the documentation site you are reading. ### What This Repository Is The source lives at [github.com/usetrmnl/trmnl-framework](https://github.com/usetrmnl/trmnl-framework). Star it, fork it, or clone it from there. The framework and the site that documents it live together in one Rails app. It has no database. It compiles the CSS, then renders these pages against it and the JavaScript runtime. Framework development happens against the docs: edit the Sass or JS, watch it rebuild, and check the result on the page it documents. TRMNL FrameworkThe ePaper design system is now open source. 3.3Version MITCode license ![TRMNL Logo](/images/plugins/trmnl--render.svg)Open Source The fixed hierarchy of Screen, View, Layout, and Title Bar is documented in [Structure](/framework/docs/3.4/structure) . ### The Design System The CSS is a Sass design system under `app/assets/stylesheets/framework/`. It renders one layout correctly across very different screens. The SCSS source and what a custom stack can build from it are documented in [Sass API](/framework/docs/3.4/sass_api) . - **Bit-depth-aware tokens:** a color dithers down to the panel's inks where needed and paints exactly on full color. See [Colors](/framework/docs/3.4/colors) and [Tokens](/framework/docs/3.4/tokens) . - **Dither and tile backgrounds:** grayscale and limited-palette fills are generated tiles, applied with `bg--{shade}`. See [Background](/framework/docs/3.4/background) . - **Device screen classes:** `.screen--` bakes each device's dimensions and palette into the build. See [Screen](/framework/docs/3.4/screen) . - **Themes:** opt-in stylesheets recolor a whole screen; the device still decides how colors print. See [Themes](/framework/docs/3.4/themes) . ### The Runtime The JavaScript runtime lives in `app/javascript/` and ships as two files: the readable `plugins.js` and the minified `plugins.min.js` production serves, both stamped with the version they were released from. It does the render-time work a static stylesheet cannot. - **terminalize:** the layout engine that fills each device's fixed space, handling overflow columns, text clamping, value fitting, and more. See [Framework Runtime](/framework/docs/3.4/framework_runtime) . - **TRMNLPaint:** returns what CSS would paint right now as Fill, BorderFill, and TypeSpec objects, so any plugin can read framework colors from JavaScript. See [Paint API](/framework/docs/3.4/paint_api) . - **TRMNLCharts:** a Highcharts adapter built on TRMNLPaint, so charts adapt to bit depth and themes with the rest of the screen. See [Chart](/framework/docs/3.4/chart) . - **TRMNLMaps:** a MapLibre GL JS adapter built on TRMNLPaint, so a plotted map over OpenStreetMap vector tiles adapts the same way. See [Map](/framework/docs/3.4/map) . ### CSS Is the Single Source of Truth for Paint One rule shapes the whole project. Every rendering rule exists in CSS first, then `TRMNLPaint` mirrors it. `TRMNLPaint` reads what CSS computed and converts it. It never re-implements color rules, adds contrast tweaks, or invents fallback values in JavaScript. The same instinct applies to new features. Before adding a pattern, tile, gradient, or one-off variable, check whether the dither and tile system, the border pipeline, or the theme slots already express it. A new bespoke mechanism for one call site is almost always wrong. The full mandate is in `AGENTS.md`. See [Paint API](/framework/docs/3.4/paint_api) for how the API does its reading. ### The Release Pipeline Releases are reproducible and need only this repository. A release regenerates the tokens, compiles the Sass, and minifies the CSS and the version-stamped JS runtime. Every artifact is then precompressed as gzip and brotli, and zipped. The published archive under `public/css` and `public/js` keeps every version, so the docs render each version against the exact bundle it shipped. Releases are maintainer-only. ### What This Site Loads from Elsewhere The framework bundles fetch one thing from outside their own origin: a map's vector tiles, from TRMNL's own endpoint by default and otherwise from whichever source the map names. Fonts, the only other asset they load by URL, come from wherever `plugins.css` came from. The docs site around them loads from four third-party origins, all in the page chrome or in a demo. Each one degrades only the feature that uses it, so a host that blocks all four still serves every page here. - **Google Fonts** (`fonts.googleapis.com`, `fonts.gstatic.com`): Inter and EB Garamond for this site, Space Mono for code, and Inter again inside every demo iframe. Blocked, the site falls back to system fonts and the screens keep their self-hosted families. See [Font Family](/framework/docs/3.4/font_family) . - **unpkg** (`unpkg.com`): the `@trmnl/picker` component behind the device selector. Blocked, the picker stays blank. - **trmnl.com** : Highcharts and Chartkick, on the chart page and in the Shopify example. Blocked, those charts render empty. See [Chart](/framework/docs/3.4/chart) . - **jsDelivr** (`cdn.jsdelivr.net`): opentype.js, which reads the glyph tables. Blocked, those tables stay empty. See [Font Glyphs](/framework/docs/3.4/font_glyphs) . The docs site can mount as an engine in another Rails app; doing so brings all four origins with it. `docs/ENGINE_INTEGRATION.md` repeats this list with the CSP directives each origin needs. Map tiles come from TRMNL's own endpoint (`maps.trmnl.com`) by default, fetched by the page itself; the `'osm'` preset opts into OpenStreetMap's public Shortbread endpoint (`vector.openstreetmap.org`), whose usage policy allows light use and forbids a fleet. A plugin names its own source and key, a host injects one per plugin instance, and the `'trmnl'` preset names the TRMNL endpoint explicitly. The engine also serves `/framework/tiles/{z}/{x}/{y}.mvt` for a host proxying a source of its own (a pass-through from `Framework.tile_source_url`; nothing is stored). `docs/MAPS_GO_LIVE.md` is the checklist for a host that renders map plugins for devices. See [Map](/framework/docs/3.4/map) . ### License The framework code is released under the MIT license. See the `LICENSE` file in the repository. The MIT grant covers the code; the bundled fonts are licensed separately. The TRMNL pixel fonts (by Heavyweight Digital Type Foundry), Inter, and the Nico fonts ship under the SIL Open Font License 1.1; BlockKie ships under CC BY 3.0. Each font bundle download carries the full terms in its README and OFL.txt. Highcharts is a commercial library the framework does not include. The chart examples load it from trmnl.com, where TRMNL serves it under its own license, and `TRMNLCharts` is only the adapter. A custom stack brings its own charting library and license: see [Chart](/framework/docs/3.4/chart) . MapLibre GL JS is BSD licensed and vendored under `vendor/javascript/`, served by the engine next to the runtime and mirrored for plugins at `trmnl.com/js/maplibre-gl/5.24.0/`; `THIRD_PARTY_NOTICES.md` carries its notice. The map data is © OpenStreetMap contributors under the ODbL, which is why every map keeps its credit visible, and `TRMNLMaps` is only the adapter: see [Map](/framework/docs/3.4/map) . ### Start Here - Want to help build it? Read [Contributing](/framework/docs/3.4/contributing) . - New to the framework? Start with [Structure](/framework/docs/3.4/structure) and [Colors](/framework/docs/3.4/colors) . - Adopting the 3.2 and 3.3 features? See [V3.4 Overview](/framework/docs/3.4/v3_overview) . Previous [ ## TRMNL X Guide Framework changes for TRMNL X compatibility ](/framework/docs/3.4/trmnl_x_guide) Next [ ## Contributing Run the framework locally, find your way around, run the tests, and open a good pull request ](/framework/docs/3.4/contributing) --- # Contributing Everything you need to make your first contribution to the TRMNL Framework: run it locally, find your way around the code, run the test suites, and open a pull request that lands. Start here, then read CONTRIBUTING.md for the fine print. ### Run It Locally Clone [github.com/usetrmnl/trmnl-framework](https://github.com/usetrmnl/trmnl-framework). You need Ruby (see `.ruby-version`) and Node (see the `engines` field in `package.json`). Then run two commands. ``` bin/setup # gems, npm, token generation, and every CSS build bin/dev # server on :3001 with live Sass and Tailwind rebuilds ``` Open `http://localhost:3001/framework` to view the docs. `server/app/assets/builds/` is gitignored, so re-run `bin/setup` after a pull if the CSS looks stale. ### Where Things Live The framework and its docs share one Rails app. These are the paths you will touch most. - `app/assets/stylesheets/framework/`: the Sass design system (base, components, utilities, themes, mixins). - `app/javascript/`: the JavaScript runtime (terminalize, TRMNLPaint, TRMNLCharts, TRMNLMaps) and the docs harness. - `app/views/framework/*.html.erb`: one docs page per framework feature. - `app/helpers/framework_helper.rb`: page intro paragraphs and docs helpers. - `db/data/`: the token and release data (colors, fonts, devices, versions). The full directory map is in `README.md`. For what this repository is and the rule that shapes it, see [Open Source](/framework/docs/3.4/open_source) . ### The Test Suites Run the layer that matches your change, or run all of them before opening a pull request. - `npm run test:stylesheets`: public classes, variables, theme contracts, and the CSS paint data. - `npm run test:scripts`: runtime syntax, the public API names, and the CSS to JavaScript paint boundary. - `bundle exec rspec`: the Rails docs routes, versioned bundle selection, and generated endpoints. - `npm run test:runtime`: terminalize, the layout engines, and the paint API in Chromium. - `npm run test:visual`: rendered CSS against platform-specific PNG baselines. The last two use Playwright. Install its browser once per machine. ``` npx playwright install chromium ``` The runtime and visual suites run entirely on your machine. Both also run in CI: Linux Chromium against committed Linux baselines for visuals, and the same runtime checks as a local run. ### Writing Documentation The docs are the framework's test bench, so most changes touch a docs page. The copy follows a house style. - Lead with what the reader can do or what the thing is. - Keep paragraphs to one to three short sentences, one idea each. - Never use em-dashes. Rewrite with a period, comma, colon, or parentheses. - Inside a `.screen` demo, use framework classes only (`title`, `description`, `label`, `value`). The full guide is the write-docs skill under `.claude/skills/write-docs/`. ### What Makes a Good PR - Keep it small and focused. One concern per pull request. - Change a utility? Update its docs page and its live example in the same change. A utility without a rendered example is incomplete. - Run the test suites and confirm `bin/build` succeeds before you push. Releases are maintainer-only. Land your change, and a maintainer cuts the release. See `CONTRIBUTING.md` for the rest. ### Report Issues and Security Found a bug? Open a GitHub issue with a minimal reproduction. Ideas and feature requests are welcome as issues too. Security issues are different. Report them through GitHub's private vulnerability reporting, not a public issue. See `SECURITY.md`. All participation follows the `CODE_OF_CONDUCT.md`. Previous [ ## Open Source What this repository is, how it fits together, and the paint mandate that shapes it ](/framework/docs/3.4/open_source) Next [ ## Size Define exact width and height dimensions for elements ](/framework/docs/3.4/size) --- # Size Utility classes for width and height. Fixed sizes, arbitrary values, dynamic sizes, and container query units, each also available as a min or max constraint and with responsive variants. ## Setting Width and Height Control element dimensions using fixed sizes from the design scale, arbitrary pixel values, or dynamic sizes that adapt to their container. ### Fixed Sizes Use predefined size classes from the design scale. Apply `w--{size}` for width and `h--{size}` for height with these size values: `w/h--0` `w/h--0.5` `w/h--1` `w/h--1.5` `w/h--2` `w/h--2.5` `w/h--3` `w/h--3.5` `w/h--4` `w/h--5` `w/h--6` `w/h--7` `w/h--8` `w/h--9` `w/h--10` `w/h--11` `w/h--12` `w/h--14` `w/h--16` `w/h--20` `w/h--24` `w/h--28` `w/h--32` `w/h--36` `w/h--40` `w/h--44` `w/h--48` `w/h--52` `w/h--56` `w/h--60` `w/h--64` `w/h--72` `w/h--80` `w/h--96` `0px` `2px` `4px` `6px` `8px` `10px` `12px` `14px` `16px` `20px` `24px` `28px` `32px` `36px` `40px` `44px` `48px` `56px` `64px` `80px` `96px` `112px` `128px` `144px` `160px` `176px` `192px` `208px` `224px` `240px` `256px` `288px` `320px` `384px` ### Arbitrary Sizes Need a specific dimension? Use arbitrary size classes for precise pixel values with `w--[Npx]` and `h--[Npx]` syntax, where N can be any value from 0 to 128. Responsive prefixes work here (`md:w--[96px]`), unlike the arbitrary values on [Gap](/framework/docs/3.4/gap) and [Rounded](/framework/docs/3.4/rounded) . `w/h--[60px]` `w/h--[90px]` `w/h--[120px]` `60px` `90px` `120px` ### Dynamic Sizes Use dynamic sizes to set dimensions relative to the container or content. `w--full` and `h--full` set dimensions to 100% of the container, while `w--auto` and `h--auto` let the browser calculate dimensions based on content. Full Width Auto Width ![TRMNL Logo](/images/plugins/trmnl--render.svg)SizeDynamic Widths ```
Full width
Auto width
Full height
Auto height
``` ### Container Query Sizes Container query sizes let you size elements as a percentage of the `.layout` container. Use `w--[Ncqw]` for width and `h--[Ncqh]` for height, where N is 0 to 100 (0% to 100% of the layout's dimensions). This works automatically because `.layout` is configured as a CSS container query context. Any element inside a layout can use these units to size itself relative to the layout's width or height. This is useful for responsive images, flexible columns, or proportional spacing. For advanced cases where you need to reference a different container (e.g., a specific column), add `style="container-type: size;"` to that element. It must have explicit dimensions set. `w--[50cqw]` `w--[75cqw]` `h--[50cqh]` `50% container width` `75% container width` `50% container height` ```
50% of layout width
33% of layout height
``` ## Min/Max Dimensions Control minimum and maximum element dimensions independently using min and max classes. These constraints work with all sizing methods: fixed sizes, arbitrary sizes, container query units, and dynamic sizes. ### Fixed Sizes Use `w--min-{size}`, `w--max-{size}`, `h--min-{size}`, and `h--max-{size}` to constrain dimensions using fixed size values. Min Width 72 (288px) Max Width 32 (128px) ![TRMNL Logo](/images/plugins/trmnl--render.svg)SizeFixed Sizes ```
Min Width 72 (288px)
Max Width 32 (128px)
Min Height 72 (288px)
Max Height 8 (32px)
``` ### Arbitrary Sizes Use `w--min-[Npx]`, `w--max-[Npx]`, `h--min-[Npx]`, and `h--max-[Npx]` to constrain dimensions using precise pixel values. N can be any value from 0 to 128, the same range as the arbitrary width and height classes. `w--min-[100px]` `w--max-[120px]` `h--min-[50px]` `h--max-[100px]` `min-width: 100px` `max-width: 120px` `min-height: 50px` `max-height: 100px` ### Dynamic Sizes Use `w--min-full`, `w--max-full`, `h--min-full`, `h--max-full`, `w--min-auto`, `w--max-auto`, `h--min-auto`, and `h--max-auto` to constrain dynamic dimensions. `w--min-full` `w--max-full` `h--min-auto` `h--max-auto` `min-width: 100%` `max-width: 100%` `min-height: auto` `max-height: none` ### Container Query Sizes Use `w--min-[Ncqw]`, `w--max-[Ncqw]`, `h--min-[Ncqh]`, and `h--max-[Ncqh]` to constrain dimensions relative to the container. `w--min-[100cqw]` `w--max-[50cqw]` `h--min-[75cqh]` `h--max-[90cqh]` `min-width: 100cqw` `max-width: 50cqw` `min-height: 75cqh` `max-height: 90cqh` ## Responsive Sizes Size utilities support responsive variants, allowing you to set different dimensions at different screen breakpoints. Use the pattern `breakpoint:size-class` to apply sizes conditionally. ### Responsive Examples Apply different width and height values at different screen sizes using responsive prefixes. The framework follows a mobile-first approach where styles apply to the target breakpoint and larger. Responsive Width Responsive Height ![TRMNL Logo](/images/plugins/trmnl--render.svg)SizeResponsive Sizes ```
Responsive Width
Responsive Height
Responsive Min Width
Responsive Container Query
``` ### Supported Responsive Classes Responsive variants are available for most size utilities. Use prefixes like `md:`, `portrait:`, and `md:portrait:` to target different breakpoints and orientations. | Category | Responsive Support | Example Usage | | --- | --- | --- | | Fixed Sizes | ✓ Supported | `md:w--16, lg:h--24` | | Full/Auto Dimensions | ✓ Supported | `md:w--full, lg:h--auto` | | Min/Max Dimensions | ✓ Supported | `md:w--min-16, lg:h--max-full` | | Arbitrary Dimensions | ✓ Supported | `md:w--[128px], lg:h--[96px]` | | Container Query Sizes | ✓ Supported | `md:w--[50cqw], lg:h--[75cqh]` | Previous [ ## Contributing Run the framework locally, find your way around, run the tests, and open a good pull request ](/framework/docs/3.4/contributing) Next [ ## Spacing Control element spacing with fixed margin and padding values ](/framework/docs/3.4/spacing) --- # Spacing Utility classes for margin and padding. Fixed steps plus decimal values for the cases where a whole step is too coarse. Available spacing sizes and their pixel values [View Size Documentation](/framework/docs/3.4/size) ### Margin Utilities Control element margins using these utility classes. Each class follows the pattern `{property}--{size}` and supports responsive modifiers for **Size** [Size](/framework/docs/3.4/size) , **Orientation** , and **Size + Orientation** [Responsive](/framework/docs/3.4/responsive) . Margins take a scale token, so an arbitrary `m--[Npx]` value does nothing and the element keeps the margin it already had. `m--{size}`All sides margin `mt--{size}`Top margin `mr--{size}`Right margin `mb--{size}`Bottom margin `ml--{size}`Left margin `mx--{size}`Horizontal margin `my--{size}`Vertical margin `md:my--{size}`Size-based example `portrait:mx--{size}`Orientation-based example `lg:portrait:mt--{size}`Size + Orientation example ### Padding Utilities Control element padding using these utility classes. Each class follows the pattern `{property}--{size}`. See [Size](/framework/docs/3.4/size) for sizing tokens, the only values padding takes. `p--{size}`All sides padding `pt--{size}`Top padding `pr--{size}`Right padding `pb--{size}`Bottom padding `pl--{size}`Left padding `px--{size}`Horizontal padding `py--{size}`Vertical padding `sm:px--{size}`Size-based example `portrait:pb--{size}`Orientation-based example `md:portrait:pt--{size}`Size + Orientation example ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--gap-large` | 20px | - | - | - | | `--gap-medium` | 16px | - | - | - | | `--gap-small` | 7px | - | - | - | | `--gap-xlarge` | 30px | - | - | - | | `--gap-xsmall` | 5px | - | - | - | | `--gap-xxlarge` | 40px | - | - | - | | `--list-gap-small` | 8px | - | - | - | Previous [ ## Size Define exact width and height dimensions for elements ](/framework/docs/3.4/size) Next [ ## Position Take an element out of flow and set its distance from each edge of its container ](/framework/docs/3.4/position) --- # Position Use Position to put one element over another instead of beside it. Offsets on the spacing scale hold it a set distance from the edges of its container, and a short stacking scale says which of two overlapping elements is drawn on top. Available offset sizes and their pixel values [View Size Documentation](/framework/docs/3.4/size) ## Relative and Absolute `relative` marks an element as a container that positioned children are placed against. `absolute` takes a child out of the normal flow, and the offsets below then measure from that container's edges. `relative`The container the offsets below measure from `absolute`Takes the element out of flow ## Offsets `top--`, `right--`, `bottom--` and `left--` each set the distance from one edge, and `inset--` sets all four. The numbers come from the spacing scale [Spacing](/framework/docs/3.4/spacing) , where one step is 4px, so `top--3` is 12px. There are half steps at the small end, where 0.5, 1.5, 2.5 and 3.5 give 2px, 6px, 10px and 14px, and the scale runs from 0 to 96, or 384px. Nothing outside those steps is a class, so `top--[Npx]` has no effect. Those pixel values describe a standard device. On one with a larger interface scale the offsets grow with it, the same way padding does [Scale](/framework/docs/3.4/scale) . `top--{size}`Distance from the top edge `right--{size}`Distance from the right edge `bottom--{size}`Distance from the bottom edge `left--{size}`Distance from the left edge `inset--{size}`All four edges at once; `inset--0` is full bleed ## Layers Where two positioned elements overlap, `z--0` through `z--3` say which of them is drawn on top. The scale ends at 3, so nothing a screen positions can cover the title bar. `z--{0-3}`Which positioned element draws on top ## Responsive Every class on this page takes a prefix for **Size** [Size](/framework/docs/3.4/size) , **Orientation** , or both [Responsive](/framework/docs/3.4/responsive) . Bit-depth prefixes are not part of the set, because these classes place an element rather than paint it. `md:top--{size}`Size-based example `portrait:inset--{size}`Orientation-based example `lg:portrait:left--{size}`Size + Orientation example ## Out of Flow Once an element leaves the flow, three things about it change: what measures its text, what decides its width, and what it needs behind it. [Clamp](/framework/docs/3.4/clamp) , [Fit Value](/framework/docs/3.4/fit_value) , [Content Limiter](/framework/docs/3.4/content_limiter) and [Table Overflow](/framework/docs/3.4/table_overflow) measure elements in normal flow, so they pass a positioned one by. Give it a size, clamp its own text, and read it back on the smallest screen you support. An element with only `left--` or only `right--` is as wide as its contents. Name both edges and it stretches from one to the other. An element drawn over other content needs a solid fill: `bg--canvas` or `bg--white`. Every shade between them is a dither pattern on a 1-bit screen, and small text over one is hard to read. Draw its edge with [Outline](/framework/docs/3.4/outline) rather than a shadow, which ePaper renders as a band of dither. ### Card over a full-bleed panel A panel takes the whole layout here, with one card in its top left corner and another in its bottom right. Each card names a vertical edge and a horizontal edge, and with no second horizontal edge to stretch to, it comes out as wide as its own label. Depot12 min Updated 17:04 ![TRMNL Logo](/images/plugins/trmnl--render.svg)PositionCard over a panel ```
Depot 12 min
Updated 17:04
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | Previous [ ## Spacing Control element spacing with fixed margin and padding values ](/framework/docs/3.4/spacing) Next [ ## Gap Set precise spacing between elements with predefined gap values ](/framework/docs/3.4/gap) --- # Gap Utility classes for the space between the children of a flex, grid, or column container. Predefined sizes, arbitrary values, distribution modifiers, and responsive variants. ### Size Variants The gap system includes predefined base sizes and arbitrary pixel values. These standardized spaces help maintain consistent spacing across your application's grid and flex layouts. #### Base The base `gap` class without size modifiers and the `gap--base` class both produce the same visual result, providing the standard spacing. Use `gap--base` when you need to explicitly set the base size in responsive contexts. See the [Responsive Gaps](#responsive-gap) section for examples. gap--none gap--none gap--none gap--xsmall gap--xsmall gap--xsmall gap--small gap--small gap--small gap gap gap gap--medium gap--medium gap--medium gap--large gap--large gap--large gap--xlarge gap--xlarge gap--xlarge gap--xxlarge gap--xxlarge gap--xxlarge ![TRMNL Logo](/images/plugins/trmnl--render.svg)Predefined GapsDesign System ```
...
...
...
...
...
...
...
...
...
``` #### Arbitrary Use `gap--[Npx]` syntax to specify exact pixel values from **0px to 50px**. This works with both grid and flex layouts, but does not support responsive variants. gap--[0px] gap--[0px] gap--[0px] gap--[10px] gap--[10px] gap--[10px] gap--[30px] gap--[30px] gap--[30px] gap--[50px] gap--[50px] gap--[50px] ![TRMNL Logo](/images/plugins/trmnl--render.svg)Arbitrary Pixel GapsDesign System ```
...
...
...
...
...
``` Arbitrary gap values using the `gap--[Npx]` syntax do not support responsive variants. Use predefined gap classes if you need responsive behavior. ### Distribution Modifiers Beyond fixed gaps, you can use special modifiers to control how space is distributed between elements. These modifiers are particularly useful for creating flexible, dynamic layouts. #### Auto Distribution The `gap--auto` modifier distributes available space evenly between elements, including equal spacing at the edges. This uses `justify-content: space-evenly`. gap--auto gap--auto gap--auto ![TRMNL Logo](/images/plugins/trmnl--render.svg)Auto Distribution GapDesign System ```
...
...
...
``` #### Distribute The `gap--distribute` modifier places the first item at the start of the container and the last item at the end, with equal spacing between items. This uses `justify-content: space-between`. gap--distribute gap--distribute gap--distribute ![TRMNL Logo](/images/plugins/trmnl--render.svg)Distribute GapDesign System ```
First item (at start)
Middle item
Last item (at end)
``` #### Legacy: Space Between The `gap--space-between` modifier is maintained for backwards compatibility. It behaves the same as `gap--auto`, using `justify-content: space-evenly`. For the actual `space-between` behavior, use `gap--distribute`. ### Responsive Gaps Gap utilities support size-based breakpoints, orientation variants, and their combination. Use prefixes like `md:`, `portrait:`, and `md:portrait:` to target conditions. #### Responsive Gap Examples Apply different gap values at different breakpoints using the size-based responsive system. The framework follows a mobile-first approach where larger breakpoints inherit smaller ones. The `--base` modifier is particularly useful for resetting to the default size at specific breakpoints. Responsive Gap Responsive Gap Responsive Gap Small by default, large on md+, xlarge on lg+, medium gap in portrait, xlarge in md+ portrait ![TRMNL Logo](/images/plugins/trmnl--render.svg)Responsive GapsSize-Based ```
...
...
...
...
...
...
...
...
``` Gap utilities only support size-based responsive variants. Bit-depth variants (like `1bit:` or `4bit:`) are not available for gap classes. ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--gap-large` | 20px | - | - | - | | `--gap-medium` | 16px | - | - | - | | `--gap-small` | 7px | - | - | - | | `--gap-xlarge` | 30px | - | - | - | | `--gap-xsmall` | 5px | - | - | - | | `--gap-xxlarge` | 40px | - | - | - | | `--list-gap-small` | 8px | - | - | - | Previous [ ## Position Take an element out of flow and set its distance from each edge of its container ](/framework/docs/3.4/position) Next [ ## Flex Arrange elements with flexible layouts and alignment options ](/framework/docs/3.4/flex) --- # Flex Utility classes for Flexbox layouts. Row and column directions with alignment, centering, and stretching modifiers. ### When to Use Flex Use Flex inside [Layout](/framework/docs/3.4/layout) when you need flexible row or column arrangements. Flex lets items size by their content: width and height follow what's inside, rather than a fixed grid. It's the right choice when you want natural, content-driven layouts without strict column structure. #### Content-Based Sizing Flex items grow and shrink based on their content by default. You can override this with stretch modifiers, grow/shrink utilities, or basis classes. Use Flex when your layout should adapt to the content rather than forcing content into a fixed grid: toolbars, inline groups of labels and values, or stacks of variable-height cards. #### Standalone or Nested Use Flex alone as the only child of Layout for simpler layouts. Nest it inside [Grid](/framework/docs/3.4/grid) and each grid cell can hold its own Flex container for row or column flexibility within that cell. Nest it inside [Columns](/framework/docs/3.4/columns) columns for per-column arrangement. #### Compared to Grid and Columns Choose Flex when you need flexible, content-sized layouts. If you need strict column alignment and spans, use Grid. If you have lots of same-type data and want the system to handle column distribution and overflow, use [Columns](/framework/docs/3.4/columns) . ### Base Structure Flex arranges content in one of two directions: horizontal (row) or vertical (column). These base structures can be combined with alignment and stretch modifiers for complex layouts. #### Row Direction Use `flex flex--row` to create a horizontal layout: Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexRow Direction ```
Item 1
Item 2
Item 3
``` #### Column Direction Use `flex flex--col` to create a vertical layout: Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexColumn Direction ```
Item 1
Item 2
Item 3
``` ### Alignment Modifiers Once you've chosen a base direction, you can apply alignment modifiers to control how items are positioned within their container. Modifiers cover directional alignment (left/right/top/bottom) and centering. `flex--start` and `flex--end` follow the container's direction: in a row they match `flex--left` and `flex--right`, in a column they match `flex--top` and `flex--bottom`. On a `flex` with no direction modifier they match `flex--left` and `flex--right`. Use them when a container flips direction responsively and the alignment should flip with it. #### Row Horizontal Alignment For row layouts, use `flex--left`, `flex--center-x`, or `flex--right` to control horizontal alignment: Left Center X Right ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexRow Horizontal Alignment ```
Item
Item
Item
Item
Item
Item
Item
Item
Item
``` #### Row Vertical Alignment For row layouts, use `flex--top`, `flex--center-y`, or `flex--bottom` to control vertical alignment: Top Center Y Bottom ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexRow Vertical Alignment ```
Item
Item
Item
Item
Item
Item
Item
Item
Item
``` #### Column Horizontal Alignment For column layouts, use `flex--left`, `flex--center-x`, or `flex--right` to control horizontal alignment: Left Center X Right ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexColumn Horizontal Alignment ```
Item
Item
Item
Item
Item
Item
Item
Item
Item
``` #### Column Vertical Alignment For column layouts, use `flex--top`, `flex--center-y`, or `flex--bottom` to control vertical alignment: Top Center Y Bottom ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexColumn Vertical Alignment ```
Item
Item
Item
``` ### Stretch Modifiers Stretch works at two levels: container modifiers affect all children, while item classes only affect the specific element they're applied to. #### Container Stretch Use `flex--stretch`, `flex--stretch-x`, or `flex--stretch-y` to control how children fill the container: Stretch All Stretch X Stretch Y ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexContainer Stretch ```
Item
Item
Item
Item
Item
Item
Item
Item
Item
``` #### Individual Item Stretch (Row) Use `stretch`, `stretch-x`, or `stretch-y` on individual items in a row layout: Stretch Normal Stretch Stretch X Normal Stretch X Stretch Y Normal Stretch Y ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexItem Stretch (Row) ```
Stretches in cross-axis
Normal item
Stretches in cross-axis
Stretches horizontally
Normal item
Stretches horizontally
Stretches vertically
Normal item
Stretches vertically
``` #### Individual Item Stretch (Column) Use `stretch`, `stretch-x`, or `stretch-y` on individual items in a column layout: Stretch Normal Stretch Stretch X Normal Stretch X Stretch Y Normal Stretch Y ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexItem Stretch (Column) ```
Stretches in cross-axis
Normal item
Stretches in cross-axis
Stretches horizontally
Normal item
Stretches horizontally
Stretches vertically
Normal item
Stretches vertically
``` #### Preventing Item Shrinkage Use `shrink-0` on flex children to prevent them from shrinking when other items try to take up more space. `no-shrink` is an alias for the same rule and stays supported: both classes set `flex-shrink: 0` under the same responsive and orientation variants. Write `shrink-0` in new markup, matching the `grow-0` naming in the item utilities below. Can Shrink Stretching Content That Pushes Others Won't Shrink Stretching Content That Pushes Others ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexPrevent Shrinking ```
Maintains its width
Stretches but won't squish the shrink-0 item
Maintains its width
``` ### Orientation-Responsive Layouts Flexbox utilities support orientation-responsive variants, allowing you to change layouts when the screen is rotated. This is particularly useful for adapting navigation bars, toolbars, and content grids. #### Adaptive Direction Use `portrait:` prefix to change flex direction or alignment when the screen is in portrait orientation. Try rotating the device preview to see the effect. Nav Item 1 Nav Item 2 Nav Item 3 Row in landscape, column in portrait ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexOrientation Responsive ```
Nav Item 1
Nav Item 2
Nav Item 3
``` ### Extended Directions In addition to standard directions, Flex supports reverse flow for quick reordering on the main axis. Use `flex--row-reverse` and `flex--col-reverse` to invert visual order without changing the DOM. Alignment modifiers answer to the reversed direction. `flex--row-reverse flex--left` aligns along the row, the same axis `flex--row flex--left` uses. #### Row Reverse A B C ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexRow Reverse ```
A
B
C
``` #### Column Reverse 1 2 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexColumn Reverse ```
1
2
3
``` ### Wrapping and Multi-Line Alignment Control line breaks with `flex--wrap`, `flex--nowrap`, and `flex--wrap-reverse`. When wrapping, use align-content modifiers to distribute lines: `flex--content-start|center|end|between|around|evenly|stretch`. #### Wrap vs No-wrap Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexWrap vs No-wrap ```
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
``` #### Wrapping Item Elements `.item` elements will wrap in flex rows. Team MeetingWeekly team sync-up Team MeetingWeekly team sync-up Team MeetingWeekly team sync-up Team MeetingWeekly team sync-up Team MeetingWeekly team sync-up ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexWrapping Items ```
Team Meeting Weekly team sync-up
``` #### Align Content Across Lines These only apply when wrapping is enabled. L1 L2 L3 L4 L5 L6 L7 L8 L1 L2 L3 L4 L5 L6 L7 L8 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexAlign Content ```
``` ### Main-Axis Distribution Use `flex--between`, `flex--around`, and `flex--evenly` to control spacing along the main axis. This differs from `gap`, which inserts physical gaps between items. #### Row Distribution Start Middle End Around Evenly ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexRow Distribution ```
...
...
...
``` ### Item-Level Controls Per-item utilities control alignment and flexing behavior without affecting siblings: self alignment, grow/shrink, flex shorthand, and basis sizing. #### Self Alignment (align-self) self--start self--center self--end self--stretch ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexSelf Alignment ```
self--start
self--center
self--end
self--stretch
``` #### Grow/Shrink and Flex Shorthand Six utilities set an item's flex behavior. The demo below shows four of them. - `grow`: the item takes a share of the spare space. - `grow-0`: the item never takes a share, the explicit opposite of `grow`. - `shrink-0`: the item keeps its size when the container runs tight. - `flex-auto`: the item both grows and shrinks, starting from its content size. - `flex-initial`: the item shrinks but never grows. - `flex-none`: the item does neither and stays at its content size. grow shrink-0 flex-none flex-initial ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexGrow/Shrink & Flex ```
grow
shrink-0
flex-none
flex-initial
``` #### Basis and Order Basis sets the size an item starts from before it grows or shrinks. Order moves an item to a different position without changing the markup. - `basis--{size}`: the whole-number steps of the size scale, `basis--0` through `basis--96`. A step is 4px scaled by the content scale, so `basis--36` is 144px at scale 1. See [Size](/framework/docs/3.4/size) . - `basis--auto`: the item starts at its content size. - `order--{n}`: any whole number from -10 to 10. A negative number takes a third dash, so -1 is `order---1`. - `order--first` and `order--last`: move the item ahead of or behind every numbered item. basis--36 basis--20 basis--24 order--last order--first order--2 order---1 ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexBasis & Order ```
basis--36
basis--20
basis--24
order--last
order--first
order--2
order---1
``` ### Inline Flex Containers Use `inline-flex` for inline-level flex containers that align alongside text. Text before Text after ![TRMNL Logo](/images/plugins/trmnl--render.svg)FlexInline Flex ``` Text before
•
•
Text after ``` Previous [ ## Gap Set precise spacing between elements with predefined gap values ](/framework/docs/3.4/gap) Next [ ## Grid Create grid layouts with predefined column structures ](/framework/docs/3.4/grid) --- # Grid Utility classes for column-based and row-based grids. Set the column count, span cells across columns, and change either at a breakpoint. ### When to Use Grid Use Grid inside [Layout](/framework/docs/3.4/layout) when you need a strict, grid-based layout. Grid gives you precise control over column count and span, so items align to a consistent rhythm and every element snaps to the same underlying grid. #### Grid-Based Distribution You define how many columns the grid has with `grid--cols-{number}`, and you can let individual cells span multiple columns with `col--span-{number}`. The result is a predictable, aligned layout where everything shares the same column structure. Ideal for Swiss-style or editorial designs where visual consistency matters. #### Multiple Grids and Nesting You can place multiple Grid components as direct children of Layout; Layout's modifiers (row/col, alignment, stretch) arrange those grids within the available space. Inside each grid cell, you can nest [Flex](/framework/docs/3.4/flex) for row or column flexibility within that cell. For example, a grid cell that stacks items vertically or aligns them horizontally. #### Compared to Flex and Columns Choose Grid when you need fixed column structure and spans. If you need content-sized flexibility (items that grow or shrink by content), use Flex. If you have lots of same-type data and want the system to handle column distribution and overflow, use [Columns](/framework/docs/3.4/columns) . ### Related [Columns](/framework/docs/3.4/columns)[Flex](/framework/docs/3.4/flex)[Gap](/framework/docs/3.4/gap)[Layout](/framework/docs/3.4/layout) ### Ways to Define the Grid Define a column layout in one of two ways: - **Column Count:** Set `grid--cols-{number}` on the parent to create equal-width columns - **Column Spans:** Set `col--span-{number}` on individual columns to control their width #### Column Count Use `grid--cols-{number}` to set the column count, from 1 to 12. A number above 12 has no class, so the grid keeps its default auto-fit template. Here are examples with 4 and 3 columns: Col 1/4 Col 1/4 Col 1/4 Col 1/4 Col 1/3 Col 1/3 Col 1/3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridColumn Count ```
1/4
1/4
1/4
1/4
1/3
1/3
1/3
``` #### Column Spans Use `col--span-{number}` to make a column span multiple grid columns, from 1 to 12 like the column count. In a grid row, the sum of all column spans should equal the total number of grid columns. For example, you might have spans of 1 and 2, or spans of 3, 6, and 2. Col Span 1 Col Span 2 Col Span 3 Col Span 6 Col Span 2 ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridColumn Spans ```
Span 1
Span 2
Span 3
Span 6
Span 2
``` ### Column Layouts Use columns to create vertical layouts within the grid. Columns can be positioned and aligned using modifier classes. #### Basic Column Layout Use the `col` class to create vertical layouts. Item 1 Item 2 Item 3 Item 4 ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridColumn Layout ```
Item
Item
Item
Item
``` #### Column Positioning Use `col--{position}` where position can be `start`, `center`, or `end` to control vertical alignment: Start Center End ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridColumn Positioning ```
Item
Item
Item
``` ### Row Layouts Use rows to create horizontal layouts within the grid. Rows can be positioned and aligned using modifier classes. #### Basic Row Layout Use the `row` class to create horizontal layouts. Item 1 Item 2 Item 3 Item 4 ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridRow Layout ```
Item
Item
Item
Item
``` #### Row Positioning Use `row--{position}` where position can be `start`, `center`, or `end` to control horizontal alignment: Start Center End ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridRow Positioning ```
Item
Item
Item
``` ### Grid Wrapping Enable responsive wrapping based on a minimum column width using `grid--wrap`. Combine with `grid--min-{size}` to set the minimum track size. #### Different Minimum Sizes As the container shrinks, the grid reduces column count to respect the minimum size. Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 Item 7 Item 8 Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 Item 7 Item 8 ![TRMNL Logo](/images/plugins/trmnl--render.svg)GridGrid Wrapping ```
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
``` ### Removing the Gap A grid separates its columns by the screen's gap. Add `grid--no-gap` to close it, so tiles meet edge to edge and read as one band. Size and orientation prefixes work on it like any other grid modifier (`portrait:grid--no-gap`), and a [Gap](/framework/docs/3.4/gap) utility sets a different gap rather than none. Mon Tue Wed Thu Mon Tue Wed Thu ![TRMNL Logo](/images/plugins/trmnl--render.svg)Gridgrid--no-gap ```
Mon
Tue
Wed
Thu
``` Previous [ ## Flex Arrange elements with flexible layouts and alignment options ](/framework/docs/3.4/flex) Next [ ## Aspect Ratio Maintain consistent proportions for elements regardless of their content ](/framework/docs/3.4/aspect_ratio) --- # Aspect Ratio Hold an element to a fixed width-to-height ratio. The utilities set the native CSS aspect-ratio property, so images, charts, and containers keep their proportions at any screen size. ### Basic Usage Use predefined aspect ratio classes to constrain element dimensions to specific proportions. These utilities apply the CSS `aspect-ratio` property directly to elements. 1:1 16:9 3:4 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Aspect RatioBasic Usage ```
...
...
...
``` ### Responsive Behavior Aspect ratio utilities take the framework's responsive prefixes. A prefixed class overrides the base ratio whenever the screen matches, so one tile can sit square on most screens and go portrait on a tall one. The prefixes are `sm:`, `md:`, `lg:`, `landscape:`, `portrait:`, and the combined `size:orientation` forms. Aspect ratio carries no bit-depth variants. See [Responsive](/framework/docs/3.4/responsive) for the size class each device carries. Cover1:1, portrait 3:4 Chart4:3, lg 16:9 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Aspect RatioResponsive Behavior ```
...
...
...
``` ## Available Aspect Ratios Complete reference of all available aspect ratio utilities. | Class | Ratio | | --- | --- | | `aspect--auto` | No constraints | | `aspect--1/1` | 1:1 | | `aspect--4/3` | 4:3 | | `aspect--3/2` | 3:2 | | `aspect--16/9` | 16:9 | | `aspect--21/9` | 21:9 | | `aspect--3/4` | 3:4 | | `aspect--2/3` | 2:3 | | `aspect--9/16` | 9:16 | | `aspect--9/21` | 9:21 | Previous [ ## Grid Create grid layouts with predefined column structures ](/framework/docs/3.4/grid) Next [ ## Responsive Adapt styles to the device's size class, orientation, and bit depth using variant prefixes ](/framework/docs/3.4/responsive) --- # Responsive The Responsive system adapts a layout to the device it renders on. **Size-based** breakpoints follow the size class each device carries, and **Bit-depth** variants follow its color capabilities. Combine them to control how your content appears across TRMNL's range of devices. ## Component Support Not all framework components support responsive variants. We're trying to keep the framework as minimal as we can while offering the features you need. This table shows which responsive features each framework component supports. Use this reference to understand what's possible with each component type. | Component | Size | Orientation | Bit-Depth | Example Usage | | --- | --- | --- | --- | --- | | Background | Yes | Yes | Yes | `md:2bit:bg--gray-50` | | Border | No | No | Auto | `border--h-30 (auto adapts)` | | Text | Yes | Yes | Yes | `lg:2bit:text--center` | | Visibility | Yes | Yes | Yes | `sm:1bit:hidden` | | Value | Yes | Yes | Yes | `lg:2bit:value--xlarge` | | Label | Yes | Yes | Yes | `md:portrait:2bit:label--filled` | | Title | Yes | Yes | Yes | `md:2bit:title--large` | | Description | Yes | Yes | Yes | `portrait:description--large` | | Content | Yes | Yes | Yes | `lg:portrait:content--large` | | Font Weight | Yes | Yes | Yes | `md:1bit:text--bold` | | Text Stroke | No | No | Yes | `1bit:text-stroke--large` | | Image Stroke | No | No | Yes | `2bit:image-stroke--large` | | Spacing | Yes | Yes | No | `md:p--16, lg:m--32, md:portrait:my--24` | | Position | Yes | Yes | No | `md:top--3, portrait:inset--0, lg:portrait:left--3` | | Layout | Yes | Yes | No | `md:layout--row, lg:layout--col` | | Gap | Yes | Yes | No | `md:gap--large, lg:gap--xlarge` | | Flexbox | Yes | Yes | No | `md:flex--row, portrait:flex--col` | | Rounded | Yes | Yes | No | `md:rounded--large, lg:rounded--xlarge` | | Aspect Ratio | Yes | Yes | No | `md:aspect--1/1, lg:landscape:aspect--16/9` | | Table | Yes | Yes | No | `lg:table--base, lg:portrait:table--xlarge` | | Size | Yes | Yes | No | `md:w--36, lg:h--full` | | Grid | Yes | Yes | No | `md:grid--cols-3, md:portrait:col--span-2` | | Clamp | Yes | Yes | No | `data-clamp-md-portrait="3"` | | Overflow (Smart columns) | Yes | Yes | No | `data-overflow-max-cols-lg="4"` | ### Legend Auto Built-in adaptive behavior Yes Full support No Not supported ## Size-Based Responsive ### How It Works Every device carries a size class (e.g., `screen--md`) that activates the matching responsive utilities. The class comes from the device model, not from a measured width, so pick a breakpoint by the devices you want to hit rather than by a pixel count. The system follows a mobile-first approach. When you use `md:value--large`, it applies on medium screens and larger. ### Basic Usage Prefix any utility class with a breakpoint name followed by a colon. The style applies at that breakpoint and all larger sizes. Responsive Value ![TRMNL Logo](/images/plugins/trmnl--render.svg)ResponsiveSize Based This example shows progressive sizing: the text starts at regular size, becomes large on medium screens (md:) and larger, then becomes xlarge on large screens (lg:) and larger. ``` Responsive Value ``` ### Available Breakpoints Three breakpoints cover every supported TRMNL device. Prefixes are mobile-first, so a prefix applies on its own size class and every larger one. | Prefix | Screen Class | Applies On | Example Devices | | --- | --- | --- | --- | | `sm:` | `screen--sm` | sm, md, lg | Kindle 2024 | | `md:` | `screen--md` | md, lg | TRMNL OG, TRMNL OG V2, Playdate, Frame | | `lg:` | `screen--lg` | lg | TRMNL V2, Kindle Scribe, reMarkable Paper 2 | ## Bit-Depth Responsive ### How It Works Bit-depth responsiveness adapts styles based on the display's color capabilities. Unlike size-based breakpoints, bit-depth variants are not progressive. Each variant targets a specific bit-depth only. When you use `4bit:bg--gray-65`, it applies only on 4-bit screens, not on 1-bit or 2-bit screens. ### Basic Usage Prefix utilities with bit-depth values to create display-specific styles. This is especially useful for optimizing appearance across monochrome and grayscale screens. ![TRMNL Logo](/images/plugins/trmnl--render.svg)ResponsiveBit Depth This example demonstrates bit-depth adaptation: the square appears black on 1-bit displays, gray-45 on 2-bit displays, and gray-75 on 4-bit displays. Each bit-depth variant targets only that specific display type. ```
``` ### Available Bit-Depths The framework supports three bit-depth variants corresponding to TRMNL's display technologies. Each targets specific color capabilities. | Prefix | Screen Class | Color Support | Example Devices | | --- | --- | --- | --- | | `1bit:` | `screen--1bit` | Monochrome (2 shades) | TRMNL OG | | `2bit:` | `screen--2bit` | Grayscale (4 shades) | TRMNL OG V2 | | `4bit:` | `screen--4bit` | Grayscale (16 shades) | TRMNL V2, Kindle 2024 | ## Orientation-Based Responsive ### How It Works Orientation variants adapt styles based on whether the screen is in landscape or portrait mode. Since landscape is the default, only `portrait:` variants are provided to avoid redundancy. Portrait variants are particularly useful for layout utilities like Flexbox, where you might want different flex directions or alignments when the screen is rotated. ### Basic Usage Use the `portrait:` prefix to apply styles only when the screen is in portrait orientation: Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ResponsiveOrientation Based This example shows orientation-responsive layout: items are arranged in a row by default (landscape), but automatically switch to a column layout when the screen is in portrait orientation using `portrait:flex--col`. ```
Item 1
Item 2
Item 3
``` ## Combining All Systems The responsive system lets you combine size, orientation, and bit-depth variants. This enables highly targeted designs that adapt to screen dimensions, orientation, and color capabilities. Aa TRMNL OG Aa TRMNL OG V2 Aa TRMNL V2 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ResponsiveAdvanced Targeting This advanced example combines size and bit-depth variants to target specific device configurations: `md:1bit:` targets medium+ 1-bit screens, `md:2bit:` targets medium+ 2-bit screens, and `lg:4bit:` targets large+ 4-bit screens. Dark-mode-aware utilities also support a dark-first prefix (for scoped utilities): `dark:md:portrait:2bit:`. ```
...
...
``` ### Pattern and Order When combining variants, follow this pattern: `size:orientation:bit-depth:utility`. This order flows from general layout concerns to specific display characteristics. Bit-depth applies only to color and typography utilities: backgrounds, text, text stroke, image stroke, font weight, value, label, title, description, content, and visibility. Layout utilities like flex, gap, grid, rounded, spacing, and size take size and orientation only. The stroke families are bit-depth-only: text stroke and image stroke have no size or orientation variants. Each modifier is optional and can be used independently. You might use just `portrait:flex--col` for orientation-specific layouts, or `md:value--large` for size-responsive typography, depending on your design needs. For utilities that support dark-mode variants (currently Visibility, Background, and Text), use: `dark:size:orientation:bit-depth:utility` with `dark:` as the first prefix. The `dark:` tier is legacy: it keeps working for the rest of Framework 3.x and will be removed in Framework 4.0. Darken a whole screen with the Dark theme ( [Themes](/framework/docs/3.4/themes) ), or one element with `inverse` ( [Inverse](/framework/docs/3.4/inverse) ). No new utility family gains the prefix. ### Specificity Hierarchy When multiple responsive variants target the same property, CSS specificity determines which style applies. The framework follows a clear hierarchy: the more modifiers in a class, the higher its specificity. For example, `portrait:2bit:value--small` will override both `portrait:value--large` and `2bit:value--base` when all conditions are met, because it has the most specific combination of modifiers. ### Available Combinations The responsive system supports flexible modifier combinations, allowing you to target specific device configurations. The table below shows all available patterns, from simple single modifiers to complex multi-modifier combinations. Each combination becomes active only when all its conditions are met. | Pattern | Example | When Active | Use Case | | --- | --- | --- | --- | | `size:` | `md:value--large` | Medium screens and larger | Responsive sizing by device size class | | `orientation:` | `portrait:flex--col` | Portrait orientation only | Layout adjustments for vertical screens | | `bit-depth:` | `4bit:bg--gray-75` | 4-bit displays only | Color optimization for specific displays | | `size:orientation:` | `md:portrait:text--center` | Medium+ screens in portrait | Size-aware orientation layouts | | `size:bit-depth:` | `lg:2bit:value--xlarge` | Large+ screens with 2-bit display | Display-specific sizing on larger screens | | `orientation:bit-depth:` | `portrait:2bit:value--small` | Portrait with 2-bit display | Orientation-aware display optimization | | `size:orientation:bit-depth:` | `md:portrait:4bit:value--large` | Medium+ screens, portrait, 4-bit display | Highly specific device targeting | | `dark:size:orientation:bit-depth:` | `dark:md:portrait:2bit:hidden` | Dark mode, medium+ screens, portrait, 2-bit display | Theme-specific responsive behavior | ### Related APIs #### The same grammar in SCSS The screen mixins generate device-aware rules from the same size, orientation, and bit-depth grammar these utility classes use, for styles that have no utility class. See [Sass Mixins](/framework/docs/3.4/sass_mixins) for the mixins and the scale functions. ``` @include trmnl.screen('md', 'portrait') { .status { display: none; } } ``` Previous [ ## Aspect Ratio Maintain consistent proportions for elements regardless of their content ](/framework/docs/3.4/aspect_ratio) Next [ ## Responsive Test Test responsive utilities and compare SCSS mixins with CSS classes ](/framework/docs/3.4/responsive_test) --- # Responsive Test This page tests responsive utilities by comparing SCSS mixins with CSS classes across different screen conditions. Each test row shows an element styled with SCSS mixins alongside the same element styled with CSS utility classes. Both columns should look identical when the conditions are met, demonstrating that mixins and classes produce equivalent results. ### Utilities #### Background Test Case SCSS Mixin Result CSS Class Result md:bg--gray-50 @include screen.screen('md') Gray bg on md+ screens portrait:bg--gray-50 @include screen.screen('portrait') Gray bg in portrait 2bit:bg--gray-50 @include screen.screen('2bit') Gray bg on 2-bit screens md:portrait:bg--gray-50 @include screen.screen('md', 'portrait') Gray bg on md+ portrait md:2bit:bg--gray-50 @include screen.screen('md', '2bit') Gray bg on md+ 2-bit portrait:2bit:bg--gray-50 @include screen.screen('portrait', '2bit') Gray bg on portrait 2-bit md:portrait:2bit:bg--gray-50 @include screen.screen('md', 'portrait', '2bit') Gray bg on md+ portrait 2-bit #### Visibility Test Case SCSS Mixin Result CSS Class Result sm:hidden @include screen.screen('sm') Hidden on sm+ screens portrait:hidden @include screen.screen('portrait') Hidden on portrait screens 4bit:hidden @include screen.screen('4bit') Hidden on 4-bit screens md:portrait:hidden @include screen.screen('md', 'portrait') Hidden on md+ portrait screens lg:2bit:hidden @include screen.screen('lg', '2bit') Hidden on lg+ 2-bit screens portrait:4bit:hidden @include screen.screen('portrait', '4bit') Hidden on portrait 4-bit screens md:portrait:2bit:hidden @include screen.screen('md', 'portrait', '2bit') Hidden on md+ portrait 2-bit screens #### Text Test Case SCSS Mixin Result CSS Class Result lg:text--center @include screen.screen('lg') Centered text on lg+ screens Aa Aa portrait:text--center @include screen.screen('portrait') Centered text in portrait Aa Aa 2bit:text--center @include screen.screen('2bit') Centered text on 2-bit screens Aa Aa md:portrait:text--center @include screen.screen('md', 'portrait') Centered on md+ portrait Aa Aa lg:4bit:text--center @include screen.screen('lg', '4bit') Centered on lg+ 4-bit screens Aa Aa portrait:2bit:text--center @include screen.screen('portrait', '2bit') Centered on portrait 2-bit screens Aa Aa md:portrait:2bit:text--right @include screen.screen('md', 'portrait', '2bit') Right-aligned on md+ portrait 2-bit Aa Aa #### Flex Test Case SCSS Mixin Result CSS Class Result md:flex--center @include screen.screen('md') Centered on md+ screens portrait:flex--col @include screen.screen('portrait') Column layout in portrait lg:portrait:flex--center @include screen.screen('lg', 'portrait') Centered on lg+ portrait #### Spacing Test Case SCSS Mixin Result CSS Class Result md:p--24 @include screen.screen('md') Padding 24 on md+ screens portrait:mx--20 @include screen.screen('portrait') Horizontal margin 20 in portrait #### Gap Test Case SCSS Mixin Result CSS Class Result lg:gap--xlarge @include screen.screen('lg') Gap xlarge on lg+ screens portrait:gap--large @include screen.screen('portrait') Large gap in portrait #### Size Test Case SCSS Mixin Result CSS Class Result md:w--36 @include screen.screen('md') Large width on md+ screens #### Rounded Test Case SCSS Mixin Result CSS Class Result md:rounded--xlarge @include screen.screen('md') Rounded xlarge on md+ screens #### Grid Test Case SCSS Mixin Result CSS Class Result md:grid--cols-3 @include screen.screen('md') 3 columns on md+ screens ### Base #### Layout Test Case SCSS Mixin Result CSS Class Result md:layout--col @include screen.screen('md') Column layout on md+ screens portrait:layout--bottom @include screen.screen('portrait') Bottom alignment in portrait lg:portrait:layout--bottom @include screen.screen('lg', 'portrait') Bottom alignment on lg+ portrait ### Elements #### Value Test Case SCSS Mixin Result CSS Class Result md:value--large Large value on md+ screens Aa portrait:value--large Large value in portrait Aa 4bit:value--large Large value on 4-bit screens Aa lg:portrait:value--large Large value on lg+ portrait Aa md:2bit:value--large Large value on md+ 2-bit screens Aa portrait:4bit:value--large Large value on portrait 4-bit Aa lg:portrait:4bit:value--xlarge XLarge on lg+ portrait 4-bit Aa #### Label Test Case SCSS Mixin Result CSS Class Result md:label--small Small label on md+ screens Label portrait:label--outline Outlined label in portrait Label 2bit:label--inverted Inverted label on 2-bit screens Label md:portrait:label--underline Underlined label on md+ portrait Label md:2bit:label--gray Gray label on md+ 2-bit Label portrait:2bit:label--small Small label on portrait 2-bit Label md:portrait:2bit:label--inverted Inverted label on md+ portrait 2-bit Label ### Components No component tests have been implemented yet Previous [ ## Responsive Adapt styles to the device's size class, orientation, and bit depth using variant prefixes ](/framework/docs/3.4/responsive) Next [ ## Visibility Control element visibility based on display bit depth ](/framework/docs/3.4/visibility) --- # Visibility Show or hide an element and set its display type. Hidden and visible controls plus display helpers like flex, grid, and inline, each with responsive and bit-depth variants for device-specific layouts. ## Visibility Across Devices Every device carries a size class: `sm`, `md`, or `lg`. Each column below targets one of them, so switching the device in the screen picker changes which column has content. Small (sm) visible md:hidden Medium (md) hidden md:visible lg:hidden Large (lg) hidden lg:visible ```
visible
md:hidden
lg:hidden
``` ## Display Utilities Control how elements are displayed with specific display types. These classes set the CSS `display` property. | Class | Effect | CSS Output | | --- | --- | --- | | `hidden` | Hide element completely | `display: none` | | `visible` | Display as block element | `display: block` | | `block` | Display as block element | `display: block` | | `inline` | Display as inline element | `display: inline` | | `inline-block` | Display as inline block element | `display: inline-block` | | `flex` | Display as flex container | `display: flex` | | `grid` | Display as grid container | `display: grid` | | `inline-grid` | Display as inline grid container | `display: inline-grid` | | `table` | Display as table element | `display: table` | | `table-row` | Display as table row element | `display: table-row` | | `table-cell` | Display as table cell element | `display: table-cell` | ## Responsive Display Control All display utilities take the size prefixes. They are mobile-first, so a prefix applies on its own size class and every larger one. See [Responsive](/framework/docs/3.4/responsive) for the size class each device carries. | Example Class | Effect | Active On | | --- | --- | --- | | `sm:hidden` | Hide on small screens and larger | sm, md, lg (every device) | | `md:flex` | Display as flex on medium screens and larger | md, lg | | `lg:grid` | Display as grid on large screens | lg | | `sm:inline-block` | Display as inline-block on small screens and larger | sm, md, lg (every device) | ```
Block by default, flex on medium+
Changes display type at each breakpoint
Cell A
Cell B
``` ## Bit-Depth Display Control All display utilities take the bit-depth prefixes. These are not progressive the way the size prefixes are: `2bit:` applies on 2-bit screens and nowhere else. | Example Class | Effect | Active On | | --- | --- | --- | | `1bit:hidden` | Hide on monochrome displays | Every 1-bit profile (TRMNL OG, Playdate, Frame, and the color panels that dither to black and white) | | `2bit:flex` | Display as flex on 4-shade grayscale displays | Every 2-bit profile (TRMNL OG V2, Waveshare 5.8" B/W) | | `4bit:grid` | Display as grid on 16-shade grayscale displays | Every 4-bit profile (TRMNL V2, Kindle 2024, reMarkable Paper 2, and most Kobo and Inkplate panels) | ## Device-Specific Display Control Combine a size and a bit-depth prefix on any display utility to narrow the target to one group of panels. Use the pattern `size:bit-depth:display`. | Example Class | Target Device | Effect | | --- | --- | --- | | `md:1bit:block` | 1-bit screens at md or lg (TRMNL OG, Frame) | Display as block | | `md:2bit:flex` | 2-bit screens at md or lg (TRMNL OG V2, Waveshare 5.8" B/W) | Display as flex | | `lg:4bit:grid` | 4-bit screens at lg (TRMNL V2, Kindle Scribe, reMarkable Paper 2, and 11 more) | Display as grid | | `sm:4bit:table` | Every 4-bit screen, since sm is the smallest size class | Display as table | ```
Simple layouts for lower bit-depth displays
``` ## Dark Mode Display Control Visibility utilities support dark-first prefixes for screen dark mode targeting. Use `dark:` to show or hide content by screen dark mode. Light-mode behavior is the default state. ```
Dark mode hides this
Hidden on dark medium+ portrait 2-bit screens
``` Previous [ ## Responsive Test Test responsive utilities and compare SCSS mixins with CSS classes ](/framework/docs/3.4/responsive_test) Next [ ## Background Apply color tokens as backgrounds with bg--{token} ](/framework/docs/3.4/background) --- # Background Use the color palette defined in [Colors](/framework/docs/3.4/colors). Apply these shades with bg--{token} for backgrounds. On 1-bit displays, grayscale uses dither patterns; on 2-bit and 4-bit+, solid colors render. ### Grayscale Grayscale background shades only, including the center spacer between 40 and 45. black 10 15 20 25 30 35 40 45 50 55 60 65 70 75 white ![TRMNL Logo](/images/plugins/trmnl--render.svg)Grayscale backgrounds **Dark Mode Notice:** The color palette appears inverted because dark mode remaps the framework tokens: black and white swap, grays and chromatic steps mirror. Images are not affected unless they opt in via `image--adaptive`. Themed screens are exempt from dark mode entirely. ### Base Colors Full base palettes for background tokens: grayscale and all chromatic hues with every shade step. 10 15 20 25 30 35 40 base 45 50 55 60 65 70 75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Base background colors ### Usage Use the `bg--{shade}` utility classes to apply these background patterns to any element. The shade comes from one of three schemes (see [Colors](/framework/docs/3.4/colors)): - **Grayscale:** `bg--black`, `bg--gray-10` through `bg--gray-75`, and `bg--white`. - **Chromatic:** `bg--{hue}` for the pure color (e.g. bg--red, bg--green), or `bg--{hue}-{step}` for a step on that hue's ladder (e.g. bg--red-50, bg--blue-40). - **Semantic:** `bg--primary`, `bg--success`, `bg--error`, and `bg--warning`. - **Surface roles:** `bg--canvas`, `bg--surface`, and `bg--backdrop` paint the screen's own background roles. The surface roles resolve through the theme slot chain, so a theme repaints them. Reach for them when a block should follow the screen instead of pinning a shade a theme cannot move. See [Theme Slots](/framework/docs/3.4/theme_slots) . ```
Black
Gray 10
Gray 15
Gray 20
Gray 25
Gray 30
Gray 35
Gray 40
Gray 45
Gray 50
Gray 55
Gray 60
Gray 65
Gray 70
Gray 75
White
``` **Device Preview tip:** Use the Device Preview (top right) to switch between grayscale and color palettes. Try Inky Impression 7.3 (color-7a) or Tidbyt (color-24bit) to see chromatic colors. #### Chromatic tokens Use `bg--{hue}-{step}` and `text--{hue}-{step}` for color backgrounds and text. ```
Pure red
Red 50
Blue 40
Green 60
Red text
``` #### Semantic tokens Use `bg--{role}` and `text--{role}` for intent-based colors. Roles: primary, success, error, warning. See [Colors](/framework/docs/3.4/colors) for the full mapping. Text inside a `bg--*` background is black or white, whichever reads better on the color the screen paints. A `text--*` class still wins, and 1-bit screens leave the text color as it is. ```
Primary
Success
Error
Warning text
``` ### Related APIs #### Reading background paint from JavaScript The `bg(token, { el })` resolver returns the exact paint a `bg--{token}` utility would apply, as a canonical Fill read from the live cascade with bit depth, dark mode, and theme resolved. Apply it to canvases, SVGs, or chart options. See [Painting Colors](/framework/docs/3.4/paint_colors) for every resolver and the Fill shape. ``` var fill = TRMNLPaint.bg("gray-30", { el: "my-node" }); ``` Previous [ ## Visibility Control element visibility based on display bit depth ](/framework/docs/3.4/visibility) Next [ ## Border Draw horizontal and vertical rules on the same shade scale as backgrounds ](/framework/docs/3.4/border) --- # Border Draw a horizontal or vertical rule on any element with the border--h and border--v utilities, named on the same 10 to 75 shade scale as backgrounds. On 1-bit displays a step renders as a dither pattern of black and white pixels, so a rule can read as gray. 4-bit and full-color screens draw all 14 steps; everywhere else two neighboring steps share a line. ### Usage Use `border--h-{step}` and `border--v-{step}` with steps 10 to 75, the same shade scale as backgrounds, plus black and white lines in both directions (`border--h-black`, `border--h-white`, and their `v-` counterparts). Borders render grayscale by default; a theme recolors all of them. 4-bit and full-color screens give each of the 14 steps its own shade. Everywhere else (1-bit, 2-bit, the limited palettes, and screens with no mode class) two neighboring steps share a line, so `border--h-40` and `border--h-45` draw the same line. Pick steps 10 apart, like 40 and 50, when two lines have to read differently on a 1-bit panel. The numbered level classes (`border--h-1` through `border--h-7`, and their vertical counterparts) are deprecated and will be removed in Framework 4.0. Use the step classes in new markup. #### Horizontal Borders 10 15 20 25 30 35 40 45 50 55 60 65 70 75 10 15 20 25 30 35 40 45 50 55 60 65 70 75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Horizontal Borders **Dark Mode Notice:** The palette appears inverted because dark mode swaps black and white and mirrors the gray and color ladders. Images are not affected unless they opt in via `image--adaptive`. Themed screens ignore dark mode entirely. ```
Dark border
Mid border
Light border
``` #### Vertical Borders 10 15 20 25 30 35 40 45 50 55 60 65 70 75 10 15 20 25 30 35 40 45 50 55 60 65 70 75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Vertical Borders ```
Vertical border
Vertical border
``` ### Themed Borders Recolor borders from a theme, not with a class: `theme-slots.utility-remap-border-grayscale($hue, $side)` repaints every rule (and `.divider`) in the hue without changing its shape. The `dark` side paints black on the hue, `bright` paints the hue on white. Pick a Style in the screen picker to watch the lines above repaint. 2-bit borders are the exception: their four tones are drawn into the line itself, so a hue remap leaves them black, white, and two grays. That is deliberate: on a 4-tone panel a hue would print as gray anyway. A theme that wants to change the 2-bit borders swaps levels instead, with `theme-slots.utility-border-level($level, $dir, $from-level)`, the way the shipped Dark theme does. See [Theme Slots](/framework/docs/3.4/theme_slots) . ``` @include theme-slots.utility-remap-border-grayscale("yellow", $side: "dark"); @include theme-slots.utility-remap-border-grayscale("red", $side: "bright"); ``` ### Black & White Borders `border--h-black` and `border--h-white` adapt to themes automatically. They read the theme's strongest and softest fill colors, so they stay the darkest and lightest lines whatever the theme. black white black white ![TRMNL Logo](/images/plugins/trmnl--render.svg)Black & White Borders ```
Black border (strongest fill)
White border (softest fill)
Black vertical border
White vertical border
``` ### Borders in JavaScript JavaScript can read the border lines too: `TRMNLPaint.border(spec, { dir })` returns the line for a step, or for `'black'` / `'white'`, with the device and the active theme already applied. `applyBorder()` paints it onto a node. These lines stay visible in every mode, which makes them the reliable source for JS-drawn hairlines like chart grids; see [Painting Borders](/framework/docs/3.4/paint_borders) . Previous [ ## Background Apply color tokens as backgrounds with bg--{token} ](/framework/docs/3.4/background) Next [ ## Rounded Control element rounding with predefined values ](/framework/docs/3.4/rounded) --- # Rounded Utility classes for corner radius. Predefined sizes, per-corner control, and arbitrary pixel values. ### Size Variants The rounded system includes predefined base sizes and arbitrary pixel values. These standardized radii help maintain consistent corner rounding across your application's components. #### Base The base `rounded` class without size modifiers and the `rounded--base` class both produce the same visual result, providing the standard border radius (10px). Use `rounded--base` when you need to explicitly set the base size in responsive contexts. See the [Responsive Rounded](#responsive-rounded) section for examples. rounded--none rounded--xsmall rounded--small rounded rounded--medium rounded--large rounded--xlarge rounded--xxlarge rounded--full ![TRMNL Logo](/images/plugins/trmnl--render.svg)Predefined RoundedDesign System ```
...
...
...
...
...
...
...
...
...
...
...
``` #### Arbitrary Use `rounded--[Npx]` syntax to specify exact pixel values from **0px to 50px**. This rounds all four corners and does not support responsive variants. Corner-specific utilities take the named sizes only. rounded--[0px] rounded--[10px] rounded--[30px] rounded--[20px] rounded--[40px] rounded--[50px] ![TRMNL Logo](/images/plugins/trmnl--render.svg)Arbitrary Pixel RoundedDesign System ```
...
...
...
...
...
...
...
``` Arbitrary rounded values using the `rounded--[Npx]` syntax do not support responsive variants. Use predefined rounded classes if you need responsive behavior. ### Corner-Specific Rounding Apply border radius to specific corners or sides of an element. This allows for more complex shapes and asymmetric designs while maintaining consistency. #### Individual Corners Target specific corners with `rounded-{corner}`, where corner is tl (top-left), tr (top-right), br (bottom-right), or bl (bottom-left). Add a size with a second double dash: `rounded-tl--large`. Every corner takes the full size scale, the same one the all-corners class uses: bare (base), `--base`, `--none`, `--xsmall`, `--small`, `--medium`, `--large`, `--xlarge`, `--xxlarge`, and `--full`. rounded-tl--large rounded-tr--large rounded-bl--large rounded-br--large ![TRMNL Logo](/images/plugins/trmnl--render.svg)Corner-Specific RoundingDesign System #### Side Rounding Round entire sides with `rounded-{side}`, where side is t (top), r (right), b (bottom), or l (left). Sides take the same size scale as the corners, so `rounded-t` is the base radius and `rounded-t--xxlarge` is the widest. rounded-t--large rounded-r--large rounded-b--large rounded-l--large ![TRMNL Logo](/images/plugins/trmnl--render.svg)Side RoundingDesign System ```
Top left corner
Top right corner
Bottom right corner
Bottom left corner
Top corners
Right corners
Bottom corners
Left corners
``` ### Responsive Rounded Rounded utilities support size-based breakpoints, orientation variants, and their combination. Use prefixes like `md:`, `portrait:`, and `md:portrait:` to target conditions. #### Base Examples Apply different border radius values at different breakpoints using the size-based responsive system. The framework follows a mobile-first approach where larger breakpoints inherit smaller ones. The `--base` modifier is particularly useful for resetting to the default size at specific breakpoints. Responsive Xlarge in landscape, small in portrait ![TRMNL Logo](/images/plugins/trmnl--render.svg)Responsive RoundedSize-Based ```
Xlarge in landscape, small in portrait
``` #### Corner-Specific Examples Corner-specific rounding utilities support responsive variants just like base rounded utilities. Use prefixes like `md:`, `portrait:`, and `md:portrait:` to apply different corner rounding at different breakpoints. Responsive Xlarge in landscape, small in portrait ![TRMNL Logo](/images/plugins/trmnl--render.svg)Responsive Corner RoundingSize-Based ```
Xlarge in landscape, small in portrait
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--progress-bar-radius` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | - | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | | `--rounded-full` | 9999px | - | - | - | | `--rounded-large` | 20px | - | - | - | | `--rounded-medium` | 15px | - | - | - | | `--rounded-none` | 0px | - | - | - | | `--rounded-small` | 7px | - | - | - | | `--rounded-xlarge` | 25px | - | - | - | | `--rounded-xsmall` | 5px | - | - | - | | `--rounded-xxlarge` | 30px | - | - | - | | `--title-bar-border-radius` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | - | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | Previous [ ## Border Draw horizontal and vertical rules on the same shade scale as backgrounds ](/framework/docs/3.4/border) Next [ ## Outline Draw a pixel-perfect dotted rounded border on any element ](/framework/docs/3.4/outline) --- # Outline The Outline utility draws a pixel-perfect dotted rounded border on any element. On 1-bit displays it places single-pixel dots at exact integer coordinates with pure CSS gradients; on 2-bit, 4-bit, and full-color displays it draws a standard CSS border with border-radius instead. ### Basic Usage #### Applying an Outline Add the `outline` class to any element to give it a pixel-perfect rounded border. The class rounds the element itself to 8px, the same curve the dots trace. A card with a background of its own ends on that curve rather than squaring off around the outline. With outline Without outline ![TRMNL Logo](/images/plugins/trmnl--render.svg)Outline UtilityDesign System ```
Content with pixel-perfect rounded border
``` #### Muted Outline `outline--muted` draws the same edge in a mid gray, quiet enough to sit over detailed content. The gray is dark enough to still print on a 1-bit screen, so the element keeps its edge on every device. outline outline--muted ![TRMNL Logo](/images/plugins/trmnl--render.svg)Outline UtilityMuted ```
Content with a mid-gray border
``` ### How It Works The outline draws every dot with CSS backgrounds, 56 layers in total, so each dot lands on an exact pixel. Four edge layers repeat a 1px dot every 4px, and sixteen corner layers place one 1x1px dot each. The remaining 36 are corner fill-ins that stay invisible until a device with a dither pixel ratio of 2 or more turns them on. `dither-pixel-ratio` is a separate profile field from `pixel-ratio`. TRMNL V2 previews at pixel ratio 1.8 and still renders its art at double density, so it gets the fill-ins. #### CSS Gradient Dots The dots are generated gradients, not image files, so they stay on the pixel grid at any element size. The dot color is looked up in three steps, and the first one set wins: `--framework-semantic-border-strong-border-color`, then `--framework-outline-strong`, then `--framework-border-strong`. A theme sets the first through `theme-slots.semantic-border`, so a theme recolors the outline with no extra assets. ``` /* How the CSS works internally (simplified) */ .outline::after { background: /* Edges: repeating 1px dot every 4px */ repeating-linear-gradient(to right, black 0 1px, transparent 1px 4px) 12px 0 / calc(100% - 24px) 1px no-repeat, /* ... 3 more edges ... */ /* Corners: individual 1x1px dots */ linear-gradient(black, black) 8px 0 / 1px 1px no-repeat, linear-gradient(black, black) 4px 1px / 1px 1px no-repeat, /* ... 14 more corner dots ... */ /* ... 36 high-DPI corner fill-ins ... */ } ``` ### Bit-Depth Behavior #### 1-bit Displays 1-bit displays get the gradient dots. Dark mode works automatically because `--framework-semantic-border-strong-border-color` flips to white. #### 2-bit, 4-bit, and Full-Color Displays Draws a standard 1px solid border instead: with real grays or full color available, a smooth line beats dots. Both treatments round on the same 8px curve, scaled by the UI scale. ``` /* 1-bit: CSS gradient dots (via outline-dots mixin) */ .outline::after { @include outline-dots; } /* 2-bit, 4-bit, and full color: falls back to a CSS border */ .screen--2bit .outline::after, .screen--4bit .outline::after, .screen--color-full .outline::after { background: none; border: 1px solid var(--framework-semantic-border-strong-border-color, var(--framework-outline-strong, var(--framework-border-strong))); border-radius: calc(8px * var(--ui-scale, 1)); } ``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--rounded-full` | 9999px | - | - | - | | `--rounded-large` | 20px | - | - | - | | `--rounded-medium` | 15px | - | - | - | | `--rounded-none` | 0px | - | - | - | | `--rounded-small` | 7px | - | - | - | | `--rounded-xlarge` | 25px | - | - | - | | `--rounded-xsmall` | 5px | - | - | - | | `--rounded-xxlarge` | 30px | - | - | - | Previous [ ## Rounded Control element rounding with predefined values ](/framework/docs/3.4/rounded) Next [ ## Image Place images with size, object fit, dithering, inversion, and adaptive icon utilities ](/framework/docs/3.4/image) --- # Image Place images on a screen and control their size, object fit, and inversion. On 1-bit displays, dithering arranges black and white pixels so an image still reads as shades of gray. ### Dithering Add `image-dither` to a raster image to have it dithered to the screen's palette. The dithering itself is a platform behavior: TRMNL applies it when it renders the screen. You mark the image and the platform dithers it to the target device's palette at render time. You dither the image yourself before you serve it. A released build from the CDN and a build compiled from this source both ship the same CSS, and neither one dithers. ``` ``` ``` ``` The demo below picks a source photo per bit depth with visibility utilities; every other mode falls back to the full-color source. The docs preview simulates the dithered look with a docs-only helper, so it approximates what the platform renders. ![Dithered photo, 1-bit source](/images/framework/image/image--1bit.png) ![Dithered photo, 2-bit source](/images/framework/image/image--2bit.png) ![Dithered photo, full-color source](/images/framework/image/image--4bit.png) ![TRMNL Logo](/images/plugins/trmnl--render.svg)Dithering ### Sizes Two utilities cap the width of an `img` that carries the `image` class: `image--small` at 80px and `image--xsmall` at 40px. Both follow the screen's content scale, and both set a maximum, so a narrower source keeps its own width and the aspect ratio is never touched. For any other dimension, use a width or height utility from [Size](/framework/docs/3.4/size) . ![Photo capped at 80px](/images/framework/image/image--4bit.png)image--small ![Photo capped at 40px](/images/framework/image/image--4bit.png)image--xsmall ![TRMNL Logo](/images/plugins/trmnl--render.svg)ImageSizes ``` ``` ### Object Fit Control how an image fills a box whose aspect ratio differs from its own. #### Options - **Fill:** The image is resized to fill the given dimension. If necessary, the image will be stretched or squished to fit. - **Contain:** The image keeps its aspect ratio, but is resized to fit within the given dimension. - **Cover:** The image keeps its aspect ratio and fills the given dimension. The image will be clipped to fit. ![Photo scaled with fill](/images/framework/image/image--4bit.png)Fill ![Photo scaled with contain](/images/framework/image/image--4bit.png)Contain ![Photo scaled with cover](/images/framework/image/image--4bit.png)Cover ![TRMNL Logo](/images/plugins/trmnl--render.svg)Object Fit Options ``` ``` ### Invert Use `invert` to flip every pixel to its opposite: black becomes white, white becomes black. It rescues artwork authored for the opposite background, such as a white-on-black glyph placed on a light screen. It composes with [Image Stroke](/framework/docs/3.4/image_stroke) . The pixels flip first, so the stroke keeps the color its shade modifier names. ![Photo, original](/images/framework/image/image--4bit.png)Original ![Photo, inverted](/images/framework/image/image--4bit.png)Inverted ![TRMNL Logo](/images/plugins/trmnl--render.svg)Invert ``` ``` ### Adaptive Icons Use `image--adaptive` to recolor a monochrome silhouette icon in the screen's icon color. Only the icon's shape is used: its own pixel colors are ignored. The color follows the device, Raw/Preview, and the active theme, exactly like framework text, so one set of icons works everywhere. The icons below are SVG silhouettes from the plugin weather set, originally solid black glyphs on a transparent background. A PNG with transparency works the same way, since only the alpha shape is read. To watch them adapt, switch the device or Style in the screen picker (top right): the icons repaint to match the screen, while a plain `image` would keep its original pixels. ![Temperature icon](/images/plugins/weather/wi-thermometer.svg) 72°Adaptive icon ![Sunny icon](/images/plugins/weather/wi-day-sunny.svg) SunnyAdaptive icon ![TRMNL Logo](/images/plugins/trmnl--render.svg)Adaptive Icons ``` ``` #### How It Works - The framework runtime (`plugins.js`) reads the icon and hands it to the stylesheet as a mask; CSS supplies the paint. - The icon color comes from `--framework-semantic-icon-{color,image,under}`. By default it matches the primary text color; a theme overrides it with `theme-slots.semantic-icon`. - Silhouettes only. The image is flattened to its alpha shape, so never use it on photos or multi-color logos; use [Image Stroke](/framework/docs/3.4/image_stroke) to keep those legible instead. - Composes with `image-stroke` (the stroke outlines the recolored shape). Not meaningful with `image-dither` or `invert`. **The icon must be readable by CSS.** Recoloring uses a CSS mask, which the browser only permits for same-origin icons or hosts that send `Access-Control-Allow-Origin`. An icon on an arbitrary third-party host stays a plain image in its own colors. Serve recolorable icons from your own origin, or inline the SVG with `fill="currentColor"`, which recolors with no classes and no hosting constraint. ### Related APIs #### Adaptive icons under themes An icon carrying `image--adaptive` is repainted with the screen's icon paint, keeping only its alpha channel, so it follows the active theme with no markup changes. Pick a Style in the screen picker to watch the icons on this page repaint. See [Themes](/framework/docs/3.4/themes) for what else a theme re-points. Previous [ ## Outline Draw a pixel-perfect dotted rounded border on any element ](/framework/docs/3.4/outline) Next [ ## Image Stroke Legible images when displayed on shaded backgrounds ](/framework/docs/3.4/image_stroke) --- # Image Stroke Outline a vector or transparent raster image so it stays legible on a shaded background. Set the stroke width and color with the image stroke utilities. ### Usage Preset size modifiers set the stroke width on an image. The default stroke is 1.5px white, with additional options for base (1.5px, equivalent to default), small (1px), medium (2px), large (2.5px), and extra large (3px). The `image-stroke--base` modifier explicitly sets the default stroke width and is useful for responsive layouts. Image Stroke is the right tool for photos and multi-color logos that cannot be recolored. It also composes with `image--adaptive`, outlining the recolored shape (see [Image](/framework/docs/3.4/image) ). ![](/assets/trmnl--glyph-black-4ca602fd.svg)No Stroke ![](/assets/trmnl--glyph-black-4ca602fd.svg)Small ![](/assets/trmnl--glyph-black-4ca602fd.svg)Base ![](/assets/trmnl--glyph-black-4ca602fd.svg)Default ![](/assets/trmnl--glyph-black-4ca602fd.svg)Medium ![](/assets/trmnl--glyph-black-4ca602fd.svg)Large ![](/assets/trmnl--glyph-black-4ca602fd.svg)Extra Large ![](/images/plugins/trmnl--render.svg)Image StrokePreset Sizes ``` ``` ### Stroke Colors Use the `image-stroke--{shade}` modifier to change the stroke color, black for images on dark backgrounds. The shades are the same palette tokens the background scale uses: - `image-stroke--black` and `image-stroke--white`. - `image-stroke--gray-10` through `image-stroke--gray-75`, in steps of five, plus the legacy `gray-1` to `gray-7` aliases. - Ten hues (red, orange, yellow, lime, green, cyan, blue, violet, purple, pink) as a bare name such as `image-stroke--red` and on the same 10 to 75 steps, so `image-stroke--red-40` works on a color panel. For the shade scale and how it adapts across bit depths, see [Background](/framework/docs/3.4/background) . A shade modifier and a width modifier combine: `image-stroke image-stroke--black image-stroke--small` is a 1px black ring. Both take the bit-depth prefixes, so `2bit:image-stroke--gray-30` restyles the ring on 2-bit screens only. A shade class also draws on its own: a lone `image-stroke--black` is the default 1.5px ring in black. [Text Stroke](/framework/docs/3.4/text_stroke) differs here, where a shade class only colors a stroke that `text-stroke` or a width modifier applies. ![](/assets/trmnl--glyph-white-9348e89a.svg)No Stroke ![](/assets/trmnl--glyph-white-9348e89a.svg)Small ![](/assets/trmnl--glyph-white-9348e89a.svg)Base ![](/assets/trmnl--glyph-white-9348e89a.svg)Default ![](/assets/trmnl--glyph-white-9348e89a.svg)Medium ![](/assets/trmnl--glyph-white-9348e89a.svg)Large ![](/assets/trmnl--glyph-white-9348e89a.svg)Extra Large ![](/images/plugins/trmnl--render.svg)Image StrokeColor Variants ``` ``` Previous [ ## Image Place images with size, object fit, dithering, inversion, and adaptive icon utilities ](/framework/docs/3.4/image) Next [ ## Scale Scale interface to affect content density and readability ](/framework/docs/3.4/scale) --- # Scale Scale the whole interface from one screen modifier by changing the UI scale factor. Use it to match content density to viewing distance or user preference. ### Basic Usage Apply scale modifiers to the `screen` element to scale all interface elements proportionally. The selected scale changes typography, component dimensions, gaps, and pixel-based utilities while leaving screen dimensions and relative units unchanged. Scale carries no bit-depth gate, so it works on every screen. #### Available Scale Levels The framework provides seven predefined scale levels: | Class | Scale Factor | Use Case | | --- | --- | --- | | `screen--scale-xxsmall` | 0.66 (66%) | Dynamic mashup content density | | `screen--scale-xsmall` | 0.75 (75%) | Maximum content density | | `screen--scale-small` | 0.875 (87.5%) | Increased content density | | `screen--scale-regular` | 1.0 (100%) | Default scale, no scaling applied | | `screen--scale-large` | 1.125 (112.5%) | Increased size for better readability | | `screen--scale-xlarge` | 1.25 (125%) | Large scale for increased readability | | `screen--scale-xxlarge` | 1.5 (150%) | Maximum scale for accessibility needs | Scale names its neutral tier `regular`, while utility families name theirs `base` (`gap--base`, `rounded--base`, `text--base`), so there is no `screen--scale-base`. Text Scale runs four tiers against Scale's seven, so read the tier list from the page you are on instead of assuming one shared ladder. ### Scale Examples The following examples demonstrate how scale levels affect the same content layout. Notice how all elements scale proportionally. #### Extra Small Scale (75%) Maximum content density: useful when viewing up close or when you need to fit more information on screen. Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelExtra Small (75%) ```
``` #### Small Scale (87.5%) Reduced scale for fitting more content while maintaining good readability. Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelSmall (87.5%) ```
``` #### Regular Scale (100%) Default scale: the baseline that all other scale levels are relative to. Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelRegular (100%) ```
``` #### Large Scale (112.5%) Increased size for better readability Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelLarge (112.5%) ```
``` #### Extra Large Scale (125%) Large scale for increased readability Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelExtra Large (125%) ```
``` #### Extra Extra Large Scale (150%) Maximum scale for accessibility needs Today 1 Morning Meeting: Threat Level Check-inTeam sync and updates 9:00 AM - 9:30 AMDaily 2 Identity Theft WatchReview suspicious 'Jim' behaviours 10:30 AM - 11:30 AMReview 3 Lunch Break: Pretzel Day PrepTeam lunch at downtown 12:30 PM - 1:30 PMBreak 4 Client Call with JanWeekly check-in with stakeholders 2:00 PM - 3:00 PMClient 5 Complaint Sorting: Product RecallPrioritize reported issues 3:30 PM - 4:30 PMComplaints 6 Bulletin Board Update: DundiesUpdate nominations and categories 4:30 PM - 5:30 PMDocs 7 End of Day Sync: Café DiscoReview progress and blockers 5:30 PM - 6:00 PMSync Tomorrow 1 Beach Games Roll-CallConfirm capacity without hot coals 10:00 AM - 12:00 PMPlanning 2 Stakeholder Presentation: Threat Level MidnightTasteful metrics, minimal fireworks 2:00 PM - 3:30 PMPresentation 3 Oscar’s Index Intervention (Of Spreadsheets)Deep dive into the budget tabs 9:00 AM - 11:00 AMNumbers 4 Parkour QA Gauntlet (Very Gentle)Functionality verified: walking 1:00 PM - 3:00 PMQA-ish 5 Campaign Analysis: WUPHF Without The WUPHFLess shouting, more smiling 4:00 PM - 5:30 PMMarketing This Week 1 Warehouse to Cloud (No Forklifts)Move boxes, label feelings WednesdayInfrastructure-ish 2 Customer Satisfaction Review: 'Did I Stutter?'Improve smiles per hour ThursdayCustomer Success 3 Benihana to Back Office CoordinationWe will know who is who FridayIntegration-ish 4 Data Deep Dive: Boom, Roasted (With Charts)Roasts limited to pie charts MondayAnalytics 5 Accessibility: Conference Room B UpgradesLess squinting, more seeing TuesdayAccessibility 6 Respect the Dashboard (Of Feelings)Set baselines for vibes WednesdayMonitoring 7 The Dundies of GrowthSkills, mentoring, zero karaoke tears FridayDevelopment ![TRMNL Logo](/images/plugins/trmnl--render.svg)Scale LevelExtra Extra Large (150%) ```
``` ### How It Works Scale modifiers set `--modifier-scale`. The screen composes it with `--device-ui-scale` for component typography and geometry, while `--content-scale` applies the modifier to plugin content. Use [Text Scale](/framework/docs/3.4/text_scale) when typography needs an additional factor without applying it to the rest of the interface. #### Affected Properties When you apply a scale modifier, it scales the following properties: - Font sizes and line heights - Component dimensions such as title bars and progress indicators - Framework gaps and pixel-based spacing utilities - Pixel-based size, flex basis, grid minimum, and image presets - Framework radii, text strokes, and image strokes - Custom properties that reference `var(--ui-scale)` or `var(--content-scale)` **Note:** Screen dimensions, percentages, container units, and physical one-pixel rails remain unchanged. Fixed pixel values emitted by framework utilities follow the selected content scale. #### Scaling Custom Values Use framework utilities for fixed dimensions whenever possible. For custom CSS, multiply pixel values by `--content-scale`; for JavaScript, resolve them with `TRMNLPaint.px()`. ```
``` Inline pixel styles, HTML width and height attributes, intrinsic image dimensions, and chart-library numbers do not scale by themselves. Convert those values explicitly or replace them with scale-aware framework utilities. ### Combining with Device Configurations Scale modifiers multiply the device's native UI scale instead of replacing it. Plugin content follows the selected modifier, while framework components also retain the device density adjustment. Every modifier except Regular also resolves typography to Inter Variable on low-density displays, because pixel bundles only render correctly at their native sizes. | Class Combination | Description | | --- | --- | | `screen screen--v2` | Uses device default scale | | `screen screen--v2 screen--scale-small` | Uses 87.5% content scale and 87.5% of the device UI scale | | `screen screen--amazon_kindle_2024 screen--scale-large` | Uses 112.5% content scale and 112.5% of the device UI scale | ```
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | | `--device-ui-scale` | 1 | - | - | - | | `--gap-large` | 20px | - | - | - | | `--gap-medium` | 16px | - | - | - | | `--gap-scale` | 1 | - | - | - | | `--gap-small` | 7px | - | - | - | | `--gap-xlarge` | 30px | - | - | - | | `--gap-xsmall` | 5px | - | - | - | | `--gap-xxlarge` | 40px | - | - | - | | `--list-gap-small` | 8px | - | - | - | | `--modifier-scale` | 1 | - | - | - | | `--ui-scale` | 1 | - | - | - | ### Related APIs #### Reading scale factors from JavaScript The `scale({ el })` and `px(value, { el, kind })` helpers read the resolved scale factors from the live screen, so JavaScript-drawn visuals follow the factors this page documents. `px()` scales by the content scale by default; pass `kind: "ui"` for framework geometry. See [Paint API](/framework/docs/3.4/paint_api) . ``` var inset = TRMNLPaint.px(6, { el: "my-chart", kind: "ui" }); ``` Previous [ ## Image Stroke Legible images when displayed on shaded backgrounds ](/framework/docs/3.4/image_stroke) Next [ ## Inverse Apply inverse framework colors to an element and its descendants ](/framework/docs/3.4/inverse) --- # Inverse The Inverse utility flips one element and its children to the opposite color scheme: light content on a dark surface, or the reverse. Use it to make one element stand out, an active item or a selected row, without touching its siblings. ### Usage Add `inverse` to a container to flip it and everything inside. ```
Active item
``` ### Active Collection Rows Invert individual rows so the active ones stand out in a longer list. The row's background, text, and other framework paint change together. Desk M1 Occupied Booked with TRMNL · 7:00 - 15:00 Desk M2 Occupied laura@example.com · Booked for the day Desk M3 Available Desk M4 Available Desk S1 Available Until 8:30 Desk S2 Available ![TRMNL Logo](/images/plugins/trmnl--render.svg)Bookable desks ```
Desk M1 Occupied
Desk M3 Available
``` ### Active Collection Cards Use inverse cards when several resources share the same grid and only some need attention. The stronger surface separates the occupied rooms from the available ones. Auxiliary meeting room Available   Board room Marketing Sync 11:00 - 12:30 Huddle Space Alpha Occupied 11:15 - 11:45 Huddle Space Beta Available   ![TRMNL Logo](/images/plugins/trmnl--render.svg)Meeting rooms ```
Available
Marketing Sync
11:00 - 12:30
``` ### State Transitions Apply inverse to a whole surface when a state change should recolor everything on it. An occupied meeting room, for example, inverts its schedule, dividers, and attendee details as one. 11:00 - 12:30 Marketing Sync laura@example.com & 5 more ![TRMNL Logo](/images/plugins/trmnl--render.svg)Board room20 seats ```
11:00 - 12:30
Marketing Sync
laura@example.com & 5 more
``` ### What Inverse Flips Inverse flips everything the framework paints inside the element: backgrounds, text, borders, strokes, icons, and chart colors. Anything you set directly on the element wins over the inverse defaults, and a theme can style its own inverse with `.screen--theme- .inverse`. See [Theme Slots](/framework/docs/3.4/theme_slots) and [Themes](/framework/docs/3.4/themes) . Inverse is not the `invert` image filter: it changes what the framework paints instead of flipping pixels. It also does not turn on `dark:` utilities; those still need `screen--dark-mode`. Previous [ ## Scale Scale interface to affect content density and readability ](/framework/docs/3.4/scale) Next [ ## Font Family Switch between Classic and TRMNL font bundles per device ](/framework/docs/3.4/font_family) --- # Font Family 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. To use your own font, add the `typeface` class to any container and name the font in `--framework-typeface` (Framework 3.4 or later). See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . The original pixel set. Three single-weight fonts: NicoPups, NicoClean, BlockKie. Default in Framework 3.0. The new pixel set. Three font families with Regular and Bold weights: TRMNL12, TRMNL16, TRMNL21. Default in Framework 3.1. ```
...
``` ```
...
``` Both bundles are available in Framework 3.x. Which one a screen renders depends on the display and the active scale: - **Low-density displays:** the selected pixel-font bundle. - **High-density displays:** **Inter Variable** , regardless of bundle or bit depth. - **Any Scale or Text Scale other than Regular:** Inter Variable, because pixel bundles only render correctly at their native sizes. - **No font-bundle class:** in Framework 3.4 the screen uses **TRMNL** ; add `screen--fonts-classic` to opt into Classic. ### Classic bundle Three single-weight pixel fonts. Activate by adding `screen--fonts-classic` to the screen root. This controls pixel-font output on low-density displays; high-density displays still resolve to Inter. #### NicoPups Designed at **16px** pixel height. Used for descriptions, small labels, and metadata. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "NicoPups" · font-size: 16px Designer[Emily Huo (emhuo)](https://emhuo.itch.io/nico-pixel-fonts-pack)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) #### NicoClean Designed at **16px** pixel height. The workhorse font, used for labels, rich text body copy, and title-bar text. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "NicoClean" · font-size: 16px Designer[Emily Huo (emhuo)](https://emhuo.itch.io/nico-pixel-fonts-pack)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) #### BlockKie Designed at **26px** pixel height. Used for titles and large rich-text. The largest pixel font in the Classic bundle. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "BlockKie" · font-size: 26px Designer[JoohnFonts](https://fontstruct.com/fontstructors/show/1669437/joohnfonts)License[Creative Commons Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/) #### On-device preview text--small · Classictext--base · Classictext--large · Classictext--base text--bold · Classic ![TRMNL Logo](/images/plugins/trmnl--render.svg)Classic bundle **High-density font notice:** This preview is using Inter because the selected device is high-density. Classic and TRMNL pixel bundles still apply on low-density displays; choose a 1x-density model in Device Preview to compare those bundles. ### TRMNL bundle Three font families, each with Regular and Bold weights. Framework 3.4 uses it when no font-bundle class is present, so add `screen--fonts-trmnl` only to pin the bundle explicitly. This controls pixel-font output on low-density displays; high-density displays still resolve to Inter. #### TRMNL12 Designed at **12px** pixel height. The smallest pixel font, used for descriptions, small labels, and metadata. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? Bold 700 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "TRMNL12" · font-size: 12px Designer[Heavyweight Digital Type Foundry](https://heavyweight-type.com)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) #### TRMNL16 Designed at **16px** pixel height. The workhorse font, used for labels, rich text body copy, and title-bar text. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? Bold 700 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "TRMNL16" · font-size: 16px Designer[Heavyweight Digital Type Foundry](https://heavyweight-type.com)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) #### TRMNL21 Designed at **21px** pixel height. The largest pixel font, used for titles, headings, and large rich-text. Regular 400 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? Bold 700 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 !@#$%^&\*()-=+[]{}|;:',./\<\>? font-family: "TRMNL21" · font-size: 21px Designer[Heavyweight Digital Type Foundry](https://heavyweight-type.com)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) #### On-device preview text--small · TRMNLtext--base · TRMNLtext--large · TRMNLtext--base text--bold · TRMNL ![TRMNL Logo](/images/plugins/trmnl--render.svg)TRMNL bundle **High-density font notice:** This preview is using Inter because the selected device is high-density. Classic and TRMNL pixel bundles still apply on low-density displays; choose a 1x-density model in Device Preview to compare those bundles. ### Component-by-component bundle map Each component picks the appropriate font based on the active bundle. On high-density displays Inter Variable is used for every component regardless of bundle. | Component | Classic (low-density) | TRMNL (low-density) | High-density | | --- | --- | --- | --- | | Title Bar | NicoClean | TRMNL16 | Inter Variable | | Title | BlockKie | TRMNL21 | Inter Variable | | Title (small) | NicoClean | TRMNL16 | Inter Variable | | Label | NicoClean | TRMNL16 | Inter Variable | | Label (small) | NicoPups | TRMNL12 | Inter Variable | | Description | NicoPups | TRMNL12 | Inter Variable | | Description (large) | NicoClean | TRMNL16 | Inter Variable | | Value (xxsmall) | NicoClean | TRMNL16 | Inter Variable | | Value (other sizes) | Inter Variable | Inter Variable | Inter Variable | | Rich Text | NicoClean | TRMNL16 | Inter Variable | | Rich Text (small) | NicoPups | TRMNL12 | Inter Variable | | Rich Text (large) | BlockKie | TRMNL21 | Inter Variable | | Item Index | NicoPups | TRMNL12 | Inter Variable | ### High-density: Inter Variable Used on high-density displays in both bundles for legibility. Designer[Rasmus Andersson](https://rsms.me/inter)License[SIL Open Font License v1.1](https://scripts.sil.org/OFL) Previous [ ## Inverse Apply inverse framework colors to an element and its descendants ](/framework/docs/3.4/inverse) Next [ ## Custom Typefaces Use your own font for the framework text, in a plugin or a theme ](/framework/docs/3.4/custom_typefaces) --- # Custom Typefaces Use your own font for the framework text. Load the font, name it on any container, and every title, label, value, and description inside switches to it. ### Use Your Own Font Load the font with `@font-face`, then name it on the element that holds your content. Framework 3.4 or later. - `typeface` puts your font on every framework text class inside the element. It works on any container: the screen, a view, a layout, or a plain wrapper. - `--framework-typeface` names the font: one quoted family name, or a comma-separated list for your own fallbacks. Inter Variable and Inter always follow it, and the text stays on them when the variable is not set. ```
Today at a glance Three meetings, one deadline
``` ### Use It From Sass `typography.typeface($family)` writes the same font rules into your own selector, with the family built in. Pass one quoted family name, or a comma-separated list. ``` @use "framework/mixins/typography"; .my-plugin { @include typography.typeface("Lexend"); } ``` A theme sets its font with `theme-slots.typeface($family)`, the same mixin under the theme-slots name. Include it in the theme's layer, and the theme class switches the whole screen to your font. ``` @use "framework/config/layers"; @use "framework/mixins/theme-slots"; @include layers.order; @font-face { font-family: "Example Sans"; font-weight: 100 900; font-style: normal; src: url("./fonts/example-sans.woff2") format("woff2"); } @layer tn--themes { .trmnl .screen--theme-example { @include theme-slots.typeface("Example Sans"); } } ``` See [Authoring Themes](/framework/docs/3.4/theme_authoring) for building and loading a theme. ### Which Font Wins - A plugin's font, set with the `typeface` class, wins inside its container. - A theme's font applies everywhere else on the themed screen. - Without either, the screen uses the device's built-in fonts. The stylesheets can load in any order. ### What Changes on Each Device - Every text class uses your font: titles, labels, values, descriptions, the title bar, item indexes, and rich text. - On 1-bit and other low-density screens, your font also replaces the Classic and TRMNL pixel fonts. - Text sizes, line heights, and Text Scale do not change. On 1-bit and other low-density screens they are the sizes drawn for the pixel fonts. - A font wider or taller than the built-in ones can wrap or clip differently at those sizes, so preview it on the devices you target. - A theme's `theme-slots.font-weight-shift` still moves every weight, as long as your font has the weights to move to. See [Font Family](/framework/docs/3.4/font_family) for the built-in fonts and when each device uses them. ### Font Files - The text classes ask for weights from 200 to 700. A variable font that covers that range matches every one of them. - With separate files per weight, the browser uses the closest weight you declare. Ship at least a regular (400) and a bold (700). - In a stylesheet you host, point `src` at files beside it with relative URLs, so it works on any host. - Include the font's license with the files, and use WOFF2 where you can. ### Fonts in JavaScript `TRMNLPaint.type()` returns your font for any text role, so text you draw yourself matches the screen. Pass an `el` inside a `typeface` container to read that container's font. Read it again after a theme switch, and wait for `document.fonts.ready` before measuring text. See [Painting Typography](/framework/docs/3.4/paint_typography) . ### Share a Theme With Its Font A theme with a custom font ships as a folder: the compiled stylesheet with the font files and their license beside it. See the portable exports on [Authoring Themes](/framework/docs/3.4/theme_authoring) . --- # Font Weight Utility classes for controlling font weight independently of size. Classic ships in a single weight, so `text--bold` is a no-op on low-density Classic; on low-density TRMNL it picks the bundled bold variant; on high-density displays it sets the Inter Variable weight. ### Usage Use `text--regular` and `text--bold` to control font weight independently of size. Density decides whether the active pixel-font bundle or Inter receives the weight. The bold variant is resolved as follows: - **Classic** bundle: every font ships in a single weight, so `text--bold` has no visual effect on low-density Classic. - **TRMNL** bundle: `text--bold` selects the matching **TRMNL12/16/21 Bold** font file at the active size. - **High-density** displays: both classes simply set the Inter Variable weight to 400 or 700. The older `font--regular` and `font--bold` spellings still render and are deprecated. See Deprecated weight aliases below. **High-density font notice:** This preview is using Inter because the selected device is high-density. Classic and TRMNL pixel bundles still apply on low-density displays; choose a 1x-density model in Device Preview to compare those bundles. | Class | Weight | Classic (low-density) | TRMNL (low-density) | High-density | | --- | --- | --- | --- | --- | | `text--regular` | 400 | NicoPups / NicoClean / BlockKie | TRMNL12/16/21 Regular | Inter Variable @ 400 | | `text--bold` | 700 | No bold variant | TRMNL12/16/21 Bold | Inter Variable @ 700 | #### Weight comparison · Classic bundle Each weight shown at every pixel-font size with `screen--fonts-classic` on the screen root. Low-density displays use that bundle; high-density displays use Inter weights instead. text--small text--regulartext--small text--bold text--base text--regulartext--base text--bold text--large text--regulartext--large text--bold ![TRMNL Logo](/images/plugins/trmnl--render.svg)Font WeightClassic #### Weight comparison · TRMNL bundle Each weight shown at every pixel-font size with `screen--fonts-trmnl` on the screen root. Low-density displays use that bundle; high-density displays use Inter weights instead. text--small text--regulartext--small text--bold text--base text--regulartext--base text--bold text--large text--regulartext--large text--bold ![TRMNL Logo](/images/plugins/trmnl--render.svg)Font WeightTRMNL ``` Small regular Small bold Base regular Base bold Large regular Large bold ``` ### Responsive & bit-depth variants Font weight utilities support responsive, orientation, and bit-depth prefixes. Combine them to fine-tune weight across screen sizes and display types. | Variant | Example | Description | | --- | --- | --- | | Responsive | `md:text--bold` | Bold at medium breakpoint and up | | Orientation | `portrait:text--regular` | Regular weight in portrait orientation | | Bit-depth | `4bit:text--bold` | Bold on 4-bit displays only | | Combined | `md:4bit:text--bold` | Bold at medium breakpoint on 4-bit displays | ``` Bold only on 4-bit displays Bold at medium breakpoint and up ``` ### Deprecated weight aliases Two `font--{weight}` classes ship as aliases of the weight utilities: `font--regular` and `font--bold`. Each renders exactly like the `text--` class with the same suffix, down to the responsive, orientation, and bit-depth variants. They are deprecated and will be removed in Framework 4.0. Prefer `text--regular` and `text--bold` in new markup. The ten `font--{size}` classes carry the same removal release; see [Text Size](/framework/docs/3.4/text_size) . ``` Bold text Bold at medium breakpoint and up Bold text Bold at medium breakpoint and up ``` Previous [ ## Custom Typefaces Use your own font for the framework text, in a plugin or a theme ](/framework/docs/3.4/custom_typefaces) Next [ ## Font Glyphs Browse every glyph available in each Framework font bundle ](/framework/docs/3.4/font_glyphs) --- # Font Glyphs Browse every glyph available in each Framework font. Switch between the Classic and TRMNL bundles to view their full character inventory. The glyph tables are read in the browser by opentype.js, loaded from jsDelivr, so they stay empty without network. The fonts themselves are served from this origin. ### Font selector Classic bundle Original TRMNL pixel fonts. Each family ships in a single weight, so there is no bold variant. TRMNL bundle New TRMNL family. Each family ships with both Regular and Bold weight variants. Loading font… Previous [ ## Font Weight Toggle between regular and bold font weight independently of size ](/framework/docs/3.4/font_weight) Next [ ## Text Size Control text size with utility classes across all display types ](/framework/docs/3.4/text_size) --- # Text Size Utility classes for controlling text size. Each class sets the correct font family, size, line-height, and smoothing for the active density tier: pixel bundle on low-density displays, Inter Variable on high-density displays. ### Text Size Utilities Use `text--{size}` utility classes to set font family, size, line-height, and smoothing in one declaration. Density decides which font family the utility resolves to. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . - **Low-density displays:** the three smallest sizes use the active pixel-font bundle (Classic NicoPups/NicoClean/BlockKie or TRMNL TRMNL12/16/21). - **High-density displays:** every text size uses Inter Variable, regardless of bundle. - **Sizes from xlarge onward:** Inter Variable on every display. - **Any Scale or Text Scale other than Regular:** Inter Variable at every size, because pixel bundles only render correctly at their native sizes. Text Size selects one typography role for an element. Use [Text Scale](/framework/docs/3.4/text_scale) to adjust every typography role from the screen. **High-density font notice:** This preview is using Inter because the selected device is high-density. Classic and TRMNL pixel bundles still apply on low-density displays; choose a 1x-density model in Device Preview to compare those bundles. | Class | Size | Line-height | Classic (low-density) | TRMNL (low-density) | High-density | | --- | --- | --- | --- | --- | --- | | `text--small` | 12px | 1 | NicoPups @ 16px | TRMNL12 | Inter Variable | | `text--base` | 16px | 1.25 | NicoClean | TRMNL16 | Inter Variable | | `text--large` | 21px | 1 | BlockKie @ 26px | TRMNL21 | Inter Variable | | `text--xlarge` | 26px | 29px | Inter Variable | Inter Variable | Inter Variable | | `text--xxlarge` | 38px | 42px | Inter Variable | Inter Variable | Inter Variable | | `text--xxxlarge` | 58px | 70px | Inter Variable | Inter Variable | Inter Variable | | `text--mega` | 74px | 86px | Inter Variable | Inter Variable | Inter Variable | | `text--giga` | 96px | 108px | Inter Variable | Inter Variable | Inter Variable | | `text--tera` | 128px | 128px | Inter Variable | Inter Variable | Inter Variable | | `text--peta` | 170px | 180px | Inter Variable | Inter Variable | Inter Variable | These sizes belong to the `text--` ladder only. The Value element reuses the same tier names on a much larger ladder, where `value--xlarge` is 74px against 26px here, so read the size table on each page before mixing the two. #### Small The `text--small` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeSmall ``` Regular text Bold text ``` #### Base The `text--base` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeBase ``` Regular text Bold text ``` #### Large The `text--large` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeLarge ``` Regular text Bold text ``` #### XLarge The `text--xlarge` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeXLarge ``` Regular text Bold text ``` #### XXLarge The `text--xxlarge` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeXXLarge ``` Regular text Bold text ``` #### XXXLarge The `text--xxxlarge` class. Low-density previews show the active pixel-font bundle where that size supports it; high-density previews show Inter. The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text SizeXXXLarge ``` Regular text Bold text ``` ### Responsive & bit-depth variants All text size utilities support responsive, orientation, and bit-depth prefixes. Combine them to fine-tune typography across screen sizes and display types. | Variant | Example | Description | | --- | --- | --- | | Responsive | `md:text--large` | Apply at medium breakpoint and up | | Orientation | `portrait:text--small` | Apply in portrait orientation | | Bit-depth | `4bit:text--xlarge` | Apply on 4-bit displays only | | Combined | `md:4bit:text--xxlarge` | Apply at medium breakpoint on 4-bit displays | ``` Responsive text sizing Larger on 4-bit displays ``` ### Deprecated font--{size} aliases Ten `font--{size}` classes ship as aliases of the text size utilities: `font--small`, `font--base`, `font--large`, `font--xlarge`, `font--xxlarge`, `font--xxxlarge`, `font--mega`, `font--giga`, `font--tera`, and `font--peta`. Each renders exactly like the `text--{size}` class with the same suffix, down to the responsive, orientation, and bit-depth variants. They are deprecated and will be removed in Framework 4.0. Prefer `text--{size}` in new markup. The weight aliases `font--bold` and `font--regular` carry the same removal release, with `text--bold` and `text--regular` as their successors; see [Font Weight](/framework/docs/3.4/font_weight) . ``` Large text Mega text Large text Mega text ``` Previous [ ## Font Glyphs Browse every glyph available in each Framework font bundle ](/framework/docs/3.4/font_glyphs) Next [ ## Text Scale Scale all framework typography independently of interface geometry ](/framework/docs/3.4/text_scale) --- # Text Scale Text Scale adjusts every framework font size and pixel line height from one screen modifier. It composes with Scale, so you can change text readability without applying the same factor to interface geometry or text strokes. ### Basic Usage Add `screen--text-scale-{size}` to the `screen`. Text Scale changes framework typography while Scale continues to control the rest of the interface. ```
``` #### Available Levels Text Scale uses four factors from 80% to 150%. Each factor applies after device density and [Scale](/framework/docs/3.4/scale) . Every factor except Regular also resolves typography to Inter Variable on low-density displays, because pixel bundles only render correctly at their native sizes. | Class | Factor | Result | | --- | --- | --- | | `screen--text-scale-small` | 0.8 | 80% of the composed text size | | `screen--text-scale-regular` | 1 | 100% of the composed text size | | `screen--text-scale-large` | 1.25 | 125% of the composed text size | | `screen--text-scale-xlarge` | 1.5 | 150% of the composed text size | Text Scale names its neutral tier `regular` and its ladder stops at `xlarge`, where Scale runs seven tiers. Utility families name their neutral tier `base` (`gap--base`, `text--base`), so there is no `screen--text-scale-base`. ### Interactive Preview Move the slider between the four Text Scale levels. The Weather example updates its framework typography while component dimensions, gaps, and text strokes keep their regular scale. Aa Aa Aa Aa ![](/images/plugins/weather/wi-day-sunny.svg) 77°Temperature (5:55 PM) ![Temperature](/images/plugins/weather/wi-thermometer.svg) 80°Feels Like ![Humidity](/images/plugins/weather/wi-raindrops.svg) 45%Humidity ![](/images/plugins/weather/wi-day-sunny.svg) SunnyRight Now ![Today weather condition](/images/plugins/weather/wi-day-cloudy.svg) Partly cloudyJul 9 ![UV Index](/images/plugins/weather/wi-hot.svg) High (8)UV ![Temperature](/images/plugins/weather/wi-thermometer.svg) 70°Low 86°High ![Tomorrow weather condition](/images/plugins/weather/wi-day-showers.svg) Light RainJul 10 ![UV Index](/images/plugins/weather/wi-hot.svg) Moderate (5)UV ![Temperature](/images/plugins/weather/wi-thermometer.svg) 65°Low 79°High ![](/images/plugins/weather--render.svg) # Weather Las Vegas ```
``` ### Combining Scale and Text Scale Scale sets the base size for the whole interface, including component dimensions, spacing, and typography. Text Scale then multiplies only the typography on top of that base while leaving the surrounding geometry unchanged. For example, a 66% Scale combined with a 150% Text Scale produces text at 99% of its original size inside an interface that remains at 66%. Device density is still part of the framework's typography calculation, so Text Scale complements rather than replaces the device's font bundle and density settings. ### Custom Typography Framework classes scale automatically. Use `--text-ui-scale` for custom CSS that follows framework typography, or pass `kind: "text"` to `TRMNLPaint.px()` for JavaScript values. ``` ``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | | `--device-ui-scale` | 1 | - | - | - | | `--modifier-scale` | 1 | - | - | - | | `--modifier-text-scale` | 1 | - | - | - | | `--text-ui-scale` | 1 | - | - | - | | `--ui-scale` | 1 | - | - | - | ### Related APIs #### Reading scale factors from JavaScript The `scale({ el })` and `px(value, { el, kind })` helpers read the resolved scale factors from the live screen, so JavaScript-drawn visuals follow the factors this page documents. Pass `kind: "text"` to scale framework typography with the text scale. See [Paint API](/framework/docs/3.4/paint_api) . ``` var fontSize = TRMNLPaint.px(16, { el: "my-chart", kind: "text" }); ``` Previous [ ## Text Size Control text size with utility classes across all display types ](/framework/docs/3.4/text_size) Next [ ## Text Alignment Control text alignment with responsive breakpoint, orientation, and bit-depth variants ](/framework/docs/3.4/text_alignment) --- # Text Alignment Utility classes for horizontal text alignment. Left, center, right, and justify, with responsive variants for breakpoints, orientation, and bit-depth. ### Usage Control text alignment using the alignment utility classes. Options include left, center, right, and justify alignment. See the [Responsive Features](#responsive-text-alignment) section for breakpoint, orientation, and bit-depth variants. | Class | Description | | --- | --- | | `text--left` | Aligns text to the left (default for most elements) | | `text--center` | Centers text horizontally | | `text--right` | Aligns text to the right | | `text--justify` | Justifies text, creating even edges on both sides | This text is left-aligned. This is the default alignment for most text content. This text is center-aligned. Useful for headings and important content. This text is right-aligned. Often used for numerical data or RTL languages. This text is justified. Creates even text edges on both sides but affects readability. Useful for multi-column text layouts. ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextAlignment ```

Left-aligned text

Center-aligned text

Right-aligned text

Justified text

``` ### Responsive Features Alignment classes support all three responsive systems: size-based breakpoints, orientation-based, and bit-depth variants. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different alignment per device size class. Responsive alignment Left by default, center on md+, right on lg+ ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text AlignmentResponsive ```

Responsive alignment

Progressive alignment

``` #### Orientation and Size+Orientation Text alignment can adapt to orientation with `portrait:` and `landscape:`, and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation variant Left by default, center in portrait ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text AlignmentOrientation ```

Orientation variant

Left by default, right on md+ portrait

``` #### Bit-Depth Responsive Alignment classes support bit-depth prefixes like `1bit:`, `2bit:`, and `4bit:` to apply different alignment on different display color capabilities. Bit-depth alignment Center by default, right on 2-bit screens ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text AlignmentBit-Depth Responsive ```

Bit-depth alignment

Left by default, center on lg+ 4-bit screens

``` #### Combined Responsive Features Combine size, orientation, and bit-depth modifiers for alignment. Use the pattern `size:orientation:bit-depth:utility` for highly targeted styling. ```

Right on md+ portrait 2-bit screens

Progressive with bit-depth override

``` Previous [ ## Text Scale Scale all framework typography independently of interface geometry ](/framework/docs/3.4/text_scale) Next [ ## Text Color Apply grayscale and chromatic color shades to text elements ](/framework/docs/3.4/text_color) --- # Text Color Set text color with the text--{token} utilities, on the same scale as the [Colors](/framework/docs/3.4/colors) palette. On 1-bit displays a grayscale token renders as a dither pattern of black and white pixels, so text can read as any shade of gray. ### Usage Use the `text--{shade}` utility classes to apply text color patterns to any element. The shade comes from one of these schemes (see [Colors](/framework/docs/3.4/colors)): - **Grayscale:** `text--black`, `text--gray-10` through `text--gray-75`, and `text--white`. - **Chromatic:** `text--{hue}` for the pure color, or `text--{hue}-{step}` for a step on that hue's ladder (e.g. text--red-50, text--blue-40). - **Semantic:** `text--primary`, `text--success`, `text--error`, and `text--warning`. See [Colors](/framework/docs/3.4/colors) for how each role resolves per device. - **Ink roles:** `text--default`, `text--muted`, and `text--inverse` paint the text colors the screen itself uses. A theme recolors them, and they mirror `bg--canvas` and its siblings on [Background](/framework/docs/3.4/background) . See the [Responsive Features](#responsive-text-color) section for responsive variants. ### Grayscale Sixteen grayscale values: black, gray-10 through gray-75, and white. Aa black Aa gray-10 Aa gray-15 Aa gray-20 Aa gray-25 Aa gray-30 Aa gray-35 Aa gray-40 Aa gray-45 Aa gray-50 Aa gray-55 Aa gray-60 Aa gray-65 Aa gray-70 Aa gray-75 Aa white Aa black Aa gray-10 Aa gray-15 Aa gray-20 Aa gray-25 Aa gray-30 Aa gray-35 Aa gray-40 Aa gray-45 Aa gray-50 Aa gray-55 Aa gray-60 Aa gray-65 Aa gray-70 Aa gray-75 Aa white ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text color shades **Dark Mode Notice:** The palette appears inverted because dark mode swaps black and white and mirrors the gray and color ladders. Images are not affected unless they opt in via `image--adaptive`. Themed screens ignore dark mode entirely. ```
Black text
Gray 10 text
Gray 15 text
Gray 20 text
Gray 25 text
Gray 30 text
Gray 35 text
Gray 40 text
Gray 45 text
Gray 50 text
Gray 55 text
Gray 60 text
Gray 65 text
Gray 70 text
Gray 75 text
White text
``` Switch the screen picker (top right) to a color device (e.g. Inky Impression 7.3, Tidbyt) to see the hue sections below in color. ### Red Two classes write red text: - `text--red`: the pure red. - `text--red-{step}`: a step on the red ladder (e.g. text--red-50), on the same 10 to 75 scale as grayscale. Aa red-10 Aa red-15 Aa red-20 Aa red-25 Aa red-30 Aa red-35 Aa red-40 Aa red Aa red-45 Aa red-50 Aa red-55 Aa red-60 Aa red-65 Aa red-70 Aa red-75 Aa red-10 Aa red-15 Aa red-20 Aa red-25 Aa red-30 Aa red-35 Aa red-40 Aa red Aa red-45 Aa red-50 Aa red-55 Aa red-60 Aa red-65 Aa red-70 Aa red-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextRed ```
Red-10 text
Red-15 text
Red-20 text
Red-25 text
Red-30 text
Red-35 text
Red-40 text
Red text
Red-45 text
Red-50 text
Red-55 text
Red-60 text
Red-65 text
Red-70 text
Red-75 text
``` ### Orange Two classes write orange text: - `text--orange`: the pure orange. - `text--orange-{step}`: a step on the orange ladder (e.g. text--orange-50), on the same 10 to 75 scale as grayscale. Aa orange-10 Aa orange-15 Aa orange-20 Aa orange-25 Aa orange-30 Aa orange-35 Aa orange-40 Aa orange Aa orange-45 Aa orange-50 Aa orange-55 Aa orange-60 Aa orange-65 Aa orange-70 Aa orange-75 Aa orange-10 Aa orange-15 Aa orange-20 Aa orange-25 Aa orange-30 Aa orange-35 Aa orange-40 Aa orange Aa orange-45 Aa orange-50 Aa orange-55 Aa orange-60 Aa orange-65 Aa orange-70 Aa orange-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextOrange ```
Orange-10 text
Orange-15 text
Orange-20 text
Orange-25 text
Orange-30 text
Orange-35 text
Orange-40 text
Orange text
Orange-45 text
Orange-50 text
Orange-55 text
Orange-60 text
Orange-65 text
Orange-70 text
Orange-75 text
``` ### Yellow Two classes write yellow text: - `text--yellow`: the pure yellow. - `text--yellow-{step}`: a step on the yellow ladder (e.g. text--yellow-50), on the same 10 to 75 scale as grayscale. Aa yellow-10 Aa yellow-15 Aa yellow-20 Aa yellow-25 Aa yellow-30 Aa yellow-35 Aa yellow-40 Aa yellow Aa yellow-45 Aa yellow-50 Aa yellow-55 Aa yellow-60 Aa yellow-65 Aa yellow-70 Aa yellow-75 Aa yellow-10 Aa yellow-15 Aa yellow-20 Aa yellow-25 Aa yellow-30 Aa yellow-35 Aa yellow-40 Aa yellow Aa yellow-45 Aa yellow-50 Aa yellow-55 Aa yellow-60 Aa yellow-65 Aa yellow-70 Aa yellow-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextYellow ```
Yellow-10 text
Yellow-15 text
Yellow-20 text
Yellow-25 text
Yellow-30 text
Yellow-35 text
Yellow-40 text
Yellow text
Yellow-45 text
Yellow-50 text
Yellow-55 text
Yellow-60 text
Yellow-65 text
Yellow-70 text
Yellow-75 text
``` ### Lime Two classes write lime text: - `text--lime`: the pure lime. - `text--lime-{step}`: a step on the lime ladder (e.g. text--lime-50), on the same 10 to 75 scale as grayscale. Aa lime-10 Aa lime-15 Aa lime-20 Aa lime-25 Aa lime-30 Aa lime-35 Aa lime-40 Aa lime Aa lime-45 Aa lime-50 Aa lime-55 Aa lime-60 Aa lime-65 Aa lime-70 Aa lime-75 Aa lime-10 Aa lime-15 Aa lime-20 Aa lime-25 Aa lime-30 Aa lime-35 Aa lime-40 Aa lime Aa lime-45 Aa lime-50 Aa lime-55 Aa lime-60 Aa lime-65 Aa lime-70 Aa lime-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextLime ```
Lime-10 text
Lime-15 text
Lime-20 text
Lime-25 text
Lime-30 text
Lime-35 text
Lime-40 text
Lime text
Lime-45 text
Lime-50 text
Lime-55 text
Lime-60 text
Lime-65 text
Lime-70 text
Lime-75 text
``` ### Green Two classes write green text: - `text--green`: the pure green. - `text--green-{step}`: a step on the green ladder (e.g. text--green-50), on the same 10 to 75 scale as grayscale. Aa green-10 Aa green-15 Aa green-20 Aa green-25 Aa green-30 Aa green-35 Aa green-40 Aa green Aa green-45 Aa green-50 Aa green-55 Aa green-60 Aa green-65 Aa green-70 Aa green-75 Aa green-10 Aa green-15 Aa green-20 Aa green-25 Aa green-30 Aa green-35 Aa green-40 Aa green Aa green-45 Aa green-50 Aa green-55 Aa green-60 Aa green-65 Aa green-70 Aa green-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextGreen ```
Green-10 text
Green-15 text
Green-20 text
Green-25 text
Green-30 text
Green-35 text
Green-40 text
Green text
Green-45 text
Green-50 text
Green-55 text
Green-60 text
Green-65 text
Green-70 text
Green-75 text
``` ### Cyan Two classes write cyan text: - `text--cyan`: the pure cyan. - `text--cyan-{step}`: a step on the cyan ladder (e.g. text--cyan-50), on the same 10 to 75 scale as grayscale. Aa cyan-10 Aa cyan-15 Aa cyan-20 Aa cyan-25 Aa cyan-30 Aa cyan-35 Aa cyan-40 Aa cyan Aa cyan-45 Aa cyan-50 Aa cyan-55 Aa cyan-60 Aa cyan-65 Aa cyan-70 Aa cyan-75 Aa cyan-10 Aa cyan-15 Aa cyan-20 Aa cyan-25 Aa cyan-30 Aa cyan-35 Aa cyan-40 Aa cyan Aa cyan-45 Aa cyan-50 Aa cyan-55 Aa cyan-60 Aa cyan-65 Aa cyan-70 Aa cyan-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextCyan ```
Cyan-10 text
Cyan-15 text
Cyan-20 text
Cyan-25 text
Cyan-30 text
Cyan-35 text
Cyan-40 text
Cyan text
Cyan-45 text
Cyan-50 text
Cyan-55 text
Cyan-60 text
Cyan-65 text
Cyan-70 text
Cyan-75 text
``` ### Blue Two classes write blue text: - `text--blue`: the pure blue. - `text--blue-{step}`: a step on the blue ladder (e.g. text--blue-50), on the same 10 to 75 scale as grayscale. Aa blue-10 Aa blue-15 Aa blue-20 Aa blue-25 Aa blue-30 Aa blue-35 Aa blue-40 Aa blue Aa blue-45 Aa blue-50 Aa blue-55 Aa blue-60 Aa blue-65 Aa blue-70 Aa blue-75 Aa blue-10 Aa blue-15 Aa blue-20 Aa blue-25 Aa blue-30 Aa blue-35 Aa blue-40 Aa blue Aa blue-45 Aa blue-50 Aa blue-55 Aa blue-60 Aa blue-65 Aa blue-70 Aa blue-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextBlue ```
Blue-10 text
Blue-15 text
Blue-20 text
Blue-25 text
Blue-30 text
Blue-35 text
Blue-40 text
Blue text
Blue-45 text
Blue-50 text
Blue-55 text
Blue-60 text
Blue-65 text
Blue-70 text
Blue-75 text
``` ### Violet Two classes write violet text: - `text--violet`: the pure violet. - `text--violet-{step}`: a step on the violet ladder (e.g. text--violet-50), on the same 10 to 75 scale as grayscale. Aa violet-10 Aa violet-15 Aa violet-20 Aa violet-25 Aa violet-30 Aa violet-35 Aa violet-40 Aa violet Aa violet-45 Aa violet-50 Aa violet-55 Aa violet-60 Aa violet-65 Aa violet-70 Aa violet-75 Aa violet-10 Aa violet-15 Aa violet-20 Aa violet-25 Aa violet-30 Aa violet-35 Aa violet-40 Aa violet Aa violet-45 Aa violet-50 Aa violet-55 Aa violet-60 Aa violet-65 Aa violet-70 Aa violet-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextViolet ```
Violet-10 text
Violet-15 text
Violet-20 text
Violet-25 text
Violet-30 text
Violet-35 text
Violet-40 text
Violet text
Violet-45 text
Violet-50 text
Violet-55 text
Violet-60 text
Violet-65 text
Violet-70 text
Violet-75 text
``` ### Purple Two classes write purple text: - `text--purple`: the pure purple. - `text--purple-{step}`: a step on the purple ladder (e.g. text--purple-50), on the same 10 to 75 scale as grayscale. Aa purple-10 Aa purple-15 Aa purple-20 Aa purple-25 Aa purple-30 Aa purple-35 Aa purple-40 Aa purple Aa purple-45 Aa purple-50 Aa purple-55 Aa purple-60 Aa purple-65 Aa purple-70 Aa purple-75 Aa purple-10 Aa purple-15 Aa purple-20 Aa purple-25 Aa purple-30 Aa purple-35 Aa purple-40 Aa purple Aa purple-45 Aa purple-50 Aa purple-55 Aa purple-60 Aa purple-65 Aa purple-70 Aa purple-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextPurple ```
Purple-10 text
Purple-15 text
Purple-20 text
Purple-25 text
Purple-30 text
Purple-35 text
Purple-40 text
Purple text
Purple-45 text
Purple-50 text
Purple-55 text
Purple-60 text
Purple-65 text
Purple-70 text
Purple-75 text
``` ### Pink Two classes write pink text: - `text--pink`: the pure pink. - `text--pink-{step}`: a step on the pink ladder (e.g. text--pink-50), on the same 10 to 75 scale as grayscale. Aa pink-10 Aa pink-15 Aa pink-20 Aa pink-25 Aa pink-30 Aa pink-35 Aa pink-40 Aa pink Aa pink-45 Aa pink-50 Aa pink-55 Aa pink-60 Aa pink-65 Aa pink-70 Aa pink-75 Aa pink-10 Aa pink-15 Aa pink-20 Aa pink-25 Aa pink-30 Aa pink-35 Aa pink-40 Aa pink Aa pink-45 Aa pink-50 Aa pink-55 Aa pink-60 Aa pink-65 Aa pink-70 Aa pink-75 ![TRMNL Logo](/images/plugins/trmnl--render.svg)TextPink ```
Pink-10 text
Pink-15 text
Pink-20 text
Pink-25 text
Pink-30 text
Pink-35 text
Pink-40 text
Pink text
Pink-45 text
Pink-50 text
Pink-55 text
Pink-60 text
Pink-65 text
Pink-70 text
Pink-75 text
``` ### Backward Compatibility For backward compatibility, the original shade names (`gray-1` through `gray-7`) are still supported but deprecated. Each maps to one of the new shade names: ```
Gray 1 text (deprecated)
Gray 2 text (deprecated)
Gray 10 text (preferred)
Gray 20 text (preferred)
``` ### Responsive Features Text color classes support size, orientation, and bit-depth variants. Bit-depth prefixes like `1bit:`, `2bit:`, and `4bit:` set the shade for a specific device color depth. The demos above use them together, for example `1bit:text--black 2bit:text--gray-55 4bit:text--gray-60`. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different colors per device size class. Responsive color Gray 50 by default, gray 30 on md+ ![TRMNL Logo](/images/plugins/trmnl--render.svg)Text ColorResponsive ``` Responsive color Progressive color Responsive chromatic color ``` #### Orientation and Size+Orientation Text colors can adapt to orientation with `portrait:` and `landscape:`, and can be combined with size breakpoints (e.g., `md:portrait:`). ``` Orientation color variant Color on md+ portrait ``` ### Patterned Ink on a Filled Element On a screen with no true gray, a gray shade is painted as a pattern clipped to the letters. That pattern uses the element's one background, so a patterned text color and a `bg--{shade}` fill cannot sit on the same element. Put the fill on the box and the text color on an element inside it; black and white never need this, because they are real colors. ```
Mon 8/3
Mon 8/3
``` ### Related APIs #### Reading text paint from JavaScript The `text(token, { el })` resolver returns the exact paint a `text--{token}` utility would apply, as a canonical Fill read from the live cascade with bit depth, dark mode, and theme resolved. Apply it to canvases, SVGs, or chart options. See [Painting Colors](/framework/docs/3.4/paint_colors) for every resolver and the Fill shape. ``` var fill = TRMNLPaint.text("gray-45", { el: "my-node" }); ``` Previous [ ## Text Alignment Control text alignment with responsive breakpoint, orientation, and bit-depth variants ](/framework/docs/3.4/text_alignment) Next [ ## Text Stroke Legible text when displayed on shaded backgrounds ](/framework/docs/3.4/text_stroke) --- # Text Stroke Outline text so it stays legible on a shaded background. Set the stroke width and color with the text stroke utilities. ### Basic Usage Apply `text-stroke` to outline text. Combine with width and shade modifiers as needed. | Class | Description | | --- | --- | | `text-stroke` | Stroke: outline (default 3.5px, default contrast ink) | | `text-stroke--{size}` | Stroke width: `small`, `medium`, `large`, `xlarge` | | `text-stroke--{shade}` | Stroke color: any palette token, from `black` and `white` to `gray-75` and `red-40`. See [Background](/framework/docs/3.4/background) for the shade scale. | ``` Outlined text ``` ### Widths Preset size modifiers set the stroke width on text. The default stroke is 3.5px in the default contrast ink, with additional options for base (3.5px, equivalent to default), small (2px), medium (4.5px), large (6px), and extra large (7.5px). The `text-stroke--base` modifier explicitly sets the default stroke width and is useful for responsive layouts. AaNo Stroke AaSmall AaBase AaDefault AaMedium AaLarge AaExtra Large ![](/images/plugins/trmnl--render.svg)Text StrokePreset Sizes ``` Aa Aa Aa Aa Aa Aa Aa ``` ### Shades Leave the shade off to stroke with the default contrast ink, which resolves through the theme chain and flips with dark mode. Use the `text-stroke--{shade}` modifier to pin a color instead. The shades are the same palette tokens the background scale uses: - `text-stroke--black` and `text-stroke--white`. - `text-stroke--gray-10` through `text-stroke--gray-75`, in steps of five, plus the legacy `gray-1` to `gray-7` aliases. - Ten hues (red, orange, yellow, lime, green, cyan, blue, violet, purple, pink) as a bare name such as `text-stroke--red` and on the same 10 to 75 steps, so `text-stroke--red-40` works on a color panel. For the shade scale and how it adapts across bit depths, see [Background](/framework/docs/3.4/background) . A shade modifier and a width modifier combine: `text-stroke text-stroke--black text-stroke--small` is a 2px black outline. Both take the bit-depth prefixes, so `2bit:text-stroke--gray-30` restyles the outline on 2-bit screens only. A bare shade class draws nothing: it sets the color and leaves the drawing to `text-stroke` or a width modifier. [Image Stroke](/framework/docs/3.4/image_stroke) differs here, where a lone shade class draws the default ring. AaNo Stroke AaSmall AaBase AaDefault AaMedium AaLarge AaExtra Large ![](/images/plugins/trmnl--render.svg)Text StrokeShades ``` Aa Aa Aa Aa Aa Aa ``` ### How It Renders The stroke draws as concentric drop-shadow rings, not as native `-webkit-text-stroke`. The ring sits behind the glyph in every browser, so text of any shade takes a stroke. That includes grayscale text, which the framework paints as a bitmap pattern revealed with `background-clip: text`. Native strokes overpaint a clipped fill; rings do not, so `text--gray-40 text-stroke` keeps both the pattern and the outline. Before 3.2 the stroke was a native outline and worked only on pure black or white text. Screens pinned to 3.0 or 3.1 still render that way. Previous [ ## Text Color Apply grayscale and chromatic color shades to text elements ](/framework/docs/3.4/text_color) Next [ ## Framework Runtime The JavaScript pass that measures the screen and fits your content into it at render time ](/framework/docs/3.4/framework_runtime) --- # Framework Runtime 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. ### What It Does It measures the space a layout has and fits the content into it, so nothing needs manual tweaking. Each pass starts by reading the screen's size, orientation, bit depth, and scale, then runs the steps below. ### Runtime Steps When the runtime runs, it takes these steps in order. #### Images Waits for every image to settle, then recolors the adaptive ones for the current mode. - Holds the pass until images have loaded, so later steps measure real heights - Repaints `image--adaptive` sources for the current device and theme Go to [Image](/framework/docs/3.4/image) #### Index Widths Ensures item index badges render at even widths to avoid artifacts. - Runs once per runtime pass, for indices outside `.columns` - Runs again inside each `.columns` container after its layout commits - Skipped on 2-bit and higher, where any width it pinned is cleared Go to [Item](/framework/docs/3.4/item) #### Value Formatting Formats numbers to fit available space and abbreviates as needed (k, M, B). - Accepts `data-value-format="true"` or `data-value-type="number"` - Respects `data-value-locale` - Works with `data-fit-value` for auto-sizing Go to [Format Value](/framework/docs/3.4/format_value) #### Fit Value Adjusts font size, line-height, and weight to fit numbers within their containers. - Minimum font size safeguard (default 8px) - Accepts `data-fit-value` or `data-value-fit` Go to [Fit Value](/framework/docs/3.4/fit_value) #### Grid Gaps Tweaks CSS gaps so grid column widths resolve to integer pixels. - Disable with `data-adjust-grid-gaps="false"` - Falls back to measuring child positions when `gap` is not explicitly set Go to [Grid](/framework/docs/3.4/grid) #### Column Gaps Normalizes gaps between `.column` elements so column widths are integers. - Disable with `data-adjust-column-gaps="false"` - Runs as a pre-pass for non-overflow columns and a final pass after Overflow Go to [Columns](/framework/docs/3.4/columns) #### Overflow Tries 1 to N columns off screen, keeps the layout that fits best, then re-clamps text to each real column's width. - Duplicates group headers across columns when needed - Optional trailing "and N more" label for hidden items (enable with `data-overflow-counter="true"`) - Enforces final fit by hiding trailing items if necessary Go to [Overflow](/framework/docs/3.4/overflow) #### Clamp Clamps text to N lines. - Word-based ellipsis - Preserves original text - Re-clamps when widths change - Supports responsive data attributes (size/orientation) - Maps legacy class utilities to `data-clamp` - Applies outside and inside columns (per-column re-clamp handled by Overflow) Go to [Clamp](/framework/docs/3.4/clamp) #### Table Overflow Trims table rows that do not fit the space the table has. - Opt in with `data-table-limit="true"` on the table - Runs after Clamp, so it measures rows at their final line count Go to [Table Overflow](/framework/docs/3.4/table_overflow) #### Content Limiter Caps content at an explicit height budget, or at the space it measures as available, and flags small content. - Set the budget with `data-content-max-height`, otherwise the limiter measures the view's layout - Adds `content--small`, then sets `data-clamp` and `data-clamp-max-height-px` on the block it has to trim Go to [Content Limiter](/framework/docs/3.4/content_limiter) #### Pixel-Perfect Fonts Wraps lines in spans and pins them to even or odd pixel widths so they render crisp; scheduled in idle time. - Skipped on higher bit-depth modes - Respects centered alignment Go to [Pixel Perfect](/framework/docs/3.4/pixel_perfect) ### Driving the Runtime from JavaScript The runtime starts itself. It runs one pass after the page load event, then runs again whenever a `screen--*` class changes on a `.screen` element. Content injected after that needs an explicit re-run. #### Functions - `terminalize()`: runs the full pipeline and returns a Promise that resolves once the screen has settled, including the deferred pixel-perfect pass. Call it after you inject or replace content. - `executeTerminalize()`: queues a run two animation frames out instead of starting one immediately. Repeated calls before it fires collapse into a single pass. - `markFrameworkReady()`: sets `window.frameworkReady` and dispatches `trmnl:framework:ready` on `window`. A host page calls it once its own setup is done. #### Ready Signals - `window.TRMNL_PLUGINS_READY`: `false` while a pass runs and `true` once it settles. A screenshot service waits for `true` before it captures. - `window.frameworkReady`: `false` until `markFrameworkReady()` runs. - `window. __TRMNL_BUILD__ `: the build stamp of the loaded `plugins.js`. A released bundle reports its own version (`plugins.js v3.2.0`) and a working checkout reports `plugins.js source`. Read it when an edit does not show up and you suspect a pinned or cached file. #### The Stats Event Every pass dispatches `trmnl:terminalize:stats` on `window`. Its `detail` carries three fields. - `steps`: every step the pass ran, each with a `name`, a `durationMs`, and its own counters. - `engines` and `engineCount`: the subset of steps that changed something. - `errors`: present only when an engine threw, as `{ engine, message }` entries. The pass runs the remaining engines and readiness still flips to `true`. ``` // Re-run after injecting content into a screen. terminalize().then(function () { console.log("screen settled"); }); // Inspect what each pass did. window.addEventListener("trmnl:terminalize:stats", function (event) { console.log(event.detail.engineCount, "engines changed something"); }); ``` #### Debug Logging Set `window. __TRMNL_DEBUG__ = true` before a pass to log what each engine decided. Renders are quiet by default. Engine failures always reach the console, debug flag or not. `plugins.js` also puts `TRMNLPaint`, `TRMNLCharts` and `TRMNLMaps` on `window`. Those are documented on [Paint API](/framework/docs/3.4/paint_api) . A pass ends by waiting for every map `TRMNLMaps` has attached to go idle, bounded by `TRMNLMaps.settle()` (6000 ms by default), so `window.TRMNL_PLUGINS_READY` flips after the tiles, the widened lines and the labels have drawn. A host that rescales the screen after the pass, the way a screenshot service sets its capture pixel ratio last, calls `TRMNLMaps.refresh()` and awaits it before it captures. ### Why This Exists A plugin has to fit its data into a fixed space that changes with the device, orientation, and density. That sounds like responsive web design, but the runtime's tools (column planning, per-column clamping, pixel alignment, value fitting) are built for TRMNL screens specifically. ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | | `--device-ui-scale` | 1 | - | - | - | | `--full-h` | calc(var(--screen-h) - var(--gap) \* 2) | - | - | - | | `--full-w` | calc(var(--screen-w) - var(--gap) \* 2) | - | - | - | | `--gap-scale` | 1 | - | - | - | | `--half_horizontal-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--half_horizontal-w` | calc((var(--screen-w) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-h` | calc((var(--screen-h) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--modifier-scale` | 1 | - | - | - | | `--modifier-text-scale` | 1 | - | - | - | | `--quadrant-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--quadrant-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--screen-h` | 480px | - | - | - | | `--screen-h-original` | 480px | - | - | - | | `--screen-w` | 800px | - | - | - | | `--screen-w-original` | 800px | - | - | - | | `--text-ui-scale` | 1 | - | - | - | | `--ui-scale` | 1 | - | - | - | ### Related APIs #### The paint half of the runtime The same `plugins.js` that runs these engines also ships TRMNLPaint, the framework's public paint API. It reads the live cascade and returns canonical Fill, BorderFill, and TypeSpec objects, so a plugin can resolve framework colors from JavaScript while the engines handle layout. See [Paint API](/framework/docs/3.4/paint_api) . Previous [ ## Text Stroke Legible text when displayed on shaded backgrounds ](/framework/docs/3.4/text_stroke) Next [ ## Overflow Handle column items overflow ](/framework/docs/3.4/overflow) --- # Overflow The Overflow engine automatically lays out items into up to N columns and adds an "and X more" label when content exceeds the available height. It also applies text clamping per-column width and handles grouped headers without leaving orphaned headings. ### Basic Usage You can enable Overflow on any container (e.g., a flex or grid column) by adding `data-overflow="true"` to the container. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowGeneric Container ```
Title goes here Description goes here
``` | Attribute | Default | Description | | --- | --- | --- | | `data-overflow` | `false` | Enable Overflow on any container (non-`.columns`). Hides trailing items to fit the height budget. | | `data-overflow-max-height` | `auto` | Override height budget. Pixel value (e.g., `320`) or `auto` to inherit the parent's content height. | | `data-overflow-counter` | `false` | Show a trailing "and N more" label when items are hidden. | | `data-overflow-max-cols` | `unset` | Best-fit columns up to N on a `.columns` container. Optimizes for the most visible items. Responsive: `-sm`, `-md`, `-lg`, `-portrait`, `-md-portrait`, etc. | | `data-overflow-cols` | `unset` | Force exactly N columns on a `.columns` container. Same responsive variants as `data-overflow-max-cols`. | ### Smart Columns Place `.item`[Item](/framework/docs/3.4/item) elements inside a single `.column` element, that's nested inside a `.columns`[Columns](/framework/docs/3.4/columns) container. Set the exact or maximum number of columns with `data-overflow-cols` or `data-overflow-max-cols`. #### Number of columns Set `data-overflow-cols` to force an exact column count. Use this when you want a consistent column count regardless of how many items fit. The engine will still respect height constraints, but it will keep the requested number of columns even if that shows fewer items than a best-fit layout. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowExactly 3 Columns ```
``` #### Maximum number of columns Set `data-overflow-max-cols` to allow the engine to choose the best number of columns up to the maximum you specify. This best-fit mode prioritizes the most visible items. For example, allowing 3 columns may still render 2 columns if that shows more items due to less wrapping. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowMax 3 Columns ```
``` #### Responsive `data-overflow-max-cols` and `data-overflow-cols` support the same size and orientation modifiers as other framework components. Specificity (most specific first): size + orientation (e.g. `data-overflow-max-cols-md-portrait`), size (`-sm`, `-md`, `-lg`), orientation (`-portrait`), then base. ```
``` ### Height Budget Control how much vertical space Overflow can use by specifying `data-overflow-max-height`. When not specified, the height budget defaults to the parent's content height. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowHeight Budget (220px) ```
``` ### Overflow Counter Opt-in to display a trailing "and N more" label by adding `data-overflow-counter="true"` The engine will reserve space for the label during planning so it fits without pushing items out of bounds. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowCounter Enabled ```
``` ### Clamp-aware Overflow The Clamp engine [Clamp](/framework/docs/3.4/clamp) re-clamps text for the actual column width. Apply `data-clamp` on titles or descriptions as needed. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)Overflow + ClampTitle 1 • Description 3 • Max 2 Columns ```
``` ### Group Headers Mark headers with `group-header`. The engine avoids orphaned headers and duplicates a header at the start of the next column when a group spills over. Today 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated Tomorrow 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness This Week 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)Group HeadersSmart Distribution ```
Today Tomorrow This Week
``` - Headers are never left as the last visible element in a column. - When a group spills, a gray duplicate header is added to the next column for context. ### Harmonious Group Columns When the number of group headers equals the maximum columns and there's enough space, the engine places each group in its own column for a more harmonious layout. Today 1 Morning Meeting at ReceptionTeam sync and birthday signings 9:00 AM - 9:30 AMDaily 2 Printer Jam Review with DwightCheck for bear-related bugs 10:30 AM - 11:30 AMReview Tomorrow 1 Beach Games PlanningPlan next two weeks 10:00 AM - 12:00 PMPlanning 2 Client Demo: WUPHFShow new 'features' of shouting 2:00 PM - 3:00 PMDemo 3 Sabre Triangle Tablet BrainstormDiscuss shape-based confidence 4:00 PM - 5:00 PMTechnical-ish This Week 1 Security Walkthrough with IT GuyQuarterly review WednesdaySecurity 2 Team Retro: Goodbye, TobySprint reflection (but no sprints) FridayRetrospective ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowHarmonious Groups 3 Columns (2/3/2) ```
Today Tomorrow This Week
``` ### With dividers When the Overflow engine hides items to fit the height or column budget, [Divider](/framework/docs/3.4/divider) elements between items are hidden when either adjacent item is hidden. That way you never see an orphaned divider: a divider is only shown when both the item above and the item below are visible. Place `.divider` between `.item`[Item](/framework/docs/3.4/item) elements in your overflow container; the engine handles visibility automatically. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowWith dividers ```
...
...
...
``` ### Backwards compatibility The Overflow engine [Overflow](/framework/docs/3.4/overflow) remains compatible with legacy list attributes used in older plugins. When these attributes are present on a descendant `.column` or `.list`, the engine will promote them onto the enclosing `.columns`[Columns](/framework/docs/3.4/columns) container at runtime. #### Supported legacy attributes - `data-list-limit="true"`: opts the list into Overflow. If the enclosing `.columns` lacks `data-overflow-max-cols`, it will default to `1`. - `data-list-max-columns`: sets the maximum column count. This attribute takes a single value. For a column count that changes with screen size or orientation, use `data-overflow-max-cols-{size}` on the `.columns` container (see Responsive above). - `data-list-max-height`: sets the height budget for the columns. Use a pixel value (e.g., `340`) or `auto` to inherit from the parent container. - `data-list-hidden-count`: older toggle for the trailing label; still honored when present. - Divider visibility when items are hidden: see the With dividers section above. #### Legacy example (still works) 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority 5 Server Closet Check & 'Kevin’s Famous' Recovery DrillOpen the mystery door, wiggle the beeping thing, and attempt a restoration without spilling anything. Update instructions: 'bring oven mitts.' 6:00 PM - 7:00 PMAutomated 6 Security Walkthrough & 'Frame Toby' Boundary ReviewMonthly pass including badge checks, warehouse notes, and a plan that does not involve planting fake drugs in HR. Angela brings a clipboard and a stare. 10:00 AM - 12:00 PMCritical 7 Quarterly Reviews & Growth Plan: The 'Assistant to the' LadderEvaluate performance using a rubric Jim swears isn’t a prank. Clarify that 'Assistant to the Regional Manager' is technically growth if you squint. 1:00 PM - 2:30 PMScheduled 8 Sabre Printer Jam Night (Bring Marshmallows)Apply latest stickers, test that smell of burning goes down, and keep watch while Michael brainstorms catchphrases for 'a printer that catches fire less.' 11:00 PM - 12:00 AMMaintenance 9 Onboarding Workshop: The Dundies for New HiresWelcome session covering desk decor, how to ignore Dwight’s evacuation drills, and what a Dundie is. Hands-on lab: unjamming the copier while smiling. 9:30 AM - 11:30 AMRequired 10 Budget Planning & 'Surplus' Allocation MeetingQ4 planning where Oscar explains the surplus gently, Michael hears 'new chairs,' and Recyclops is briefly appointed Treasurer of Petty Cash. 3:00 PM - 5:00 PMPlanning 11 Vendor Check-In & Utica DétenteQuarterly vendor review covering deliveries, pricing, and whether Karen will accept a peace offering in the form of soft pretzels. 2:00 PM - 3:00 PMBusiness 12 Bulletin Board Refresh & 'Fun Run' PostmortemUpdate flyers, retire 'Run for Rabies' glitter, and pin a cautionary note on carbo-loading before 5Ks. Remove anything last edited by 'William M. Buttlicker.' 4:00 PM - 5:30 PMDocumentation 13 Office Health Check & Beet Farm Capacity PlanCheck plants, temperature wars, and snack drawer diplomacy. Forecast beet yields just in case we pivot to agriculture. 8:00 AM - 9:00 AMMonitoring 14 Coffee Chat & 'Pretzel Day' HypeInformal bonding to share wins, learnings, and toppings. Scheduling anything on Pretzel Day is punishable by Stanley’s glare. 10:15 AM - 10:45 AMSocial 15 Vance Refrigeration Contract Walk-ThroughGo over terms with Phyllis and Bob. Everyone nods, someone says 'classy,' and the thermostat mysteriously gets colder. 11:00 AM - 12:30 PMBusiness 16 Suggestion Box ArchaeologyAnalyze themes, rank 'more jazz' vs 'less jazz,' and translate 'Stop stealing my pens (looking at you, Jim)' into action items. 1:30 PM - 2:30 PMResearch 17 Beach Games Capacity AlignmentPlan teams, veto sumo suits, and identify who has 'coal-walk energy.' Crown no one via hot-dog-eating contest this time. 3:00 PM - 4:00 PMPlanning 18 Michael Scott Paper Company Comeback RitualsCodify traditions like celebratory pancakes, van air freshener procurement, and how many cheese puffs is 'a business expense.' 4:30 PM - 6:00 PMCeremony 19 Learning Session: 'Scott’s Tots' (What Not To Promise)A cautionary tale about promises, tuition, and the importance of reading fine print. Bring tissues and a respectful silence. 5:00 PM - 6:00 PMEducation 20 Branch Wars: Lessons LearnedReflect on what went well (matching bandanas), what didn’t (kidnapping a copier), and pledge fewer disguises on weekdays. 6:00 PM - 7:00 PMReview 21 Parkour Safety CommitteeEstablish rules: no vaulting over reception, no roof-to-truck, and absolutely no yelling 'PARKOUR!' near coffee cups. 7:30 PM - 8:30 PMSafety 22 Monthly 'WUPHF' Etiquette & Usage ReportsReview how many people were pinged on fax, pager, and pigeon. Identify experiments that use fewer exclamation points. 9:00 PM - 10:00 PMAnalytics-ish 23 Emergency Response & Fire Drill Protocol (Real, Not Dwight)Runbook review for real incidents including paging, escalation, and humane door-unlocking. Cat remains on the ground. 12:00 AM - 12:00 AMEmergency 24 Quality Check: 'Product Recall' EditionFocus on critical paths, error stickers, and watermarking so the kids don’t prank us back. Apologies printed only last resort. 4:00 PM - 5:30 PMConfirmed 25 Calendar Review & 'Casino Night' PlanningAlign dates, identify risks, and pretend this is not the most dramatic meeting since 'Ryan started a fire.' 10:00 AM - 11:30 AMAlignment 26 Incident Postmortem: 'The Injury' Prevention PlanWalk through the timeline, document contributing factors such as foreheads meeting George Foreman grills, assign owners for fixes. 1:00 PM - 2:00 PMPostmortem 27 Cleanup Session & 'Prison Mike' Speech PracticeTarget messy drawers, reduce chaos, add labels, and rehearse the part about dementors so it lands but HR doesn’t call. 2:30 PM - 4:00 PMTidy 28 ‘Booze Cruise’ Dry Run & Do-Not-Rock-the-BoatSimulate a trip, practice speeches only when the boat is actually moving, and identify the life jacket with Michael’s name. 7:00 PM - 8:00 PMDR Drill 29 New Copier Button Rollout & 'Subtle(?)' Signage PlanDefine phased enablement, teach Michael that a toggle is not a clapper, and pick fonts that don’t start an argument. 11:00 AM - 12:00 PMRollout 30 Benihana Team Dinner Logistics & The Two-Tables ProblemCoordinate seating so we know who is who this time. Confirm orders, identify the correct server, and practice gentle nodding. 3:00 PM - 4:30 PMLogistics 31 Privacy Review & 'Email Surveillance' BoundariesAudit peeking habits, update 'no snooping' posters, and remind the team that surveillance episodes belong on DVDs. 9:00 AM - 10:00 AMCompliance 32 Hiring Panel & 'Chair Model' CalibrationRun interviews, agree that singing to a photo is not a necessary skill, and choose someone who understands copy paper. 12:30 PM - 3:00 PMHiring 33 Community AMA & 'Ask Me Anything Except Sales Numbers'Live Q&A with fans and power users about direction, upcoming bits, and whether Creed actually works here. He does. We think. 5:00 PM - 6:00 PMCommunity 34 Finance Reconciliation & 'Boom Roasted' ReportMonthly tally of invoices, payouts, refunds, and unexplained charges marked 'wolf dot com.' Oscar translates all of it. 4:30 PM - 6:00 PMFinance ![TRMNL Logo](/images/plugins/trmnl--render.svg)OverflowLegacy attributes ```
``` #### Notes - `data-overflow-cols` (fixed count) takes precedence over `data-overflow-max-cols` (best-fit up to N) when both are present. - When only `data-list-limit` is present, the engine defaults to 1 column. - By default, the trailing hidden count label is not shown. Enable with `data-overflow-counter="true"`. Legacy `data-list-hidden-count` remains supported. Previous [ ## Framework Runtime The JavaScript pass that measures the screen and fits your content into it at render time ](/framework/docs/3.4/framework_runtime) Next [ ## Table Overflow Handle table rows overflow ](/framework/docs/3.4/table_overflow) --- # Table Overflow When a table has more rows than can fit within the available vertical space, it constrains its height and appends a trailing "and X more" row to indicate the hidden entries. ### Basic usage To enable table overflow handling, add `data-table-limit="true"` to your ``[Table](/framework/docs/3.4/table) . Control the maximum height with `data-table-max-height` using a pixel value (for example, `240`) or set `auto` to inherit from the parent container. When rows exceed the available height, a trailing "and X more" row is added automatically. | Attribute | Default | Description | | --- | --- | --- | | `data-table-limit="true"` | - | Enable overflow handling and trailing "and X more" row | | `data-table-max-height` | auto | Maximum table height (px). Use `auto` to inherit from parent | | `data-table-overflow-counter` | true | Set to `false` to hide the "and X more" counter row while still clipping overflow rows | ```
...
``` ### Table Overflow A regular-sized table demonstrating the overflow behavior within a fixed height budget. | | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | --- | | 1 | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | 2 | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | 3 | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | 4 | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | 5 | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | 6 | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | 7 | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | 8 | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | 9 | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | 10 | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | 11 | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | 12 | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | 13 | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | 14 | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | 15 | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | 16 | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | 17 | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | 18 | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | 19 | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | 20 | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | 21 | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | 22 | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | 23 | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | 24 | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | 25 | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | 26 | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | 27 | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | 28 | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | 29 | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | 30 | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)Table OverflowRegular • Auto height ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Small table overflow When a table uses `table--small` or `table--xsmall`, the "and X more" counter row automatically scales to match using a smaller label style. | | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | --- | | 1 | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | 2 | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | 3 | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | 4 | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | 5 | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | 6 | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | 7 | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | 8 | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | 9 | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | 10 | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | 11 | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | 12 | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | 13 | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | 14 | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | 15 | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | 16 | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | 17 | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | 18 | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | 19 | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | 20 | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | 21 | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | 22 | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | 23 | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | 24 | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | 25 | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | 26 | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | 27 | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | 28 | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | 29 | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | 30 | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)Table OverflowSmall • Auto height ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Hidden counter Use `data-table-overflow-counter="false"` to clip the table at its height budget without showing the "and X more" row. The legacy `table-overflow-counter` attribute still works for existing plugins, but the `data-` prefixed form is valid HTML and preferred. | | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | --- | | 1 | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | 2 | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | 3 | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | 4 | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | 5 | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | 6 | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | 7 | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | 8 | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | 9 | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | 10 | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | 11 | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | 12 | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | 13 | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | 14 | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | 15 | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | 16 | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | 17 | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | 18 | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | 19 | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | 20 | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | 21 | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | 22 | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | 23 | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | 24 | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | 25 | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | 26 | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | 27 | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | 28 | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | 29 | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | 30 | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)Table OverflowRegular • Counter hidden ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--table-tbody-height` | calc(46px \* var(--ui-scale)) | - | - | - | | `--table-thead-height` | calc(36px \* var(--ui-scale)) | - | - | - | | Xsmall | | `--table-xsmall-tbody-height` | calc(22px \* var(--ui-scale)) | - | - | - | | `--table-xsmall-thead-height` | calc(18px \* var(--ui-scale)) | - | - | - | | Small | | `--table-small-tbody-height` | calc(31px \* var(--ui-scale)) | - | - | - | | `--table-small-thead-height` | calc(24px \* var(--ui-scale)) | - | - | - | | Large | | `--table-large-tbody-height` | calc(56px \* var(--ui-scale)) | - | - | - | | `--table-large-thead-height` | calc(44px \* var(--ui-scale)) | - | - | - | | Xlarge | | `--table-xlarge-tbody-height` | calc(72px \* var(--ui-scale)) | - | - | - | | `--table-xlarge-thead-height` | calc(56px \* var(--ui-scale)) | - | - | - | Previous [ ## Overflow Handle column items overflow ](/framework/docs/3.4/overflow) Next [ ## Clamp Manage text overflow with single and multi-line truncation ](/framework/docs/3.4/clamp) --- # Clamp The Clamp engine truncates text to a specified number of lines using word-based ellipsis. It preserves the original text, measures available width, and re-applies clamping whenever layouts change. ### Basic usage Add `data-clamp="N"` to any text element to clamp it to N lines. The engine preserves the original text and re-applies clamping when layouts change. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority ![TRMNL Logo](/images/plugins/trmnl--render.svg)ClampStandalone: Descriptions 1 to 4 lines ``` Example description text that will be clamped to two lines ``` ### Responsive Use the same size and orientation modifiers as other framework components. Specificity (most specific first): size + orientation (e.g. `data-clamp-md-portrait`), size only (`data-clamp-sm`, `-md`, `-lg`), orientation only (`data-clamp-portrait`), then base `data-clamp`. ``` Clamp to 2 lines by default, 4 on medium+ screens, 1 in portrait ``` ### Backward Compatibility Legacy class tokens are supported and mapped to the Clamp engine automatically: `clamp--none` disables clamping, and `clamp--1` through `clamp--50` request N lines. 1 Scranton Strategy Alignment & Cross-Department Fire Drill PostmortemWeekly alignment where Michael brings a whiteboard titled 'Vision Board' and Dwight brings a megaphone. We cover priorities (sell paper), blockers (also sell paper), and stakeholder comms (Jan keeps emailing 'no'). Includes a risk roundtable about candles near plasma TVs. 9:00 AM - 10:00 AMConfirmed 2 Client Presentation: 'Threat Level Midnight' Business CutMarquee client session featuring tasteful charts by Pam, heartfelt narration by Agent Michael Scarn, and exactly one confetti pop. Feedback collected, dignity mostly preserved. 2:00 PM - 3:30 PMTentative 3 Café Disco Launch & Dundies Seating ChartTurn in every form, tape down every extension cord, and finalize seating so no one sits near the speaker labelled 'BASS.' Creed volunteers to be DJ and then vanishes. 11:59 PMImportant 4 Complaint Box Sorting & Schrute Compliance SweepSort grievances ('too much jazz,' 'too little jazz'), verify stapler locations, and initial the 'identity theft = not a joke' acknowledgement sheet. 3:30 PM - 4:30 PMHigh Priority ![TRMNL Logo](/images/plugins/trmnl--render.svg)Clamp (Legacy)Descriptions: clamp--1, clamp--2, clamp--3, clamp--none ``` Example description with legacy class clamped to two lines of text ``` Previous [ ## Table Overflow Handle table rows overflow ](/framework/docs/3.4/table_overflow) Next [ ## Format Value Format numbers and values with consistent styling ](/framework/docs/3.4/format_value) --- # Format Value Format numbers so they fit their container and stay readable. Abbreviations (K, M, B), precision that adjusts to the space, and currency values with the symbol in the right place. ### Basic Usage To enable automatic value formatting, add the `data-value-format="true"` attribute to your element. `data-value-type="number"` is an accepted alias for the same opt-in, and only the exact value `number` opts in. The runtime selects both attributes in one pass, so an element carrying either one is formatted the same way and honors the same companion attributes. The examples on this page use `data-value-format`. 2345678XLarge 2345678Regular 2345678Small 456789XLarge 456789Regular 456789Small 34562XLarge 34562Regular 34562Small ![TRMNL Logo](/images/plugins/trmnl--render.svg)Value FormattingSize Comparison ``` 2345678 456789 34562 ``` To add a delimiter to large numbers, for example 1234 =\> 1,234, see [custom filters](https://intercom.help/trmnl/en/articles/10347358-custom-plugin-filters). ### Currency Values Values with currency symbols are automatically formatted while maintaining the symbol placement. $2345678XLarge $2345678Regular $2345678Small $456789XLarge $456789Regular $456789Small $34562XLarge $34562Regular $34562Small ![TRMNL Logo](/images/plugins/trmnl--render.svg)Value FormattingCurrency Example ``` $2345678 $456789 $34562 ``` To add a currency symbol, for example 1234 =\> $1,234, see [custom filters](https://intercom.help/trmnl/en/articles/10347358-custom-plugin-filters). Supported currency symbols include: `$`US Dollar `€`Euro `£`British Pound `¥`Japanese Yen / Chinese Yuan `₴`Ukrainian Hryvnia `₹`Indian Rupee `₪`Israeli Shekel `₩`Korean Won `₫`Vietnamese Dong `₱`Philippine Peso `₽`Russian Ruble `₿`Bitcoin ### Regional Number Formats Numbers can be formatted according to different regional standards using the `data-value-locale` attribute. $123456.78United States (en-US) $123456.78United States (en-US) €123456.78German (de-DE) €123456.78German (de-DE) €123456.78French (fr-FR) €123456.78French (fr-FR) ![TRMNL Logo](/images/plugins/trmnl--render.svg)Value FormattingRegional Formats ``` $123456.78 €123456.78 €123456.78 ``` Common locale options include: `en-US`United States (123,456.78) `de-DE`German (123.456,78) `fr-FR`French (123 456,78) `en-GB`British English (123,456.78) `ja-JP`Japanese (123,456.78) If no locale is specified, numbers will be formatted using US format (en-US) by default. Previous [ ## Clamp Manage text overflow with single and multi-line truncation ](/framework/docs/3.4/clamp) Next [ ## Fit Value Automatically resize numbers and values to fit within their containers ](/framework/docs/3.4/fit_value) --- # Fit Value Fit text to its container by adjusting font size, weight, and line height. Use it where the space available changes between devices, orientations, or view sizes. ### Basic Usage To enable automatic text resizing, add the `data-value-fit="true"` attribute to your element. The system adjusts the element's font size and weight to fit the content on a single line within the container width, for both numeric and text values. For multi-line text fitting, see the `data-value-fit-max-height` option below. $1,000 Dundies Budget $1,000,000 Scott's Tots $1,000,000,000 Sabre Revenue ![TRMNL Logo](/images/plugins/trmnl--render.svg)Fit ValueDunder Mifflin ``` $1,000 $1,000,000 $1,000,000,000 ``` ### Single-line Text Fitting Text values are automatically fitted to a single line when using `data-value-fit="true"` without specifying a max height. The system derives the single-line height from the element's computed line-height, then shrinks the font size and weight until the text fits on one line within the container. Assistant to the Regional Manager Dwight's Title Threat Level Midnight Michael's Film World's Best Boss Michael's Mug ![TRMNL Logo](/images/plugins/trmnl--render.svg)Fit ValueDunder Mifflin ``` Assistant to the Regional Manager Threat Level Midnight World's Best Boss ``` ### Multi-line Text Fitting To allow text to wrap across multiple lines while still fitting within a constrained area, specify a maximum height using the `data-value-fit-max-height` attribute (in pixels). This overrides the default single-line behavior, allowing the text to wrap while ensuring it stays within both width and height constraints through automatic font size and weight adjustments. Would I rather be feared or loved? Easy. Both. I want people to be afraid of how much they love me. Michael Scott Identity theft is not a joke, Jim! Millions of families suffer every year. Dwight Schrute That's what she said. Michael Scott ![TRMNL Logo](/images/plugins/trmnl--render.svg)Fit ValueDunder Mifflin ``` Would I rather be feared or loved? Easy. Both. I want people to be afraid of how much they love me. Identity theft is not a joke, Jim! Millions of families suffer every year. That's what she said. ``` Previous [ ## Format Value Format numbers and values with consistent styling ](/framework/docs/3.4/format_value) Next [ ## Content Limiter Change font size when content overflows to fit within the container ](/framework/docs/3.4/content_limiter) --- # Content Limiter Content areas are capped in height by the view type they sit in. Past that threshold the limiter steps typography down and the Clamp engine truncates the first block that still overflows. ### Basic usage To enable automatic content limiting, add the `data-content-limiter="true"` attribute to your content element. Dinner Party Michael finally manages to trick Jim and Pam into coming over to his condo for a couples’ dinner with him and Jan. He has been begging them for ages, and they finally run out of excuses. The first thing Michael shows off is his pride and joy: a plasma TV mounted on the living room wall. The problem is that it is laughably tiny, barely larger than a computer monitor. Michael beams with pride as he demonstrates how it can “push right back against the wall” to save space, while Jim and Pam exchange polite smiles that barely conceal their disbelief. Things take a sharper turn when Jan puts on a CD. The music is recorded by her former assistant, Hunter, and the lyrics make it sound like the two of them were more than just colleagues. Jan sways to the music with a dreamy smile, while Michael tries to ignore the implication. Jim and Pam sit frozen, realizing they have front-row seats to a relationship meltdown. Dinner itself is no relief. Jan’s cooking is nowhere near ready, so the group is stuck nibbling on appetizers for what feels like hours. When the food does arrive, Jan scolds Michael for trying to eat early, and their bickering turns openly hostile. Andy and Angela, the other guests, sit uncomfortably as the couple jabs at each other across the table. Every sarcastic comment cuts deeper, and the laughter that should fill a dinner party never comes. Michael, in a desperate attempt to lighten the mood, suggests games. They try charades, but even that devolves into more fighting. Jan mocks Michael’s answers, Michael whines back, and suddenly it feels less like a game and more like another round of public humiliation. The tiny condo seems to shrink with every cutting remark. It all finally explodes when Jan accuses Michael of being childish and Michael lashes out in return. In her fury, Jan grabs one of his beloved Dundie trophies and hurls it at the plasma TV, shattering it. For Michael, this little TV was his greatest treasure, and now it lies in pieces on the floor. The room falls into stunned silence as everyone realizes the night has gone completely off the rails. The guests slowly make their exit while Jan and Michael continue to argue in the background. Jim and Pam are relieved just to escape with their sanity intact. What started as a simple dinner party turned into one of the most uncomfortable nights anyone could imagine. For viewers, it is both painful and hilarious, a perfect storm of Michael’s desperate need to impress and Jan’s seething resentment. And right at the center of it all, that ridiculous little plasma TV never stood a chance. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Content LimiterText Exceeding Threshold World’s Best Boss Michael sits proudly at his desk, sipping from his 'World’s Best Boss' mug - a mug he bought for himself at Spencer’s Gifts. He points it out to the documentary crew, insisting that it’s not just a mug, but an irrefutable piece of evidence of his leadership skills. The cameras meet the Scranton branch: Jim, casually wry; Dwight, rigid and overzealous; Pam, quietly patient at reception; and Ryan, the new temp, still figuring out the terrain. Michael parades the crew through introductions, performing for them as much as managing his team. News of possible downsizing blows in with Jan from corporate. Michael tries to project calm and control, but he dodges straight answers and leans on bad jokes, more concerned with optics than clarity. The unease trickles through the bullpen. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Content LimiterText Within Threshold ```

Contrary to what one might think, the Lorem ipsum text, despite being meaningless, has noble...

``` When content exceeds the height threshold, the limiter adds the `content--small` class and automatically truncates the first overflowing block using the Clamp Engine [Clamp](/framework/docs/3.4/clamp) so it fits the remaining space. Subsequent blocks are hidden. ### Custom Height Threshold You can specify a custom maximum height using the `data-content-max-height` attribute. George Foreman Grill Accident Michael explains to the office that every morning he wakes up to the smell of sizzling bacon. His method is both elaborate and ill-advised: he sets a George Foreman Grill at the foot of his bed the night before, lays out strips of bacon, and switches it on so he can rise to the aroma like a king. He frames it as self-care; everyone else hears 'fire hazard.' One morning, the fantasy meets physics. Half-asleep, he swings his legs out of bed and plants his bare foot directly onto the hot metal. There is a hiss, a yelp, and a chaotic dance around the bedroom as he tries to untangle himself from the cord. By the time he calls in, the drama has grown from 'burn' to 'catastrophic workplace injury.' He arrives at the office limping with exaggerated gravitas, demanding sympathy, rides, and special parking, and comparing his situation to permanent disability. He asks HR about accommodations, requests that meetings be moved closer to his desk, and insists that no one truly understands the daily challenges he now faces. Pam offers ice and a ride to the clinic, Jim suggests - deadpan - that perhaps bacon should not be cooked in bed, and Dwight prescribes a bizarre regimen of ointments and battlefield procedures. The staff cycle between concern and disbelief as Michael narrates the incident like an inspirational keynote about resilience. Throughout the day he milks the moment for attention, turning routine tasks into obstacles that require applause when completed. He stages slow, heroic walks through the bullpen, interrupts conversations to retell the story, and peppers in phrases like 'bravery' and 'adversity' as though he has survived a mountaineering accident. Prison Mike When the office staff complain that work feels like prison, Michael decides the only responsible thing to do is educate them - by transforming into a cautionary tale. He frames it as a necessary intervention, the kind of hard truth only a courageous leader can deliver, and you can see the excitement building as he prepares to debut his latest persona. He strides back in with a purple bandana tied tight, drops his voice into a cartoonish growl, and declares himself 'Prison Mike.' He prowls the floor between desks, demanding attention like a substitute teacher who has watched one too many crime dramas, punctuating every other sentence with dramatic pauses and finger-pointing as if he’s narrating a documentary only he can see. What follows is a torrent of wildly inconsistent 'facts' about prison life. Michael talks about hardened criminals and gangs, then veers into a menu of gruel, gruel sandwiches, and gruel omelets, insisting that dessert is 'sometimes more gruel' delivered by 'mean guards' who hate birthdays. The contradictions pile up with every step as he tries to sell a world he clearly only knows from pop culture and half-remembered movie trailers. The infamous moment arrives when he proclaims that the very worst part of prison was 'the Dementors' - a dead giveaway that his knowledge is borrowed from Harry Potter. The room goes silent. Eyes shift. Someone smirks, someone coughs, and even Michael seems to realize he’s said something unfixable, yet he barrels ahead as though this were privileged information from a maximum-security wizarding wing. Undeterred, Michael adds threats and warnings that sound like Mad Libs tough-guy talk: no birthday cake, no daylight, cement pillows, and constant danger. He paints the air with big, frightening shapes, then pivots into a lecture about gratitude for fluorescent lights, ergonomic chairs, and the bounty of the vending machine, as if Snickers bars and swivel bases are society’s thin line against chaos. Jan and Toby try to intervene from the sidelines, steering him toward something resembling a real HR conversation, but Michael only doubles down. He declares this a 'teachable moment,' commands silence with a raised hand, and instructs everyone to thank him for his service as an educator, as though applause could retroactively turn improv into policy. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Content LimiterCustom Max Height: 140px ```

Contrary to what one might think, the Lorem ipsum text, despite being meaningless, has noble...

Contrary to what one might think, the Lorem ipsum text, despite being meaningless, has noble...

``` ### Mashup Example This demonstrates content limiting within a multi-view mashup. CPR Training After a scare that puts office safety under the microscope, a CPR instructor arrives with a plastic mannequin and a stack of laminated handouts. The room is supposed to be quiet and focused; it never is, because Michael treats 'training' like a stage and 'protocol' like a suggestion. Within minutes, Michael hijacks the lesson with questions that are neither helpful nor on topic. He is equal parts class clown and self-appointed co-instructor, correcting the professional with confidence born of zero expertise, leaning on phrases like 'best practices' and 'synergy' as though corporate jargon could resuscitate a heartbeat. When the instructor suggests keeping rhythm to the Bee Gees’ 'Stayin’ Alive,' Michael hears an invitation to perform. He claps, sings too loudly, and turns compressions into choreography. Andy, never one to resist a harmony, joins in until the room resembles a karaoke night held in a first-aid class, with Kevin attempting a bass line and Phyllis swaying like it’s a slow dance. Dwight, determined to demonstrate 'real' preparedness, starts issuing commands and measuring breaths with militaristic seriousness. His eagerness to escalate quickly outpaces his understanding of what’s appropriate in any setting, let alone a medical one, and he begins inventing scenarios that require handcuffs and a field promotion. The instructor tries to regain control, but the demonstration has become a Michael Scott production. He monologues about leadership, teamwork, and the importance of morale, somehow managing to miss every actual learning objective while drawing a flowchart that labels 'CPR' as 'Celebrate Positive Resilience.' Then comes the unforgettable turn: Dwight produces a knife, slices the face from the mannequin, and wears it like a mask. The room recoils in a synchronized gasp as the instructor’s expression travels from confusion to horror, and Michael declares, inexplicably, that this is 'advanced tactics' they will not be tested on. By the end, no one can say they learned CPR, though everyone can keep time to 'Stayin’ Alive' and will forever remember what not to do with training equipment. The chaos is so complete it loops back into comedy, a memory that will haunt the break room for months every time the song plays on the radio. I Declare Bankruptcy! As bills stack up and the numbers stop making sense, Michael does what he always does when adulthood becomes overwhelming - he looks for a grand gesture that will make the problem disappear. He imagines a clean slate delivered not by accountants or courts, but by the sheer force of a bold announcement. Oscar tries to help, carefully explaining what bankruptcy actually is: a legal process, forms, courts, a plan. Michael nods, absorbing none of it, because he has already decided on a solution that feels simpler and much more theatrical, the financial equivalent of cutting a ribbon and calling it a day. He walks into the bullpen, squares his shoulders like a man about to make history, clears his throat, and bellows at full volume: 'I DECLARE BANKRUPTCY!' The final word echoes against the ceiling tiles as if volume alone could reset his bank account, and he looks around expectantly, awaiting the bureaucratic magic he believes he has just triggered. Silence follows. A few heads pop up over monitors. Confused looks ripple through the room. Kevin wonders out loud if that’s actually how it works; Creed nods as though he’s tried it in three countries. Michael stands tall, waiting for the visible relief that never arrives - no confetti, no instant credit score bump, just awkward quiet and the hum of the copier. Oscar pulls him aside again, gently clarifying that bankruptcy is not an incantation. It is paperwork, not pageantry. Michael seems genuinely stunned, as if someone told him that wishing on a star requires a notary, and he repeats the word 'forms' like it’s a personal insult. Refusing to let the moment die, he pivots to half-baked fixes: whiteboard budgets with arrows and underlines, promises to 'tighten belts,' and a plan that mostly consists of other people making sacrifices. He proposes eliminating 'non-essential' expenditures that suspiciously exclude novelty mugs and a rotating snack budget, none of which addresses the actual math. Back in his office, he stares at bank statements with the intensity of a person trying to will the numbers into alignment. He whispers 'I declare bankruptcy' one more time under his breath, as if a quieter version might be legally binding, then practices saying it in a more official tone for the camera. Scott’s Tots Michael proudly struts into a high school classroom filled with expectant seniors, all of whom have been promised by him years earlier that he would pay for their college tuition. The moment is set up like a triumphant return; the students cheer and clap as Michael enters, and for a fleeting second, he basks in the illusion of being a hero. Their excitement is palpable, with handmade signs and chants of 'Thank you, Mr. Scott!' ringing in his ears. But underneath Michael’s forced smile lies sheer panic. As he begins his speech, his voice quivers ever so slightly, betraying his nerves. He tries to stretch out his introduction with jokes and awkward pauses, desperately searching for a way to soften the devastating truth he has to deliver. The students, however, hang on his every word, their faces glowing with hope and admiration for the man they believe has single-handedly secured their futures. When the truth finally comes out - that Michael does not, in fact, have the money to pay for their tuition - the atmosphere in the room collapses instantly. Gasps and groans replace the cheers, and disbelief spreads across the room like a wave. Michael attempts to salvage the moment by offering to pay for everyone’s laptop batteries, a pathetic gesture that only highlights the absurdity of his promise. The crushing disappointment on the students’ faces makes the scene almost unbearable to watch. The pain of the moment is amplified by how sincerely Michael believed in his original promise. Years earlier, he had genuinely thought he would become wealthy enough to follow through, blinded by his own optimism and detachment from reality. Now, forced to confront the impossibility of his pledge, he tries to laugh it off and hide behind humor, but the room is heavy with betrayal and crushed dreams. As the students press him with questions and accusations, Michael becomes visibly smaller, almost shrinking into himself. His usual bravado evaporates as he stammers and dodges eye contact. The power dynamic has shifted completely - no longer the adored benefactor, he is now the object of ridicule and anger. The laughter in this scene comes not from jokes, but from the unbearable awkwardness of Michael’s failed attempt to maintain dignity in an impossible situation. What makes 'Scott’s Tots' legendary is the raw, secondhand embarrassment it evokes. Viewers squirm in discomfort as Michael struggles, yet it is impossible to look away. It’s a perfect example of how The Office blends comedy and tragedy, creating a scene so painful that it loops back around into being hilarious. The genius lies in how Michael’s delusions of grandeur are shattered not with slapstick, but with the crushing weight of reality. By the time Michael leaves the classroom, he is utterly defeated, his reputation destroyed in front of dozens of hopeful young people. For the students, it’s the death of a dream. For Michael, it’s yet another reminder of how his desperate need to be loved and admired leads him into catastrophic decisions. And for the audience, it’s a masterclass in cringe comedy, one of the most excruciating yet unforgettable moments in television history. Previous [ ## Fit Value Automatically resize numbers and values to fit within their containers ](/framework/docs/3.4/fit_value) Next [ ## Pixel Perfect Ensure text renders with crisp edges by aligning to the pixel grid ](/framework/docs/3.4/pixel_perfect) --- # Pixel Perfect Pixel Perfect aligns text to the pixel grid so it renders with crisp edges. It uses pixel fonts designed for specific sizes, so text stays sharp instead of turning blurry or unevenly bold when a layout is converted to 1-bit for ePaper displays. ### Usage To enable pixel perfect text rendering, add the `data-pixel-perfect="true"` attribute to your text element. Pixel Perfect Title This text is rendered with pixel perfect alignment, ensuring that each line falls exactly on the pixel grid. Depending on the parent width, the system automatically breaks text into lines and adjusts each line's width. This produces crisp, sharp text edges that renders perfectly in a 1-bit color space. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Pixel PerfectWith Pixel Perfect Applied ``` Pixel Perfect Title

This text will be aligned to the pixel grid for crisp rendering.

``` ### Why? Text rendering on digital displays involves complex anti-aliasing techniques to make text appear smooth at various sizes. This process creates partially opaque pixels (gray pixels) at the edges of characters to create the illusion of smoothness. When text isn't perfectly aligned to the pixel grid, these anti-aliased pixels can appear inconsistently, particularly with centered text. This is especially problematic for ePaper displays that use a 1-bit color space (just black and white, no grays), where anti-aliased gray pixels are forced to become either fully black or fully white. The result is text that appears randomly bold or distorted in final renders, creating an unprofessional and difficult-to-read presentation. Our system uses pixel fonts that are specifically designed to work at particular pixel sizes and their multipliers. These fonts are meticulously crafted to perfectly align with the pixel grid, ensuring each character renders with maximum sharpness and clarity. By combining these specialized fonts with our pixel-perfect alignment technique, we achieve optimal text rendering for ePaper displays. ### How It Works The Pixel Perfect system works by applying the following techniques to elements with `data-pixel-perfect="true"`: - The system measures the parent element's width to determine whether it's odd or even - The text content is broken into individual lines - Each line is wrapped in a span element - Each span's width is adjusted to match the parent's pattern: even widths for even-width parents, odd widths for odd-width parents By analyzing the parent container's dimensions and adjusting each line accordingly, the system ensures text falls precisely on the pixel grid. This precise adjustment ensures text is perfectly aligned to the pixel grid, eliminating sub-pixel rendering issues on 1-bit displays. ### Cross-Platform Consistency Different browsers render text differently across operating systems. For example, Chrome on macOS handles font rendering differently than Chrome on Linux or Windows. This means developers see different results depending on their development environment. The Pixel Perfect system unifies the developer experience across platforms by enforcing consistent rendering rules regardless of the browser or operating system. This ensures that text renders with the same crispness and weight consistency on the final ePaper display, regardless of where it was developed or previewed. Previous [ ## Content Limiter Change font size when content overflows to fit within the container ](/framework/docs/3.4/content_limiter) Next [ ## Paint API TRMNLPaint: read the exact colors and patterns CSS paints right now, from JavaScript ](/framework/docs/3.4/paint_api) --- # Paint API TRMNLPaint is the framework's JavaScript API for its paint: colors, border lines, and text styles. Ask it for any framework color and it returns what CSS would actually paint right now, with the device and the active theme already applied. Use it wherever JavaScript draws: charts, maps, canvases, or your own rendering. ### The Paint Domains The functions are documented by what they paint. Every one takes an optional `{ el }`: the id or element to read the paint at, 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 Read background, text, stroke, and semantic colors as Fills, and paint them onto nodes. - `bg()`, `text()`, `stroke()` - `semantic()`, `textColor()` - `apply()` Go to [Painting Colors](/framework/docs/3.4/paint_colors) #### Painting Charts Colors for chart series, and the Highcharts fill adapter. - `series()`, `ramp()` - `toHighcharts()` Go to [Painting Charts](/framework/docs/3.4/paint_charts) #### Painting Maps Map slots for land, water, roads and labels, and the MapLibre GL JS paint adapter. - `slot()` - `toMapLibre()` Go to [Painting Maps](/framework/docs/3.4/paint_maps) #### Painting Borders Border and divider lines as BorderFills, for your own lines and Highcharts axes. - `border()`, `divider()` - `applyBorder()` - `toHighchartsAxis()`, `applyHighchartsAxisPaint()` Go to [Painting Borders](/framework/docs/3.4/paint_borders) #### Painting Typography Text styles and strokes as TypeSpecs, for your own text and chart labels. - `type()`, `strokeSpec()` - `applyType()` - `toHighchartsText()` Go to [Painting Typography](/framework/docs/3.4/paint_typography) ### How It Works All framework colors live in CSS, as custom properties on the `.screen` element; a theme is only CSS that changes them. `TRMNLPaint` reads those values, it never defines its own. To answer a query, TRMNLPaint adds a hidden element to the target, gives it the real utility class (`bg--`, `text--`, ...), and reads back what the browser computed. That is why everything is honored automatically: bit depth, inverse, themes, and limited palettes shape the answer the same way they shape the screen. No call ever throws: a missing `.screen` or an unknown token returns a Fill with null fields instead. Plugins render on a screenshot service, where a thrown error would mean a blank device screen. ### Scale Values Scale pixel numbers in JavaScript the way the framework scales its own. `TRMNLPaint` reads the same variables CSS uses, so device density, Scale, and Text Scale never drift apart. - `scale({ el })`: returns `{ name, device, modifier, ui, content, textName, textModifier, textUi }` for the target screen. The names identify the active Scale and Text Scale, device is the density factor, and the rest are the numeric multipliers they produce. - `px(value, { el, kind })`: scales one number or an array of numbers. It uses content scale by default; pass `kind: "ui"` for framework geometry or `kind: "text"` for framework typography. Use `px()` for numeric chart options, canvas dimensions, and other library configuration. Leave percentages, data values, and deliberate one-pixel lines unscaled. ``` 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 component sizes also include 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 function returns a `Fill`, a plain object that says what to paint with. A Fill with a `url` and `size` is a dither pattern; a Fill with only a `color` is a solid. Anything library-specific lives in the adapters, never in the Fill. ``` Fill = { color: string | null, // resolved rgb(...); the solid color, or the color under a pattern 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](/framework/docs/3.4/paint_borders) and `TypeSpec` on [Painting Typography](/framework/docs/3.4/paint_typography) . ### Painting and Reactivity - `apply(node, fill)`: paints a node's background from a Fill, layering the color under the pattern exactly as the framework itself does. - `watch(el, onChange, { immediate })`: runs `onChange` now (unless `immediate: false`) and again whenever the device, scale, mode, dark or theme classes change on the screen or on a wrapper above it. Returns a `stop()` function. - `screen(el)`: the nearest `.screen` for a target element, or the first screen on the page. `applyBorder()` and `applyType()` do the same for lines and text; both are documented on [Painting Borders](/framework/docs/3.4/paint_borders) and [Painting Typography](/framework/docs/3.4/paint_typography) . ### Extending a Theme from JavaScript A theme that wants to hand extra values to plugin code (a brand accent, say) sets its own `--*` variables on the screen, and JavaScript reads them back with `cssVar()`. Both sides read the same CSS. `cssVar(name, { el })` returns the computed value of any public `--*` variable on the screen. Read public var families only. The CSS minifier renames private variables in the released `plugins.min.css`; the readable `plugins.css` keeps the source names. `cssVar("--border-step-40-h-color")` answers in the dev build and returns an empty string in every release. See [CSS Variables](/framework/docs/3.4/variables_api) 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 [ ## Scale ](/framework/docs/3.4/scale)[ ## Text Scale ](/framework/docs/3.4/text_scale)[ ## Framework Runtime ](/framework/docs/3.4/framework_runtime)[ ## Tokens ](/framework/docs/3.4/tokens)[ ## Rendering Modes ](/framework/docs/3.4/rendering_modes) Previous [ ## Pixel Perfect Ensure text renders with crisp edges by aligning to the pixel grid ](/framework/docs/3.4/pixel_perfect) Next [ ## Painting Colors Read background, text, stroke, and semantic colors from JavaScript as Fill objects ](/framework/docs/3.4/paint_colors) --- # Painting Colors These functions tell you the exact color a CSS class paints right now: a background, a text color, a stroke, or a semantic color like error. Use them when something you draw in JavaScript has to match. ### Color and Token Fills Every function takes an optional `{ el }`: the id or element whose nearest `.screen` supplies the paint. Each returns a `Fill`; the shape and how the reading works are on [Paint API](/framework/docs/3.4/paint_api) . The token names match the [Colors](/framework/docs/3.4/colors) palette. - `bg(token, { el })`: a background token (`bg--`), e.g. `'black'`, `'gray-40'`, `'red-55'`, `'error'`. On solid modes you get a color-only Fill; on dither modes, a pattern Fill. - `text(token, { el })`: a text token (`text--`). Framework text is painted by clipping a background to the letters, so the Fill carries both the color and the image. - `stroke(token, { el })`: a stroke token (`text-stroke--`), returned as a concrete `rgb` in a color-only Fill. - `semantic(slot, { el })`: one of the framework's named color roles: `canvas`, `surface`, `text-primary`, `text-secondary`, `text-inverse`, `backdrop`, `border-strong`, `border-muted`, `fill-strong`, `fill-muted`, `fill-soft`, `stroke-contrast`, or `icon`. Patterns come through intact. - `textColor(token, { el })`: the single color of a named text utility (`'default'`, `'muted'`, ...), for SVG or canvas text. When the text is painted with a pattern, you get the pattern's ink, the color the letters actually read as. ``` // Resolve a token for the current screen mode/theme. var fill = TRMNLPaint.bg("red-55", { el: "my-chart" }); // => solid mode: { color: "rgb(204, 0, 0)", image: null, url: null, size: null } // => dither mode: { color: "rgb(255,255,255)", image: "url(...)", url: "data:...", size: 16 } // The default text color as one plain color, for SVG or canvas text. var textColor = TRMNLPaint.textColor("default", { el: "my-chart" }); ``` ### Painting Fills A resolved Fill is painted back with one call. Wrap the painting in `watch()` so it re-runs when the screen's mode, dark or theme classes change; see [Paint API](/framework/docs/3.4/paint_api) . - `apply(node, fill)`: paints a node's background from a Fill, layering the color under the pattern exactly as the framework itself does. ``` var swatch = document.getElementById("legend-swatch"); TRMNLPaint.watch(swatch, function () { // Solid on 4-bit+ panels, a composited dither tile on 1- and 2-bit screens. TRMNLPaint.apply(swatch, TRMNLPaint.bg("gray-30", { el: swatch })); }); ``` ### Where This Applies [ ## Background ](/framework/docs/3.4/background)[ ## Text Color ](/framework/docs/3.4/text_color)[ ## Colors ](/framework/docs/3.4/colors) Previous [ ## Paint API TRMNLPaint: read the exact colors and patterns CSS paints right now, from JavaScript ](/framework/docs/3.4/paint_api) Next [ ## Painting Charts Chart series colors for the current device, mode, and theme, with Highcharts adapters ](/framework/docs/3.4/paint_charts) --- # Painting Charts These functions pick the colors for a chart. Ask for series 2 of 5 and you get its paint, correct for the current device, mode, and theme. Adapters then turn each answer into the exact option Highcharts expects. ### Chart Series Every function takes an optional `{ el }`: the id or element whose nearest `.screen` supplies the paint. Each returns a `Fill`; see [Paint API](/framework/docs/3.4/paint_api) for the shape. - `series(i, count, { el })`: the paint for series _i_ of _count_. - `ramp({ el })`: every chart series color at once, as an array of Fills. CSS says how many there are in `--framework-chart-series-count`, so a theme that adds more chart colors shows up in JavaScript automatically. Entries past a shorter theme's last color come back empty. Series colors start at the screen's ink and spread only as far as stays readable; the public `--framework-chart-series-span` variable sets how far, so a theme can narrow it from CSS. Full-color screens use a set of distinct colors: the ink first, then seven hues in a fixed order, tuned separately for light and dark backgrounds. Grayscale and limited-palette screens step through shades instead, and a theme's own chart colors win over both. ``` // The paint for series 1 of 4. var s = TRMNLPaint.series(1, 4, { el: "my-chart" }); // Every series color at once. var fills = TRMNLPaint.ramp({ el: "my-chart" }); ``` ### Highcharts Adapters Adapters translate a `Fill`, `BorderFill`, or `TypeSpec` into the shape a charting library expects. They only copy values across; no adapter invents colors or contrast rules of its own. - `toHighcharts(fill)`: a solid Fill returns a flat color string; a tile Fill returns a `{ pattern: { image, width, height, backgroundColor } }` object for the Highcharts pattern-fill module. The background color is baked into the pattern image, under the dark pixels, so the pattern is self-contained. An empty Fill returns `null`. Patterns render through the Highcharts pattern-fill module; load it next to Highcharts, as the [Chart](/framework/docs/3.4/chart) examples do. The axis and label adapters live with their resolvers: `toHighchartsAxis()` on [Painting Borders](/framework/docs/3.4/paint_borders) , `toHighchartsText()` on [Painting Typography](/framework/docs/3.4/paint_typography) . `TRMNLCharts` (next section) builds on these adapters: its `grid()`, `axisLine()`, and `textStyle()` methods pick the right chart parts and convert them for Highcharts. There is no adapter for other libraries yet (a Chart.js `CanvasPattern`, a D3 ``); until one ships, read a Fill and translate it yourself. The Fill already carries everything a library needs. ``` // Highcharts: flat color on solid panels, a self-contained pattern tile // on 1- and 2-bit screens. var hc = TRMNLPaint.toHighcharts(TRMNLPaint.series(0, 4, { el: "my-chart" })); // Any other library: resolve a Fill and translate it however you like. var fill = TRMNLPaint.bg("gray-30", { el: "my-chart" }); var css = fill.image ? fill.image + " repeat" // dither tile : fill.color; // solid ``` ### The TRMNLCharts API `TRMNLCharts` builds Highcharts options that follow the device and the active theme. It ships in the same `plugins.js` runtime as `TRMNLPaint`, and every color it returns comes from TRMNLPaint. [Chart](/framework/docs/3.4/chart) puts it to work in line, bar, and gauge charts. `TRMNLMaps` does the same for MapLibre GL JS, composing map styles out of the map slots: see [Painting Maps](/framework/docs/3.4/paint_maps) . Every method takes the same optional `{ el }` as the paint functions above. #### Resolving Chart Paint - `paint(token, { el })`: one palette token as a Highcharts fill, flat color in solid modes and a pattern object in dither modes. - `series(i, count, { el })`: the Highcharts fill for series _i_ of _count_. - `applySwatches({ el })`: paints every element under the screen tagged `data-chart-series="i"` with that series' color, so legend markers always match. An optional `data-chart-series-count` sets the series total; it defaults to the number of tagged elements. Call it every time the chart builds. #### Building the Options - `options({ el })`: the recommended Highcharts options for the TRMNL aesthetic, with a transparent background, no animation, no chrome, and framework-resolved axes and text styles. - `merge(base, overrides)`: a deep merge of two plain objects, where arrays and scalars replace. Layer your chart's own config over `options()` with it. - `grid({ el, dir })`: the grid-line options (`gridLineColor`, `gridLineWidth`, `gridLineDashStyle`), taken from the framework's muted border line (step 65). `dir: 'h'` (the default) is the horizontal yAxis grid, `'v'` the vertical xAxis grid. - `axisLine({ el })`: the `lineColor` and `tickColor` options, taken from the framework's black border line; axis and ticks share it. - `textStyle(role, { el })`: a Highcharts text style for one framework text role. It carries the font settings and a solid text color, and sets `textOutline: 'none'` so data labels lose the default white halo. Pass `{ stroke: '' }` for an intentional outline. #### Keeping the Chart Current - `watch(el, buildFn)`: builds the chart now and again whenever the device, scale, mode, dark-mode or theme classes change on the screen or on a wrapper above it. `buildFn` creates and returns the chart instance; the previous one is destroyed before each rebuild. Returns a stop function. Highcharts numbers do not read CSS, so resolve heights, spacing, and offsets with `TRMNLPaint.px()` inside the build function. See [Paint API](/framework/docs/3.4/paint_api) . ``` var el = "my-chart"; // watch() rebuilds on every device, mode, dark-mode and theme change. TRMNLCharts.watch(el, function () { var chart = Highcharts.chart(el, TRMNLCharts.merge(TRMNLCharts.options({ el: el }), { chart: { type: "column", height: TRMNLPaint.px(180, { el: el }) }, xAxis: { categories: ["Mon", "Tue", "Wed"] }, series: [ { name: "Sent", data: [3, 5, 8], color: TRMNLCharts.series(0, 2, { el: el }) }, { name: "Read", data: [2, 4, 6], color: TRMNLCharts.series(1, 2, { el: el }) } ] })); // Legend markers tagged data-chart-series="0" / "1" get the same series colors. TRMNLCharts.applySwatches({ el: el }); return chart; }); ``` ### Live Example The bar rows below are painted with `TRMNLPaint.series()` and `TRMNLPaint.apply()`, wrapped in `TRMNLPaint.watch()`, with no charting library involved. Change the device mode or Style in the screen picker and the bars repaint to match: flat colors on solid panels, dither patterns on 1- and 2-bit screens. $48.6kPaper Sales 6Branches ![TRMNL Logo](/images/plugins/trmnl--render.svg)Paint APITRMNLPaint.apply ```
``` ### Where This Applies [ ## Rendering Modes ](/framework/docs/3.4/rendering_modes)[ ## Chart ](/framework/docs/3.4/chart) Previous [ ## Painting Colors Read background, text, stroke, and semantic colors from JavaScript as Fill objects ](/framework/docs/3.4/paint_colors) Next [ ## Painting Maps Map slot colors for the current device, mode, and theme, with MapLibre GL JS adapters ](/framework/docs/3.4/paint_maps) --- # Painting Maps These functions give you the color of every part of a map: land, water, roads, parks, buildings, and labels, correct for the current device, mode, and theme. Adapters turn each answer into a MapLibre GL JS paint property, and TRMNLMaps assembles whole map styles out of them. ### Map slots Every resolver takes an optional `{ el }`: the id or element whose nearest `.screen` ancestor supplies the paint. Each returns a canonical `Fill`; see [Paint API](/framework/docs/3.4/paint_api) for the shape. - `slot(name, { el, kind })`: the Fill of one component slot. `kind` is `'bg'` (the default) for an area, `'text'` for a label, or `'border'` for a line, which returns a BorderFill whose `render.stroke` is the one color a line can take. - `series(i, count, { el })`: the fill for route _i_ of _count_, from the same chart-series ramp charts use. See [Painting Charts](/framework/docs/3.4/paint_charts) . The area slots are `map-land` (the canvas), `map-water`, `map-forest` (forest, wood), `map-green` (parks, grass, meadow, heath, scrub, wetland), `map-farmland`, `map-rock` (bare rock, scree), `map-sand` (beach, sand, shingle), `map-area` (residential, commercial, industrial; the canvas itself on 1-bit), `map-site` (schools, hospitals, parking), `map-building` (also piers, dams, bridges) and `map-transit` (station dots). The line slots are `map-road` (motorway to tertiary), `map-road-minor` (residential, service, pedestrian, runways), `map-path` (footway, cycleway, track, steps), `map-rail` (rail, tram, subway, aerialways), `map-boundary` and `map-water-line` (rivers, canals, ferries). The text role `map-label` inks every place and water label; tiers differ by size, not tone, because a small dithered label would not survive a busy map. Every map slot is a bg slot, lines included, so each resolves through the device mode: a dither tile on 1- and 2-bit screens, a solid on 4-bit and up, a hue on the color panels. A line painted from a tile is drawn as the polygon of its stroke filled with that tile, so a 1-bit road is the same screen-aligned dither its area would be, never a gray the panel cannot print; every line defaults to a gray short of the ink, so a plotted route in the ink reads on top of the roads. Land follows the canvas and the labels follow the text channel, so a theme restates the token-bound areas and lines with `bg-slot`, the mixin it uses for every other component: see [Theme Slots](/framework/docs/3.4/theme_slots) . ``` // An area: a tile on 1-bit, a solid on 4-bit, blue on a color panel. var water = TRMNLPaint.slot("map-water", { el: "my-map" }); // A line is a bg slot too: a tile on 1-bit (drawn as a line pattern), a gray on 4-bit. var road = TRMNLPaint.slot("map-road", { el: "my-map" }); // A label: the ink of the map-label text role. var label = TRMNLPaint.slot("map-label", { el: "my-map", kind: "text" }); ``` ### MapLibre adapters Adapters shape a resolved `Fill` or `BorderFill` for one specific renderer. They copy resolved values into the renderer's native form; no adapter adds contrast heuristics, thresholds or substitute design rules. - `toMapLibre(fill)`: returns `{ color, ink, pattern }`. A solid Fill gives its color as both `color` and `ink`; a tile Fill gives its painted ink and a registered pattern image for `fill-pattern`, with the field composited in and a pixel ratio that lands one tile pixel on one device pixel; a line Fill gives its stroke and never a pattern. `TRMNLMaps` (see the next section) is the MapLibre composition layer over TRMNLPaint, the way `TRMNLCharts` is for Highcharts: [Painting Charts](/framework/docs/3.4/paint_charts) . Pattern images are added to the map as the style asks for them, so a plugin never handles the image itself. ``` // MapLibre: a flat color on solid panels, a registered pattern on 1- and 2-bit screens. var water = TRMNLPaint.toMapLibre(TRMNLPaint.slot("map-water", { el: "my-map" })); var paint = water.pattern ? { "fill-pattern": water.pattern.id } // dither tile : { "fill-color": water.color }; // solid // A line layer: TRMNLMaps.style() widens a tile line into a fill with the tile after the // tiles load; by hand, a solid rail gives you the color and a dashed line takes the ink. var road = TRMNLPaint.toMapLibre(TRMNLPaint.slot("map-road", { el: "my-map" })); var linePaint = { "line-color": road.color || road.ink, "line-width": TRMNLPaint.px(2, { el: "my-map" }) }; ``` ### The TRMNLMaps API `TRMNLMaps` ships in the same `plugins.js` runtime as `TRMNLPaint` and composes MapLibre GL JS styles and options out of it. It resolves no paint of its own: every value it returns comes from a TRMNLPaint resolver and adapter. [Map](/framework/docs/3.4/map) puts it to work in street, route and Strava examples. Every method takes the same optional `{ el }` as the resolvers: the map container id or element whose nearest `.screen` supplies the paint. Omit it on a single-screen plugin. #### Resolving map paint - `paint(token, { el })`: one palette token as MapLibre paint, `{ color, ink, pattern }`. - `series(i, count, { el })`: the MapLibre paint for route _i_ of _count_ from the screen's chart-series ramp. - `route(map, coords, { el, i, n, width, id, casing })`: plots a route as the polygon of its stroke (default width 4, through `TRMNLPaint.px()`) filled with series _i_ of _n_ over a two pixel contrast casing, crisp and re-widened for every camera. Call it once the map has loaded. - `dot(map, lngLat, { el, i, n, radius, hollow, id })`: plots a disc (default radius 5) in that series paint with a two pixel contrast ring; `hollow: true` makes it a ring around a contrast core, so a start dot and an end ring read apart on one ink. - `applySwatches({ el })`: paints every element under the screen carrying `data-map-slot="map-water"` (and `data-map-slot-kind="text"` or `"border"`) from that slot, for a legend. #### Building the style - `tiles(preset)`: a tile source, the vector tile URL template, zoom range and attribution. Resolved in order: the argument (`'osm'`, `'trmnl'`, or `{ url, key, preset }` merging over a preset), then the host's `window. __TRMNL_MAPS__.tiles`, then `'osm'`, the public endpoint. A url may carry `{key}`. - `style(preset, { el, tiles, labels, buildings })`: a complete MapLibre style for `streets`, `minimal`, `outline` or `blank`, every layer painted from the map slots. `labels: false` drops the labels, `labels: 'major'` keeps the big place names, `buildings: false` drops the footprints. - `options({ el, preset, center, zoom })`: Map options for a still map: the container, every handler and animation off, no controls, the screen's pixel ratio, and the style for the preset. Pass the result to `new maplibregl.Map()`. - `merge(base, overrides)`: a deep merge of two plain objects, where arrays and scalars replace. Layer your own ids, sources and options over the defaults with it. #### Fitting and decoding - `fit(map, coords, { padding, maxZoom })`: frames the coordinates without animation, on an integer zoom with the center snapped to the pixel grid so dither patterns stay crisp. Returns the `{ center, zoom }` it jumped to. - `decodePolyline(str, precision)`: a Google encoded polyline (Strava's `map.summary_polyline`) as `[lng, lat]` pairs, ready for a GeoJSON LineString. #### Keeping the map current - `watch(el, buildFn)`: builds the map now and again whenever the device, scale, mode, dark-mode or theme classes change. `buildFn` creates and returns the map; the previous one is removed first and the new one attached. Returns a stop function. - `attach(map, { el })`: registers a map you built yourself: pattern images, the pixel-grid snap, the labels, the attribution and readiness. `watch()` calls it for you. - `ready(map)`: a promise that resolves once the map has drawn everything it knows about. - `settle({ maxWaitMs })`: the bounded wait the runtime runs at the end of a pass, so `window.TRMNL_PLUGINS_READY` flips only once every attached map is idle. Default 6000 ms, or `window. __TRMNL_MAPS_SETTLE_MS__ `. - `refresh({ maxWaitMs })`: rebuilds every watched map from the live cascade and settles them, without re-running the pass. For a host that rescales the screen after the pass, the way a screenshot service sets its capture pixel ratio last. - `supported()`: whether this browser can draw a MapLibre map. Without WebGL, `watch()` flags the container `data-map-unsupported` and shows a `.map__fallback` child if you placed one. MapLibre numbers do not read CSS, so resolve widths, radii and padding with `TRMNLPaint.px()` inside the build function. See [Paint API](/framework/docs/3.4/paint_api) . #### Every mark is a fill Nothing TRMNLMaps draws is a MapLibre line or circle, which would be anti-aliased and, for a tile, filtered along the line. Every road, dash, stop, route and dot is the polygon of its stroke, filled with the slot's tile or solid without anti-aliasing, so it lands on the pixel grid like every other tile on the screen. The runtime widens them after the tiles load and again for every camera, in device pixels: a 1px line is one row of the panel's pixels on a 1.8x device as on a 1x one. #### Labels Place and water labels are framework elements, not MapLibre text. After every idle the runtime reads the label features out of the loaded tiles, writes each one into the container as a `label` (big places) or `label label--small` (towns, suburbs, water) with `text-stroke text-stroke--large`, snaps it to whole pixels, and keeps the biggest that fit without overlap. Every tier takes the map-label ink. The biggest names win the space. Small kinds wait for closer zooms (towns from zoom 9, villages from 11, suburbs from 12, neighbourhoods from 13), water earns a name once it covers about a label's worth of screen, and a small map holds a few names instead of a crowd. No label lands on the credit. So labels take the screen's own typography (TRMNL pixel fonts on 1-bit and 2-bit low-density panels, Inter on 4-bit and high density), the text-stroke halo, and the map-label slot for ink, with no glyph endpoint involved. `style()` decides which tiers a preset shows. ``` var el = "my-map"; // watch() rebuilds on every device, mode, dark-mode and theme change. TRMNLMaps.watch(el, function () { var map = new maplibregl.Map(TRMNLMaps.options({ el: el, preset: "minimal" })); map.on("load", function () { TRMNLMaps.route(map, coords, { el: el, width: 3 }); TRMNLMaps.dot(map, coords[0], { el: el, id: "start" }); }); TRMNLMaps.fit(map, coords, { padding: TRMNLPaint.px(20, { el: el }), maxZoom: 15 }); return map; }); ``` ### Live example The swatches below are painted with `TRMNLMaps.applySwatches()` inside `TRMNLPaint.watch()`, with no map library involved. Change the device mode or Style in the screen picker and they repaint from the live cascade: tiles on 1- and 2-bit screens, solids on 4-bit, hues on a color panel. Land Water Green Building Road Minor road ![TRMNL Logo](/images/plugins/trmnl--render.svg)Painting MapsMap slots ```
``` ### Where This Applies [ ## Map ](/framework/docs/3.4/map) Previous [ ## Painting Charts Chart series colors for the current device, mode, and theme, with Highcharts adapters ](/framework/docs/3.4/paint_charts) Next [ ## Painting Borders Read framework border lines as BorderFill objects, for your own lines and Highcharts axes ](/framework/docs/3.4/paint_borders) --- # Painting Borders These functions tell you exactly how the framework draws a border line. Use them so lines you draw yourself, or Highcharts axes and grid lines, match the borders around them. ### Borders and Dividers Every function takes an optional `{ el }`: the id or element whose nearest `.screen` supplies the paint. How the reading works is on [Paint API](/framework/docs/3.4/paint_api) . - `border(spec, { dir, el })`: one framework border line as a `BorderFill`. The `spec` is a shade step from 10 to 75, or `'black'` / `'white'`; `dir` is `'h'` (default) or `'v'`. Framework borders are painted as a background on a pseudo-element, so that is where the paint is read. - `divider({ dir, el })`: the `.divider` line (level 6) as a `BorderFill`, read on the element itself. A dithered line is painted from several stacked layers, so `BorderFill` keeps `size`, `position`, and `repeat` as verbatim strings. It also carries the ready-made SVG drawing instructions the CSS declares, so a renderer copies paths and colors verbatim instead of parsing gradients. ``` BorderFill = { color: string | null, // resolved background-color (solid/black-white modes) image: string | null, // resolved background-image gradient(s); null when "none" url: string | null, // first url(...) ink tile (black/white lines); null otherwise size: string | null, // VERBATIM background-size string (dither lines are list-valued) position: string | null, // VERBATIM background-position string repeat: string | null, // VERBATIM background-repeat string render: { // CSS-declared; copied verbatim by renderer adapters stroke: string | null, width: string | null, height: string | null, viewBox: string | null, path1: string | null, color1: string | null, path2: string | null, color2: string | null, } | null, dir: "h" | "v", // which pseudo/orientation was read } ``` `border()` is the right call for drawing lines: it reads the `.border--*` utilities, which stay visible in every mode and follow themes for free. `semantic('border-strong')` and `semantic('border-muted')` return the role's color as a plain Fill, not the line artwork; reach for them only when you need the color itself. ``` // Framework border lines, read for the current mode/theme. var gridLine = TRMNLPaint.border(65, { el: "my-chart" }); // -> BorderFill var axisLine = TRMNLPaint.border("black", { dir: "v", el: "my-chart" }); var step40 = TRMNLPaint.border(40, { el: "my-chart" }); ``` ### Painting Lines A BorderFill is written back verbatim, so it paints exactly as the CSS `.border--*` utilities do. Wrap the painting in `watch()` so lines re-read on mode, dark, and theme changes; see [Paint API](/framework/docs/3.4/paint_api) . - `applyBorder(node, borderFill)`: writes a `BorderFill` onto a node as its five background properties; solids, gradients, and dithered lines all arrive unchanged. ### Highcharts Axes The axis adapters copy a border line into Highcharts' native options; the fill adapter and the rules all adapters follow are on [Painting Charts](/framework/docs/3.4/paint_charts) . - `toHighchartsAxis(borderFill)`: turns a `BorderFill` into a `{ gridLineColor, gridLineWidth, gridLineDashStyle, lineColor, tickColor }` block. - `applyHighchartsAxisPaint(chart, fills)`: paints the rendered grid, axis, and tick paths from `{ xGrid, yGrid, axis }` BorderFills, re-running on the chart's render event. `TRMNLCharts.options()` wires it up for you. Flat modes carry a stroke; dither modes carry their complete pattern, paths and colors. ``` // Border lines into Highcharts axis/grid options. var grid = TRMNLPaint.toHighchartsAxis( TRMNLPaint.border(65, { el: "my-chart" })); ``` ### Live Example The separator under each schedule row is painted with `TRMNLPaint.border()` and `TRMNLPaint.applyBorder()`, wrapped in `TRMNLPaint.watch()`. Each row uses a different spec (shade steps 65 and 40, then black and white), labelled on the right. Change the device mode or Style in the picker and the lines repaint, picking up dither patterns and theme colors. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Paint APITRMNLPaint.applyBorder ```
``` ### Where This Applies [ ## Border ](/framework/docs/3.4/border) Previous [ ## Painting Maps Map slot colors for the current device, mode, and theme, with MapLibre GL JS adapters ](/framework/docs/3.4/paint_maps) Next [ ## Painting Typography Read text roles as TypeSpec objects for custom text and chart labels ](/framework/docs/3.4/paint_typography) --- # Painting Typography These functions read everything a text class sets: font, size, weight, color, and optional stroke. Apply the result to text you draw yourself, or convert it for Highcharts labels. ### Custom Fonts and JavaScript From Framework 3.4 a theme or a plugin can use its own font. The JavaScript typography APIs read the computed CSS, so they return that font with the device's sizes. See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . A plugin's font applies inside its `typeface` container, so pass an `el` inside that container to read it. A theme switch changes the screen's CSS class. Previously returned specs and copied inline styles are snapshots: read and apply them again after the switch, and update chart options from a fresh `TRMNLCharts.textStyle()` result. See [Painting Charts](/framework/docs/3.4/paint_charts) for chart integration. ``` // Both theme stylesheets are already loaded. const screen = document.querySelector(".screen"); screen.classList.replace("screen--theme-example", "screen--theme-alternate"); const node = document.getElementById("custom-label"); const spec = TRMNLPaint.type("label", { el: screen }); TRMNLPaint.applyType(node, spec); ``` For canvas or chart layout that measures text, wait for `document.fonts.ready` after applying the font. The existing APIs cover custom typefaces; no separate JavaScript typeface setter is required. ### Typography Every function takes an optional `{ el }`: the id or element whose nearest `.screen` supplies the paint. How the reading works is on [Paint API](/framework/docs/3.4/paint_api) . - `type(classOrRole, { el, stroke })`: the typography of a role or class as a `TypeSpec`. Pass `stroke` (a size token, or `true` for the base size) to include a text stroke. - `strokeSpec(sizeToken, { el })`: one text stroke as `{ color, width, radius }`. The `sizeToken` is `'small'` / `'medium'` / `'large'` / `'xlarge'`, or `null` for the base `text-stroke`. The roles map to framework classes: `'chart-label'` to `text--small`; `'value'`, `'label'`, `'title'`, and `'description'` to their own classes. Any other string passes through as a literal class list (e.g. `'value value--xxlarge'`). Font family, size, weight, and line-height are measured live, so they match the active font bundle, custom theme font, and density. ``` TypeSpec = { fontFamily: string | null, // resolved family for the active bundle and density fontSize: string | null, // e.g. "38px" for .value fontWeight: string | null, fontStyle: string | null, fontVariantNumeric: string | null, fontVariationSettings: string | null, webkitFontSmoothing: string | null, letterSpacing: string | null, lineHeight: string | null, color: string | null, // exact computed color; transparent when text paints with a pattern backgroundColor: string | null, // the color under the text pattern backgroundImage: string | null, // exact computed image; the dither pattern lives here backgroundSize: string | null, backgroundPosition: string | null, backgroundRepeat:string | null, clip: string | null, // computed background-clip textShadow: string | null, // the stacked drop shadows when a stroke was requested filter: string | null, overflow: string | null, stroke: null | { color, width, radius }, // present only when stroke was requested } ``` ``` // The .value role, size/family/weight resolved for the active bundle + density. var big = TRMNLPaint.type("value", { el: "my-chart" }); // -> TypeSpec var axis = TRMNLPaint.type("chart-label", { el: "my-chart" }); ``` ### Painting Type A TypeSpec is written back complete, so dither patterns and strokes survive instead of collapsing to a solid color. Wrap the painting in `watch()` so text re-reads when the bundle, density, or theme changes; see [Paint API](/framework/docs/3.4/paint_api) . - `applyType(node, typeSpec)`: writes a `TypeSpec`'s font, text paint, clipping, and optional stroke onto a node, exactly as computed. ### Highcharts Labels The text adapter copies a TypeSpec into Highcharts' label options; the fill adapter and the rules all adapters follow are on [Painting Charts](/framework/docs/3.4/paint_charts) . - `toHighchartsText(typeSpec)`: turns a `TypeSpec` into `{ color, textOutline, fontFamily?, fontSize?, ... }`. Font keys appear only when resolved, and `textOutline` is `'none'` unless a stroke was requested, so data labels lose Highcharts' default white halo. ``` // Typography into Highcharts label options. var label = TRMNLPaint.toHighchartsText( TRMNLPaint.type("chart-label", { el: "my-chart" })); // => { color, textOutline: "none", fontFamily, fontSize, fontWeight } ``` ### Live Example The two stat tiles below are built from plain spans that carry no framework classes. `TRMNLPaint.type()` resolves the `value` role (the big-number face used by stat tiles) and the `chart-label` role, and `TRMNLPaint.applyType()` writes the font and text paint onto them. Toggle the font bundle or density in the picker and the tiles follow. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Paint APITRMNLPaint.applyType ```
``` Previous [ ## Painting Borders Read framework border lines as BorderFill objects, for your own lines and Highcharts axes ](/framework/docs/3.4/paint_borders) Next [ ## Sass API The framework SCSS source: architecture, cascade layers, and what a custom stack can build from it ](/framework/docs/3.4/sass_api) --- # Sass API The Sass API is the framework's SCSS source, open since 3.2. These pages cover the build-your-own path: compiling from source, adding device profiles, and using the mixins in your own SCSS. A custom stack can also skip the build entirely and serve an official released build, which is what the TRMNL Platform always does. The TRMNL Platform serves the framework for you, rendering every plugin against its official builds. Recipes can pin a specific release, and the developer license lets a private plugin do the same. You choose the framework build your screens load: a released build straight from the releases CDN, or your own build compiled from this SCSS source. ```
...
``` ``` ``` ### The Source at a Glance Everything compiles from one entrypoint. `plugins.scss` loads the `framework` module (`framework/index.scss`), which loads the configuration, the mixins, and the style layers in a fixed order. ``` // framework/index.scss: configuration and mixins load first, // then every style lands in a declared cascade layer. @forward 'config/custom'; @use 'config/tokens' as vars; @use 'mixins' as mixins; @layer tn--normalize, tn--elements, tn--components, tn--base, tn--device-overrides, tn--themes, tn--utilities; ``` Every rule lands in a `tn--` prefixed cascade layer, so a custom stack can run the framework next to other CSS without specificity fights. The codebase uses the Dart Sass module system throughout: `@use` and `@forward`, never `@import`. - `config/`: the device map, design-token maps, generated color maps, fonts, and the root CSS variables. - `mixins/`: screen targeting, theme slots, and the scale functions. This is the consumer-facing Sass surface; see [Sass Mixins](/framework/docs/3.4/sass_mixins) . - `base/`, `elements/`, `components/`, `utilities/`: the screen scaffolding, text elements, larger components, and utility classes these docs cover page by page. - `themes/`: standalone theme stylesheets, compiled separately from `plugins.css`. See [Themes](/framework/docs/3.4/themes) . The source lives at [github.com/usetrmnl/trmnl-framework](https://github.com/usetrmnl/trmnl-framework). For the repository tour, the license, and the release pipeline, see [Open Source](/framework/docs/3.4/open_source) . ### Public and Internal Surface - **Public:** the `$custom-devices` configuration, the four supported `framework/mixins` families (screen targeting, scale functions, typography, theme slots) named on [Sass Mixins](/framework/docs/3.4/sass_mixins) , the compiled CSS classes, and the public `--*` variables listed on [Tokens](/framework/docs/3.4/tokens) . - **Internal:** members whose names start with `-` or `_` (private to their module), every other mixin and function that `framework/mixins` forwards, and the paint variables (`--bg-*`, `--text-*`, `--border-*`) the framework's rendering owns. Internal members can change without notice. - **Generated:** files with a DO NOT EDIT header (the device map, the color maps) regenerate from `db/data/` via rake tasks. Configure or extend them; do not hand-edit them. Theme stylesheets have their own authoring contract built on the theme-slot mixins; it is documented on [Authoring Themes](/framework/docs/3.4/theme_authoring) and enforced by `rake framework:themes:lint`. Custom fonts use `typography.typeface($family)`, or `theme-slots.typeface($family)` in a theme (Framework 3.4 or later); see [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . ### Go Deeper #### Compiling the Framework The toolchain, the entrypoint, themes, the JS runtime, and what to serve. Go to [Compiling the Framework](/framework/docs/3.4/sass_build) #### Custom Devices The device map schema and the `$custom-devices` configuration for your own panels. Go to [Custom Devices](/framework/docs/3.4/sass_devices) #### Sass Mixins Screen-targeting mixins and scale functions for authoring device-aware SCSS. Go to [Sass Mixins](/framework/docs/3.4/sass_mixins) ### Where This Applies [ ## Tokens ](/framework/docs/3.4/tokens) Previous [ ## Painting Typography Read text roles as TypeSpec objects for custom text and chart labels ](/framework/docs/3.4/paint_typography) Next [ ## Compiling the Framework Compile plugins.css and the theme stylesheets from source with Dart Sass ](/framework/docs/3.4/sass_build) --- # 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. Clone the source from [github.com/usetrmnl/trmnl-framework](https://github.com/usetrmnl/trmnl-framework). ``` // 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](/framework/docs/3.4/sass_devices) . The compiled stylesheet embeds every dither pattern as a data URI, so it is large on disk. Serve it compressed; the release pipeline ships a `.gz` next to every artifact for the same reason. Fonts are the only asset it loads by URL. ### 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, but the type is 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`, and `TRMNL21` in Regular and Bold, each as `.woff2`, `.woff`, and `.ttf` (18 files). - `NicoPups-Regular.ttf`, `NicoClean-Regular.ttf`, and `BlockKie.ttf` for the Classic bundle (3 files). - `Inter.ttf` and `Inter-Italic.ttf`, which both bundles use on high-density displays (2 files). Both bundles download as zips from [Releases](/framework/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](/framework/docs/3.4/font_family) 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. ### Themes Each theme under `framework/themes/` compiles to its own stylesheet, separate from `plugins.css`, and loads as a second `` next to the framework. Usage is on [Themes](/framework/docs/3.4/themes) ; the authoring contract is on [Authoring Themes](/framework/docs/3.4/theme_authoring) . ``` 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/docs/3.4/framework_runtime) ) and TRMNLPaint ( [Paint API](/framework/docs/3.4/paint_api) ). ### 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. Minifying your own build is optional. If you do, keep custom properties intact: the framework's JavaScript reads public `--*` variables at runtime. Previous [ ## Sass API The framework SCSS source: architecture, cascade layers, and what a custom stack can build from it ](/framework/docs/3.4/sass_api) Next [ ## Custom Devices Device profiles and the $custom-devices configuration for custom builds ](/framework/docs/3.4/sass_devices) --- # Custom Devices Every device the compile knows about becomes a screen--{name} class, with its dimensions, density, and color depth baked in. Add your own panels through $custom-devices without touching framework source. ### The Device Map `$devices` in `framework/config/_devices.scss` defines every device profile the framework ships. The map is generated from the platform's device registry (`db/data/framework_devices.yml`), so it is not edited by hand. Each entry becomes a `screen--` class carrying the device's dimensions and rendering variables. [Screen](/framework/docs/3.4/screen) shows how the class is used in markup. Every profile carries the same schema: | Key | What it sets | | --- | --- | | `screen-w`, `screen-h` | Screen dimensions in CSS pixels. | | `pixel-ratio` | Physical-to-CSS pixel ratio of the display. | | `dither-pixel-ratio` | The ratio dither patterns render at, so they stay crisp on high-DPI panels. | | `ui-scale` | How much the device scales the interface by default. | | `color-depth` | The grayscale tier: 1, 2, or 4. Full-color profiles carry 12 or 24. | | `density-tier` | `'1x'` or `'2x'`: picks the pixel-font or vector typography tier. | | `gap-scale` | Multiplier applied to the gap variables. | | `size` | `'sm'`, `'md'`, or `'lg'`: the size class the device extends. | ### Configure Your Own Custom stacks add device profiles without touching framework source: configure `$custom-devices` where you load the framework. Everything the framework generates per device includes your profile, so a configured device behaves exactly like a built-in one. ``` // my-build.scss @use 'framework' with ( $custom-devices: ( 'my-panel': ( 'screen-w': 1024px, 'screen-h': 758px, 'pixel-ratio': 1.0, 'dither-pixel-ratio': 1.0, 'ui-scale': 1.0, 'color-depth': 2, 'density-tier': '1x', 'gap-scale': 1.0, 'size': 'lg' ) ) ); ``` ```
...
``` Profiles are validated at compile time: a missing key or an invalid `size` or `density-tier` fails the build with a named error. An entry that reuses a built-in device name overrides that device. Compile as usual; see [Compiling the Framework](/framework/docs/3.4/sass_build) . ### What a Profile Generates - A `screen--` class setting the device variables (`--screen-w`, `--pixel-ratio`, `--color-depth`, ...). - The size behavior of its `size` class, and the typography tier of its `density-tier`. See [Scale](/framework/docs/3.4/scale) for how density and scale compose. - The per-device utility rules, like high-DPI outline dots for profiles with a `dither-pixel-ratio` of 2 and up. The JavaScript runtime reads the same variables at render time; see [Framework Runtime](/framework/docs/3.4/framework_runtime) . ### The Built-in byod Profile The map ships a generic `byod_custom` profile, so even a released build has a bring-your-own-device class. Apply `screen--byod_custom` for a quick start without compiling anything, then graduate to `$custom-devices` when you need your panel's exact dimensions and depth. ### Where This Applies [ ## Screen ](/framework/docs/3.4/screen)[ ## Devices ](/framework/docs/3.4/devices) Previous [ ## Compiling the Framework Compile plugins.css and the theme stylesheets from source with Dart Sass ](/framework/docs/3.4/sass_build) Next [ ## Sass Mixins Screen-targeting mixins and scale functions for authoring device-aware SCSS ](/framework/docs/3.4/sass_mixins) --- # Sass Mixins The screen mixins let your own SCSS target the same conditions the responsive utilities do: device size, orientation, and bit depth. The scale functions wrap pixel values so your CSS scales with the device the way framework CSS does. ### Screen Targeting Load the mixins module and wrap declarations. Each wrapped block applies only on screens that match, using the same grammar as the responsive utility classes ( [Responsive](/framework/docs/3.4/responsive) ). Your build needs the framework source on the load path; see [Compiling the Framework](/framework/docs/3.4/sass_build) . ``` @use 'framework/mixins' as trmnl; .forecast-tile { border: 1px solid var(--black); // 1-bit screens: thicker rule so the dithered edge stays legible. @include trmnl.for-1bit { border-width: 2px; } // md and larger screens in portrait. @include trmnl.screen('md', 'portrait') { padding: 20px; } } ``` A mixin wraps the current rule in the matching screen ancestor: the 1-bit block above compiles to `.trmnl .screen--1bit .forecast-tile`. - `screen($modifiers...)`: one to three modifiers in any order. A size modifier is mobile-first (that size and up). Bit depth accepts `'1bit'` or `'1'`. - `screen-only($modifiers...)`: the same grammar with exact size targeting instead of mobile-first. - `for-sm` / `for-md` / `for-lg` and their `-only` variants: size shorthands. - `for-portrait` / `for-landscape`: orientation shorthands. - `for-1bit` / `for-2bit` / `for-4bit`: bit-depth shorthands. - `for-dark-mode` and `for-dark-1bit` / `for-dark-2bit` / `for-dark-4bit`: dark-mode targeting. Themed screens are exempt, matching the theme contract on [Themes](/framework/docs/3.4/themes) . - `for-color-palette($id)` / `for-color-full`: limited-palette and full-color targeting. The palette ids are `'3bwr'`, `'3bwy'`, `'4bwry'`, `'6a'`, and `'7a'`, each documented on [Color Palettes](/framework/docs/3.4/color_palettes) . - `for-density($tier)`: density tier targeting (`'1x'`, `'2x'`). - `for-combo` / `for-combo-up` / `for-triple` / `for-triple-up`: target an exact combination directly. `for-combo` takes two modifiers, `for-triple` takes three, and the `-up` variants are mobile-first; `screen()` and `screen-only()` call these for you. ### Scale Functions Each function wraps a pixel value in one of the framework's scale variables. Non-px and zero values pass through unchanged; lists are scaled item by item. - `content-scaled($value)`: multiplies by `--content-scale`. Use for plugin content geometry. - `ui-scaled($value)`: multiplies by `--ui-scale`, which includes device density. Use for geometry that should scale like the framework's own components. - `text-ui-scaled($value)`: multiplies by `--text-ui-scale`, which includes density, Scale, and Text Scale. Use for typography. ``` @use 'framework/mixins' as trmnl; .forecast-tile { padding: trmnl.content-scaled(10px); // => padding: calc(10px * var(--content-scale, 1)); } ``` ### Public Surface `framework/mixins` forwards every module the framework compiles itself with, which is far more than it supports. Four families are public: their names and signatures hold from one release to the next, and the docs cover them page by page. - **Screen targeting:** `screen()`, `screen-only()`, and the `for-*` shorthands listed above. - **Scale functions:** `content-scaled()`, `ui-scaled()`, `text-ui-scaled()`. - **Typography:** the `framework/mixins/typography` module. `typography.typeface($family)` puts your own font on every text role, keeping device sizes and line heights (Framework 3.4 or later). See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . - **Theme slots:** the `framework/mixins/theme-slots` module, documented on [Theme Slots](/framework/docs/3.4/theme_slots) with the authoring contract on [Authoring Themes](/framework/docs/3.4/theme_authoring) . `theme-slots.typeface($family)` is the same typeface mixin for a theme. Everything else the module forwards is internal: the selector helpers (`scope-selector`, `generate-screen-utilities`) and the variant emitters (`with-all-variants`, `generate-triple-variants`). The internal set also includes the shared utility emitters, the pattern, border-level, and text-paint helpers, and the `trmnl-namespace` wrapper. They exist to compile the framework's own classes and can change or disappear in any release, with no deprecation notice and no entry in the release notes. The same split holds inside a public module: a member the docs do not name is internal even when it sits next to one they do. The device configuration is public and documented on [Custom Devices](/framework/docs/3.4/sass_devices) ; the whole SCSS contract is summarized on [Sass API](/framework/docs/3.4/sass_api) . ### Where the Rest Lives The class-based variants for markup are on [Responsive](/framework/docs/3.4/responsive) , and [Responsive Test](/framework/docs/3.4/responsive_test) renders mixins and classes side by side to prove they produce the same result. ### Where This Applies [ ## Responsive ](/framework/docs/3.4/responsive) Previous [ ## Custom Devices Device profiles and the $custom-devices configuration for custom builds ](/framework/docs/3.4/sass_devices) Next [ ## Themes Visually customize any plugin with a drop-in stylesheet that recolors the whole screen ](/framework/docs/3.4/themes) --- # Themes Themes are a simple way to visually customize any TRMNL plugin. A theme adjusts the framework's colors (backgrounds, text, borders, chart colors) without touching plugin markup, and gracefully adapts to every supported device, from 1-bit ePaper to full color. ### Usage Include the theme stylesheet and add `screen--theme-` to your screen element. Theme stylesheets ship with every framework release, next to `plugins.css`. A theme already decides every color, so `screen--dark-mode` has no effect on a themed screen. A theme that wants its own dark variant can style `.screen--theme-.screen--dark-mode` in its stylesheet. Monochrome plugin icons follow the theme automatically when they carry the `image--adaptive` class. See [Image](/framework/docs/3.4/image) . ```
...
``` ### Available Themes The framework ships these themes: - **Black and Yellow** : `screen--theme-black-and-yellow` - **Dark** : `screen--theme-dark` - **White and Red** : `screen--theme-white-and-red` To see them in action, pick a Style in the screen picker (top right). It applies to every example on the page and persists across pages. 43Sales Calls 7New Clients 4,283Reams Sold Q4 Paper Quota60% ![TRMNL Logo](/images/plugins/trmnl--render.svg)Dunder MifflinSales ### Themes in JavaScript JavaScript can read everything it needs from a theme's CSS: - `TRMNLPaint`: reads every color and pattern from the CSS itself, so charts and other JS-drawn visuals follow the active theme. - `TRMNLPaint.cssVar()`: reads any `--*` variable a theme sets on the screen. A theme that wants to hand its own values to plugin code declares them; no framework changes needed. See [Paint API](/framework/docs/3.4/paint_api) . ### Build Your Own Theme A theme never changes your markup. The pages below cover the workflow, custom fonts, and every part a theme can recolor. #### Authoring Themes The workflow: start from the boilerplate, point each part of the screen at new colors, register the id, and lint. Go to [Authoring Themes](/framework/docs/3.4/theme_authoring) #### Custom Typefaces Use your own font in a theme, or in one plugin on any screen. Go to [Custom Typefaces](/framework/docs/3.4/custom_typefaces) #### Theme Slots Every surface a theme can recolor, from semantic colors and components to utilities, borders, and chart series. Go to [Theme Slots](/framework/docs/3.4/theme_slots) ### Where This Applies [ ## Image ](/framework/docs/3.4/image) Previous [ ## Sass Mixins Screen-targeting mixins and scale functions for authoring device-aware SCSS ](/framework/docs/3.4/sass_mixins) Next [ ## Authoring Themes How to build your own theme: start from the boilerplate, map the slots, register the id, and lint ](/framework/docs/3.4/theme_authoring) --- # Authoring Themes Build your own theme by filling in slots: named parts of the screen, like the background, the text, or the title bar, that you point at new colors. This page walks through the workflow: start from the boilerplate, map your slots, register the id, and lint. ### The Contract A theme picks colors by name, through the theme-slot mixins; it never writes raw color values. Because of that, every color still renders correctly on every device: dithered down to the panel's inks where needed, exact on full color. - Set the whole screen's colors at once with the semantic channels: `theme-slots.semantic-bg/text/stroke/border`. - Recolor a single part, like the title bar, with a component slot: `theme-slots.bg-slot/text-slot/border-level-slot/border-token-slot`. - Recolor what plugins set with `bg--` and `text--` classes through the utility remaps: `theme-slots.utility-*` and the bulk `utility-remap-*` helpers. - Optionally give icons their own color with `theme-slots.semantic-icon`; without it, `image--adaptive` icons follow the theme's primary text color. - Pick the colors charts draw their series with: `theme-slots.chart-series-ramp`. - Pair the title bar's field with its own inks: `theme-slots.ink-slot/icon-slot/stroke-slot`. - Shape the screen's structure with factors, and its vector weight with one shift: `theme-slots.layout-factors` and `theme-slots.font-weight-shift`. - Set a custom font with `theme-slots.typeface($family)` (Framework 3.4 or later). See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . - Declare nothing outside the contract namespaces (`--framework-*`, `--theme-*`, `--tn-text-stroke-*`). Raw paint, the root palette, geometry, and font metrics are engine- or device-owned; the linter rejects them all. The full list of channels, slots, and remaps lives on [Theme Slots](/framework/docs/3.4/theme_slots) . ### Start from the Boilerplate `framework/themes/_theme-boilerplate.scss` is the starting point. A theme is one file: load the theme-slot mixins, declare the framework layer order, open the `tn--themes` layer, and scope every rule to `.trmnl .screen--theme-`. Keep `@include layers.order;` above the layer block. The browser locks in layer order from the first stylesheet it reads, so without that line a theme loaded before `plugins.css` loses to the framework defaults. ``` @use "../config/layers" as layers; @use "../mixins/theme-slots" as theme-slots; @include layers.order; @layer tn--themes { .trmnl .screen--theme-example { // 1. Semantic channels: the whole screen in a few lines. @include theme-slots.semantic-bg("canvas", "yellow"); @include theme-slots.semantic-text("text-primary", "black"); // 2. Component slots: tune specific surfaces. @include theme-slots.bg-slot("title-bar", "yellow-40"); // 3. Utility remaps: re-point the raw bg--/text-- utilities. @include theme-slots.utility-remap-grayscale("yellow"); // Optional: give image--adaptive icons their own paint // (they follow text-primary when this is omitted) // @include theme-slots.semantic-icon("yellow-20"); } } ``` Work in that order: semantic channels first, then component slots, then utility remaps. The shipped themes follow the same order, so read them as worked examples: `black-and-yellow-theme.scss` turns the screen dark, `white-and-red-theme.scss` keeps it bright and makes one targeted utility exception. ### Register and Lint The file name, the registry id, and the screen class stay in sync: `themes/-theme.scss` registers as `` in `lib/framework/themes.rb` and applies as `screen--theme-`. `rake framework:themes:lint` enforces the contract on every theme file: - The theme files on disk match the registry ids. - Every declared variable lives in a contract namespace: `--framework-*`, `--theme-*`, or `--tn-text-stroke-*`. Paint internals, the root palette, geometry, and font metrics all fail. - No calls to the deprecated `role-token` helper. ### Compile and Ship Each theme compiles to its own stylesheet, never into `plugins.css`. It ships as a second `` next to the framework, and the screen opts in with its theme class; see [Themes](/framework/docs/3.4/themes) for usage and [Compiling the Framework](/framework/docs/3.4/sass_build) for the compile command. ### Portable Theme Exports The TRMNL theme builder offers a ZIP download after you save a theme. Extract the whole ZIP into one directory on your host, load its compiled CSS alongside Framework, and add the manifest's theme class to your screen. - `theme.css` is the compiled stylesheet you can serve directly. - `theme.scss` is the editable source; compiling it requires the Framework Sass source. - `fonts/` contains the selected font files and their license when the theme selects a custom font. - `manifest.json` names the theme class, stylesheet, and minimum Framework version. Keep the font directory beside the stylesheet so relative URLs resolve. The theme class activates the selected font, and the exported files work without TRMNL. The SCSS-only download requires you to supply the font files separately. ### Dark Mode `screen--dark-mode` has no effect on a themed screen: a theme already decides every color, so the framework's own dark rules step aside on their own. A theme that wants a dark variant styles the combination itself, with the same mixins as the rest of the theme. ``` .trmnl .screen--theme-example.screen--dark-mode { @include theme-slots.semantic-bg("canvas", "black"); @include theme-slots.semantic-bg("surface", "black"); @include theme-slots.semantic-text("text-primary", "yellow"); } ``` **Do not use `$raw: true` in a theme.** It bypasses device rendering, so the surface paints one flat color instead of dithering down to the panel's inks where needed. ### Expose Values to JavaScript A theme hands extra values to plugin code through CSS alone: set your own variables on the screen inside the theme namespace (`--theme--*`), and JavaScript reads them back with `TRMNLPaint.cssVar()`. See [Paint API](/framework/docs/3.4/paint_api) . ### Where This Applies [ ## Tokens ](/framework/docs/3.4/tokens) Previous [ ## Themes Visually customize any plugin with a drop-in stylesheet that recolors the whole screen ](/framework/docs/3.4/themes) Next [ ## Theme Slots Every part of a screen a theme can recolor, from whole-screen colors down to single components, utilities, borders, and chart series ](/framework/docs/3.4/theme_slots) --- # Theme Slots This page lists every slot: each part of a screen a theme can recolor, and the mixin that sets it. A slot takes a framework token, not a raw color, so whatever you map still renders correctly on every device. ### Semantic Channels Start with the semantic channels: a few lines recolor the whole screen, because every component reads them. The set is fixed: - Backgrounds: `canvas`, `surface`, `backdrop`, `fill-strong`, `fill-muted`, `fill-soft` - Text: `text-primary`, `text-secondary`, `text-inverse` - Stroke: `stroke-contrast` - Borders: `border-strong`, `border-muted` - Icon: one global channel for `image--adaptive` icons; it defaults to text-primary. See [Image](/framework/docs/3.4/image) . - `semantic-bg($semantic, $token)`: sets a background channel to a token. - `semantic-text($semantic, $token)`: sets a text channel to a token, with everything text needs (color, dither pattern, clipping). - `semantic-stroke($semantic, $token)`: sets the stroke channel to a token. - `semantic-border($semantic, $token)`: sets a border channel to a token's fill. - `semantic-icon($token)`: optional color for adaptive icons; omit it and icons follow text-primary. Every semantic mixin takes `$raw: true`, which writes a plain color and skips device rendering; a theme never needs it. See [Authoring Themes](/framework/docs/3.4/theme_authoring) . ``` @include theme-slots.semantic-bg("canvas", "yellow"); @include theme-slots.semantic-bg("surface", "yellow"); @include theme-slots.semantic-text("text-primary", "black"); @include theme-slots.semantic-text("text-secondary", "yellow-30"); @include theme-slots.semantic-stroke("stroke-contrast", "yellow"); @include theme-slots.semantic-border("border-muted", "yellow-30"); ``` ### Component Slots Component slots recolor one surface, like the title bar, without moving a whole channel. Each slot is named after the part it colors and takes one kind of paint. These are the slots components read today: - Background slots: `screen-backdrop`, `title-bar`, `item-meta`, `item-meta-emphasis-2`, `item-meta-emphasis-3`, `progress-track`, `progress-fill`, `progress-fill-emphasis-2`, `progress-fill-emphasis-3`, `progress-dot`, `progress-dot-current`, `table-meta`, `table-meta-device`, `chip` - Text slots: `label-gray`, `chip`, `title-bar`, `title-bar-instance` - Border slots: `table-head-row`, `table-body-row`, `label-underline` `chip` is two slots for one surface: a solid box with readable text on it, for markers the framework has no component for. Its defaults point at the semantic channels (box from `fill-strong`, text from `text-inverse`), so it follows the theme on its own. Set both halves yourself when the box needs a specific pairing, the way White and Red gives it a white box with red text. Using the wrong mixin on a slot fails silently. Give a background slot `text-slot` and it writes variables no component reads; nothing on the screen changes. - `bg-slot($slot, $token)`: a slot's background paint. - `text-slot($slot, $token)`: a slot's text paint, dither pattern and clipping included. - `border-level-slot($slot, $level, $dir: h)`: sets a slot's border to one of the framework border levels. - `border-token-slot($slot, $token, $dir: h)`: sets a slot's border to a token's fill instead of a level. Slot values cascade down from the screen, so you can set them again on a component or a state wrapper. On an unthemed screen, `inverse` flips its subtree to the opposite scheme. On a themed screen the subtree keeps the theme's slots; the theme itself restates the slots that should flip, as in the example below. ``` @include theme-slots.bg-slot("title-bar", "yellow-40"); @include theme-slots.bg-slot("progress-fill", "yellow-55"); @include theme-slots.text-slot("label-gray", "yellow-30"); @include theme-slots.border-token-slot("label-underline", "yellow-30"); .screen--theme-example .inverse { @include theme-slots.bg-slot("progress-fill", "yellow-20"); } ``` ### Title-Bar Inks The title bar is a paired surface like the chip: a field with inks on it. Its background is the `title-bar` background slot above; four more slots recolor what sits on the field without moving the screen-wide channels. Unset, each falls back to exactly what the bar read before: the title follows `text-primary`, the instance follows the title (then `text-secondary` on 2-bit and up), the icon follows the icon channel, and halos follow `stroke-contrast`. - `ink-slot($slot, $token)`: a text slot plus the solid companion inherited `color` needs; use it for `title-bar` and `title-bar-instance`. - `icon-slot($slot, $token)`: paint for the bar's mask-recolored icon, ahead of the global icon channel. - `stroke-slot($slot, $token)`: the halo outline around the bar's glyphs and icon. Point it at the bar's own field tone so outlines separate ink from fill instead of tracking the canvas. ``` // A black bar with white title and icon. @include theme-slots.bg-slot("title-bar", "black"); @include theme-slots.ink-slot("title-bar", "white"); @include theme-slots.ink-slot("title-bar-instance", "gray-60"); @include theme-slots.icon-slot("title-bar", "white"); @include theme-slots.stroke-slot("title-bar", "black"); ``` ### Item Cards An item is a paired surface, a fill on the card and the ink its content takes over that fill. Set the two together so a theme that darkens the card carries its text with it. - `item-fill($token)`: the card's background, from a palette token. - `item-ink($token)`: the color the card's content takes. It repoints the primary text channel inside the card rather than restating each element, so `label--gray` and its siblings keep working. - `item-fill-art($art, $size, $repeat)`: art in place of a token tile. Unset, it takes the generated ordered-dither ramp for the current rail, tiled down the card at its own width. Never scale a ramp: stretching it re-blurs the pixels the dither already decided. - `item-border($art)`: the card's edge. Two arts ship, `--framework-item-border-art-outline`, the same drawing [Outline](/framework/docs/3.4/outline) uses, and `--framework-item-border-art-corner-brackets`. The mixin sets the matching corner radius: brackets stay square, dotted outlines keep their drawn curve, and solid outlines follow the theme's corner setting. The border art is declared on `.item` itself, because a `var()` inside a custom property resolves on the element that declares it. The card reads it as both a background and a border, and whichever one the art is invalid for drops out, which is how one value covers every bit depth. For custom art, set `--framework-slot-item-border-radius` on `.item` to control its clipping. Use `0px` for drawings with square corners. ``` // A dithered card with light text and a dotted edge. @include theme-slots.item-fill("gray-20"); @include theme-slots.item-ink("white"); @include theme-slots.item-fill-art(); @include theme-slots.item-border(var(--framework-item-border-art-outline)); @include theme-slots.spacing-slots($item-padding-x: 12px, $item-padding-y: 8px); ``` ### Custom Typefaces A theme can also give the screen its own font with `theme-slots.typeface($family)`. See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . ### Structure and Weight Structure moves through factors: unitless multipliers over the unthemed geometry, so 1 is always the unthemed screen and every device density stays correct underneath. `layout-factors($whitespace, $corners, $title-bar-height, $progress)` takes any subset. Whitespace rides the content scale, so gaps, pads, and table rows breathe together; corners ride every radius, named and component-owned alike (0 squares the screen off); the other two size the title bar with its icon, and the progress bars and dots. `font-weight-shift($shift)` moves every vector role weight by one signed amount, clamped to renderable weights where it lands. The bitmap bundles carry single-weight faces and ignore it. Explicit utilities (`text--bold`, numeric `rounded--*` values) stay literal: they are a plugin author's stated intent, not a theme default. `text-modifiers(...)` sets case and tracking per text role, as a `$-transform` and a `$-tracking` pair. The roles are title, value, label and description, the title bar's own title and instance, and table heads and bodies, with table bodies taking tracking alone. Sizes and line heights are deliberately not part of it: those are the device and reader scale axes, and a theme that moved them would fight the reader's own text-scale setting. `spacing-slots($title-bar-padding, $item-padding-x, $item-padding-y)` sets the padding a theme owns. The title bar takes a factor over the screen gap, where 0 sits its content flush with the edge. The item pair takes literal lengths, because an item pads by nothing unthemed and no factor lifts a value off zero. Tracking takes whole positive pixels, and the mixin refuses anything else. The bitmap bundles are drawn on the pixel grid, so a fractional advance from an `em` value puts their glyphs off-grid, and a negative one eats sidebearings that are part of the drawing. Adding a whole pixel between letters is the one move that stays on the grid. ``` // Airy, square-cornered, slightly bolder, editorial labels. @include theme-slots.layout-factors($whitespace: 1.35, $corners: 0); @include theme-slots.font-weight-shift(100); @include theme-slots.text-modifiers($label-transform: uppercase, $label-tracking: 1px); ``` ### Utility Remaps Utility remaps recolor the raw palette utilities (`bg--*`, `text--*`, `text-stroke--*`), so markup that says `bg--gray-50` follows the theme without edits. - `utility-remap-grayscale($to-hue, $side)`: the bulk remap, moving the whole grayscale ladder to a hue; `$side` controls how. - `utility-remap-border-grayscale($to-hue, $side)`: the same remap for border lines: their two inks recolor, their patterns keep their shape. - `utility-bg($token, $remap-to)` / `utility-text($token, $remap-to)` / `utility-stroke($token, $remap-to)`: remap one utility token, for exceptions on top of a bulk remap. - `utility-border-token($level, $dir, $token)`: remap one border level and direction to a token's paint. - `utility-border-level($level, $dir, $from-level)`: swap one border level for another level's line art, colors and rendering included. The 2-bit borders need this mixin: their four tones are drawn into the line itself, where no token remap can reach them. Restating a level onto itself is not a no-op: it also pins the two ink colors the level's line draws with. The `$side` values: `'dark'` (default) presses the grays into the hue's dark steps 10 to 40, keeps black as ink, and sends white to the hue. `'bright'` presses them into steps 45 to 75, sends black to the hue, and keeps white. `'linear'` maps gray steps to hue steps 1:1. Strokes are the one exception: whichever token maps to the hue itself keeps a visible stroke, taken from the nearest hue step (45 under bright, 40 under dark, 75 under linear), so outlines never dissolve into the canvas. Black and Yellow remaps to the dark side, and White and Red remaps to the bright side with one exception, `utility-bg("white", "red")`, so white surfaces turn red while white text stays ink. Dark flips the grayscale token by token with the single-token remaps, and uses `utility-border-level` for the 2-bit borders and for the one level that maps to itself. ``` // White and Red: bright-side remap with one exception. @include theme-slots.utility-remap-grayscale("red", $side: "bright"); @include theme-slots.utility-remap-border-grayscale("red", $side: "bright"); @include theme-slots.utility-bg("white", "red"); ``` ### Border Levels and Lines Border levels are the seven steps the framework draws borders in: 1 is black, 2 through 6 step through the grays (gray-15, gray-30, gray-40, gray-50, gray-65), 7 is white, each with an `h` and a `v` direction. `border-level-slot` sets a component's border to a level; `utility-border-token` recolors a level itself, and `utility-remap-border-grayscale` recolors the two line inks in bulk. See [Border](/framework/docs/3.4/border) and [Divider](/framework/docs/3.4/divider) for how levels render. Every border slot and remap changes two things at once: the CSS line, and what `TRMNLPaint.border()` and `TRMNLPaint.divider()` hand to JavaScript. Bind a slot to a level and JavaScript gets that level's pattern. Bind it to a token and JavaScript gets a flat line in the token's stroke color, because only levels have patterns. ### The Chart Ramp `chart-series-ramp($tokens, $span: 6)` sets the colors charts pick their series from: list the tokens in order, strongest contrast against the background first, and series 0 gets the first one. Each entry keeps the token's own rendering, so series dither or paint solid per device. JavaScript spreads series across the first `$span` entries of the list. A short list is safe. The ramp always has 16 entries; the ones past your last token are cleared instead of keeping the framework's default grays, and `$span` never reaches past your last token. Charts read the ramp through `TRMNLPaint.series()`; see [Painting Charts](/framework/docs/3.4/paint_charts) and [Chart](/framework/docs/3.4/chart) . ``` @include theme-slots.chart-series-ramp(( black, yellow-10, yellow-20, yellow-30, yellow-40, yellow-55, yellow-75, yellow )); ``` ### Where This Applies [ ## Border ](/framework/docs/3.4/border)[ ## Colors ](/framework/docs/3.4/colors)[ ## Title Bar ](/framework/docs/3.4/title_bar)[ ## Label ](/framework/docs/3.4/label)[ ## Item ](/framework/docs/3.4/item)[ ## Table ](/framework/docs/3.4/table)[ ## Progress ](/framework/docs/3.4/progress) Previous [ ## Authoring Themes How to build your own theme: start from the boilerplate, map the slots, register the id, and lint ](/framework/docs/3.4/theme_authoring) Next [ ## CSS Variables The CSS variable contract: which families are public, which are internal, and who reads, changes, and generates them ](/framework/docs/3.4/variables_api) --- # CSS Variables Some framework CSS variables are yours to use; the rest are internal and can change at any time. This page draws that line, family by family. It also shows how the Paint API, themes, and the Sass source each use the public ones. ### Who Reads, Who Writes The public `--*` variables are shared ground: Sass writes them, themes change them, and the Paint API reads them. - **Sass generates them.** The SCSS source emits every variable with its per-device values, and a custom build can extend the set. See [Sass API](/framework/docs/3.4/sass_api) . - **Themes change them.** A theme picks colors by name through the slot mixins and never writes raw values. See [Themes](/framework/docs/3.4/themes) and [Authoring Themes](/framework/docs/3.4/theme_authoring) . - **Paint reads them.** TRMNLPaint turns what CSS computed into Fill, BorderFill, and TypeSpec objects for JavaScript. See [Paint API](/framework/docs/3.4/paint_api) . The palette behind the variables is defined on [Colors](/framework/docs/3.4/colors) . Every variable with its per-mode values is listed on [Tokens](/framework/docs/3.4/tokens) . ### Public Variables A variable is public unless its name marks it as internal, and a release keeps every public name in every published bundle. Read public variables with `var()` in custom CSS or `cssVar()` in JavaScript, and reference them from theme slots. - **Palette:** `--black`, `--white`, the `--gray-*` ladder, and one ladder per hue: `--red-*`, `--orange-*`, `--yellow-*`, `--lime-*`, `--green-*`, `--cyan-*`, `--blue-*`, `--violet-*`, `--purple-*`, `--pink-*`. - **Semantic roles:** the `--color-*` family, which holds the roles (`--color-primary`, `--color-success`, `--color-error`, `--color-warning`) and the palette-mode ids. Hues live in the palette ladders above. - **Geometry and scale:** `--screen-*`, `--gap-*`, `--rounded-*`, and the resolved `--ui-scale`, `--content-scale`, and `--text-ui-scale` factors. - **Component tokens:** the families components read, from `--title-bar-*` to `--progress-*`, all listed on [Tokens](/framework/docs/3.4/tokens) . The `--framework-*` channels get their own section below because the theme layer and the runtime write them. ``` /* Custom plugin CSS: reference public variables freely. */ .stat-card { padding: var(--gap); border-radius: var(--rounded); } ``` ``` // JavaScript: read the same variable at runtime. var gap = TRMNLPaint.cssVar("--gap", { el: "my-chart" }); ``` ### Framework Channels and Slots The `--framework-*` families carry the current paint between themes, components, and the runtime. They are public names: the minifier renames only the internal families below, so a theme, a plugin stylesheet, or `cssVar()` can read them in any build. - **Semantic channels:** `--framework-semantic-{channel}-*` for canvas, surface, backdrop, fill-strong, fill-muted, fill-soft, text-primary, text-secondary, text-inverse, stroke-contrast, border-strong, border-muted, and icon. A channel carries its whole paint: backgrounds have `-bg-color` and `-bg-image`, text adds `-text-under` and `-text-clip` to its color and image, and borders carry `-border-color`, `-border-image`, and `-border-size`. - **Role aliases:** one flat color per role, resolved from the matching channel: `--framework-canvas-bg`, `--framework-surface-bg`, `--framework-backdrop-bg`, `--framework-text-primary`, `--framework-text-secondary`, `--framework-text-inverse`, `--framework-border-strong`, `--framework-border-muted`, `--framework-outline-strong`, `--framework-stroke-contrast`, `--framework-fill-strong`, `--framework-fill-muted`, `--framework-fill-soft`. They drop the dither image, so use them only where a single color is what you want. - **Component slots:** `--framework-slot-{slot}-*`, one group per component surface (title bar, screen backdrop, item meta, progress, table, label). A slot repaints one surface without moving a whole channel. See [Theme Slots](/framework/docs/3.4/theme_slots) . - **Chart ramp:** `--framework-chart-series-{i}-color` and `--framework-chart-series-{i}-image` for series indexes 0 to 15, plus `--framework-chart-series-span` for the number of entries JavaScript spreads series across. See [Painting Charts](/framework/docs/3.4/paint_charts) . - **Border render contract:** `--framework-border-render-*` (`width`, `height`, `view-box`, `stroke`, `path-1`, `path-2`, `color-1`, `color-2`): the SVG drawing instructions the framework declares for each border, which the Paint API copies into charts. See [Painting Borders](/framework/docs/3.4/paint_borders) . - **Icon source:** `--framework-icon-src` holds the URL an `image--adaptive` element is masked with. The runtime writes it as an inline style, and markup can set it directly. See [Image](/framework/docs/3.4/image) . - **Typeface:** `--framework-typeface` names the font a `typeface` container puts on its text. Markup sets it. See [Custom Typefaces](/framework/docs/3.4/custom_typefaces) . Themes write these through the slot mixins, where one call fills a whole channel or slot. Setting a single name by hand leaves the rest of its group behind, so plugin markup sets only `--framework-icon-src` and `--framework-typeface`. ### Internal Variables Six name families are implementation details. The minified bundle (`plugins.min.css`, the file production serves) renames them to `--_tn*` names. Do not read or set them; they can change or disappear in any release. - `--_*`: module-private helpers scoped to one component's rules. - `--framework-internal-*`: plumbing that carries resolved values between framework layers. - `--tile-*`: the generated dither tile assets. - `--bline-{n}`: deduplicated border line gradients the border pipeline references. - `--border-*`: the border pipeline, except the theme-contract names below. - `--tn-*`: engine plumbing, except `--tn-text-stroke-color`, `--tn-text-stroke-width` and `--tn-text-stroke-radius`, which the runtime reads. The development build and the readable `plugins.css` keep the source names, so you will see them in devtools. Treat them as off-limits anyway. ### Framework-owned Paint Variables The `--bg-*` and `--text-*` families keep their names in every build, but the framework's rendering owns them. They encode how each device prints: dithered down to the panel's inks where needed, exact colors on full color. Three border families survive the rename because the theme contract preserves them: the numbered slots `--border-1-h-*` through `--border-7-v-*`, the `--border-token-*` names a shipped theme references, and `--border-line-dark` / `--border-line-light`. Everything else under `--border-*`, including all the `--border-step-*` names, is renamed in released bundles. Never set them from a plugin or a theme; the theme linter rejects any theme that tries. To consume the resolved paint from JavaScript, go through [Paint API](/framework/docs/3.4/paint_api) instead of reading them raw. Previous [ ## Theme Slots Every part of a screen a theme can recolor, from whole-screen colors down to single components, utilities, borders, and chart series ](/framework/docs/3.4/theme_slots) Next [ ## Colors Complete palette definition: grayscale, chromatic hues, and semantic roles ](/framework/docs/3.4/colors) --- # Colors The Colors system defines the complete palette for the framework: grayscale, chromatic hues, and semantic roles (primary, success, error, warning). Use these tokens with bg--, text--, and other utilities. See Background and Text Color for usage examples. ### Grayscale Palette The complete range of grayscale shades available in the framework, from pure black to pure white. These tokens use the same lightness scale as the chromatic palette. black black 10 gray-10 15 gray-15 20 gray-20 25 gray-25 30 gray-30 35 gray-35 40 gray-40 45 gray-45 50 gray-50 55 gray-55 60 gray-60 65 gray-65 70 gray-70 75 gray-75 white white **Dark Mode Notice:** The color palette appears inverted because dark mode remaps the framework tokens: black and white swap, grays and chromatic steps mirror. Images are not affected unless they opt in via `image--adaptive`. Themed screens are exempt from dark mode entirely. ### Chromatic Palette The framework offers 10 hues × 14 lightness steps (red, orange, yellow, lime, green, cyan, blue, violet, purple, pink), using the same steps as grayscale (10, 15, 20, …, 75). Select a color device (e.g. Inky Impression 7.3, Tidbyt) from the Device Preview above to see these colors in action. Use the Raw Colors / Preview Colors toggle to compare full-bright tokens vs device-representative rendering. red red-10 red-15 red-20 red-25 red-30 red-35 red-40 red red-45 red-50 red-55 red-60 red-65 red-70 red-75 orange orange-10 orange-15 orange-20 orange-25 orange-30 orange-35 orange-40 orange orange-45 orange-50 orange-55 orange-60 orange-65 orange-70 orange-75 yellow yellow-10 yellow-15 yellow-20 yellow-25 yellow-30 yellow-35 yellow-40 yellow yellow-45 yellow-50 yellow-55 yellow-60 yellow-65 yellow-70 yellow-75 lime lime-10 lime-15 lime-20 lime-25 lime-30 lime-35 lime-40 lime lime-45 lime-50 lime-55 lime-60 lime-65 lime-70 lime-75 green green-10 green-15 green-20 green-25 green-30 green-35 green-40 green green-45 green-50 green-55 green-60 green-65 green-70 green-75 cyan cyan-10 cyan-15 cyan-20 cyan-25 cyan-30 cyan-35 cyan-40 cyan cyan-45 cyan-50 cyan-55 cyan-60 cyan-65 cyan-70 cyan-75 blue blue-10 blue-15 blue-20 blue-25 blue-30 blue-35 blue-40 blue blue-45 blue-50 blue-55 blue-60 blue-65 blue-70 blue-75 violet violet-10 violet-15 violet-20 violet-25 violet-30 violet-35 violet-40 violet violet-45 violet-50 violet-55 violet-60 violet-65 violet-70 violet-75 purple purple-10 purple-15 purple-20 purple-25 purple-30 purple-35 purple-40 purple purple-45 purple-50 purple-55 purple-60 purple-65 purple-70 purple-75 pink pink-10 pink-15 pink-20 pink-25 pink-30 pink-35 pink-40 pink pink-45 pink-50 pink-55 pink-60 pink-65 pink-70 pink-75 **Device Preview tip:** Use the Device Preview (top right) to switch between grayscale and color palettes, then toggle Raw Colors / Preview Colors to compare full-bright and device-accurate previews. Try Inky Impression 7.3 (color-7a) or Tidbyt (color-24bit) to see chromatic colors. [Color Palettes](/framework/docs/3.4/color_palettes) lists the palette classes and how each token resolves on them. ### Semantic Colors Semantic color roles map meaning to base hues: use `bg--primary`, `text--success`, and similar utilities for intent-based styling. These alias underlying tokens (e.g. primary → blue) and inherit all device/bit-depth behavior. Themes repaint these roles through the semantic channels [Theme Slots](/framework/docs/3.4/theme_slots) (`theme-slots.semantic-bg`, `theme-slots.semantic-text`), never by setting `--color-{role}` directly. Primary bg--primary → blue Success bg--success → green Error bg--error → red Warning bg--warning → orange | Role | Underlying Token | Use | | --- | --- | --- | | primary | `blue` | Main actions, accents | | success | `green` | Confirmations, positive states | | error | `red` | Errors, destructive actions | | warning | `orange` | Cautions, alerts | ### Token Syntax Apply these color tokens with utility prefixes. The [Background](/framework/docs/3.4/background) page documents `bg--{token}`; the [Text Color](/framework/docs/3.4/text_color) page documents `text--{token}`. Other utilities (border, outline, etc.) may use the same tokens where applicable. | Utility | Example | Use | | --- | --- | --- | | bg-- | `bg--gray-50`, `bg--red-40` | Background colors | | text-- | `text--gray-50`, `text--blue-60`, `text--success` | Text colors | | bg-- / text-- (semantic) | `bg--primary`, `text--error`, `text--success` | Semantic roles (primary, success, error, warning) | Three naming schemes make up the token set. A fourth, the legacy gray-1 to gray-7 names, is covered under Backward Compatibility below. - **Grayscale:** `black`, `gray-10` through `gray-75`, and `white`. - **Chromatic:** `{hue}` for the pure color, or `{hue}-{step}` for a step on that hue's ladder (e.g. red-50, blue-40). - **Semantic:** `primary`, `success`, `error`, and `warning` alias the base hues. ### Backward Compatibility For backward compatibility, the original shade names (`gray-1` through `gray-7`) are still supported but deprecated. These map to equivalent extended shades: gray-1 (deprecated) gray-1 gray-2 (deprecated) gray-2 gray-3 (deprecated) gray-3 gray-4 (deprecated) gray-4 gray-5 (deprecated) gray-5 gray-6 (deprecated) gray-6 gray-7 (deprecated) gray-7 ```
Gray 1
Gray 2
Gray 10
Gray 20
``` ### Resolved Color Manifest Releases publish `framework_colors.resolved.json`, this palette resolved to plain data. It ships with 3.2.0 and every release after it, plus 3.1.2. It carries the hex value of every chromatic token, the shade steps, the limited color palettes, and the fallback maps that pick a gray for every color token. The manifest is what the framework's own generators read: the Sass color tokens and the dither bitmaps are built from it, and the TRMNL server loads it for its palette definitions. Read it when you quantize colors outside the browser, so your renderer uses the framework's numbers instead of a copy. Download it from the asset list on [Releases](/framework/releases), at `/framework/colors/{version}/framework_colors.resolved.json`. The zip for those releases carries the same file at its root. ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Semantic | | `--black` | #000000 | - | - | - | | `--color-error` | var(--red) | - | - | - | | `--color-primary` | var(--blue) | - | - | - | | `--color-success` | var(--green) | - | - | - | | `--color-warning` | var(--orange) | - | - | - | | `--white` | #FFFFFF | - | - | - | | Grayscale | | `--gray-10` | #111111 | - | - | - | | `--gray-15` | #222222 | - | - | - | | `--gray-20` | #333333 | - | - | - | | `--gray-25` | #444444 | - | - | - | | `--gray-30` | #555555 | - | - | - | | `--gray-35` | #666666 | - | - | - | | `--gray-40` | #777777 | - | - | - | | `--gray-45` | #888888 | - | - | - | | `--gray-50` | #999999 | - | - | - | | `--gray-55` | #AAAAAA | - | - | - | | `--gray-60` | #BBBBBB | - | - | - | | `--gray-65` | #CCCCCC | - | - | - | | `--gray-70` | #DDDDDD | - | - | - | | `--gray-75` | #EEEEEE | - | - | - | | Legacy Grayscale | | `--gray-1` | #111111 | - | - | - | | `--gray-2` | #333333 | - | - | - | | `--gray-3` | #555555 | - | - | - | | `--gray-4` | #777777 | - | - | - | | `--gray-5` | #999999 | - | - | - | | `--gray-6` | #BBBBBB | - | - | - | | `--gray-7` | #DDDDDD | - | - | - | ### Related APIs #### Reading the palette from JavaScript The `bg(token, { el })` resolver returns the exact paint a `bg--{token}` utility would apply, as a canonical Fill read from the live cascade with bit depth, dark mode, and theme resolved. Apply it to canvases, SVGs, or chart options. See [Painting Colors](/framework/docs/3.4/paint_colors) for every resolver and the Fill shape. ``` var fill = TRMNLPaint.bg("red-55", { el: "my-chart" }); ``` #### Remapping the palette from a theme A theme can re-point the raw palette utilities (`bg--*`, `text--*`, `text-stroke--*`) at its own hue, in bulk or one token at a time, so plugin markup that names gray tokens follows the theme without edits. See [Theme Slots](/framework/docs/3.4/theme_slots) for the remap mixins and the two shipped examples. ``` @include theme-slots.utility-remap-grayscale("red", $side: "bright"); ``` Previous [ ## CSS Variables The CSS variable contract: which families are public, which are internal, and who reads, changes, and generates them ](/framework/docs/3.4/variables_api) Next [ ## Color Palettes Every palette a screen can carry: grayscale tiers, limited ink sets, and full color, with the class each one maps to ](/framework/docs/3.4/color_palettes) --- # Color Palettes A palette tells a screen which inks its panel can print. Grayscale panels pick from four grayscale palettes, and five limited color palettes dither every framework color down to the panel's fixed inks. On 12-bit and 24-bit panels, screen--color-full paints every color exactly as defined. ### Grayscale Palettes Each of the four grayscale palettes maps onto a bit-depth class on the [Screen](/framework/docs/3.4/screen) element, so a grayscale screen carries `screen--1bit`, `screen--2bit`, or `screen--4bit` instead of a palette class. - `bw` (2 levels) renders as `screen--1bit`. Every color renders as a black and white dither pattern. - `gray-4` (4 grays) renders as `screen--2bit`. - `gray-16` (16 grays) renders as `screen--4bit`, where the grays paint as solids. - `gray-256` renders as `screen--4bit` too. It is a delivery format, not a fifth tier. #### Two Ways to Deliver 16 Levels `gray-16` and `gray-256` share a class because they share the glass: 16 panel levels either way. They differ in who reduces the image. - `gray-16`: the platform posterizes and dithers to those 16 levels before it sends the screen. - `gray-256`: the platform sends a smooth 8-bit PNG and the device quantizes it to its own 16 levels. The registry name for `gray-256`, Smooth Grayscale (device-quantized), describes that delivery, not 256 displayable grays. Build against `screen--4bit`. ### Color Palette Classes Every color panel the framework supports has a palette class on the [Screen](/framework/docs/3.4/screen) element. Five classes cover the limited-ink e-paper panels; `screen--color-full` covers displays that render arbitrary color. A device can support more than one palette. Inky Impression 7.3 carries six: black and white, 7-color, 6-color, 4-ink B/W/R/Y, 3-ink B/W/R, and 3-ink B/W/Y. The selected mode picks the class. | Class | Inks | Panels | | --- | --- | --- | | `screen--color-3bwr` | Black, white, red | Waveshare 7.5" B/W/R | | `screen--color-3bwy` | Black, white, yellow | No native B/W/Y panel yet, but nine profiles offer it as a mode (Inky Impression 7.3 and 13.3, Seeed E1002 and E1004, and others) | | `screen--color-4bwry` | Black, white, red, yellow | TRMNL OG (B/W/R/Y), Waveshare 7.5" B/W/R/Y | | `screen--color-6a` | Black, white, red, green, blue, yellow | Inky Impression 7.3 and 13.3, Seeed E1002 and E1004 | | `screen--color-7a` | Black, white, red, green, blue, yellow, orange | Inkplate 6COLOR, Inky Impression 7.3 | | `screen--color-full` | Whatever the display can show | 12-bit and 24-bit displays: Onyx BOOX Nova Air C, Tidbyt | ### Full Color Two palettes share `screen--color-full`. - `color-24bit` (16.7 million colors) covers LCD, OLED, browser, and virtual screens. The render ships as lossless sRGB: no remap, no dithering, no posterize. - `color-12bit` (4096 colors) covers Kaleido-class color e-paper. It shares the class, and the platform posterizes the render to those 4096 colors. Both report `--framework-bit-depth: 12`, the lower of the two, so code reading the depth never over-promises what a Kaleido panel prints. Every color the framework paints is sRGB. ```
Delayed
``` ### Every Palette and Its Class Every palette maps to one framework class. That class also reports its paint depth as `--framework-bit-depth`, which [Paint API](/framework/docs/3.4/paint_api) reads. | Palette | Registry name | Framework class | Published depth | | --- | --- | --- | --- | | `bw` | Black & White (1-bit) | `screen--1bit` | 1 | | `gray-4` | 4 Grays (2-bit) | `screen--2bit` | 2 | | `gray-16` | 16 Grays (4-bit) | `screen--4bit` | 4 | | `gray-256` | Smooth Grayscale (device-quantized) | `screen--4bit` | 4 | | `color-3bwr` | Color (3 colors) | `screen--color-3bwr` | 4 | | `color-3bwy` | Color (3 colors) | `screen--color-3bwy` | 4 | | `color-4bwry` | Color (4 colors) | `screen--color-4bwry` | 4 | | `color-6a` | Color (6 colors) | `screen--color-6a` | 4 | | `color-7a` | Color (7 colors) | `screen--color-7a` | 4 | | `color-12bit` | Color (4096 colors) | `screen--color-full` | 12 | | `color-24bit` | Color (16777216 colors) | `screen--color-full` | 12 | The depth describes how the screen paints, not the panel's raw capability. Limited palettes print solid inks the way 4-bit prints solid grays, so they report 4. **The numeric variants stop at `4bit:`.** Grayscale glass stops at 16 levels, so `1bit:`, `2bit:`, and `4bit:` cover every grayscale palette. Color has its own classes and no numeric prefix; see [Rendering Modes](/framework/docs/3.4/rendering_modes) . ### How Colors Print per Palette A limited palette prints solid inks and nothing in between, so the framework dithers every color in the [Colors](/framework/docs/3.4/colors) palette down to the panel's inks. Your markup does not change: the same `bg--` and `text--` utilities print differently per palette. - **Grays dither in black and white.** On every limited palette, `bg--gray-50` is a 1-bit pattern rather than a solid gray. - **Hues snap to the closest ink.** On `screen--color-3bwr` every hue prints red, so blue and green stop reading as separate categories. `screen--color-7a` carries its own orange ink. - **Shade steps dither the ink.** `bg--red-20` mixes red with black and `bg--red-60` mixes red with white, which is how one ink covers a ladder of shades. - **Full color skips dithering.** `screen--color-full` paints every token as a solid color. **Device Preview tip:** Pick a color device in the Device Preview (top right) to see the palette applied to every demo on this site. ### Applying a Palette The palette class sits on the screen, next to the device class; grayscale palettes put their bit-depth class in the same place. Who writes it depends on where the screen renders. You don't write the palette class. The platform renders your layout against the device's own profile, so one plugin covers 1-bit panels and 7-color panels alike. You add the palette class to the screen yourself, next to the device class, and the framework repaints every token for that ink set. ```
Northbound On time
``` ```
Northbound On time
``` ### Preview Modes Panel inks are darker and flatter than the same hex on a monitor. Two modifiers repaint a limited palette with device-accurate values so a preview on a screen matches the print. - `screen--preview-colors`: swaps the full-bright inks for the muted ones the panel actually prints. - `screen--preview-white-limited`: mutes white to the panel's off-white. Add it alongside `screen--preview-colors` for panels whose white is not paper white. The Device Preview's Raw Colors and Preview Colors toggle sets both for you. Rendered output on a device uses neither. ```
...
``` ### Targeting a Palette in SCSS Scope your own SCSS to a palette with the same mixins the framework uses. The mixin ids drop the `color-` prefix the class carries: `'3bwr'`, `'3bwy'`, `'4bwry'`, `'6a'`, and `'7a'`. - `for-color-palette($id)`: scopes to one limited palette. - `for-color-full`: scopes to full-color displays. - `for-preview-color-palette($id)`: scopes to the device-accurate preview of one limited palette. - `for-1bit`, `for-2bit`, `for-4bit`: scope to a grayscale tier. ``` @use 'framework/mixins' as trmnl; .legend-swatch { width: 12px; height: 12px; // Three-ink panels print every hue in the same accent, so carry the // distinction in shape instead of color. @include trmnl.for-color-palette('3bwr') { border-radius: 50%; } @include trmnl.for-color-full { border-radius: 0; } } ``` The full mixin surface is documented on [Sass Mixins](/framework/docs/3.4/sass_mixins) , and [Custom Devices](/framework/docs/3.4/sass_devices) covers adding a device profile that selects a palette. ### Palettes from JavaScript TRMNLPaint returns what CSS would paint on the current palette, so JavaScript needs no palette handling of its own. Ask on a 7-color screen and the answer carries that palette's dither; the same call on a full-color screen returns a solid. See [Paint API](/framework/docs/3.4/paint_api) . Previous [ ## Colors Complete palette definition: grayscale, chromatic hues, and semantic roles ](/framework/docs/3.4/colors) Next [ ## Tokens Complete CSS variable reference with root defaults, density, and bit-depth overrides ](/framework/docs/3.4/tokens) --- # Tokens The Tokens reference lists every Framework CSS variable from `_variables_root.scss` and display overrides in `_variables_overrides.scss`. Use it to understand defaults, 2-bit visual/layout behavior, high-density typography, and 4-bit-and-up scaling. ### How To Read This Table Each row is a CSS custom property token. `Root` comes from `_variables_root.scss`. `2-bit`, `density 2x`, and `4-bit and up` come from mixins in `_variables_overrides.scss`. ### Palette | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Semantic | | `--black` | #000000 | - | - | - | | `--color-error` | var(--red) | - | - | - | | `--color-primary` | var(--blue) | - | - | - | | `--color-success` | var(--green) | - | - | - | | `--color-warning` | var(--orange) | - | - | - | | `--white` | #FFFFFF | - | - | - | | Grayscale | | `--gray-10` | #111111 | - | - | - | | `--gray-15` | #222222 | - | - | - | | `--gray-20` | #333333 | - | - | - | | `--gray-25` | #444444 | - | - | - | | `--gray-30` | #555555 | - | - | - | | `--gray-35` | #666666 | - | - | - | | `--gray-40` | #777777 | - | - | - | | `--gray-45` | #888888 | - | - | - | | `--gray-50` | #999999 | - | - | - | | `--gray-55` | #AAAAAA | - | - | - | | `--gray-60` | #BBBBBB | - | - | - | | `--gray-65` | #CCCCCC | - | - | - | | `--gray-70` | #DDDDDD | - | - | - | | `--gray-75` | #EEEEEE | - | - | - | | Legacy Grayscale | | `--gray-1` | #111111 | - | - | - | | `--gray-2` | #333333 | - | - | - | | `--gray-3` | #555555 | - | - | - | | `--gray-4` | #777777 | - | - | - | | `--gray-5` | #999999 | - | - | - | | `--gray-6` | #BBBBBB | - | - | - | | `--gray-7` | #DDDDDD | - | - | - | ### Scaling | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | | `--device-ui-scale` | 1 | - | - | - | | `--modifier-scale` | 1 | - | - | - | | `--modifier-text-scale` | 1 | - | - | - | | `--text-ui-scale` | 1 | - | - | - | | `--ui-scale` | 1 | - | - | - | ### Description | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--description-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--description-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--description-font-smoothing` | none | none | auto | - | | `--description-font-weight` | 400 | 400 | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-line-height` | 1 | 1 | 1.2 | - | | Large | | `--description-large-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--description-large-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--description-large-font-smoothing` | none | none | auto | - | | `--description-large-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-large-line-height` | 1.25 | 1.25 | 1.2 | - | | Xlarge | | `--description-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--description-xlarge-font-size` | calc(21px \* var(--text-ui-scale)) | - | calc(21px \* var(--text-ui-scale)) | - | | `--description-xlarge-font-smoothing` | auto | - | auto | - | | `--description-xlarge-font-weight` | 500 | - | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--description-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--description-xxlarge-font-size` | calc(24px \* var(--text-ui-scale)) | - | calc(24px \* var(--text-ui-scale)) | - | | `--description-xxlarge-font-smoothing` | auto | - | auto | - | | `--description-xxlarge-font-weight` | 475 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-xxlarge-line-height` | 1.2 | - | 1.2 | - | ### Other | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--font-base-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--font-base-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--font-base-font-smoothing` | none | none | auto | - | | `--font-base-line-height` | 1.25 | 1.25 | calc(22px \* var(--text-ui-scale)) | - | | `--font-giga-font-family` | "Inter Variable", Inter | - | - | - | | `--font-giga-font-size` | calc(96px \* var(--text-ui-scale)) | - | - | - | | `--font-giga-font-smoothing` | auto | - | - | - | | `--font-giga-line-height` | calc(108px \* var(--text-ui-scale)) | - | - | - | | `--font-large-font-family` | "BlockKie" | "BlockKie" | #{meta.inspect($family)} | - | | `--font-large-font-size` | calc(26px \* var(--text-ui-scale)) | calc(26px \* var(--text-ui-scale)) | calc(21px \* var(--text-ui-scale)) | - | | `--font-large-font-smoothing` | none | none | auto | - | | `--font-large-line-height` | 1 | 1 | 1.2 | - | | `--font-mega-font-family` | "Inter Variable", Inter | - | - | - | | `--font-mega-font-size` | calc(74px \* var(--text-ui-scale)) | - | - | - | | `--font-mega-font-smoothing` | auto | - | - | - | | `--font-mega-line-height` | calc(86px \* var(--text-ui-scale)) | - | - | - | | `--font-peta-font-family` | "Inter Variable", Inter | - | - | - | | `--font-peta-font-size` | calc(170px \* var(--text-ui-scale)) | - | - | - | | `--font-peta-font-smoothing` | auto | - | - | - | | `--font-peta-line-height` | calc(180px \* var(--text-ui-scale)) | - | - | - | | `--font-small-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--font-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--font-small-font-smoothing` | none | none | auto | - | | `--font-small-line-height` | 1 | 1 | calc(18px \* var(--text-ui-scale)) | - | | `--font-tera-font-family` | "Inter Variable", Inter | - | - | - | | `--font-tera-font-size` | calc(128px \* var(--text-ui-scale)) | - | - | - | | `--font-tera-font-smoothing` | auto | - | - | - | | `--font-tera-line-height` | calc(128px \* var(--text-ui-scale)) | - | - | - | | `--font-xlarge-font-family` | "Inter Variable", Inter | - | - | - | | `--font-xlarge-font-size` | calc(26px \* var(--text-ui-scale)) | - | - | - | | `--font-xlarge-font-smoothing` | auto | - | - | - | | `--font-xlarge-line-height` | calc(29px \* var(--text-ui-scale)) | - | - | - | | `--font-xxlarge-font-family` | "Inter Variable", Inter | - | - | - | | `--font-xxlarge-font-size` | calc(38px \* var(--text-ui-scale)) | - | - | - | | `--font-xxlarge-font-smoothing` | auto | - | - | - | | `--font-xxlarge-line-height` | calc(42px \* var(--text-ui-scale)) | - | - | - | | `--font-xxxlarge-font-family` | "Inter Variable", Inter | - | - | - | | `--font-xxxlarge-font-size` | calc(58px \* var(--text-ui-scale)) | - | - | - | | `--font-xxxlarge-font-smoothing` | auto | - | - | - | | `--font-xxxlarge-line-height` | calc(70px \* var(--text-ui-scale)) | - | - | - | ### Layout | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--full-h` | calc(var(--screen-h) - var(--gap) \* 2) | - | - | - | | `--full-w` | calc(var(--screen-w) - var(--gap) \* 2) | - | - | - | | `--half_horizontal-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--half_horizontal-w` | calc((var(--screen-w) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-h` | calc((var(--screen-h) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--quadrant-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--quadrant-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--screen-h` | 480px | - | - | - | | `--screen-h-original` | 480px | - | - | - | | `--screen-w` | 800px | - | - | - | | `--screen-w-original` | 800px | - | - | - | ### Spacing | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--gap` | 10px | - | - | - | | `--gap-large` | 20px | - | - | - | | `--gap-medium` | 16px | - | - | - | | `--gap-scale` | 1 | - | - | - | | `--gap-small` | 7px | - | - | - | | `--gap-xlarge` | 30px | - | - | - | | `--gap-xsmall` | 5px | - | - | - | | `--gap-xxlarge` | 40px | - | - | - | | `--list-gap-small` | 8px | - | - | - | ### Item | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--item-index-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--item-index-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--item-index-font-smoothing` | none | none | auto | - | | `--item-index-font-weight` | 400 | 400 | clamp(100, calc(600 + var(--framework-font-weight-shift, 0)), 900) | - | | `--item-index-line-height` | 1 | 1 | 1 | - | | `--item-meta-width` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale)) | - | calc(10px \* var(--ui-scale)) | ### Label | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--label-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--label-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--label-font-smoothing` | none | none | auto | - | | `--label-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-line-height` | 1.25 | 1.25 | 1.25 | - | | Small | | `--label-small-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--label-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--label-small-font-smoothing` | none | none | auto | - | | `--label-small-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-small-line-height` | 1 | 1 | 1 | - | | Large | | `--label-large-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-large-font-size` | calc(21px \* var(--text-ui-scale)) | - | calc(21px \* var(--text-ui-scale)) | - | | `--label-large-font-smoothing` | auto | - | auto | - | | `--label-large-font-weight` | 500 | - | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-large-line-height` | 1.2 | - | 1.2 | - | | Xlarge | | `--label-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-xlarge-font-size` | calc(26px \* var(--text-ui-scale)) | - | calc(26px \* var(--text-ui-scale)) | - | | `--label-xlarge-font-smoothing` | auto | - | auto | - | | `--label-xlarge-font-weight` | 475 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--label-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-xxlarge-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--label-xxlarge-font-smoothing` | auto | - | auto | - | | `--label-xxlarge-font-weight` | 450 | - | clamp(100, calc(450 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-xxlarge-line-height` | 1.2 | - | 1.2 | - | ### Progress | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--progress-bar-height` | calc(24px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-large` | calc(32px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-small` | calc(12px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-xsmall` | calc(6px \* var(--ui-scale)) | - | - | - | | `--progress-bar-radius` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | - | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | | `--progress-dot-size` | calc(16px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-large` | calc(20px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-small` | calc(12px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-xsmall` | calc(8px \* var(--ui-scale)) | - | - | - | ### Rich Text | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--richtext-content-max-width` | calc(640px \* var(--ui-scale)) | - | - | - | | `--richtext-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--richtext-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--richtext-font-smoothing` | none | none | auto | - | | `--richtext-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-line-height` | calc(22px \* var(--text-ui-scale)) | calc(22px \* var(--text-ui-scale)) | calc(22px \* var(--text-ui-scale)) | - | | Small | | `--richtext-small-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--richtext-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--richtext-small-font-smoothing` | none | none | auto | - | | `--richtext-small-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-small-line-height` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(18px \* var(--text-ui-scale)) | - | | Large | | `--richtext-large-font-family` | "BlockKie" | "BlockKie" | #{meta.inspect($family)} | - | | `--richtext-large-font-size` | calc(26px \* var(--text-ui-scale)) | calc(26px \* var(--text-ui-scale)) | calc(21px \* var(--text-ui-scale)) | - | | `--richtext-large-font-smoothing` | none | none | auto | - | | `--richtext-large-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-large-line-height` | 1 | 1 | 1.2 | - | | Xlarge | | `--richtext-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xlarge-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--richtext-xlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xlarge-font-weight` | 425 | - | clamp(100, calc(425 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--richtext-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xxlarge-font-size` | calc(35px \* var(--text-ui-scale)) | - | calc(35px \* var(--text-ui-scale)) | - | | `--richtext-xxlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xxlarge-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xxlarge-line-height` | 1.2 | - | 1.2 | - | | Xxxlarge | | `--richtext-xxxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xxxlarge-font-size` | calc(40px \* var(--text-ui-scale)) | - | calc(40px \* var(--text-ui-scale)) | - | | `--richtext-xxxlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xxxlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xxxlarge-line-height` | 1.2 | - | 1.2 | - | ### Rounded | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--rounded` | 10px | - | - | - | | `--rounded-full` | 9999px | - | - | - | | `--rounded-large` | 20px | - | - | - | | `--rounded-medium` | 15px | - | - | - | | `--rounded-none` | 0px | - | - | - | | `--rounded-small` | 7px | - | - | - | | `--rounded-xlarge` | 25px | - | - | - | | `--rounded-xsmall` | 5px | - | - | - | | `--rounded-xxlarge` | 30px | - | - | - | ### Table | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--table-tbody-height` | calc(46px \* var(--ui-scale)) | - | - | - | | `--table-thead-height` | calc(36px \* var(--ui-scale)) | - | - | - | | Xsmall | | `--table-xsmall-tbody-height` | calc(22px \* var(--ui-scale)) | - | - | - | | `--table-xsmall-thead-height` | calc(18px \* var(--ui-scale)) | - | - | - | | Small | | `--table-small-tbody-height` | calc(31px \* var(--ui-scale)) | - | - | - | | `--table-small-thead-height` | calc(24px \* var(--ui-scale)) | - | - | - | | Large | | `--table-large-tbody-height` | calc(56px \* var(--ui-scale)) | - | - | - | | `--table-large-thead-height` | calc(44px \* var(--ui-scale)) | - | - | - | | Xlarge | | `--table-xlarge-tbody-height` | calc(72px \* var(--ui-scale)) | - | - | - | | `--table-xlarge-thead-height` | calc(56px \* var(--ui-scale)) | - | - | - | ### Title Bar | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--title-bar-border-radius` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | - | calc(10px \* var(--ui-scale) \* var(--framework-layout-corner-factor, 1)) | | `--title-bar-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--title-bar-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-bar-font-smoothing` | none | none | auto | - | | `--title-bar-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-bar-height` | calc(40px \* var(--ui-scale)) | calc(40px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(40px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-image-height` | calc(28px \* var(--ui-scale)) | calc(28px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(28px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-line-height` | 1 | 1 | calc(22px \* var(--text-ui-scale)) | - | | `--title-bar-padding-top` | calc(5px \* var(--ui-scale)) | calc(5px \* var(--ui-scale)) | 0px | 0px | | `--title-bar-text-stroke-width` | calc(3.5px \* var(--ui-scale)) | calc(3.5px \* var(--ui-scale)) | calc(2px \* var(--ui-scale)) | calc(2px \* var(--ui-scale)) | | Small | | `--title-bar-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-bar-small-height` | calc(32px \* var(--ui-scale)) | calc(32px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(32px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-small-image-height` | calc(24px \* var(--ui-scale)) | calc(24px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(24px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | ### Title | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--title-font-family` | "BlockKie" | "BlockKie" | #{meta.inspect($family)} | - | | `--title-font-size` | calc(26px \* var(--text-ui-scale)) | calc(26px \* var(--text-ui-scale)) | calc(21px \* var(--text-ui-scale)) | - | | `--title-font-smoothing` | none | none | auto | - | | `--title-font-weight` | 400 | 400 | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-line-height` | 1 | 1 | 1.2 | - | | Small | | `--title-small-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--title-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-small-font-smoothing` | none | none | auto | - | | `--title-small-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-small-line-height` | 1 | 1 | 1.2 | - | | Large | | `--title-large-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-large-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--title-large-font-smoothing` | auto | - | auto | - | | `--title-large-font-weight` | 425 | - | clamp(100, calc(425 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-large-line-height` | 1.2 | - | 1.2 | - | | Xlarge | | `--title-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-xlarge-font-size` | calc(35px \* var(--text-ui-scale)) | - | calc(35px \* var(--text-ui-scale)) | - | | `--title-xlarge-font-smoothing` | auto | - | auto | - | | `--title-xlarge-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--title-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-xxlarge-font-size` | calc(40px \* var(--text-ui-scale)) | - | calc(40px \* var(--text-ui-scale)) | - | | `--title-xxlarge-font-smoothing` | auto | - | auto | - | | `--title-xxlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-xxlarge-line-height` | 1.2 | - | 1.2 | - | ### Value | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--value-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--value-font-size` | calc(38px \* var(--text-ui-scale)) | - | calc(38px \* var(--text-ui-scale)) | - | | `--value-font-smoothing` | auto | - | auto | - | | `--value-font-weight` | 450 | - | clamp(100, calc(450 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-line-height` | calc(42px \* var(--text-ui-scale)) | - | calc(42px \* var(--text-ui-scale)) | - | | Xxsmall | | `--value-xxsmall-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--value-xxsmall-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--value-xxsmall-font-smoothing` | none | none | auto | - | | `--value-xxsmall-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxsmall-line-height` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(14px \* var(--text-ui-scale)) | - | | Xsmall | | `--value-xsmall-font-size` | calc(20px \* var(--text-ui-scale)) | - | calc(20px \* var(--text-ui-scale)) | - | | `--value-xsmall-font-weight` | 600 | - | clamp(100, calc(600 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xsmall-line-height` | calc(24px \* var(--text-ui-scale)) | - | calc(24px \* var(--text-ui-scale)) | - | | Small | | `--value-small-font-size` | calc(26px \* var(--text-ui-scale)) | - | calc(26px \* var(--text-ui-scale)) | - | | `--value-small-font-weight` | 500 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-small-line-height` | calc(29px \* var(--text-ui-scale)) | - | calc(29px \* var(--text-ui-scale)) | - | | Large | | `--value-large-font-size` | calc(58px \* var(--text-ui-scale)) | - | calc(58px \* var(--text-ui-scale)) | - | | `--value-large-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-large-line-height` | calc(70px \* var(--text-ui-scale)) | - | calc(70px \* var(--text-ui-scale)) | - | | Xlarge | | `--value-xlarge-font-size` | calc(74px \* var(--text-ui-scale)) | - | calc(74px \* var(--text-ui-scale)) | - | | `--value-xlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xlarge-line-height` | calc(86px \* var(--text-ui-scale)) | - | calc(86px \* var(--text-ui-scale)) | - | | Xxlarge | | `--value-xxlarge-font-size` | calc(96px \* var(--text-ui-scale)) | - | calc(96px \* var(--text-ui-scale)) | - | | `--value-xxlarge-font-weight` | 350 | - | clamp(100, calc(350 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxlarge-line-height` | calc(108px \* var(--text-ui-scale)) | - | calc(108px \* var(--text-ui-scale)) | - | | Xxxlarge | | `--value-xxxlarge-font-size` | calc(128px \* var(--text-ui-scale)) | - | calc(128px \* var(--text-ui-scale)) | - | | `--value-xxxlarge-font-weight` | 300 | - | clamp(100, calc(300 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxxlarge-line-height` | calc(128px \* var(--text-ui-scale)) | - | calc(128px \* var(--text-ui-scale)) | - | | Mega | | `--value-mega-font-size` | calc(170px \* var(--text-ui-scale)) | - | calc(170px \* var(--text-ui-scale)) | - | | `--value-mega-font-weight` | 275 | - | clamp(100, calc(275 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-mega-line-height` | calc(180px \* var(--text-ui-scale)) | - | calc(180px \* var(--text-ui-scale)) | - | | Giga | | `--value-giga-font-size` | calc(220px \* var(--text-ui-scale)) | - | calc(220px \* var(--text-ui-scale)) | - | | `--value-giga-font-weight` | 250 | - | clamp(100, calc(250 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-giga-line-height` | calc(230px \* var(--text-ui-scale)) | - | calc(230px \* var(--text-ui-scale)) | - | | Tera | | `--value-tera-font-size` | calc(290px \* var(--text-ui-scale)) | - | calc(290px \* var(--text-ui-scale)) | - | | `--value-tera-font-weight` | 225 | - | clamp(100, calc(225 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-tera-line-height` | calc(300px \* var(--text-ui-scale)) | - | calc(300px \* var(--text-ui-scale)) | - | | Peta | | `--value-peta-font-size` | calc(380px \* var(--text-ui-scale)) | - | calc(380px \* var(--text-ui-scale)) | - | | `--value-peta-font-weight` | 200 | - | clamp(100, calc(200 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-peta-line-height` | calc(390px \* var(--text-ui-scale)) | - | calc(390px \* var(--text-ui-scale)) | - | ### Related APIs #### Reading variables from JavaScript `cssVar(name, { el })` reads any variable on this page back from the live cascade, theme and mode overrides included. The CSS stays the source of truth; nothing is duplicated in JavaScript. See [Paint API](/framework/docs/3.4/paint_api) for the full paint surface. ``` var gap = TRMNLPaint.cssVar("--gap", { el: "my-chart" }); ``` #### Variables a theme may re-point A theme re-points token references through the slot mixins and never sets paint values directly. The framework-owned paint variables (`--bg-*`, `--text-*`, `--border-*`) stay untouched, and the theme linter enforces that boundary. See [Authoring Themes](/framework/docs/3.4/theme_authoring) for the contract and workflow. #### Where variables are defined The SCSS source emits every variable on this page: root defaults from `_variables_root.scss`, per-mode overrides from `_variables_overrides.scss`. A custom build can reconfigure or extend the set. See [Sass API](/framework/docs/3.4/sass_api) for the source layout and its public surface. Previous [ ## Color Palettes Every palette a screen can carry: grayscale tiers, limited ink sets, and full color, with the class each one maps to ](/framework/docs/3.4/color_palettes) Next [ ## Structure The framework's exact div hierarchy and how Screen, View, Layout, Title Bar, Columns, and Mashup work together ](/framework/docs/3.4/structure) --- # Structure Screen, View, Layout, Title Bar, Columns, and Mashup form the fixed hierarchy that defines the display environment. Plugins render their content inside Views. Follow the exact div setup; deviating causes layout and rendering issues. You don't specify Screen, Mashup, or View. The platform provides them automatically, and you specify the Layout and optionally a Title Bar. You provide the full hierarchy yourself: Screen, View, Layout, and optionally a Mashup container and a Title Bar. ```
...
...
``` ```
...
...
``` ### The Exact Structure The framework uses a fixed div hierarchy. Each level has a specific role. The canonical structure is: **Screen** → ( **Mashup** →) **View** → **Layout** (+ optional **Title Bar** ) [Screen](/framework/docs/3.4/screen)--portrait --no-bleed --dark-mode --og --v2 --backdrop [Mashup](/framework/docs/3.4/mashup) --1x1 --1Lx1R --1Tx1B --2x2 --1Lx2R --2Lx1R --2Tx1B --1Tx2B --3x3 [View](/framework/docs/3.4/view)--full --half\_vertical --half\_horizontal --quadrant [Layout](/framework/docs/3.4/layout)--row --col --left --center-x --right --top --center-y --bottom --center --stretch --stretch-x --stretch-y [Title Bar](/framework/docs/3.4/title_bar) ### Component Roles Each foundation component has a specific role. Use them as intended. #### Screen Root container. Defines viewport dimensions, padding, and CSS variables that cascade throughout. Go to [Screen](/framework/docs/3.4/screen) #### View Container for a plugin slot. Size modifiers (`view--full`, `view--half_horizontal`, `view--half_vertical`, `view--quadrant`) set how much space the plugin gets. Non-full views must be nested inside a Mashup. Go to [View](/framework/docs/3.4/view) #### Layout The content container, exactly one per View. Its direct children are typically Columns, Grid, or Flex, arranged with `layout--row`, `layout--col`, and alignment modifiers. See the Layout page's "What Goes Inside Layout" section for when to use each. Go to [Layout](/framework/docs/3.4/layout) #### Title Bar Optional. Sibling to Layout within a View. Displays icon, title, and instance label. Go to [Title Bar](/framework/docs/3.4/title_bar) #### Columns Use _inside_ Layout for column-based content organization. Go to [Columns](/framework/docs/3.4/columns) #### Mashup Wraps multiple Views and arranges them within the Screen (1Lx1R, 1Tx1B, 2x2, etc.). Go to [Mashup](/framework/docs/3.4/mashup) #### Single View For a single plugin occupying the full screen: Layout ![TRMNL Logo](/images/plugins/trmnl--render.svg)PluginInstance ```
...
``` #### Mashup (Multiple Views) For multiple plugins on one screen, wrap views in a [Mashup](/framework/docs/3.4/mashup) . Each view has exactly one [Layout](/framework/docs/3.4/layout) . Plugin A Plugin B ```
...
...
``` Previous [ ## Tokens Complete CSS variable reference with root defaults, density, and bit-depth overrides ](/framework/docs/3.4/tokens) Next [ ## Screen Device screen dimensions, orientation, and display properties ](/framework/docs/3.4/screen) --- # Screen The Screen component is the outermost container that defines the device dimensions and provides global settings for your content. You don't specify the `screen`. The platform provides the correct `screen` container based on the target device. You provide the `screen` yourself. Include it with the appropriate device class (`screen--og`, `screen--v2`) and optional modifiers like `screen--portrait`, `screen--no-bleed`, or `screen--dark-mode`. ```
...
...
``` ```
...
...
``` ### Base Structure The Screen component serves as the root container for all content. It establishes the viewport dimensions, padding, and provides CSS variables that cascade throughout the framework. #### Default Screen The base `screen` class creates a container with default dimensions (800x480px landscape). It includes padding controlled by the `--gap` variable. Default Screen ```
``` ### CSS Variables The Screen sets CSS variables that cascade through the framework. They recalculate automatically when device variants or orientation modifiers are applied. #### Available Variables These variables are set on the `screen` element and available to all nested components. | Variable | Description | Default Value | | --- | --- | --- | | `--screen-w` | Screen width | 800px | | `--screen-h` | Screen height | 480px | | `--full-w` | Full width minus padding | `calc(--screen-w - --gap * 2)` | | `--full-h` | Full height minus padding | `calc(--screen-h - --gap * 2)` | | `--device-ui-scale` | Device-native UI density factor | 1 | | `--modifier-scale` | Selected scale modifier | 1 | | `--ui-scale` | Composed device and modifier scale for framework UI | 1 | | `--content-scale` | Scale modifier for plugin-authored content | 1 | | `--modifier-text-scale` | Selected Text Scale modifier | 1 | | `--text-ui-scale` | Composed device, Scale, and Text Scale factor for framework typography | 1 | | `--gap-scale` | Gap scaling factor | 1 | | `--color-depth` | Display color depth (bits) | 1 | ### Orientation Screens can be displayed in landscape (default) or portrait orientation. #### Orientation Toggle The `screen--portrait` modifier swaps the width and height dimensions. All layout calculations automatically adjust to the new dimensions. ```
``` ### Device Variants The Screen component supports device-specific configurations that adjust dimensions, scaling, and color depth. These variants ensure content displays correctly across different TRMNL devices. #### Available Devices Each device variant sets specific dimensions and scaling factors. Combine with orientation and bit-depth modifiers for complete control. ```
``` ### Modifiers Screen modifiers provide additional control over display properties and behavior. #### No Bleed Modifier The screen container that wraps your views has a no-bleed option that removes padding. This can be controlled through Private and Public Plugin settings, or applied directly in your code when developing locally. The `screen--no-bleed` modifier removes the default padding around the screen container, allowing content to extend fully to the edges. Screen No Bleed / Layout ```
``` #### Dark Mode The `screen--dark-mode` modifier remaps framework color tokens and utility output for dark rendering (background, text, border, and stroke utilities included). Images are not remapped. Opt icons in with the `image--adaptive` utility (see [Image](/framework/docs/3.4/image) ) so they follow the screen's semantic text-primary paint. Themed screens are exempt: a theme fully owns its colors, so `screen--dark-mode` has no effect while a `screen--theme-*` class is present. A theme can opt into its own dark treatment by styling `.screen--theme-.screen--dark-mode` in its own stylesheet. See [Themes](/framework/docs/3.4/themes) . Use the [Inverse](/framework/docs/3.4/inverse) utility inside a dark-mode screen to flip one subtree back to light. ```
``` #### Backdrop Mashups By default, mashups display with a white background and bordered views. The `screen--backdrop` modifier changes this to a patterned background (1-bit) or solid gray background (2-bit/4-bit) with plain white views. See [Mashup](/framework/docs/3.4/mashup) for more details. ```
...
...
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | `--content-scale` | 1 | - | - | - | | `--device-ui-scale` | 1 | - | - | - | | `--full-h` | calc(var(--screen-h) - var(--gap) \* 2) | - | - | - | | `--full-w` | calc(var(--screen-w) - var(--gap) \* 2) | - | - | - | | `--gap-scale` | 1 | - | - | - | | `--half_horizontal-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--half_horizontal-w` | calc((var(--screen-w) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-h` | calc((var(--screen-h) - var(--gap) \* 2)) | - | - | - | | `--half_vertical-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--modifier-scale` | 1 | - | - | - | | `--modifier-text-scale` | 1 | - | - | - | | `--quadrant-h` | calc((var(--screen-h) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--quadrant-w` | calc((var(--screen-w) - var(--gap) \* 2) / 2 - var(--gap) / 2) | - | - | - | | `--screen-h` | 480px | - | - | - | | `--screen-h-original` | 480px | - | - | - | | `--screen-w` | 800px | - | - | - | | `--screen-w-original` | 800px | - | - | - | | `--text-ui-scale` | 1 | - | - | - | | `--ui-scale` | 1 | - | - | - | ### Related APIs #### Custom device profiles Each entry in the device map compiles into a `screen--{name}` class with its dimensions, density, and color depth baked in. Configure `$custom-devices` to produce the same classes for your own panels without touching framework source. See [Custom Devices](/framework/docs/3.4/sass_devices) for the profile schema. Previous [ ## Structure The framework's exact div hierarchy and how Screen, View, Layout, Title Bar, Columns, and Mashup work together ](/framework/docs/3.4/structure) Next [ ## Devices Device profiles: the geometry, size, and density a screen--{keyname} class carries, and how to rate a panel of your own ](/framework/docs/3.4/devices) --- # Devices A device profile makes a screen render true to one panel: its exact dimensions, pixel density, and interface scale. Add one class, screen--{keyname}, and a screen renders with all of it. The profiles come from TRMNL's own device list, so each class matches a real panel. ### What a Device Profile Is The framework turns each profile into a `screen--` class. The class sets the profile as CSS variables on the [Screen](/framework/docs/3.4/screen) element, and everything inside reads them. On the TRMNL platform you never write this class: the platform renders your layout against each device's own profile. Add it yourself only when you render the screen in your own stack. | Variable | What it holds | | --- | --- | | `--screen-w`, `--screen-h` | The panel's dimensions in CSS pixels. Every view and layout size derives from them. | | `--pixel-ratio` | Physical-to-CSS pixel ratio. The screen scales by it, so a 2x panel still lays out in CSS pixels. | | `--dither-pixel-ratio` | The ratio dither patterns render at, so they stay crisp on high-DPI panels. | | `--device-ui-scale` | The device's own interface scale. It multiplies with the Scale modifier to produce `--ui-scale`. | | `--gap-scale` | Multiplier on every gap variable, for panels that need tighter or looser spacing than their size class gives. | | `--device-name` | The profile keyname as a string. | | `--color-depth` | The profile's depth rating, as metadata. | `--color-depth` paints nothing. What a screen prints is decided by its mode class, so a profile rated 24 still renders in grayscale the moment it carries a grayscale class. See [Rendering Modes](/framework/docs/3.4/rendering_modes) . [Tokens](/framework/docs/3.4/tokens) lists these variables with their defaults, next to every other framework token. ```
...
``` ### Size and Density Every profile names a size (`sm`, `md`, or `lg`) and a density tier (`1x` or `2x`). The device class includes both, so their variables come with it. Keep the size and density classes on the screen anyway: the responsive prefixes and the runtime match those classes. `md:value--large` matches `screen--md`, never the device class that includes it. [Responsive](/framework/docs/3.4/responsive) covers the size grammar. - `screen--sm`, `screen--md`, `screen--lg`: the breakpoint the device sits at, and the rich text width that goes with it. - `screen--density-2x`: high-DPI typography, which switches the pixel fonts for Inter Variable. See [Font Family](/framework/docs/3.4/font_family) . - Low density is the default and needs no class. `screen--density-1x` exists only as a marker; no CSS rule reads it. Pick the size by the devices you want to hit rather than by a pixel count. A profile's own `--device-ui-scale` and `--gap-scale` handle the fine tuning inside a size. ### Palettes Select the Mode, Not the Geometry A device lists every palette it can print. The palette chosen for a render decides the mode class; the dimensions stay the device's either way. [Color Palettes](/framework/docs/3.4/color_palettes) lists the palettes and their classes. ```
...
...
``` ### Rating a Panel Rate a panel by what the glass can show, not by the format the image arrives in. These are the four hardware classes and the depth each one takes. | Hardware class | Depth | Palettes it can carry | | --- | --- | --- | | Grayscale e-paper | 1, 2, or 4 | By gray levels: 2 levels rate 1, 4 levels rate 2, 16 levels rate 4. Each panel carries the grayscale palettes up to its own level. | | Limited-color e-paper | 1 | Its own ink set, every smaller ink set it can print, and `bw`. | | Kaleido-class color e-paper | 12 | `color-12bit`, which renders through `screen--color-full`. | | LCD, OLED, browser, virtual | 24 | Every palette, including `color-24bit`. These panels can render any mode the framework has. | Grayscale ratings stop at 4: past 16 grays the next step is color, so a panel rates 12 or 24 instead of a deeper gray. A smooth 8-bit image on the wire is a delivery format, not a rating. The panel behind it still prints 16 grays, so the screen still renders on `screen--4bit`. A full-color rating does not lock a device into color. Rate the profile for the glass, then render it with whichever mode class the palette selects. ### The Shipped Device Classes Every profile in the registry ships a class. Devices with identical dimensions share one: nine of them resolve to `screen--og` or `screen--ogv2`. | Class | Devices | Size | Depth | | --- | --- | --- | --- | | `screen--amazon_kindle_2024` | Amazon Kindle 2024 | sm | 4-bit | | `screen--amazon_kindle_7` | Amazon Kindle 7 | md | 4-bit | | `screen--amazon_kindle_oasis_2` | Amazon Kindle Oasis 2 | md | 4-bit | | `screen--amazon_kindle_paperwhite_6th_gen` | Amazon Kindle PW 6th Gen | md | 4-bit | | `screen--amazon_kindle_paperwhite_7th_gen` | Amazon Kindle PW 7th Gen | md | 4-bit | | `screen--amazon_kindle_paperwhite_signature_11th_gen` | Amazon Kindle PW Signature 11th Gen | md | 4-bit | | `screen--amazon_kindle_scribe` | Amazon Kindle Scribe | lg | 4-bit | | `screen--amazon_kindle_voyage` | Amazon Kindle Voyage | lg | 4-bit | | `screen--avalue_epd_42s` | Avalue EPD-42S 42" Display Board | lg | 4-bit | | `screen--ed133ut2` | ED133UT2 Active Matrix | lg | 4-bit | | `screen--generic_16_9` | Generic 16:9 Display | md | 4-bit | | `screen--inkplate_10` | Inkplate 10 | md | 4-bit | | `screen--inkplate_13_spectra` | Inkplate 13 Spectra | md | 1-bit | | `screen--inkplate_5_2` | Inkplate 5.2 | lg | 4-bit | | `screen--inkplate_6_color` | Inkplate 6COLOR | md | 1-bit | | `screen--inkplate_6_plus` | Inkplate 6 Plus | lg | 4-bit | | `screen--inky_impression_13_3` | Inky Impression 13.3 | md | 1-bit | | `screen--inky_impression_7_3` | Inky Impression 7.3 | md | 1-bit | | `screen--kobo_aura_h2o_2` | Kobo Aura H2O Edition 2 | lg | 4-bit | | `screen--kobo_aura_hd` | Kobo Aura HD | md | 4-bit | | `screen--kobo_aura_one` | Kobo Aura One | lg | 4-bit | | `screen--kobo_forma` | Kobo Forma | lg | 4-bit | | `screen--kobo_glo` | Kobo Glo | md | 4-bit | | `screen--kobo_libra_2` | Kobo Libra 2 | md | 4-bit | | `screen--kobo_sage` | Kobo Sage | lg | 4-bit | | `screen--kobo_touch` | Kobo Touch | md | 4-bit | | `screen--m5_paper_s3` | M5PaperS3 | md | 4-bit | | `screen--nook_simple_touch` | Nook Simple Touch | md | 4-bit | | `screen--og` | TRMNL OG (B/W/R/Y), Waveshare 7.5" B/W, Waveshare 7.5" B/W/R, Waveshare 7.5" B/W/R/Y | md | 1-bit | | `screen--ogv2` | Seeed E1001 Monochrome, Seeed E1002, TRMNL OG (2-bit), Waveshare 4.26" (2-bit), Xteink X4 | md | 2-bit | | `screen--onyx_boox_go_7` | Onyx BOOX Go 7 | lg | 4-bit | | `screen--onyx_boox_poke_5` | Onyx BOOX Poke 5 | md | 4-bit | | `screen--palma` | Onyx BOOX Palma | md | 4-bit | | `screen--remarkable_paper_2` | reMarkable 2 | lg | 4-bit | | `screen--seeed_e1003` | Seeed E1003 (4-bit) | lg | 4-bit | | `screen--seeed_e1004` | Seeed E1004 | lg | 1-bit | | `screen--v2` | TRMNL X | lg | 4-bit | | `screen--waveshare_5_8_bw` | Waveshare 5.83" (Steam Machine) | md | 2-bit | ### Where Profiles Come From The framework ships the platform's device list as generated SCSS. A released build carries a class for every device the platform knows; the framework never invents one. Bring your own panel in one of two ways. Apply `screen--byod_custom`, the generic profile every build ships, or compile the framework with your exact values in `$custom-devices`. [Custom Devices](/framework/docs/3.4/sass_devices) has the profile schema, the compile-time validation, and what a configured profile generates. ### Related APIs #### Custom device profiles Each entry in the device map compiles into a `screen--{name}` class with its dimensions, density, and color depth baked in. Configure `$custom-devices` to produce the same classes for your own panels without touching framework source. See [Custom Devices](/framework/docs/3.4/sass_devices) for the profile schema. Previous [ ## Screen Device screen dimensions, orientation, and display properties ](/framework/docs/3.4/screen) Next [ ## Rendering Modes The grayscale tiers and color modes a screen can carry, what each one paints, and the bit depth JavaScript can read ](/framework/docs/3.4/rendering_modes) --- # Rendering Modes A rendering mode is the class that tells a screen what its panel can print. Grayscale panels pick one of three bit-depth classes; color panels pick the class for their palette. Everything else follows that one class, from the dither patterns to the colors JavaScript reads. ### The Three Grayscale Tiers A grayscale screen carries one tier class. It names how many grays the panel prints, and it decides both how colors paint and a handful of display details. - `screen--1bit`: every gray token paints as a dither pattern of black and white pixels, borders and text included. This is the baseline the other tiers adjust from. - `screen--2bit`: tokens still dither, now between four gray tones instead of two. Table and progress sizing follows the interface scale. - `screen--4bit`: every gray token paints as a solid, and each border step gets its own shade instead of two steps sharing one. The title bar drops the padding adjustment it carries for pixel fonts, and its text stroke thins. On all three tiers, color tokens fall back to their gray equivalents, so a hue never reaches a panel that cannot print it. The demo below labels the tier it is rendering on. Switch devices in the Device Preview to see the same screen on each one. Cold chain1-bit2-bit4-bit Bay 182% Bay 264% Bay 338% ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rendering ModesGrayscale tiers ```
...
...
``` ### The Color Modes A color panel carries a palette class in place of a tier class. Two families cover them. - One class per limited ink set (`screen--color-3bwr`, `screen--color-3bwy`, `screen--color-4bwry`, `screen--color-6a`, `screen--color-7a`): every framework color, grays included, dithers down to that palette's fixed inks. - `screen--color-full` for 12-bit and 24-bit displays: every color paints exactly as defined, with no dithering. [Color Palettes](/framework/docs/3.4/color_palettes) lists every palette and the class it maps to. ```
...
...
``` ### How the Runtime Learns the Depth Each mode class also sets `--framework-bit-depth`, its paint depth as a number. The JavaScript runtime reads that variable and never parses class names, so a palette added in CSS needs no JavaScript change. | Mode class | Published depth | | --- | --- | | No mode class | 1 | | `screen--1bit` | 1 | | `screen--2bit` | 2 | | `screen--4bit` | 4 | | Any limited palette class | 4 | | `screen--color-full` | 12 | The number describes how the screen paints, not the panel's raw capability. A limited palette prints solid inks the way 4-bit prints solid grays, so it reports 4. A screen with no mode class reports 1: an unstated device is treated as the most constrained one. A screen with two mode classes reports the one that actually painted: `screen--2bit screen--color-7a` paints palette inks and reports 4. The runtime acts on the number: pixel-perfect fonts switch off at 4 and up, and item indexes stop forcing an even pixel width at 2 and up. See [Framework Runtime](/framework/docs/3.4/framework_runtime) , or [Paint API](/framework/docs/3.4/paint_api) to read the variable from your own JavaScript. ### Where the Numeric Variants Stop Utility classes take three numeric variant prefixes: `1bit:`, `2bit:`, and `4bit:`. They end at 4-bit because grayscale glass ends at 16 levels, so those three cover every grayscale panel. No numeric prefix matches a color screen. Target the color modes from SCSS instead, with the `for-color-palette($id)` and `for-color-full` mixins on [Sass Mixins](/framework/docs/3.4/sass_mixins) . ```
...
``` **Do not build on `screen--8bit` or `screen--16bit`.** Both are compatibility aliases scheduled for removal. They render exactly like `screen--4bit`, except they report their own number as the depth instead of 4. Nothing above 4-bit grayscale needs them: past 16 grays the next step is color, through `screen--color-full`. [Responsive](/framework/docs/3.4/responsive) covers the full variant grammar, including how bit-depth prefixes combine with breakpoints and orientation. ### Chart Series per Mode Chart series colors follow the mode too. On grayscale tiers and limited palettes, series start at the screen's ink and step toward the background. `screen--color-full` uses a set of distinct colors instead: the ink first, then seven hues in a fixed order. A theme's own chart colors still win over both. [Painting Charts](/framework/docs/3.4/paint_charts) reads these colors from JavaScript, and [Chart](/framework/docs/3.4/chart) covers the markup. ### Related APIs #### Reading variables from JavaScript `cssVar(name, { el })` reads any variable on this page back from the live cascade, theme and mode overrides included. The CSS stays the source of truth; nothing is duplicated in JavaScript. See [Paint API](/framework/docs/3.4/paint_api) for the full paint surface. ``` var gap = TRMNLPaint.cssVar("--gap", { el: "my-chart" }); ``` Previous [ ## Devices Device profiles: the geometry, size, and density a screen--{keyname} class carries, and how to rate a panel of your own ](/framework/docs/3.4/devices) Next [ ## View Show your plugin in different sizes with Mashup view containers ](/framework/docs/3.4/view) --- # View A View holds content (e.g. a plugin instance). Single views use `view--full` inside the Screen; multiple views go inside a Mashup, where the view modifier sets each view's share of space and the Mashup modifier sets the arrangement. View and Layout receive calculated dimensions from the device and orientation. You don't specify the `view`. The markup you write for any plugin layout is automatically wrapped in the appropriate `view` container by the platform. You provide the `view` yourself. Include the appropriate wrapper in your markup: `view view--full`, `view view--half_vertical`, `view view--half_horizontal`, or `view view--quadrant`. ```
...
...
``` ```
...
...
``` ### Base Structure The Layout element [Layout](/framework/docs/3.4/layout) is the core component of every View [View](/framework/docs/3.4/view) , providing a consistent container for your content. Views can optionally include a Title Bar [Title Bar](/framework/docs/3.4/title_bar) for additional context. There are four view types: `view--full`, `view--half_horizontal`, `view--half_vertical`, and `view--quadrant`. The default full view (`view view--full`) lives directly inside the `screen` div. Other view types must be nested inside a [Mashup](/framework/docs/3.4/mashup) component. #### With Layout and Title Bar When combined with a title bar, it provides context and navigation options. Layout ![TRMNL Logo](/images/plugins/trmnl--render.svg)TitleInstance ```
TRMNL Logo Title Instance
``` #### With only Layout For simpler interfaces, you can create a view without a title bar using just the base view classes. Layout ```
``` ### Views in Mashups When multiple plugins share a single screen, each one gets its own view, and those views must be wrapped in a [Mashup](/framework/docs/3.4/mashup) container. The view modifier (`view--half_vertical`, `view--quadrant`, etc.) determines how much space each plugin gets. The mashup modifier (`mashup--1Lx1R`, `mashup--2x2`, etc.) determines how those views are arranged on screen. Previous [ ## Rendering Modes The grayscale tiers and color modes a screen can carry, what each one paints, and the bit depth JavaScript can read ](/framework/docs/3.4/rendering_modes) Next [ ## Layout Primary container for organizing plugin content ](/framework/docs/3.4/layout) --- # Layout The Layout is the content container inside a View, exactly one `layout` per `view`. It arranges content horizontally (`layout--row`) or vertically (`layout--col`), with alignment and stretch modifiers. Use one `layout` per `view`. Organize content inside it with `flex`, `columns`, or `grid`. Don't nest `layout` inside `layout`. There should be exactly one `layout` per `view`. ```
Item 1
Item 2
``` ```
Item 1
Item 2
``` ### What Goes Inside Layout Layout is the main content wrapper inside a View. It defines the available space: its height is calculated from the device type, the orientation, and whether a title bar is present. Its direct children are usually Columns, Grid, or Flex. A direct child can also sit over the layout instead of inside it, with its distance measured from the layout's own edges [Position](/framework/docs/3.4/position) . #### Three ways to lay out content #### Grid Use when you need a strict grid: define column count and spans, so items align to a consistent rhythm. Good for Swiss-style layouts where everything lines up to a fixed grid. Go to [Grid](/framework/docs/3.4/grid) #### Flex Use when you want flexible arrangements where items size by content (width/height). You can use Flex alone for simpler layouts, or nest it inside Grid for per-cell flexibility. Go to [Flex](/framework/docs/3.4/flex) #### Columns Use when you have lots of same-type data and want to display as few or as many items as there are, with the Columns system handling the layout. See the Columns page for details. Go to [Columns](/framework/docs/3.4/columns) You can use multiple of each: multiple Columns components, multiple Grids, multiple Flex containers. You can mix them. The Layout modifiers (`layout--row`, `layout--col`, alignment, stretch) control how these direct children are arranged within the Layout space. 1 2 3 4 5 6 7 8 9 10 11 12 #### Nesting These components can be nested: put a Grid inside Layout, give that Grid a column count, and place Flex containers inside each grid cell. Inside each Flex you then place your actual content (items, text, etc.). Layout arranges the top-level Grid(s); the Grid arranges its cells; the Flex arranges items within each cell. ### Base Structure Layout arranges content in one of two directions: horizontal or vertical. These base structures are the building blocks for more complex layouts. #### Row Layout The `layout layout--row` classes create a horizontal layout. Items are arranged horizontally from left to right, with center alignment as the default positioning. Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutHorizontal ```
Item 1
Item 2
Item 3
``` #### Column Layout The `layout layout--col` classes create a vertical layout. Items are arranged vertically from top to bottom, with center alignment as the default positioning. Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutVertical ```
Item 1
Item 2
Item 3
``` ### Alignment Modifiers Once you've chosen a base layout structure, you can apply these modifier classes to control how items are aligned within their container. Modifiers cover directional alignment (top/bottom/left/right) and centering. #### Horizontal Alignment Use `layout--left`, `layout--center-x`, or `layout--right` to control horizontal alignment. Left ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutLeft Alignment ```
Item 1
Item 2
Item 3
``` #### Vertical Alignment Use `layout--top`, `layout--center-y`, or `layout--bottom` to control vertical alignment. Top ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutTop Alignment ```
Item 1
Item 2
Item 3
``` #### Center Alignment Use `layout--center` to center items both horizontally and vertically, or use `layout--center-x` and `layout--center-y` for individual axis control. Center ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutCenter Alignment ```
Item 1
Item 2
Item 3
Item 1
Item 2
Item 3
``` #### Axis Alignment The modifiers above are screen-directional: `layout--left` means left whether the layout is a row or a column. The modifiers below follow the flex axes instead, so `layout--justify-*` moves items along the layout direction and `layout--align-*` moves them across it. - `layout--justify-start` / `layout--justify-center` / `layout--justify-end`: position items along the main axis. - `layout--align-start` / `layout--align-center` / `layout--align-end`: position items across the cross axis. - `layout--start` / `layout--end`: both axes at once, the corner-anchored counterparts of `layout--center`. ```
Item 1
Item 2
Item 1
Item 2
``` ### Stretch Modifiers Stretch modifiers allow you to control how child elements fill the available space within a layout. You can apply these modifiers either to the layout container or to individual child elements. #### Container Stretch Use `layout--stretch` to make all children stretch in both directions. You can also use `layout--stretch-x` and `layout--stretch-y` for individual axis control. These modifiers work with both row and column layouts. `layout--stretch-x` and `layout--stretch-y` are screen-directional, so each one swaps its rule between a row and a column. Reach for `layout--stretch-main` (children fill the layout direction) or `layout--stretch-cross` (children fill across it) when you want the axis to follow the layout instead of the screen. #### Row Layout Stretch Examples of stretch behavior in row layouts. Use `layout--stretch` for both directions, `layout--stretch-x` for horizontal, or `layout--stretch-y` for vertical stretch. Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Row LayoutFull Stretch ```
Item 1
Item 2
Item 3
``` Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Row LayoutHorizontal Stretch ```
Item 1
Item 2
Item 3
``` Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Row LayoutVertical Stretch ```
Item 1
Item 2
Item 3
``` #### Column Layout Stretch Examples of stretch behavior in column layouts. The same modifiers work consistently regardless of layout direction. Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Column LayoutFull Stretch ```
Item 1
Item 2
Item 3
``` Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Column LayoutHorizontal Stretch ```
Item 1
Item 2
Item 3
``` Item 1 Item 2 Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Column LayoutVertical Stretch ```
Item 1
Item 2
Item 3
``` #### Child Element Stretch Use `stretch-x` and `stretch-y` classes on individual elements to control their stretch behavior within row or column layouts. Item 1 Item 2 (stretched) Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutRow + Individual Stretch ```
Item 1
Stretched Item
Item 3
``` Item 1 Item 2 (stretched) Item 3 ![TRMNL Logo](/images/plugins/trmnl--render.svg)LayoutColumn + Individual Stretch ```
Item 1
Stretched Item
Item 3
``` Previous [ ## View Show your plugin in different sizes with Mashup view containers ](/framework/docs/3.4/view) Next [ ## Title Bar Standardized title bar with plugin information and instance details ](/framework/docs/3.4/title_bar) --- # Title Bar A header strip for a View, holding an icon, a title, and an optional instance label. Place it as a sibling of the Layout, not inside it. Place Title Bar as a sibling of Layout inside a View. Both `layout` and `title_bar` should be direct children of the view. Don't nest Title Bar inside Layout. `title_bar` and `layout` must be siblings, not parent and child. ```
...
...
``` ```
...
``` ### Base Structure The Title Bar [Title Bar](/framework/docs/3.4/title_bar) consists of three main elements: an icon [Image](/framework/docs/3.4/image) , a title [Title](/framework/docs/3.4/title) , and an optional instance label [Label](/framework/docs/3.4/label) . These elements are arranged horizontally and automatically spaced. #### Basic Title Bar The basic Title Bar includes an icon and title. Use the `title_bar` class [Title Bar](/framework/docs/3.4/title_bar) for the container. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Basic Title Bar ```
Basic Title Bar
``` #### Title Bar with Instance Add an instance label using the `instance` class to display additional context. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Title Bar with InstanceProduction ```
Title Bar with Instance Production
``` ### Title Bar in Mashups When the Title Bar is placed inside a [Mashup](/framework/docs/3.4/mashup) , it automatically receives different styling. Inside a view with a mashup layout (`view--half_vertical`, `view--half_horizontal`, or `view--quadrant`), the title bar uses a reduced height, a smaller icon, and no top or side border radius, with rounded bottom corners only so it aligns with the view's bordered outline. #### Example The same `title_bar` markup is used; the framework applies the compact styling automatically when the title bar is inside a mashup view. Plugin A ![TRMNL Logo](/images/plugins/trmnl--render.svg)Calendar Plugin B ![TRMNL Logo](/images/plugins/trmnl--render.svg)RSS ```
Plugin A
Calendar
Plugin B
RSS
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--title-bar-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--title-bar-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-bar-font-smoothing` | none | none | auto | - | | `--title-bar-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-bar-height` | calc(40px \* var(--ui-scale)) | calc(40px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(40px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-image-height` | calc(28px \* var(--ui-scale)) | calc(28px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(28px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-line-height` | 1 | 1 | calc(22px \* var(--text-ui-scale)) | - | | `--title-bar-padding-top` | calc(5px \* var(--ui-scale)) | calc(5px \* var(--ui-scale)) | 0px | 0px | | `--title-bar-text-stroke-width` | calc(3.5px \* var(--ui-scale)) | calc(3.5px \* var(--ui-scale)) | calc(2px \* var(--ui-scale)) | calc(2px \* var(--ui-scale)) | | Small | | `--title-bar-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-bar-small-height` | calc(32px \* var(--ui-scale)) | calc(32px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(32px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | | `--title-bar-small-image-height` | calc(24px \* var(--ui-scale)) | calc(24px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | - | calc(24px \* var(--ui-scale) \* var(--framework-layout-title-bar-height-factor, 1)) | ### Related APIs #### Theming the title bar A theme can re-point the title bar's paint through its named slot (`title-bar`) without touching geometry. Slots take palette token references, so the surface still resolves through the device mode at render time. See [Theme Slots](/framework/docs/3.4/theme_slots) for every slot and mixin. ``` @include theme-slots.bg-slot("title-bar", "yellow-40"); ``` Previous [ ## Layout Primary container for organizing plugin content ](/framework/docs/3.4/layout) Next [ ## Columns Implement zero-config column layouts for content organization ](/framework/docs/3.4/columns) --- # Columns The Columns system handles lots of same-type data. You provide the items; it distributes them into columns and manages overflow, so you can display as few or as many items as there are in any given situation. For other layout needs, use Grid or Flex. Columns go inside Layout. Use them in your plugin markup to organize content into balanced columns. Same rules apply. Columns go inside Layout, which you provide as part of the full hierarchy. ```
...
...
...
``` ```
...
...
...
``` ### When to Use Columns Use Columns when you have a lot of same-type data to show and you want to display as few or as many items as there are in any given situation. The Columns system takes care of the layout: it distributes content into columns, adapts column count to the available space, and handles overflow when content exceeds the viewport. #### Variable Data, Automatic Layout You provide the items; Columns figures out how to fit them. It distributes content into multiple columns based on available screen real estate, adapts column count when the viewport or orientation changes, and works seamlessly with the framework's overflow and clamping systems. Set a maximum column count or let the system choose the best fit. #### Overflow Handling When content exceeds the available height, Columns doesn't break or overflow. It gracefully hides items that don't fit and, when configured, adds an "and X more" indicator so users know there's additional content. See the [Overflow](/framework/docs/3.4/overflow) page for details. #### Item Grouping and Flow Items can be grouped (for example, by date or category), and the Columns system keeps those groups together as they flow into columns. Group headers stay with their items, so you don't end up with orphaned headings or broken visual hierarchy when space is limited. #### Compared to Grid and Flex Choose Columns when you have lists or feeds of same-type items and want the system to handle distribution and overflow. If you need strict grid alignment with fixed column spans, use [Grid](/framework/docs/3.4/grid) . If you need flexible, content-sized row or column arrangements (toolbars, inline groups, etc.), use [Flex](/framework/docs/3.4/flex) . ### Basic Column Layout The basic column layout is flexible: add as many columns as your content needs. Column 1 Item Item Item Item Column 2 Item Item Column 3 Item ![TRMNL Logo](/images/plugins/trmnl--render.svg)Columns ```
Content for column 1
Content for column 2
Content for column 3
``` ### Vertical Alignment Columns align to the top of the row by default, so uneven columns share a baseline at the top. Add `columns--center` to center each column against the tallest one instead. Today Standup Review Retro Tomorrow 1:1 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Columnscolumns--center ```
Three items
One item, vertically centered
``` Previous [ ## Title Bar Standardized title bar with plugin information and instance details ](/framework/docs/3.4/title_bar) Next [ ## Mashup Assemble multiple plugin views into a single interface ](/framework/docs/3.4/mashup) --- # Mashup A Mashup arranges multiple plugin views within a single screen. A fixed mashup modifier (e.g. `mashup--1Lx1R`, `mashup--2x2`) positions the views, while each view's own modifier sets how much space it occupies. Fluid Mashups use the `mashup--3x3` layout and cell placement modifiers for custom tilings. You don't specify the Mashup. When you configure multiple plugins on a single screen, the platform provides the appropriate Mashup container automatically. You provide the Mashup yourself. Include the `mashup` container with the appropriate layout class in your markup (e.g. `mashup--1Lx1R`, `mashup--2x2`). ```
...
...
``` ```
...
...
...
...
``` ### Mashup Layouts #### Single In the 1x1 layout, one view spans both columns and both rows, so a single plugin fills the mashup. Pair it with `view--full`, which is sized to the same area. Plugin A ```
Plugin A
``` #### 1 Left, 1 Right In the 1Lx1R layout, the first plugin occupies the left column while the second occupies the right column. Plugin A Plugin B ```
Plugin A
Plugin B
``` #### 1 Top, 1 Bottom In the 1Tx1B layout, one plugin spans the top row while the other occupies the bottom row. Plugin A Plugin B ```
Plugin A
Plugin B
``` #### 1 Left, 2 Right In the 1Lx2R layout, one plugin occupies the left column while two plugins stack in the right column. Plugin A Plugin B Plugin C ```
Plugin A
Plugin B
Plugin C
``` #### 2 Left, 1 Right The 2Lx1R layout stacks two plugins in the left column, with a single plugin in the right column. Plugin A Plugin B Plugin C ```
Plugin A
Plugin B
Plugin C
``` #### 2 Top, 1 Bottom In the 2Tx1B layout, two plugins are presented side by side in the top row, with a single plugin in the bottom row. Plugin A Plugin B Plugin C ```
Plugin A
Plugin B
Plugin C
``` #### 1 Top, 2 Bottom The 1Tx2B layout places one plugin in the top row, with two plugins side by side in the bottom row. Plugin A Plugin B Plugin C ```
Plugin A
Plugin B
Plugin C
``` #### 2 x 2 Grid The 2x2 layout places four plugins in two rows of two. Plugin A Plugin B Plugin C Plugin D ```
Plugin A
Plugin B
Plugin C
Plugin D
``` ### Fluid Mashups The `mashup--3x3` layout arranges [View](/framework/docs/3.4/view) instances on a three by three grid that you carve up yourself. Each `mashup-cell` uses column, row, and span modifiers to set its place. A grid can hold nine equal tiles or a few large regions, and every cell draws its own border and surface at any size. A view inside a mashup cell always fills the cell, whatever view size class it carries. `w--*` and `h--*` utilities on that view have no effect; size the content inside the view instead. #### Available Modifiers Combine one modifier from each group to place a cell. Column, row, and span values range from 1 to 3. Keep start plus span at 4 or less. The grid is three columns wide, so `mashup-cell--col-2` with `mashup-cell--col-span-3` reaches past the edge and adds an auto-sized fourth column, which re-lays the whole mashup. The same holds for rows. | Class | Description | | --- | --- | | `mashup-cell--col-1` | Starts the cell at column 1 | | `mashup-cell--col-2` | Starts the cell at column 2 | | `mashup-cell--col-3` | Starts the cell at column 3 | | `mashup-cell--col-span-1` | Spans 1 column | | `mashup-cell--col-span-2` | Spans 2 columns | | `mashup-cell--col-span-3` | Spans 3 columns | | `mashup-cell--row-1` | Starts the cell at row 1 | | `mashup-cell--row-2` | Starts the cell at row 2 | | `mashup-cell--row-3` | Starts the cell at row 3 | | `mashup-cell--row-span-1` | Spans 1 row | | `mashup-cell--row-span-2` | Spans 2 rows | | `mashup-cell--row-span-3` | Spans 3 rows | #### 3 x 3 Grid Nine `mashup-cell` elements with no placement fill the grid from left to right, top to bottom, giving an even grid of equal tiles. Plugin A Plugin B Plugin C Plugin D Plugin E Plugin F Plugin G Plugin H Plugin I ```
Plugin A
``` #### Feature and Sidebar Add `mashup-cell--col-*` and `mashup-cell--row-*` to choose the starting cell. Add the matching `*-span-*` modifiers to span up to three cells in either direction. Plugin A Plugin B Plugin C Plugin D ```
Plugin A
Plugin B
Plugin C
Plugin D
``` #### Banner and Split Cells span in either direction. This grid runs a full width banner across the top, a two by two block below it, and a tall cell down the right. Plugin A Plugin B Plugin C ```
Plugin A
Plugin B
Plugin C
``` #### Title Bars Add a [Title Bar](/framework/docs/3.4/title_bar) to a cell to label its plugin. Place the `title_bar` as a sibling of `layout` inside the view, the same as a standalone view. Every cell uses the compact title bar, whatever its size, and the layout above shrinks to make room for it. 72° ![TRMNL Logo](/images/plugins/trmnl--render.svg)Weather 2 PM ![TRMNL Logo](/images/plugins/trmnl--render.svg)Agenda Wed 14 ![TRMNL Logo](/images/plugins/trmnl--render.svg)Calendar 84% ![TRMNL Logo](/images/plugins/trmnl--render.svg)Battery ```
72°
Weather
2 PM
Agenda
Wed 14
Calendar
84%
Battery
``` ### Screen Backdrop Modifier #### Default vs Backdrop Mashups By default, a mashup is a white screen with a border around each view. `screen--backdrop` swaps that for a patterned background on 1-bit, or a solid gray field on 2-bit and deeper screens, with plain white views on top. Plugin A Plugin B ```
...
...
...
...
``` Previous [ ## Columns Implement zero-config column layouts for content organization ](/framework/docs/3.4/columns) Next [ ## Element Sizes Every size class of every text element, side by side on one page ](/framework/docs/3.4/element_sizes) --- # Element Sizes Each text element has its own set of sizes, and the sets differ. This page shows every size of every element side by side, so you can see which classes exist before you use one. A class an element does not have does nothing, and the text stays at its default size. ### Size Grid Each row is one element and each column is one size name, from smallest to largest. Add the size class next to the element class, as in `value value--large`. The same size name is a different size on each element: `value--xlarge` is much larger than `title--xlarge`. | Element | xxsmall | xsmall | small | base | large | xlarge | xxlarge | xxxlarge | mega | giga | tera | peta | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | [Title](/framework/docs/3.4/title) | Not available | Not available | Aa`title--small` | Aa`title--base` | Aa`title--large` | Aa`title--xlarge` | Aa`title--xxlarge` | Not available | Not available | Not available | Not available | Not available | | [Value](/framework/docs/3.4/value) | Aa`value--xxsmall` | Aa`value--xsmall` | Aa`value--small` | Aa`value--base` | Aa`value--large` | Aa`value--xlarge` | Aa`value--xxlarge` | Aa`value--xxxlarge` | Aa`value--mega` | Aa`value--giga` | Aa`value--tera` | Aa`value--peta` | | [Label](/framework/docs/3.4/label) | Not available | Not available | Aa`label--small` | Aa`label--base` | Aa`label--large` | Aa`label--xlarge` | Aa`label--xxlarge` | Not available | Not available | Not available | Not available | Not available | | [Description](/framework/docs/3.4/description) | Not available | Not available | Not available | Aa`description--base` | Aa`description--large` | Aa`description--xlarge` | Aa`description--xxlarge` | Not available | Not available | Not available | Not available | Not available | | [Rich Text](/framework/docs/3.4/rich_text) | Not available | Not available | Aa`content--small` | Aa`content--base` | Aa`content--large` | Aa`content--xlarge` | Aa`content--xxlarge` | Aa`content--xxxlarge` | Not available | Not available | Not available | Not available | | [Text Size](/framework/docs/3.4/text_size) | Not available | Not available | Aa`text--small` | Aa`text--base` | Aa`text--large` | Aa`text--xlarge` | Aa`text--xxlarge` | Aa`text--xxxlarge` | Aa`text--mega` | Aa`text--giga` | Aa`text--tera` | Aa`text--peta` | Every size class also takes the `lg:`, `portrait:`, and `lg:portrait:` prefixes, for example `lg:portrait:value--xlarge`. See [Responsive](/framework/docs/3.4/responsive) . Previous [ ## Mashup Assemble multiple plugin views into a single interface ](/framework/docs/3.4/mashup) Next [ ## Title Style headings with consistent typography ](/framework/docs/3.4/title) --- # Title Headings for a plugin screen. Five size variants from small to xxlarge, each with responsive prefixes for breakpoints and orientation. ### Size Variations The Title system offers five size variants: small, base (default), large, xlarge, and xxlarge. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . Small TitleBase TitleLarge TitleExtra Large TitleXXL Title ![TRMNL Logo](/images/plugins/trmnl--render.svg)TitleSize Variations ``` Small Title Base Title Base Title Large Title Extra Large Title XXL Title Small by default, base on large screens ``` ### Responsive Titles The Title system supports responsive variants using breakpoint prefixes. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different sizes per device size class. Responsive TitleSmall by default, xlarge on lg screens ![TRMNL Logo](/images/plugins/trmnl--render.svg)TitleResponsive ``` Responsive Title Base by default, xlarge on lg screens ``` #### Orientation and Size+Orientation Title sizes can adapt to orientation with `portrait:` and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation VariantLarge by default, small in portrait. ![TRMNL Logo](/images/plugins/trmnl--render.svg)TitleOrientation ``` Orientation Variant Large by default, small in portrait. ``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--title-font-family` | "BlockKie" | "BlockKie" | #{meta.inspect($family)} | - | | `--title-font-size` | calc(26px \* var(--text-ui-scale)) | calc(26px \* var(--text-ui-scale)) | calc(21px \* var(--text-ui-scale)) | - | | `--title-font-smoothing` | none | none | auto | - | | `--title-font-weight` | 400 | 400 | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-line-height` | 1 | 1 | 1.2 | - | | Small | | `--title-small-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--title-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--title-small-font-smoothing` | none | none | auto | - | | `--title-small-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-small-line-height` | 1 | 1 | 1.2 | - | | Large | | `--title-large-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-large-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--title-large-font-smoothing` | auto | - | auto | - | | `--title-large-font-weight` | 425 | - | clamp(100, calc(425 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-large-line-height` | 1.2 | - | 1.2 | - | | Xlarge | | `--title-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-xlarge-font-size` | calc(35px \* var(--text-ui-scale)) | - | calc(35px \* var(--text-ui-scale)) | - | | `--title-xlarge-font-smoothing` | auto | - | auto | - | | `--title-xlarge-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--title-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--title-xxlarge-font-size` | calc(40px \* var(--text-ui-scale)) | - | calc(40px \* var(--text-ui-scale)) | - | | `--title-xxlarge-font-smoothing` | auto | - | auto | - | | `--title-xxlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--title-xxlarge-line-height` | 1.2 | - | 1.2 | - | Previous [ ## Element Sizes Every size class of every text element, side by side on one page ](/framework/docs/3.4/element_sizes) Next [ ## Value Display data values with consistent formatting ](/framework/docs/3.4/value) --- # Value Figures and readouts on a plugin screen. Twelve size variants from xxsmall to peta, plus value--tnums for tabular numbers that keep columns aligned. ### Size Variants The Value system offers twelve size variants, from XXSmall to Peta. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . #### Size Ladder Each tier maps to one font size, and that size holds on every device, bit depth, and font bundle. A bare `value` renders at the Base tier. [Scale](/framework/docs/3.4/scale) and [Text Scale](/framework/docs/3.4/text_scale) multiply these sizes from the screen. | Class | Font size | | --- | --- | | `value--xxsmall` | 16px | | `value--xsmall` | 20px | | `value--small` | 26px | | `value--base` | 38px | | `value--large` | 58px | | `value--xlarge` | 74px | | `value--xxlarge` | 96px | | `value--xxxlarge` | 128px | | `value--mega` | 170px | | `value--giga` | 220px | | `value--tera` | 290px | | `value--peta` | 380px | Value and the `text--` utilities share tier names but not sizes. `value--xlarge` is 74px where `text--xlarge` is 26px, and the utility that matches `value--xlarge` is `text--mega`. #### XXSmall The `value--xxsmall` class creates the smallest text size. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueXXSmall ``` Example 48,206.62 ``` #### XSmall The `value--xsmall` class is one step larger than XXSmall. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueXSmall ``` Example 48,206.62 ``` #### Small The `value--small` class creates a smaller text size. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueSmall ``` Example 48,206.62 ``` #### Base The base `value` class without size modifiers and the `value--base` class both produce the same visual result. See the [Responsive Values](#responsive-values) section for examples. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueBase ``` Example 48,206.62 Example 48,206.62 ``` #### Large The `value--large` class creates larger text. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueLarge ``` Example 48,206.62 ``` #### XLarge The `value--xlarge` class provides larger text. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueXLarge ``` Example 48,206.62 ``` #### XXLarge The `value--xxlarge` class creates very large text. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueXXLarge ``` Example 48,206.62 ``` #### XXXLarge The `value--xxxlarge` class provides very large text. Example48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueXXXLarge ``` Example 48,206.62 ``` #### Mega The `value--mega` class creates extremely large text. 42 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueMega ``` 42 ``` #### Giga The `value--giga` class provides massive text. 42 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueGiga ``` 42 ``` #### Tera The `value--tera` class creates colossal text. 42 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueTera ``` 42 ``` #### Peta The `value--peta` class provides the largest text. 42 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValuePeta ``` 42 ``` ### Numerical Display The Value system includes special formatting options for numerical values. #### Tabular Numbers Add the `value--tnums` modifier to enable tabular numbers. Regular: 48,206.62Tabular: 48,206.62 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueTabular Numbers ``` Regular: 48,206.62 Tabular: 48,206.62 ``` ### Responsive Values The Value system supports responsive variants using breakpoint prefixes. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different sizes per device size class. Responsive Value1,234.56 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueResponsive ``` Responsive Value 1,234.56 Small by default, base on large screens ``` #### Orientation and Size+Orientation Value sizes can adapt to orientation with `portrait:` and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation Variant42,000.00 ![TRMNL Logo](/images/plugins/trmnl--render.svg)ValueOrientation ``` Orientation Variant 42,000.00 ``` ### Values in JavaScript The value typography is readable from JS via `TRMNLPaint.type('value', { el })`, which probes the resolved font family, size, weight and line-height from the live cascade (so it follows the active font bundle and density automatically), and `applyType()` writes it onto a node. This is how JS-drawn visuals borrow the same big-number face as `.value` stat tiles (for example the chart gauge’s weekly value). See [Painting Typography](/framework/docs/3.4/paint_typography) . ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--value-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--value-font-size` | calc(38px \* var(--text-ui-scale)) | - | calc(38px \* var(--text-ui-scale)) | - | | `--value-font-smoothing` | auto | - | auto | - | | `--value-font-weight` | 450 | - | clamp(100, calc(450 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-line-height` | calc(42px \* var(--text-ui-scale)) | - | calc(42px \* var(--text-ui-scale)) | - | | Xxsmall | | `--value-xxsmall-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--value-xxsmall-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--value-xxsmall-font-smoothing` | none | none | auto | - | | `--value-xxsmall-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxsmall-line-height` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(14px \* var(--text-ui-scale)) | - | | Xsmall | | `--value-xsmall-font-size` | calc(20px \* var(--text-ui-scale)) | - | calc(20px \* var(--text-ui-scale)) | - | | `--value-xsmall-font-weight` | 600 | - | clamp(100, calc(600 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xsmall-line-height` | calc(24px \* var(--text-ui-scale)) | - | calc(24px \* var(--text-ui-scale)) | - | | Small | | `--value-small-font-size` | calc(26px \* var(--text-ui-scale)) | - | calc(26px \* var(--text-ui-scale)) | - | | `--value-small-font-weight` | 500 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-small-line-height` | calc(29px \* var(--text-ui-scale)) | - | calc(29px \* var(--text-ui-scale)) | - | | Large | | `--value-large-font-size` | calc(58px \* var(--text-ui-scale)) | - | calc(58px \* var(--text-ui-scale)) | - | | `--value-large-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-large-line-height` | calc(70px \* var(--text-ui-scale)) | - | calc(70px \* var(--text-ui-scale)) | - | | Xlarge | | `--value-xlarge-font-size` | calc(74px \* var(--text-ui-scale)) | - | calc(74px \* var(--text-ui-scale)) | - | | `--value-xlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xlarge-line-height` | calc(86px \* var(--text-ui-scale)) | - | calc(86px \* var(--text-ui-scale)) | - | | Xxlarge | | `--value-xxlarge-font-size` | calc(96px \* var(--text-ui-scale)) | - | calc(96px \* var(--text-ui-scale)) | - | | `--value-xxlarge-font-weight` | 350 | - | clamp(100, calc(350 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxlarge-line-height` | calc(108px \* var(--text-ui-scale)) | - | calc(108px \* var(--text-ui-scale)) | - | | Xxxlarge | | `--value-xxxlarge-font-size` | calc(128px \* var(--text-ui-scale)) | - | calc(128px \* var(--text-ui-scale)) | - | | `--value-xxxlarge-font-weight` | 300 | - | clamp(100, calc(300 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-xxxlarge-line-height` | calc(128px \* var(--text-ui-scale)) | - | calc(128px \* var(--text-ui-scale)) | - | | Mega | | `--value-mega-font-size` | calc(170px \* var(--text-ui-scale)) | - | calc(170px \* var(--text-ui-scale)) | - | | `--value-mega-font-weight` | 275 | - | clamp(100, calc(275 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-mega-line-height` | calc(180px \* var(--text-ui-scale)) | - | calc(180px \* var(--text-ui-scale)) | - | | Giga | | `--value-giga-font-size` | calc(220px \* var(--text-ui-scale)) | - | calc(220px \* var(--text-ui-scale)) | - | | `--value-giga-font-weight` | 250 | - | clamp(100, calc(250 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-giga-line-height` | calc(230px \* var(--text-ui-scale)) | - | calc(230px \* var(--text-ui-scale)) | - | | Tera | | `--value-tera-font-size` | calc(290px \* var(--text-ui-scale)) | - | calc(290px \* var(--text-ui-scale)) | - | | `--value-tera-font-weight` | 225 | - | clamp(100, calc(225 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-tera-line-height` | calc(300px \* var(--text-ui-scale)) | - | calc(300px \* var(--text-ui-scale)) | - | | Peta | | `--value-peta-font-size` | calc(380px \* var(--text-ui-scale)) | - | calc(380px \* var(--text-ui-scale)) | - | | `--value-peta-font-weight` | 200 | - | clamp(100, calc(200 + var(--framework-font-weight-shift, 0)), 900) | - | | `--value-peta-line-height` | calc(390px \* var(--text-ui-scale)) | - | calc(390px \* var(--text-ui-scale)) | - | Previous [ ## Title Style headings with consistent typography ](/framework/docs/3.4/title) Next [ ## Label Create clear labels for unified content identification ](/framework/docs/3.4/label) --- # Label Short captions and status chips for a plugin screen. Five sizes plus style variants (filled, outline, underline, inverted) and semantic variants such as label--success and label--error. ### Size and Style Variants Labels come in several style variants. Each one combines with any size modifier. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . Small Base Large XLarge XXLarge Default Label Label Label Label Label Outline Label Label Label Label Label Underline Label Label Label Label Label Gray Label Label Label Label Label Filled Label Label Label Label Label ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelStyle Variants ``` Default Label Outline Label Underline Label Gray Label Filled Primary Success Error Inverted (alias) Large Outline Label XLarge Filled Label ``` #### Semantic variants Use `label--primary`, `label--success`, `label--error`, `label--warning` for intent-based colors. `label--filled` uses black (darkest), and warning uses a yellow background. See [Colors](/framework/docs/3.4/colors) for the semantic mapping. Each role's text is black or white, whichever reads better on the color the screen paints. 1-bit screens keep white text on primary and error, and black on success and warning. FilledPrimarySuccessErrorWarning ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelSemantic Colors ### Text Overflow Behavior Labels can handle longer text content through natural wrapping or text clamping. Understanding how labels behave with overflow content helps ensure your interface remains readable and visually balanced. #### Multi-line Wrapping By default, labels will wrap to multiple lines when content exceeds the available width, maintaining readability for longer text. Small Base Large Default This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width Underline This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width Filled This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width This longer label will wrap to multiple lines when it exceeds the width ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelMulti-line ``` This longer label will wrap to multiple lines when it exceeds the width ``` #### Text Clamping Use the framework's `data-clamp` attribute to limit labels to a specific number of lines with ellipsis overflow. Small Base Large 1-line This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow 2-line This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow Underline 1 This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow Underline 2 This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long label text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelClamped ``` This text will be clamped to one line This text will be clamped to exactly two lines with ellipsis ``` ### Responsive Features Label components support all three responsive systems: size-based, orientation-based, and bit-depth variants. This enables precise control over label appearance across different device configurations. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different sizes and styles per device size class. Responsive LabelSmall by default, xlarge on lg screens ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelResponsive ``` Responsive Label Small by default, xlarge on lg screens Small by default, base on medium+ screens Progressive Label Sizing ``` #### Orientation and Size+Orientation Label sizes can adapt to orientation with `portrait:` and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation VariantLarge by default, small in portrait. ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelOrientation ``` Orientation Variant Large by default, small in portrait. ``` #### Bit-Depth Responsive Use bit-depth prefixes like `1bit:`, `2bit:`, and `4bit:` to optimize label appearance for different display color capabilities. Display OptimizedFilled (1bit) → Outline (2bit) → Underline (4bit) Selective StylingOutline (1bit) → Gray (4bit) ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelBit-Depth Responsive ``` Display Optimized Selective Styling ``` #### Combined Responsive Features Combine multiple responsive systems for highly targeted label styling. Use size, orientation, and bit-depth modifiers together following the pattern: `size:orientation:bit-depth:utility`. Advanced TargetingComplex responsive combinations Multi-ConditionMultiple responsive conditions ![TRMNL Logo](/images/plugins/trmnl--render.svg)LabelCombined Responsive ``` Advanced Targeting Multi-Condition ``` ### Backward Compatibility The gray-out label variant has been renamed from `label--gray-out` to `label--gray`. The legacy class name still works and maps to the same bit-depth responsive styling. Prefer the new name going forward. The inverted label has been renamed to `label--filled` (black background). Use `label--primary`, `label--success`, etc. for semantic colors. `label--inverted` remains as an alias for `label--filled`. ``` Gray label (deprecated) Inverted (alias) Gray label (preferred) Filled label (preferred) ``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--label-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--label-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--label-font-smoothing` | none | none | auto | - | | `--label-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-line-height` | 1.25 | 1.25 | 1.25 | - | | Small | | `--label-small-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--label-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--label-small-font-smoothing` | none | none | auto | - | | `--label-small-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-small-line-height` | 1 | 1 | 1 | - | | Large | | `--label-large-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-large-font-size` | calc(21px \* var(--text-ui-scale)) | - | calc(21px \* var(--text-ui-scale)) | - | | `--label-large-font-smoothing` | auto | - | auto | - | | `--label-large-font-weight` | 500 | - | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-large-line-height` | 1.2 | - | 1.2 | - | | Xlarge | | `--label-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-xlarge-font-size` | calc(26px \* var(--text-ui-scale)) | - | calc(26px \* var(--text-ui-scale)) | - | | `--label-xlarge-font-smoothing` | auto | - | auto | - | | `--label-xlarge-font-weight` | 475 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--label-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--label-xxlarge-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--label-xxlarge-font-smoothing` | auto | - | auto | - | | `--label-xxlarge-font-weight` | 450 | - | clamp(100, calc(450 + var(--framework-font-weight-shift, 0)), 900) | - | | `--label-xxlarge-line-height` | 1.2 | - | 1.2 | - | ### Related APIs #### Theming the label A theme can re-point the label's paint through its named slots (`label-gray`, `label-underline`) without touching geometry. Slots take palette token references, so the surface still resolves through the device mode at render time. See [Theme Slots](/framework/docs/3.4/theme_slots) for every slot and mixin. ``` @include theme-slots.border-token-slot("label-underline", "yellow-30"); ``` Previous [ ## Value Display data values with consistent formatting ](/framework/docs/3.4/value) Next [ ## Description Format descriptive text with standardized styles ](/framework/docs/3.4/description) --- # Description Supporting body text, sized to sit under a Title or a Value rather than compete with it. Four size variants from base to xxlarge, with wrapping or line clamping for longer copy. ### Size Variants Descriptions come in four size variants. Pick the one that matches the weight the line should carry in the content hierarchy. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . Base Large XLarge XXLarge Description Description Description Description ![TRMNL Logo](/images/plugins/trmnl--render.svg)DescriptionSize Variants ``` Base Description Large Description XLarge Description XXLarge Description Large by default, base on medium+ screens ``` ### Text Overflow Behavior Descriptions can handle longer text content through natural wrapping or text clamping. Understanding how descriptions behave with overflow content helps ensure your interface remains readable and visually balanced. #### Multi-line Wrapping By default, descriptions will wrap to multiple lines when content exceeds the available width, maintaining readability for longer text. Base Large XLarge XXLarge This longer description will wrap to multiple lines when it exceeds the available width This longer description will wrap to multiple lines when it exceeds the available width This longer description will wrap to multiple lines when it exceeds the available width This longer description will wrap to multiple lines when it exceeds the available width ![TRMNL Logo](/images/plugins/trmnl--render.svg)DescriptionMulti-line ``` This longer description will wrap to multiple lines when it exceeds the width ``` #### Text Clamping Use the framework's `data-clamp` attribute to limit descriptions to a specific number of lines with ellipsis overflow. Base Large XLarge XXLarge This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow This is a very long description text that would normally wrap to many lines but demonstrates how clamping behavior works with different variants and line limits to show ellipsis overflow ![TRMNL Logo](/images/plugins/trmnl--render.svg)DescriptionClamped ``` This text will be clamped to one line This text will be clamped to exactly two lines with ellipsis This larger text will be clamped to three lines ``` ### Responsive Features Description components support all three responsive systems: size-based, orientation-based, and bit-depth variants. This enables precise control over description appearance across different device configurations. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different sizes per device size class. Responsive DescriptionBase by default, xlarge on lg screens ![TRMNL Logo](/images/plugins/trmnl--render.svg)DescriptionResponsive ``` Responsive Description Base by default, xlarge on lg screens Large by default, base on medium+ screens Progressive Description Sizing ``` #### Orientation and Size+Orientation Description sizes can adapt to orientation with `portrait:` and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation VariantLarge by default, base in portrait. ![TRMNL Logo](/images/plugins/trmnl--render.svg)DescriptionOrientation ``` Orientation Variant Large by default, base in portrait. ``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--description-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--description-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--description-font-smoothing` | none | none | auto | - | | `--description-font-weight` | 400 | 400 | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-line-height` | 1 | 1 | 1.2 | - | | Large | | `--description-large-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--description-large-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--description-large-font-smoothing` | none | none | auto | - | | `--description-large-font-weight` | 400 | 400 | clamp(100, calc(700 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-large-line-height` | 1.25 | 1.25 | 1.2 | - | | Xlarge | | `--description-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--description-xlarge-font-size` | calc(21px \* var(--text-ui-scale)) | - | calc(21px \* var(--text-ui-scale)) | - | | `--description-xlarge-font-smoothing` | auto | - | auto | - | | `--description-xlarge-font-weight` | 500 | - | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--description-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--description-xxlarge-font-size` | calc(24px \* var(--text-ui-scale)) | - | calc(24px \* var(--text-ui-scale)) | - | | `--description-xxlarge-font-smoothing` | auto | - | auto | - | | `--description-xxlarge-font-weight` | 475 | - | clamp(100, calc(475 + var(--framework-font-weight-shift, 0)), 900) | - | | `--description-xxlarge-line-height` | 1.2 | - | 1.2 | - | Previous [ ## Label Create clear labels for unified content identification ](/framework/docs/3.4/label) Next [ ## Divider Create horizontal or vertical dividers between elements ](/framework/docs/3.4/divider) --- # Divider The Divider element provides a simple, standalone shorthand for horizontal and vertical separators. It draws the same line as the border utilities at level 6, the line the steps 60 and 65 draw. ### Usage Use `divider` or `divider--h` for horizontal dividers, and `divider--v` for vertical dividers. The background variants (`divider--on-white`, `divider--on-light`, `divider--on-dark`, `divider--on-black`) are deprecated and will be removed in Framework 4.0. Pick an explicit border level or token instead. #### Border Shorthand Use it when you want a one-pixel horizontal or vertical separator without writing a full border utility class. Horizontal Divider Equivalent intent: border--h-6 Left Right ![TRMNL Logo](/images/plugins/trmnl--render.svg)Divider Border Shorthand ```
``` #### Vertical Dividers Vertical dividers draw the same way as horizontal ones. Left SideWhite background Right SideSame divider Left SideBlack background Right SideSame divider ![TRMNL Logo](/images/plugins/trmnl--render.svg)Vertical Dividers ```
``` #### Common Usage Patterns $1,234Revenue 42Orders $29.38AOV ![TRMNL Logo](/images/plugins/trmnl--render.svg)Section Separation ```
``` ### Dividers in JavaScript JavaScript can read the divider line too: `TRMNLPaint.divider({ dir })` returns the level-6 line with the device and the active theme already applied. `applyBorder()` paints the returned `BorderFill` onto a node. Unlike the `.border--*` utilities, a divider paints on the element itself, so that is where the paint is read; see [Painting Borders](/framework/docs/3.4/paint_borders) . Previous [ ## Description Format descriptive text with standardized styles ](/framework/docs/3.4/description) Next [ ## Rich Text Display formatted paragraphs with alignment and size variants ](/framework/docs/3.4/rich_text) --- # Rich Text A container for long-form text: paragraphs, headings, lists, and quotes. The framework styles the children of the content block, so text from a feed renders in the framework font without a class on every tag. ### Understanding Richtext Components The richtext system consists of two key parts working together: the parent `.richtext` container and its natural child `.content`[Content Limiter](/framework/docs/3.4/content_limiter) component. The parent `.richtext` container is designed for flexibility and can hold any content. It controls the overall placement and spacing of the component within your layout. The `.content` component is where your actual text content lives. It carries the type styles and the alignment modifiers specific to text. Rich Text is the one component that takes plain HTML children. A `.content` block styles its children directly, so paragraphs, headings, and lists coming from a feed render in the framework font without a class on every tag. Everywhere else on a screen, use the framework elements ( [Title](/framework/docs/3.4/title) , [Description](/framework/docs/3.4/description) , [Label](/framework/docs/3.4/label) , [Value](/framework/docs/3.4/value) ). Both components have separate alignment modifiers that serve different purposes. The table below summarizes each modifier: | Class | Modifiers | Applies To | Controls | Example | | --- | --- | --- | --- | --- | | `richtext` | `left`, `center`, `right` | Container (`.richtext`) | Aligns the richtext container within its parent | `richtext--center` | | `content` | `left`, `center`, `right` | Child content (`.content`) | Aligns the content block within the richtext container | `content--right` | | `text` | `left`, `center`, `right` | Text elements inside `.content` | Aligns inline text within the content block | `text--center` | The two levels are independent, so you can place the component one way and align its text another. ### Rich Text Alignment The Rich Text component can be aligned in three different ways: left, center, or right. Each alignment option provides different text positioning to suit various design needs. #### Left Aligned Left alignment is the default and most readable format for longer text content, ideal for paragraphs and articles. ![](/assets/trmnl--glyph-black-large-f3c4ad75.svg) This is an example of left-aligned rich text content. This alignment is generally best for readability with longer paragraphs of text. Multiple paragraphs will maintain the same alignment, making it easy to read through longer content while maintaining visual consistency. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextLeft Aligned ```

This is an example of left-aligned rich text content.

Multiple paragraphs will maintain the same alignment.

``` #### Center Aligned Center alignment is ideal for headings, quotes, or shorter text that needs to be highlighted or visually balanced within the layout. ![](/assets/trmnl--glyph-black-large-f3c4ad75.svg) This is an example of center-aligned rich text content. Centered text works well for quotes, headings, or highlighted information that needs visual emphasis. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextCenter Aligned ```

This is an example of center-aligned rich text content.

Centered text works well for quotes or highlighted information.

``` #### Right Aligned Right alignment is less common but can be useful for specific design scenarios or to create visual tension in layouts. ![](/assets/trmnl--glyph-black-large-f3c4ad75.svg) This is an example of right-aligned rich text content. Right alignment can be used for captions, sidebars, or to create visual interest through contrasting alignments. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextRight Aligned ```

This is an example of right-aligned rich text content.

Right alignment can be used for captions or sidebars.

``` ### Content Size Variants The Rich Text component offers six size variants: `small`, `base`, `large`, `xlarge`, `xxlarge`, and `xxxlarge`. The `content` class without size modifiers and the `content--base` class produce the same visual result, so use `content--base` when you need to set the base size explicitly in responsive contexts. All size variants support responsive prefixes like `sm:`, `md:`, `lg:`, and `portrait:`. To compare these sizes with every other element, see [Element Sizes](/framework/docs/3.4/element_sizes) . This is xxxlarge size rich text content. This is xxlarge size rich text content. This is xlarge size rich text content. This is large size rich text content. This is base size rich text content. This is small size rich text content. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextSize Variants ```

This is xxxlarge size rich text content.

This is xxlarge size rich text content.

This is xlarge size rich text content.

This is large size rich text content.

This is base size rich text content.

This is small size rich text content.

Base by default, xxxlarge on large screens

``` ### Controlling Width By default, the Rich Text content takes up as much space as it needs and is centered in the layout, expanding up to a maximum width. However, you can precisely control the width of content using Size utility classes [Size](/framework/docs/3.4/size) . This Rich Text content has a fixed width of 240 pixels using utility classes. Notice how the text is constrained to this specific width regardless of the container size. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextFixed Width: 240px ```

This Rich Text content has a fixed width of 240 pixels using utility classes.

Notice how the text is constrained to this specific width regardless of the container size.

``` You can use any of the Size system's fixed sizes (`w--32`, `w--64`, etc.), arbitrary sizes up to 128px (`w--[120px]`), or responsive sizes (`w--full`, `w--auto`). This flexibility lets you create perfectly sized text blocks for any layout need. ### Responsive Features The `content` component supports all three responsive systems: size-based, orientation-based, and bit-depth variants. This enables precise control over content text size across different device configurations. #### Breakpoint Prefixes Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different content sizes per device size class. Responsive content Base by default, xxlarge on lg screens ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextResponsive ```

Responsive content

Small by default, base on medium+ screens

Progressive content sizing

``` #### Orientation and Size+Orientation Content sizes can adapt to orientation with `portrait:` and `landscape:`, and can be combined with size breakpoints (e.g., `md:portrait:`). Orientation variant content Large by default, small in portrait. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextOrientation ```

Orientation variant content

Large by default, small in portrait.
``` #### Bit-Depth Responsive Use bit-depth prefixes like `1bit:`, `2bit:`, and `4bit:` to optimize content text size for different display color capabilities. Display optimized content Large (1bit) → XLarge (2bit) → XXLarge (4bit) Selective sizing Base (1bit) → XLarge (4bit) ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextBit-Depth Responsive ```

Display optimized content

Selective sizing

``` #### Combined Responsive Features Combine multiple responsive systems for highly targeted content sizing. Use size, orientation, and bit-depth modifiers together following the pattern: `size:orientation:bit-depth:content--size`. Advanced targeting Complex responsive combinations Multi-condition content Multiple responsive conditions ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextCombined Responsive ```

Advanced targeting

Multi-condition content

``` ### Integration with Content Limiter The Rich Text component works seamlessly with the Content Limiter utility [Content Limiter](/framework/docs/3.4/content_limiter) to handle overflowing text. When combined, it automatically adjusts text size to fit the available space, which is particularly useful in constrained layouts. Simply add the `data-content-limiter="true"` attribute to your richtext content element. You can also specify a custom maximum height using the `data-content-max-height` attribute (e.g., `data-content-max-height="140"`). When `data-content-limiter="true"` is present, the limiter auto-measures the available height in the nearest container and adjusts text to fit. You can override the auto height by specifying `data-content-max-height` with a pixel value. Automatically resize text when content exceeds height limits [View Content Limiter Documentation](/framework/docs/3.4/content_limiter) The Rich Text component with Content Limiter will automatically adjust text size when content exceeds the auto-measured available height. This is particularly useful for views with limited vertical space such as quadrants or half-horizontal layouts. Notice how this text is rendered smaller to fit within the quadrant view. Without Content Limiter, this text would overflow the container. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextWith Content Limiter ```

The Rich Text component with Content Limiter will automatically adjust text size when content exceeds the height threshold. This is particularly useful for views with limited vertical space.

``` ### Integration with Pixel Perfect For optimal text rendering on ePaper displays, the Rich Text component can be enhanced with the Pixel Perfect utility [Pixel Perfect](/framework/docs/3.4/pixel_perfect) . This ensures text is rendered with crisp edges by aligning precisely to the pixel grid, preventing blurry or inconsistent text weight. Simply add the `data-pixel-perfect="true"` attribute to your richtext content element. Ensure crisp text rendering on 1-bit displays [View Pixel Perfect Documentation](/framework/docs/3.4/pixel_perfect) This text is rendered with pixel perfect alignment, ensuring that each character aligns precisely with the pixel grid. Notice how the text appears crisp and clear with consistent weight. Pixel Perfect is especially important for ePaper displays that use a 1-bit color space (just black and white), where anti-aliased gray pixels are forced to become either fully black or fully white. ![TRMNL Logo](/images/plugins/trmnl--render.svg)Rich TextWith Pixel Perfect ```

This text is rendered with pixel perfect alignment, ensuring that each character aligns precisely with the pixel grid. Notice how the text appears crisp and clear with consistent weight.

Pixel Perfect is especially important for ePaper displays that use a 1-bit color space (just black and white), where anti-aliased gray pixels are forced to become either fully black or fully white.

``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--richtext-content-max-width` | calc(640px \* var(--ui-scale)) | - | - | - | | `--richtext-font-family` | "NicoClean" | "NicoClean" | #{meta.inspect($family)} | - | | `--richtext-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | - | | `--richtext-font-smoothing` | none | none | auto | - | | `--richtext-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-line-height` | calc(22px \* var(--text-ui-scale)) | calc(22px \* var(--text-ui-scale)) | calc(22px \* var(--text-ui-scale)) | - | | Small | | `--richtext-small-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--richtext-small-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--richtext-small-font-smoothing` | none | none | auto | - | | `--richtext-small-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-small-line-height` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(18px \* var(--text-ui-scale)) | - | | Large | | `--richtext-large-font-family` | "BlockKie" | "BlockKie" | #{meta.inspect($family)} | - | | `--richtext-large-font-size` | calc(26px \* var(--text-ui-scale)) | calc(26px \* var(--text-ui-scale)) | calc(21px \* var(--text-ui-scale)) | - | | `--richtext-large-font-smoothing` | none | none | auto | - | | `--richtext-large-font-weight` | 400 | 400 | clamp(100, calc(500 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-large-line-height` | 1 | 1 | 1.2 | - | | Xlarge | | `--richtext-xlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xlarge-font-size` | calc(30px \* var(--text-ui-scale)) | - | calc(30px \* var(--text-ui-scale)) | - | | `--richtext-xlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xlarge-font-weight` | 425 | - | clamp(100, calc(425 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xlarge-line-height` | 1.2 | - | 1.2 | - | | Xxlarge | | `--richtext-xxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xxlarge-font-size` | calc(35px \* var(--text-ui-scale)) | - | calc(35px \* var(--text-ui-scale)) | - | | `--richtext-xxlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xxlarge-font-weight` | 400 | - | clamp(100, calc(400 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xxlarge-line-height` | 1.2 | - | 1.2 | - | | Xxxlarge | | `--richtext-xxxlarge-font-family` | "Inter Variable", Inter | - | #{meta.inspect($family)} | - | | `--richtext-xxxlarge-font-size` | calc(40px \* var(--text-ui-scale)) | - | calc(40px \* var(--text-ui-scale)) | - | | `--richtext-xxxlarge-font-smoothing` | auto | - | auto | - | | `--richtext-xxxlarge-font-weight` | 375 | - | clamp(100, calc(375 + var(--framework-font-weight-shift, 0)), 900) | - | | `--richtext-xxxlarge-line-height` | 1.2 | - | 1.2 | - | Previous [ ## Divider Create horizontal or vertical dividers between elements ](/framework/docs/3.4/divider) Next [ ## Item Build standardized list items and content blocks ](/framework/docs/3.4/item) --- # Item A row for lists, schedules, and other repeating content, with optional meta text, an index, or an icon. Stack items in a Layout and let the Overflow engine handle the ones that do not fit. ### Item Variants Items can be displayed in four variants: with meta and index, with meta only, with meta emphasis levels, or in a simple format. Each variant provides different levels of visual hierarchy and information density. #### With Meta This variant includes a meta section without an index, providing space for optional metadata while maintaining a clean appearance. Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant Code ReviewReview pull requests for Project Beta 3:30 PM - 4:30 PMHigh Priority Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant ![TRMNL Logo](/images/plugins/trmnl--render.svg)ItemWith Meta ```
Team Meeting Weekly team sync-up
9:00 AM - 10:00 AM Confirmed
``` #### With Meta Emphasis Items support three emphasis levels: default, emphasis-2, and emphasis-3. Apply `item--emphasis-2` or `item--emphasis-3` to progressively darken the meta bar and draw attention. Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant ![TRMNL Logo](/images/plugins/trmnl--render.svg)ItemEmphasis Levels ```
Team Meeting Weekly team sync-up
9:00 AM - 10:00 AM Confirmed
Client Presentation Quarterly review with XYZ Corp
2:00 PM - 3:30 PM Tentative
Project Deadline Submit final deliverables for Project Alpha
11:59 PM Important
``` #### With Meta and Index The most detailed variant includes both a meta section and an index number, useful for ordered lists or when additional context is needed. 1 Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed 2 Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative 3 Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant 4 Code ReviewReview pull requests for Project Beta 3:30 PM - 4:30 PMHigh Priority 1 Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed 2 Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative 3 Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant ![TRMNL Logo](/images/plugins/trmnl--render.svg)ItemWith Meta and Index ```
1
Team Meeting Weekly team sync-up
9:00 AM - 10:00 AM Confirmed
``` #### Simple The simplest variant focuses purely on content, ideal for basic lists or when metadata isn't needed. Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant Code ReviewReview pull requests for Project Beta 3:30 PM - 4:30 PMHigh Priority Team MeetingWeekly team sync-up 9:00 AM - 10:00 AMConfirmed Client PresentationQuarterly review with XYZ Corp 2:00 PM - 3:30 PMTentative Project DeadlineSubmit final deliverables for Project Alpha 11:59 PMImportant ![TRMNL Logo](/images/plugins/trmnl--render.svg)ItemSimple ```
Team Meeting Weekly team sync-up
9:00 AM - 10:00 AM Confirmed
``` #### With Icon Add an `icon` div between meta and content to display an icon alongside the item. Give monochrome icons the `image--adaptive` class so they follow the screen's semantic text-primary paint across Raw/Preview, themes, and dark mode (see [Image](/framework/docs/3.4/image) ). ![](/images/plugins/weather/wi-thermometer.svg) 72°Temperature ![](/images/plugins/weather/wi-strong-wind.svg) 12 mphWind Speed ![](/images/plugins/weather/wi-hot.svg) 6UV Index ![](/images/plugins/weather/wi-day-sunny.svg) SunnyToday ![](/images/plugins/weather/wi-day-cloudy.svg) Partly CloudyTomorrow ![](/images/plugins/weather/wi-rain.svg) RainyWednesday ![TRMNL Logo](/images/plugins/trmnl--render.svg)ItemWith Icon ```
72° Temperature
``` ### Filling Spare Space An item sizes to its content. Add `item--shrink` to make it flexible instead: it grows into the container's spare space along the main axis and gives that space back when the container runs tight. Use it for the one item that should absorb the leftover room, such as the body row above a fixed footer row. Applying it to every item in a container splits the space between them. ```
72° Temperature
Updated 4 min ago
``` ### List component (deprecated) The `.list` class is deprecated. Prefer a column component, flex column, grid column, or a layout wrapper with a [Gap](/framework/docs/3.4/gap) utility instead. The [Overflow](/framework/docs/3.4/overflow) engine still supports legacy `.list` for backward compatibility. ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--item-index-font-family` | "NicoPups" | "NicoPups" | #{meta.inspect($family)} | - | | `--item-index-font-size` | calc(16px \* var(--text-ui-scale)) | calc(16px \* var(--text-ui-scale)) | calc(13px \* var(--text-ui-scale)) | - | | `--item-index-font-smoothing` | none | none | auto | - | | `--item-index-font-weight` | 400 | 400 | clamp(100, calc(600 + var(--framework-font-weight-shift, 0)), 900) | - | | `--item-index-line-height` | 1 | 1 | 1 | - | | `--item-meta-width` | calc(10px \* var(--ui-scale)) | calc(10px \* var(--ui-scale)) | - | calc(10px \* var(--ui-scale)) | ### Related APIs #### Theming the item A theme can re-point the item's paint through its named slots (`item-meta`, `item-meta-emphasis-2`, `item-meta-emphasis-3`) without touching geometry. Slots take palette token references, so the surface still resolves through the device mode at render time. See [Theme Slots](/framework/docs/3.4/theme_slots) for every slot and mixin. ``` @include theme-slots.text-slot("item-meta", "black"); ``` Previous [ ## Rich Text Display formatted paragraphs with alignment and size variants ](/framework/docs/3.4/rich_text) Next [ ## Table Create data tables optimized for 1-bit rendering ](/framework/docs/3.4/table) --- # Table Tabular data with optional row indexes. Five size variants, and the Overflow and Clamp engines drop rows and truncate cells that do not fit the space available. ### Base Structure Tables are built using standard HTML table elements with additional classes for styling. The base structure includes headers and data cells with consistent spacing and typography. #### Default Table The `table` class provides the standard table styling with comfortable spacing and clear visual hierarchy. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableRegular ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` #### Indexed Table Add an opt-in index column by placing a meta block in the cells you want indexed: `td .meta > span.index`. Add `table--indexed` to dock the meta block to the left and add padding for that column. | | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | --- | | 1 | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | 2 | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | 3 | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | 4 | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | 5 | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | 6 | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | 7 | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | 8 | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | 9 | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | 10 | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | 11 | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | 12 | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | 13 | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | 14 | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | 15 | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | 16 | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | 17 | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | 18 | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | 19 | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | 20 | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | 21 | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | 22 | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | 23 | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | 24 | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | 25 | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | 26 | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | 27 | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | 28 | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | 29 | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | 30 | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableIndexed ```
Employee Role Pranks Sales Score Fun Fact
1 Pam Beesly Receptionist 3 $0 0.00 Loves drawing
``` ### Size Variants Tables support five sizes: Base, Large, XLarge, Small, and XSmall. Use modifier classes to change row heights. Size and orientation prefixes both apply, so `lg:portrait:table--xlarge` raises the row height only on large portrait screens (see the [Responsive](/framework/docs/3.4/responsive) docs). #### Base The `table` class without size modifiers and the `table--base` class both produce the same visual result, providing the standard table styling with comfortable spacing and clear visual hierarchy. Use `table--base` when you need to explicitly set the base size in responsive contexts, such as `table--small lg:table--base`. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableBase ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` #### Large Use `table--large` to increase row heights for more spacious tables. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableLarge ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` #### XLarge Use `table--xlarge` for larger screens. Pairs well with larger font-sizes. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableXLarge ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` #### Small Use `table--small` for a compact table with reduced row heights. The older `table--condensed` class remains supported as a backward-compatible alias. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableSmall ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` #### XSmall Use `table--xsmall` for the most compact row heights. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableXSmall ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Overflow Engine Demonstrates the Overflow behavior [Table Overflow](/framework/docs/3.4/table_overflow) and trailing "and X more" row when content exceeds the height budget. | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableOverflow ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Clamp Engine Apply `data-clamp`[Clamp](/framework/docs/3.4/clamp) to each cell's content to ensure consistent single-line truncation with ellipsis. This works with the Table Overflow behavior [Table Overflow](/framework/docs/3.4/table_overflow) . | Employee | Role | Pranks | Sales | Score | Fun Fact | | --- | --- | --- | --- | --- | --- | | Dwight Schrute | Assistant to the Regional Manager | 24 | 44 | 12.91 | Owns a beet farm | | Jim Halpert | Sales Rep | 42 | 21 | 8.69 | Dwight hates him | | Stanley Hudson | Sales Rep | 0 | 28 | 5.83 | Only smiles on Pretzel Day | | Phyllis Vance | Sales Rep | 0 | 18 | 3.79 | Married to Bob Vance | | Andy Bernard | Sales Rep | 2 | 14 | 3.18 | Cornell graduate | | Creed Bratton | Quality Assurance | ??? | ??? | ??? | ??? | | Karen Filippelli | Sales / Utica Manager | 0 | 12 | 2.57 | Jim’s ex from Stamford | | Michael Scott | Regional Manager | 15 | 0 | 1.65 | World’s Best Boss mug | | Todd Packer | Traveling Salesman | 0 | 6 | 1.34 | Terrible human being | | Ryan Howard | Temp / VP / Janitor | 1 | 2 | 0.63 | Pitched the Sabre Pyramid | | Pam Beesly | Receptionist / Office Admin | 3 | 0 | 0.43 | Art school dreamer | | Meredith Palmer | Supplier Relations | 0 | 1 | 0.32 | Exchanged paper for steak | | Holly Flax | HR (Nashua) | 2 | 0 | 0.32 | Michael’s soulmate | | Darryl Philbin | Warehouse Foreman | 1 | 0 | 0.22 | Started a band | | Kevin Malone | Accountant | 1 | 0 | 0.22 | Spilled the chili | | Erin Hannon | Receptionist | 1 | 0 | 0.22 | Dates Gabe, then Andy | | Kelly Kapoor | Customer Service | 0 | 0 | 0.00 | Obsessed with Ryan | | Angela Martin | Accountant | 0 | 0 | 0.00 | Owns 12 cats | | Oscar Martinez | Accountant | 0 | 0 | 0.00 | “Actually...” guy | | Roy Anderson | Warehouse | 0 | 0 | 0.00 | Pam’s ex-fiancé | | Toby Flenderson | HR | 0 | 0 | 0.00 | Michael hates him | | Jan Levinson | Corporate | 0 | 0 | 0.00 | Serenity by Jan | | David Wallace | CFO | 0 | 0 | 0.00 | Invented “Suck It” | | Robert California | CEO | 0 | 0 | 0.00 | The Lizard King | | Nellie Bertram | Special Projects Manager | 0 | 0 | 0.00 | Took Andy’s job | | Deangelo Vickers | Regional Manager | 0 | 0 | 0.00 | Juggled invisible balls | | Charles Miner | Corporate VP | 0 | 0 | 0.00 | Hated Jim’s pranks | | Gabe Lewis | Sabre Liaison | 0 | 0 | 0.00 | Tall, awkward, hates horror movies | | Clark Green | Sales | 0 | 0 | 0.00 | Mini Dwight | | Pete Miller | Sales | 0 | 0 | 0.00 | Nickname: Plop | ![TRMNL Logo](/images/plugins/trmnl--render.svg)TableClamp: 1 line ```
Employee Role Pranks Sales Score Fun Fact
Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--table-tbody-height` | calc(46px \* var(--ui-scale)) | - | - | - | | `--table-thead-height` | calc(36px \* var(--ui-scale)) | - | - | - | | Xsmall | | `--table-xsmall-tbody-height` | calc(22px \* var(--ui-scale)) | - | - | - | | `--table-xsmall-thead-height` | calc(18px \* var(--ui-scale)) | - | - | - | | Small | | `--table-small-tbody-height` | calc(31px \* var(--ui-scale)) | - | - | - | | `--table-small-thead-height` | calc(24px \* var(--ui-scale)) | - | - | - | | Large | | `--table-large-tbody-height` | calc(56px \* var(--ui-scale)) | - | - | - | | `--table-large-thead-height` | calc(44px \* var(--ui-scale)) | - | - | - | | Xlarge | | `--table-xlarge-tbody-height` | calc(72px \* var(--ui-scale)) | - | - | - | | `--table-xlarge-thead-height` | calc(56px \* var(--ui-scale)) | - | - | - | ### Related APIs #### Theming the table A theme can re-point the table's paint through its named slots (`table-meta`, `table-meta-device`, `table-head-row`, `table-body-row`) without touching geometry. Slots take palette token references, so the surface still resolves through the device mode at render time. See [Theme Slots](/framework/docs/3.4/theme_slots) for every slot and mixin. ``` @include theme-slots.bg-slot("table-meta", "yellow-55"); ``` Previous [ ## Item Build standardized list items and content blocks ](/framework/docs/3.4/item) Next [ ## Chart Visualize data with charts that adapt to the device and theme ](/framework/docs/3.4/chart) --- # Chart Plugins can draw charts with any JavaScript charting library. The TRMNLCharts helper supplies the framework's colors, so a chart adapts to the device and themes like the rest of the screen. ### Usage Any JavaScript library you can load from a CDN can draw charts. The examples below use [Highcharts](https://highcharts.com) and [Chartkick](https://chartkick.com). Use the `TRMNLCharts` helper for your chart colors. It is bundled in the plugin runtime and reads the right paint from the screen as the chart builds, so a chart follows the device and the active theme like the rest of the screen. The methods below are the ones these examples use; [Painting Charts](/framework/docs/3.4/paint_charts) carries the full list. - `series(i, n, { el })`: the fill for series _i_ of _n_, correct for the current device, mode, and theme. - `applySwatches({ el })`: paints legend markers tagged `data-chart-series="i"` with matching series colors. Add `data-chart-series-count="n"` when the series total differs from the number of marks on screen. - `textStyle(role, { el })`: resolves a framework typography role for SVG text. - `options({ el })` and `merge()`: the recommended adaptive Highcharts defaults, merged under your overrides. - `watch(el, buildFn)`: rebuilds the chart when device, scale, mode, dark mode or theme changes. - `paint(token, { el })`: one framework color, as a flat color on solid panels or a dither pattern on 1- and 2-bit screens. - `grid({ el, dir })` and `axisLine({ el })`: the grid-line and axis and tick options that `options()` already applies, for an axis you build by hand. `{ el }` is the chart container id or element. Omit it on a single-screen plugin. `TRMNLCharts` is the Highcharts adapter built on `TRMNLPaint`, the framework's public JS paint API. Grid lines and plotted text use the same border and typography systems as the rest of the screen. For anything beyond Highcharts, use TRMNLPaint directly: see [Paint API](/framework/docs/3.4/paint_api) . On screens that dither, series paints are patterns that Highcharts draws through its pattern-fill module; load `pattern-fill.js` next to `highcharts.js` as the examples do. Maps follow the same pattern: `TRMNLMaps` composes MapLibre GL JS styles from framework paint over OpenStreetMap vector tiles. See [Map](/framework/docs/3.4/map) . Highcharts takes sizes as plain numbers, so they do not follow the device scale on their own. Pass heights, spacing, line widths, and label offsets through `TRMNLPaint.px()` inside `TRMNLCharts.watch()`. The watcher rebuilds the chart after a scale change, and the paint API supplies the new numbers. Set `height: null` in the chart options and the chart expands to fill the available space. Disable animations, or TRMNL's screenshot service may capture the chart only partially drawn. These examples load Highcharts and Chartkick from trmnl.com, so they render empty without network. Highcharts is a commercial library that TRMNL licenses, and a custom stack brings its own charting library and license. #### Line Chart A line chart tracks a value over time. 25,388Pageviews 4,771Visitors 2.23Mins on Page ![TRMNL Logo](/images/plugins/trmnl--render.svg)ChartsLine Chart ```
25,388 Pageviews
4,771 Visitors
2.23 Mins on Page
Simple Analytics Logo Simple Analytics trmnl.com
``` #### Multi-Series Line Chart A multi-series chart compares several lines in one plot. This example plots the current period against the previous one. $85,240Total Sales 32Pending Orders Jul 01 - Jul 15 Current $128AOV 665Fulfilled Orders Jun 15 - Jun 30 Previous ![TRMNL Logo](/images/plugins/trmnl--render.svg)ChartsMulti-Series Line Chart ```
$85,240 Total Sales
32 Pending Orders
Jul 01 - Jul 15
Current
$128 AOV
665 Fulfilled Orders
Jun 15 - Jun 30
Previous
Charts Multi-Series Line Chart
``` #### Bar Chart Bar charts compare discrete categories side by side. This example plots four metrics across six months. $31,883Revenue $22,910Expenses $8,990Marketing $14,930Operations ![TRMNL Logo](/images/plugins/trmnl--render.svg)ChartsBar Chart ```
$31,883 Revenue
$22,910 Expenses
$8,990 Marketing
$14,930 Operations
``` #### Gauge Chart A gauge shows a single score. This example puts seven daily gauges in a row above one weekly summary gauge. Monday Tuesday Wednesday Thursday Friday Saturday Sunday 18%REM Sleep 23%Deep Sleep 12mTime to Sleep 7h 32minSleep Duration 8Toss & Turns 0.5%Snoring ![TRMNL Logo](/images/plugins/trmnl--render.svg)ChartsGauge Chart ```
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
18% REM Sleep
23% Deep Sleep
12m Time to Sleep
7h 32min Sleep Duration
8 Toss & Turns
0.5% Snoring
``` Previous [ ## Table Create data tables optimized for 1-bit rendering ](/framework/docs/3.4/table) Next [ ## Map Plot locations and routes on a vector map that adapts to the device and theme ](/framework/docs/3.4/map) --- # Map Maps render OpenStreetMap vector tiles through MapLibre GL JS, with every layer painted by the framework. The TRMNLMaps helper composes the map style from the live screen, so a map adapts to the device and themes like the rest of the screen. Maps are plotted, never satellite, and never interactive. ### Usage Maps are [MapLibre GL JS](https://maplibre.org) compositions over [OpenStreetMap](https://www.openstreetmap.org) vector tiles, and every layer is painted by the framework. The plugin runtime bundles a `TRMNLMaps` helper that builds the map style from the live screen, the way `TRMNLCharts` builds Highcharts options. The methods below are the ones these examples use; [Painting Maps](/framework/docs/3.4/paint_maps) carries the full list and the resolvers behind it. - `options({ el, preset, center, zoom })`: the Map options for a still map, with the style for a preset, the container, and every handler and animation off. - `watch(el, buildFn)`: builds the map now and again when device, scale, mode, dark mode or theme changes, removing the previous one first. - `route(map, coords, { el, width })` and `dot(map, lngLat, { el, radius, hollow })`: plot a route and its markers as crisp fills painted from the screen's chart-series ramp. - `fit(map, coords, { padding, maxZoom })`: frames the coordinates on an integer zoom, with no animation. - `decodePolyline(str)`: a Google encoded polyline (the shape Strava returns) as `[lng, lat]` pairs. - `merge(base, overrides)`: layer your own layer ids, sources and Map options over the defaults. `{ el }` is the map container id or element. Omit it on a single-screen plugin. Four style presets ship: `streets` draws roads, water, parks, buildings and labels, `minimal` keeps the land, water and main roads a route sits on, `outline` is coast, water, main roads and the big place names for a small view, and `blank` is the land alone for your own overlays. Each preset is the same slot set drawn differently, so a theme restyles all four at once. `TRMNLMaps` resolves no paint of its own: every color comes from a `TRMNLPaint` map slot, so a map on a 1-bit screen draws dither tiles and a map on a color panel draws solids. For anything beyond MapLibre, read the slots directly: see [Paint API](/framework/docs/3.4/paint_api) . #### Tiles and keys A map that names no tile source fetches TRMNL's own tiles (`maps.trmnl.com`) itself, and whoever renders the plugin pays nothing. Name a source when you have one: - `options({ tiles: { url, key } })`: your own source. `url` is a `{z}/{x}/{y}` template and may carry `{key}`, which the key fills. - `options({ tiles: 'trmnl' })`: TRMNL's own source, `maps.trmnl.com`, a Shortbread planet behind a CDN. The docs site and TRMNL's plugins use it. - `window. __TRMNL_MAPS__ = { tiles: { url, key } }`: set by the host per plugin instance, so a plugin author's key or a user's key from the plugin settings reaches the map without a key in the markup. A source named in code wins over it. The style speaks the Shortbread tile schema, so a source has to as well. Keep the OpenStreetMap credit whatever the source: the data is theirs either way. Maps are built non-interactive. A device has no input and the screenshot service captures one still frame, so options() disables every handler and animation: set the camera in the options or with fit(), never with flyTo or easeTo. OpenStreetMap data is licensed under the ODbL and needs visible credit. watch() and attach() place a framework-styled "© OpenStreetMap contributors" label on every map: keep it. A plugin loads MapLibre GL JS from trmnl.com/js/maplibre-gl/5.24.0/, the same build the framework serves next to the runtime for these examples, and the tiles come from maps.trmnl.com, so a map renders an empty canvas without network. MapLibre is BSD licensed; the map data is © OpenStreetMap contributors. #### Streets The streets preset on its own: one place, one zoom, nothing plotted. Load MapLibre from the framework, give the map a container with the map class, and build it inside watch(). ![TRMNL Logo](/images/plugins/trmnl--render.svg)MapStreets ```
TRMNL Logo Map Streets
``` #### Map styles The same view through three presets. Streets is the full map, minimal keeps the shapes a route sits on, and outline is coast, water, main roads and the big names for the smallest views. The small detail waits for the zoom that has room for it. Buildings, sites, minor roads, paths and transit stops start at zoom 13 and 14, so below that streets and minimal draw close to the same map. Leibnizstraße 62 streets Leibnizstraße 62 minimal Leibnizstraße 62 outline ![TRMNL Logo](/images/plugins/trmnl--render.svg)MapStyles ``` // One watch() per container; only the preset changes. var office = [13.3133, 52.5038]; ["streets", "minimal", "outline"].forEach(function (preset) { var el = "map-style-" + preset; TRMNLMaps.watch(el, function () { var map = new maplibregl.Map(TRMNLMaps.options({ el: el, preset: preset, center: office, zoom: 14 })); // The label engine only names tile features, so a place of your own is a // dot plus your own markup over the map. map.on("load", function () { TRMNLMaps.dot(map, office, { el: el }); }); return map; }); }); ``` #### Markers and routes Plot your own data. route() and dot() draw it as crisp fills painted from the screen's chart-series ramp, and fit() frames it on an integer zoom. A second route takes the next step of the ramp (a dither on 1-bit), so the walk out and the walk back read apart. 4.4 kmDistance 55 minWalk +12 mClimb ![TRMNL Logo](/images/plugins/trmnl--render.svg)MapTiergarten walk ``` // Brandenburger Tor west through the woods, back through the north of the park. var route = [ [13.3778, 52.5163], [13.3766, 52.5160], [13.3769, 52.5158], [13.3763, 52.5154], [13.3765, 52.5138], [13.3755, 52.5131], [13.3741, 52.5127], [13.3723, 52.5129], [13.3712, 52.5128], [13.3691, 52.5120], [13.3637, 52.5137], [13.3631, 52.5133], [13.3614, 52.5130], [13.3579, 52.5133], [13.3570, 52.5132], [13.3545, 52.5144], [13.3527, 52.5143], [13.3519, 52.5144], [13.3519, 52.5146], [13.3508, 52.5146] ]; var back = [ [13.3508, 52.5146], [13.3518, 52.5149], [13.3518, 52.5155], [13.3524, 52.5158], [13.3617, 52.5176], [13.3690, 52.5174], [13.3700, 52.5179], [13.3741, 52.5177], [13.3768, 52.5166], [13.3778, 52.5163] ]; whenReady(function () { var el = "map-walk"; TRMNLMaps.watch(el, function () { var map = new maplibregl.Map(TRMNLMaps.options({ el: el, preset: "minimal" })); map.on("load", function () { // route() and dot() draw crisp fills from the chart-series ramp: // the walk out takes step 0 of 2, the walk back step 1. TRMNLMaps.route(map, route, { el: el, i: 0, n: 2 }); TRMNLMaps.route(map, back, { el: el, id: "back", i: 1, n: 2 }); TRMNLMaps.dot(map, route[0], { el: el, id: "start" }); TRMNLMaps.dot(map, route[route.length - 1], { el: el, id: "end", hollow: true }); }); // fit() frames both routes on an integer zoom, no animation. TRMNLMaps.fit(map, route.concat(back), { padding: TRMNLPaint.px(24, { el: el }), maxZoom: 14 }); return map; }); }); ``` #### Grayscale and color The map slots default to grays tuned for 1-bit: land and the built-up blocks are the canvas, water, parks and buildings are dither tiles, roads are grays short of the ink, labels are the ink. A full-color screen re-points the same slots at chromatic tokens and a limited palette dithers them to its ink set, so the plugin changes nothing. Labels are framework elements placed over the canvas, so they take the screen's own fonts (TRMNL pixel fonts on 1-bit and 2-bit low-density panels, Inter on 4-bit and high density) with a large text stroke, on whole pixels. The biggest names win the space: small places wait for closer zooms, and a small map holds a few names instead of a crowd. Switch the device picker to a color device or dark mode and the map repaints. A theme restyles the map with the same slot mixins it uses for every other component: see [Theme Slots](/framework/docs/3.4/theme_slots) . ![TRMNL Logo](/images/plugins/trmnl--render.svg)MapSydney Harbour ``` // Nothing here names a device or a color: the slots resolve per screen. TRMNLMaps.watch(el, function () { return new maplibregl.Map(TRMNLMaps.options({ el: el, preset: "streets", center: [151.2153, -33.8568], zoom: 12 })); }); // A theme re-points a map slot like any other component slot (SCSS): // @include theme-slots.bg-slot("map-water", "blue-65"); // @include theme-slots.bg-slot("map-road-minor", "gray-50"); ``` #### Floating card A card belongs inside the `map` container, which positions its children already, so `top--2 left--2` measures from the map's own corner. Give the card a width and lay its contents out yourself, since nothing sizes or arranges an element that is out of flow. [Position](/framework/docs/3.4/position) has the offset and stacking classes. The bottom right corner belongs to the data credit, which has to stay visible. Place names land wherever the map data puts them, so a smaller card leaves more of them legible. A route map has a lever a fixed center does not: more padding on the card's side of `fit()` frames the route in what is left. Over a detailed map, `outline--muted`[Outline](/framework/docs/3.4/outline) draws that edge in a mid gray. It still separates the card from the map, with a quieter line across whatever it covers. A card over a map takes a solid fill and no shade in between: `bg--canvas` follows the screen's own background, `bg--white` and `bg--black` pin it, and every gray between them is a dither tile that small text cannot survive on 1-bit. Its edge comes from `outline`, which reads against a light and a dark map alike, and never from a box-shadow, which ePaper renders as a smear of dither. Atlanta UnitedMercedes-Benz Stadium | Opponent | Orlando City | | Kickoff | 7:30 PM | | Gates | 5:30 PM | | Seats | 118, Row 12 | | MARTA | GWCC/CNN Center | ![TRMNL Logo](/images/plugins/trmnl--render.svg)MapFloating card ```
Atlanta United Mercedes-Benz Stadium
Opponent Orlando City
Kickoff 7:30 PM
Gates 5:30 PM
Seats 118, Row 12
MARTA GWCC/CNN Center
TRMNL Logo Map Floating card
``` ### Strava activity Strava returns each activity with `map.summary_polyline`, a Google encoded polyline. Decode it, fit the camera to it, draw the route with a start dot and an end ring, and show the activity stats in the framework layout. In a plugin the `activity` object comes from your polling URL; the example inlines one run. The roads are grays and the route takes the ink, so it reads on top; the minimal preset keeps the streets under it quiet. Morning RunCentral Park, New York 9.7 kmDistance 49:58Time 5:09 /kmPace +78 mElev Gain 152 bpmAvg Heart Rate 683 calCalories ![TRMNL Logo](/images/plugins/trmnl--render.svg)StravaActivity ```
{{ activity.name }} Central Park, New York
Distance
Time
Pace
Elev Gain
Avg Heart Rate
Calories
TRMNL Logo Strava Activity
``` Previous [ ## Chart Visualize data with charts that adapt to the device and theme ](/framework/docs/3.4/chart) Next [ ## Progress Display progress bars in different styles ](/framework/docs/3.4/progress) --- # Progress Progress bars and step dots for completion state. The fill renders as a bitmap pattern on 1-bit displays and as a solid color on 4-bit+ displays. ### Progress Bar Progress bars display continuous progress with a filled track. They support multiple sizes and emphasis levels for different visual weights and contexts. #### Sizes Progress bars come in four sizes: xsmall, small, base (the default, which a bare `progress-bar` already renders), and large. Use the `fill` element with inline width styling to set the progress percentage. The `progress-bar--base` modifier spells the default out, which is what a responsive layout needs to switch back to it: `progress-bar--large md:progress-bar--base`. Xsmall Progress25% Small Progress25% Base Progress50% Regular Progress50% Large Progress75% ![TRMNL Logo](/images/plugins/trmnl--render.svg)ProgressBar Sizes ```
Xsmall Progress 25%
Small Progress 25%
Base Progress 50%
Regular Progress 50%
Large Progress 75%
``` #### Emphasis Progress bars support three emphasis levels: default, emphasis-2, and emphasis-3 for different visual weights. Default Emphasis60% Emphasis 260% Emphasis 360% ![TRMNL Logo](/images/plugins/trmnl--render.svg)ProgressBar Emphasis ```
Default Emphasis 60%
Emphasis 2 60%
Emphasis 3 60%
``` ### Progress Dots Progress dots display discrete steps or stages in a process. They come in five sizes and show different states: filled (completed), current (active), and empty (upcoming). #### Sizes Progress dots come in four sizes: xsmall, small, base (the default, which a bare `progress-dots` already renders), and large. Each size maintains the same dot states and functionality. The `progress-dots--base` modifier spells the default out, which is what a responsive layout needs to switch back to it: `progress-dots--large md:progress-dots--base`. Xsmall Progress Small Progress Base Progress Regular Progress Large Progress ![TRMNL Logo](/images/plugins/trmnl--render.svg)ProgressDots Sizes ```
``` ### Related Tokens These tokens are automatically mapped to this page by token prefix. | Token | 1-bit | 2-bit | Density 2x | 4-bit and up | | --- | --- | --- | --- | --- | | Base | | `--progress-bar-height` | calc(24px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-large` | calc(32px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-small` | calc(12px \* var(--ui-scale)) | - | - | - | | `--progress-bar-height-xsmall` | calc(6px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size` | calc(16px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-large` | calc(20px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-small` | calc(12px \* var(--ui-scale)) | - | - | - | | `--progress-dot-size-xsmall` | calc(8px \* var(--ui-scale)) | - | - | - | ### Related APIs #### Theming the progress bar A theme can re-point the progress bar's paint through its named slots (`progress-track`, `progress-fill`, `progress-dot`, `progress-dot-current`) without touching geometry. Slots take palette token references, so the surface still resolves through the device mode at render time. See [Theme Slots](/framework/docs/3.4/theme_slots) for every slot and mixin. ``` @include theme-slots.bg-slot("progress-fill", "yellow-55"); ``` Previous [ ## Map Plot locations and routes on a vector map that adapts to the device and theme ](/framework/docs/3.4/map)