# V3 Overview
Framework v3 introduces a complete color system for ePaper devices. The framework now supports chromatic palettes, semantic color roles, and an extended grayscale scale, while shifting its internal architecture to CSS custom properties for cleaner, smaller stylesheets. Existing markup continues to work without changes.
### Most Important Upgrades
- **Color support:** v3 adds a complete color system with chromatic palettes, semantic color roles, and automatic adaptation to each device's supported palette and bit-depth.
- **Architecture overhaul:** the framework moves from a rules-based selector system to CSS custom properties, greatly reducing combinatorial mode rules while keeping existing class names stable.
- **High-density support:** framework rendering now supports high-density `1bit` and `2bit` output modes.
- **Expanded 1bit grayscale:** the usable `1bit` grayscale palette increases from 7 shades to 14.
- **Raw / Preview simulation:** Device Preview can compare full-bright tokens (Raw) against panel-accurate output that simulates true colors and white point.
- **Text Scale:** [Text Scale](/framework/docs/3.1/text_scale) adjusts every framework font size and pixel line height without applying the same factor to interface geometry or text strokes.
- **Fluid Mashups:** [Mashup](/framework/docs/3.1/mashup) adds a three by three grid with column, row, and span modifiers for custom tilings.
### What's New
- **Chromatic utilities:** `bg--red`, `bg--blue-40`, `text--green-60` and similar classes for all 10 hues and 14 steps.
- **Semantic colors:** `bg--primary`, `text--success`, `label--error`, `bg--warning` - intent-based styling that maps to underlying hues.
- **Colors reference:** new [Colors](/framework/docs/3.1/colors) page documenting the full grayscale, chromatic, and semantic palette.
- **Label color variants:** [Label](/framework/docs/3.1/label) gains `label--primary`, `label--success`, `label--error`, and `label--warning` for colored badges.
- **Closest-hue mapping:** when a selected device cannot render a requested color directly, framework tokens map to the nearest supported hue automatically.
- **Color pattern images:** auto-generated dither patterns for limited-palette devices in `public/images/color-*/`.
### What's Enhanced
- **Background utility:** [Background](/framework/docs/3.1/background) refactored to reference CSS custom properties. Now supports grayscale, chromatic, and semantic tokens in a single class, including high-density `1bit` and `2bit` rendering modes.
- **Text utility:** [Text](/framework/docs/3.1) follows the same CSS variable pattern, supporting chromatic and semantic text colors alongside grayscale.
- **Border and Outline:** [Border](/framework/docs/3.1/border) and [Outline](/framework/docs/3.1/outline) use shared mixins for DRY, consistent rendering across bit-depths and color palettes, including high-density `1bit` and `2bit` modes.
- **Dark mode:** grayscale tokens invert automatically; chromatic hues stay stable while their lightness steps mirror (light to dark).
- **Progress component:** [Progress](/framework/docs/3.1/progress) updated to render with color palette awareness.
### What's Changed
- **Grayscale scale:** the primary naming convention is now `gray-10` through `gray-75` (14 steps of 5). In `1bit`, the usable grayscale palette expands from 7 shades to 14. The legacy names `gray-1` through `gray-7` remain functional but are deprecated.
- **Rendering model:** mode-dependent styling (bit-depth, dark mode, palette) is driven by CSS custom properties on `.screen` rather than combinatorial selector rules. This is an internal change - existing class names are unaffected.
- **Default font bundle:** Framework 3.1 uses the TRMNL bundle from [Font Family](/framework/docs/3.1/font_family) by default on low-density displays. Add `screen--fonts-classic` to keep the 3.0 Classic typography.
### Start Here
- Upgrading from v2? → [V3 Upgrade Guide](/framework/docs/3.1/v3_upgrade_guide).
- Looking to use colors and the new palette system? → [V3 Enhancement Guide](/framework/docs/3.1/v3_enhancement_guide).
- New to the framework? Start with the [Colors](/framework/docs/3.1/colors) reference page.
Next
[V3 Upgrade Guide Steps to upgrade your plugins from Framework v2 to v3](/framework/docs/3.1/v3_upgrade_guide)
---
# V3 Upgrade Guide
Framework v3 is structurally backward-compatible with v2: your existing class names still compile and render. However, the `1bit` grayscale dither patterns have been rebuilt on a new linear scale, so most shade names now produce different lightness values than they did in v2. Review the migration table below to restore your original look or take advantage of the finer 14-step palette.
### 1-Bit Grayscale Dither Scale
In `1bit` mode, v3 replaces the 7-step non-linear dither scale (12×12 px tiles) with a 14-step linear scale (16×16 px tiles). Each step now increments by 6.25% white-pixel density. In v2 the 14 extended shade names were rendered in pairs: `gray-15` produced the same pattern as `gray-10`, `gray-25` the same as `gray-20`, and so on. In v3 every shade has its own distinct pattern, so nearly all shades now sit at **different lightness values** than they did in v2.
#### Lightness Shift
The table below shows the white-pixel density each shade produced in v2 `1bit` mode, what it now produces in v3, and which v3 shade to use to restore the original appearance.
| Class | v2 Lightness | v3 Lightness | Shift | Use Instead |
| --- | --- | --- | --- | --- |
| `gray-10` | 6.25% | 6.25% | — | — |
| `gray-15` | 6.25% | 12.5% | +6.25% | `gray-10` |
| `gray-20` | 12.5% | 18.75% | +6.25% | `gray-15` |
| `gray-25` | 12.5% | 25% | +12.5% | `gray-15` |
| `gray-30` | 25% | 31.25% | +6.25% | `gray-25` |
| `gray-35` | 25% | 37.5% | +12.5% | `gray-25` |
| `gray-40` | 50% | 43.75% | −6.25% | `gray-45` |
| `gray-45` | 50% | 56.25% | +6.25% | `gray-40` |
| `gray-50` | 75% | 62.5% | −12.5% | `gray-60` |
| `gray-55` | 75% | 68.75% | −6.25% | `gray-60` |
| `gray-60` | 87.5% | 75% | −12.5% | `gray-70` |
| `gray-65` | 87.5% | 81.25% | −6.25% | `gray-70` |
| `gray-70` | 93.75% | 87.5% | −6.25% | `gray-75` |
| `gray-75` | 93.75% | 93.75% | — | — |
To preserve your v2 look, switch to the shade listed in the “Use Instead” column. To adopt the new linear scale and take advantage of the finer 14-step palette, keep your current class names - they now each produce a unique dither pattern. See the [Colors](/framework/docs/3.1/colors) reference for the full scale.
### CSS Variable Architecture
Under the hood, utilities like [Background](/framework/docs/3.1/background) and [Text](/framework/docs/3.1) now reference CSS custom properties instead of containing per-mode selector rules.
This is an internal refactor. The class names you use in your markup (`bg--gray-50`, `text--black`, etc.) are unchanged. If you have custom CSS that directly targets framework internals (e.g., overriding `.screen--1bit .bg--gray-3` selectors), you may need to update those overrides to use the new CSS variable approach.
If you use only framework-provided class names, no action is needed. If you have custom CSS overriding framework internals, review the updated [Background](/framework/docs/3.1/background) and [Text](/framework/docs/3.1) pages for the new variable-based approach.
### Background and Border Rendering Modes
Utility class names are unchanged, but rendering support is broader in v3. Both [Background](/framework/docs/3.1/background) and [Border](/framework/docs/3.1/border) now support high-density `1bit` and `2bit` output modes without markup changes.
### Color Palette on Grayscale Devices
If you adopt the new chromatic color classes (e.g., `bg--red`, `text--blue-40`) in your plugin, they automatically adapt by device capability. On grayscale hardware, colors fall back to perceptually equivalent gray values; on limited-color hardware, unavailable colors map to the closest supported hue. No conditional logic is needed. The framework handles this mapping via perceptual lightness (LAB L*).
### Raw / Preview Verification
Use the Raw Colors / Preview Colors toggle in Device Preview while upgrading. Raw mode shows full-bright token values; Preview mode simulates panel-accurate output, including true-color behavior and device white point.
### Next Steps
Once you've reviewed the deprecation notes above, head to the [V3 Enhancement Guide](/framework/docs/3.1/v3_enhancement_guide) to learn how to use chromatic colors, semantic roles, and the new label variants in your plugins.
Previous
[V3 Overview What's new in Framework v3: color palette, semantic colors, extended grayscale, and CSS variable architecture](/framework/docs/3.1/v3_overview)
Next
[V3 Enhancement Guide Use chromatic colors, semantic roles, and label variants in your plugins](/framework/docs/3.1/v3_enhancement_guide)
---
# V3 Enhancement Guide
Framework v3 opens up the full color spectrum for ePaper plugins. This guide walks through the key enhancements you can adopt, from chromatic backgrounds and text to semantic labels and the extended grayscale.
### 1. Use Chromatic Colors
The framework now provides 10 hues, each with 14 lightness steps. Apply them with the same `bg--` and `text--` prefixes you already know.
- **Base hue:** `bg--red`, `text--blue` - the pure, full-saturation color.
- **Hue + step:** `bg--red-40`, `text--green-60` - lighter or darker variants using steps 10 (darkest) through 75 (lightest).
- **Available hues:** red, orange, yellow, lime, green, cyan, blue, violet, purple, pink.
On grayscale devices, chromatic tokens automatically fall back to perceptually equivalent gray shades. On limited-color panels, unavailable colors map to the closest supported hue automatically, so no conditional markup is needed. See the full palette on the [Colors](/framework/docs/3.1/colors) page.
### 2. Apply Semantic Colors
Semantic roles let you style by intent rather than specific hue. The framework maps each role to an underlying color token that can be themed via CSS variables.
- `bg--primary` / `text--primary` - blue (highlights, accents)
- `bg--success` / `text--success` - green (confirmations, positive states)
- `bg--error` / `text--error` - red (errors, critical states)
- `bg--warning` / `text--warning` - orange (cautions, alerts)
Semantic tokens inherit all device and bit-depth behavior. To override the default mapping, set `--color-primary`, `--color-success`, etc. in your custom CSS.
### 3. Use Label Color Variants
The [Label](/framework/docs/3.1/label) element now supports semantic color variants for colored badges and status indicators.
- `label--primary` - blue label for key information
- `label--success` - green label for positive states
- `label--error` - red label for errors or alerts
- `label--warning` - orange label for cautions
The default filled label (`label--filled`) continues to use black (the darkest shade). The new color variants use semantic color backgrounds while adapting to the current device palette.
### 4. Leverage the Extended Grayscale
The extended scale doubles the available gray shades from 7 to 14, giving you finer control over tonal variation in your layouts. In `1bit` mode, the usable grayscale palette now expands from 7 shades to 14.
- **Old scale:** `gray-1` through `gray-7` (still works, deprecated)
- **New scale:** `gray-10`, `gray-15`, `gray-20`, …, `gray-75`
Intermediate steps like `gray-15`, `gray-25`, and `gray-35` let you create subtler contrasts and more nuanced visual hierarchies, especially on 2-bit and 4-bit displays.
### 5. Use the Raw / Preview Toggle
The device picker now includes a Raw / Preview switch that lets you compare how colors render across different modes.
- **Raw:** shows the full-bright token values, useful for verifying your color choices.
- **Preview:** shows panel-accurate simulation, including device true-color behavior and lowered white points on displays like TRMNL BWRY, so you can see what end users actually see on hardware.
### 6. Target High-Density 1bit / 2bit Rendering
Core utilities now render consistently on high-density ePaper modes. Use the same classes and let the framework adapt to panel capabilities.
- **Background utility:** [Background](/framework/docs/3.1/background) supports high-density `1bit` and `2bit` output in addition to broader color-capable modes.
- **Border utility:** [Border](/framework/docs/3.1/border) supports the same high-density `1bit` and `2bit` rendering behavior for consistent edge contrast.
Previous
[V3 Upgrade Guide Steps to upgrade your plugins from Framework v2 to v3](/framework/docs/3.1/v3_upgrade_guide)
Next
[TRMNL X Guide Framework changes for TRMNL X compatibility](/framework/docs/3.1/trmnl_x_guide)
---
# TRMNL X Guide
TRMNL X is a larger, 4-bit ePaper display. This guide covers everything that changed in the Framework to support it. Your existing plugins will continue to work, but by adopting these new features you can build layouts that take full advantage of 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:
Dashboard48,206
...
The `--base` modifier is available on:
- **Typography:** [Title](/framework/docs/3.1/title) (`title--base`), [Value](/framework/docs/3.1/value) (`value--base`), [Label](/framework/docs/3.1/label) (`label--base`), [Description](/framework/docs/3.1/description) (`description--base`)
- **Rich Text:** [Rich Text](/framework/docs/3.1/rich_text) (`content--base`)
- **Components:** [Table](/framework/docs/3.1/table) (`table--base`), [Progress](/framework/docs/3.1/progress) (`progress-bar--base`, `progress-dots--base`)
- **Utilities:** [Gap](/framework/docs/3.1/gap) (`gap--base`), [Rounded](/framework/docs/3.1/rounded) (`rounded--base`), [Text Stroke](/framework/docs/3.1/text_stroke) (`text-stroke--base`), [Image Stroke](/framework/docs/3.1/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.1/value) docs for full details.
4242424242
#### Title
Three new large sizes for headings. See the [Title](/framework/docs/3.1/title) docs.
HeadingHeadingHeading
#### Label, Description, Rich Text
New large sizes for [Label](/framework/docs/3.1/label) (`label--large`, `label--xlarge`, `label--xxlarge`), [Description](/framework/docs/3.1/description) (`description--large`, `description--xlarge`, `description--xxlarge`), and [Rich Text](/framework/docs/3.1/rich_text) content (`content--xlarge`, `content--xxlarge`, `content--xxxlarge`).
### Container Query Units
The [Layout](/framework/docs/3.1/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.1/size) docs for full details.
### Responsive Arbitrary Sizes
The `w--[Npx]` and `h--[Npx]` utilities now support responsive prefixes. Previously, these were static-only. The maximum value has been reduced from 1000px to 800px. See the [Size](/framework/docs/3.1/size) docs.
Responsive arbitrary width
Orientation-aware height
### Responsive Overflow Columns
The [Overflow](/framework/docs/3.1/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.1/layout) direction. In `layout--row`, `stretch-x` grows along the horizontal axis and `stretch-y` stretches vertically. 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.1/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.1/item) component now gives `.icon` elements the same flex styling as `.content`. Items in [Flex](/framework/docs/3.1/flex) row containers automatically stretch to match the tallest sibling, and items in [Grid](/framework/docs/3.1/grid) containers stretch to fill their cell height.
### Gap and Rounded Utilities
#### Gap
New [Gap](/framework/docs/3.1/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.1/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.1/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.1/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 now work correctly even without an explicit `.screen--landscape` class on the screen element. Since landscape is the default orientation, `landscape:` prefixes activate whenever `.screen--portrait` is not present. See the [Responsive](/framework/docs/3.1/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 Enhancement Guide Use chromatic colors, semantic roles, and label variants in your plugins](/framework/docs/3.1/v3_enhancement_guide)
Next
[Size Define exact width and height dimensions for elements](/framework/docs/3.1/size)
---
# Size
The Size system provides utility classes for controlling width and height dimensions. It includes both fixed sizes and responsive utilities to handle various layout needs.
## 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 800.
`w/h--[150px]`
`w/h--[225px]`
`w/h--[300px]`
`150px`
`225px`
`300px`
### 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
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-100 (representing 0-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—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)
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.
`w--min-[100px]`
`w--max-[200px]`
`h--min-[50px]`
`h--max-[150px]`
`min-width: 100px`
`max-width: 200px`
`min-height: 50px`
`max-height: 150px`
### 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
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 | ✗ Not Supported | `md:w--[150px], lg:w--[225px]` |
| Container Query Sizes | ✓ Supported | `md:w--[50cqw], lg:h--[75cqh]` |
Previous
[TRMNL X Guide Framework changes for TRMNL X compatibility](/framework/docs/3.1/trmnl_x_guide)
Next
[Spacing Control element spacing with fixed margin and padding values](/framework/docs/3.1/spacing)
---
# Spacing
The Spacing system provides utility classes for controlling margins and padding. It includes both fixed sizes and decimal values to handle precise spacing needs.
Available spacing sizes and their pixel values
[View Size Documentation](/framework/docs/3.1/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.1/size), **Orientation**, and **Size + Orientation** [Responsive](/framework/docs/3.1/responsive).
`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.1/size) for sizing tokens.
`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/8/16-bit |
| --- | --- | --- | --- | --- |
| `--gap-large` | 20px | — | — | — |
| `--gap-medium` | 16px | — | — | — |
| `--gap-small` | 7px | — | — | — |
| `--gap-xlarge` | 30px | — | — | — |
| `--gap-xsmall` | 5px | — | — | — |
| `--gap-xxlarge` | 40px | — | — | — |
| `--list-gap-large` | 16px | — | — | — |
| `--list-gap-small` | 8px | — | — | — |
Previous
[Size Define exact width and height dimensions for elements](/framework/docs/3.1/size)
Next
[Gap Set precise spacing between elements with predefined gap values](/framework/docs/3.1/gap)
---
# Gap
The Gap system provides consistent spacing between elements using CSS gap property. It offers predefined sizes, responsive spacing, and custom values to maintain visual rhythm throughout your interface.
### 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
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]
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
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
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
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/8/16-bit |
| --- | --- | --- | --- | --- |
| `--gap-large` | 20px | — | — | — |
| `--gap-medium` | 16px | — | — | — |
| `--gap-small` | 7px | — | — | — |
| `--gap-xlarge` | 30px | — | — | — |
| `--gap-xsmall` | 5px | — | — | — |
| `--gap-xxlarge` | 40px | — | — | — |
| `--list-gap-large` | 16px | — | — | — |
| `--list-gap-small` | 8px | — | — | — |
Previous
[Spacing Control element spacing with fixed margin and padding values](/framework/docs/3.1/spacing)
Next
[Flex Arrange elements with flexible layouts and alignment options](/framework/docs/3.1/flex)
---
# Flex
The Flex system provides utility classes for creating flexible layouts using Flexbox. It supports both row and column directions with various alignment, centering, and stretching options.
### When to Use Flex
Use Flex inside [Layout](/framework/docs/3.1/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. Examples: toolbars, inline groups of labels and values, or stacks of variable-height cards.
#### Standalone or Nested
You can use Flex alone as the only child of Layout for simpler layouts. You can also nest Flex inside [Grid](/framework/docs/3.1/grid). Each grid cell can contain a Flex container for row or column flexibility within that cell. And you can nest Flex inside [Columns](/framework/docs/3.1/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.1/columns).
### Base Structure
The Flex system provides two fundamental ways to organize content: horizontal (row) and vertical (column) arrangements.
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
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
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. The system provides directional alignment (left/right/top/bottom) and centering options.
#### Row Horizontal Alignment
For row layouts, use `flex--left`, `flex--center-x`, or `flex--right` to control horizontal alignment:
Left
Center X
Right
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
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
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
FlexColumn Vertical Alignment
Item
Item
Item
### Stretch Modifiers
The Flex system provides both container-level and individual item stretch controls. 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
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
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
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 `no-shrink` on flex children to prevent them from shrinking
when other items try to take up more space:
Can Shrink
Stretching Content That Pushes Others
Won't Shrink
Stretching Content That Pushes Others
FlexPrevent Shrinking
Maintains its width
Stretches but won't squish the no-shrink item
### 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
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.
#### Row Reverse
A
B
C
FlexRow Reverse
A
B
C
#### Column Reverse
1
2
3
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
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
FlexWrapping Items
Team MeetingWeekly 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
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
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
FlexSelf Alignment
#### Basis and Order
basis--36
basis--20
basis--24
order--last
order--first
order--2
order---1
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
FlexInline Flex
Text before
•
•
Text after
Previous
[Gap Set precise spacing between elements with predefined gap values](/framework/docs/3.1/gap)
Next
[Grid Create grid layouts with predefined column structures](/framework/docs/3.1/grid)
---
# Grid
The Grid system provides a flexible way to create both column-based and row-based layouts. It supports various column counts, column spans, and responsive behaviors to create complex layouts easily.
### When to Use Grid
Use Grid inside [Layout](/framework/docs/3.1/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.1/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.1/columns).
### Related
[Columns](/framework/docs/3.1/columns)[Flex](/framework/docs/3.1/flex)[Gap](/framework/docs/3.1/gap)[Layout](/framework/docs/3.1/layout)
### Ways to Define the Grid
The grid system provides two ways to define column layouts:
- **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 specify any number of columns.
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
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. 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
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
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
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
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
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
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
Previous
[Flex Arrange elements with flexible layouts and alignment options](/framework/docs/3.1/flex)
Next
[Aspect Ratio Maintain consistent proportions for elements regardless of their content](/framework/docs/3.1/aspect_ratio)
---
# Aspect Ratio
The Aspect Ratio utility uses the native CSS aspect-ratio property to maintain consistent proportions for elements. Perfect for images, videos, and containers that need to maintain specific width-to-height ratios across different screen sizes.
### 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
Aspect RatioBasic Usage
...
...
...
## 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.1/grid)
Next
[Responsive Adapt styles based on screen width using breakpoint prefixes](/framework/docs/3.1/responsive)
---
# Responsive
The Responsive system provides two complementary approaches for creating adaptive layouts: **Size-based** breakpoints that respond to screen dimensions, and **Bit-depth** variants that adapt to color capabilities. Together, they enable precise control over how your content appears across TRMNL's diverse 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 | Auto | `md:bg--gray-50` |
| Border | No | No | Auto | `border--h-3 (auto adapts)` |
| Text | Yes | Yes | Auto | `lg:2bit:text--center` |
| Visibility | Yes | Yes | Yes | `sm:1bit:hidden` |
| Value | Yes | Yes | No | `md:value--large` |
| Label | Yes | Yes | Yes | `md:portrait:2bit:label--filled` |
| Spacing | Yes | Yes | No | `md:p--large, lg:m--xlarge, md:portrait:my--24` |
| 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--center, portrait:flex--col` |
| Rounded | Yes | Yes | No | `md:rounded--large, lg:rounded--xlarge` |
| Size | Yes | Yes | No | `md:w--large, 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
Each device automatically sets a size class (e.g., `screen--md`) based on its width, activating the appropriate responsive utilities.
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
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 standard breakpoints cover all current supported TRMNL devices. Each breakpoint
represents a minimum screen width.
| Prefix | Screen Class | Min Width | Example Devices |
| --- | --- | --- | --- |
| `sm:` | `screen--sm` | 600px | Kindle 2024 |
| `md:` | `screen--md` | 800px | TRMNL OG, TRMNL OG V2 |
| `lg:` | `screen--lg` | 1024px | TRMNL V2 |
## 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.
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
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
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.
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.
### 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--medium` 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 based on screen width |
| `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:gap--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 |
Previous
[Aspect Ratio Maintain consistent proportions for elements regardless of their content](/framework/docs/3.1/aspect_ratio)
Next
[Responsive Test Test responsive utilities and compare SCSS mixins with CSS classes](/framework/docs/3.1/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 based on screen width using breakpoint prefixes](/framework/docs/3.1/responsive)
Next
[Visibility Control element visibility based on display bit depth](/framework/docs/3.1/visibility)
---
# Visibility
The visibility and display utilities provide comprehensive control over element visibility and display types across devices. They include hidden/visible controls and display helpers like flex, grid, and inline, with responsive and bit-depth variants for device-specific layouts.
## Visibility Across Devices
See how visibility classes behave across different screen sizes. Each column represents a different device size.
Small (600px)
visible md:hidden
Medium (800px)
hidden md:visible lg:hidden
Large (1024px)
hidden lg:visible
visible
hidden
md:visible
md:hidden
lg:hidden
md:flex
lg:grid
## 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 work with responsive prefixes [Responsive](/framework/docs/3.1/responsive). Size prefixes are mobile-first (apply at that size and larger).
You can combine any display utility with responsive breakpoints [Responsive](/framework/docs/3.1/responsive).
| Example Class | Effect | Active On |
| --- | --- | --- |
| `sm:hidden` | Hide on small screens and larger | All devices (600px+) |
| `md:flex` | Display as flex on medium screens and larger | TRMNL OG, TRMNL V2 (800px+) |
| `lg:grid` | Display as grid on large screens | TRMNL V2 (1024px+) |
| `sm:inline-block` | Display as inline-block on small screens and larger | All devices (600px+) |
Show as block on medium+
Block by default, flex on medium+
Show as inline-grid on large screens
Changes display type at each breakpoint
Different layout per screen size
Cell A
Cell B
## Bit-Depth Display Control
All display utilities work with bit-depth prefixes to target specific display capabilities.
Perfect for optimizing layouts for different ePaper displays.
| Example Class | Effect | Active On |
| --- | --- | --- |
| `1bit:hidden` | Hide on monochrome displays | TRMNL OG only |
| `2bit:flex` | Display as flex on 4-shade grayscale displays | TRMNL OG V2 only |
| `4bit:grid` | Display as grid on 16-shade grayscale displays | TRMNL V2, Kindle 2024 only |
## Device-Specific Display Control
Combine size and bit-depth prefixes with any display utility to target specific devices precisely.
Use the pattern: `size:bit-depth:display`
| Example Class | Target Device | Effect |
| --- | --- | --- |
| `md:1bit:block` | TRMNL OG only | Display as block (800px, monochrome) |
| `md:2bit:flex` | TRMNL OG V2 only | Display as flex (800px, 4-shade grayscale) |
| `lg:4bit:grid` | TRMNL V2 only | Display as grid (1024px, 16-shade grayscale) |
| `sm:4bit:table` | Kindle 2024 only | Display as table (600px, 16-shade grayscale) |
Different display types per device generation
Simple layouts for lower bit-depth displays
Progressive enhancement across all device capabilities
## 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
Dark mode shows 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.1/responsive_test)
Next
[Background Apply color tokens as backgrounds with bg--{token}](/framework/docs/3.1/background)
---
# Background
Use the color palette defined in [Colors](/framework/docs/3.1/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
Grayscale backgrounds
**Dark Mode Notice:** The color palette appears inverted because TRMNL's dark mode inverts the entire screen, except the images.
### 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
Base background colors
### Usage
Use the `bg--{shade}` utility
classes to apply these background patterns to any element. Grayscale: black, gray-10 through gray-75, and white.
Chromatic: `bg--{hue}` (pure color, e.g. bg--red, bg--green) or `bg--{hue}-{step}` (e.g. bg--red-50, bg--blue-40).
Semantic: `bg--primary`, `bg--success`, `bg--error`, etc. (see [Colors](/framework/docs/3.1/colors)).
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.1/colors) for the full mapping.
### Backward Compatibility
The Border utility in Framework v2 is **not backward compatible** with the legacy v1 Border utility. This is the only non-backward compatible utility in the new framework.
#### What changed?
- The visual scale has been redefined to a full-spectrum grayscale that works on *any* background shade: `1` = black … `7` = white.
- In v1, borders only produced a faux grayscale on white surfaces and appeared solid black (invisible) on black surfaces. In v2, borders render consistently on both light and dark backgrounds.
- Class names remain the same (`border--h-{n}`, `border--v-{n}`), but the visual output for a given `{n}` looks different.
#### How to upgrade existing plugins
- Keep your markup unchanged. Continue using `border--h-{n}` and `border--v-{n}`.
- Re-evaluate the chosen `{n}` values based on the new scale.
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| `--rounded-full` | 9999px | — | — | — |
| `--rounded-large` | calc(20px * var(--content-scale)) | — | — | — |
| `--rounded-medium` | calc(15px * var(--content-scale)) | — | — | — |
| `--rounded-none` | 0px | — | — | — |
| `--rounded-small` | calc(7px * var(--content-scale)) | — | — | — |
| `--rounded-xlarge` | calc(25px * var(--content-scale)) | — | — | — |
| `--rounded-xsmall` | calc(5px * var(--content-scale)) | — | — | — |
| `--rounded-xxlarge` | calc(30px * var(--content-scale)) | — | — | — |
Previous
[Background Apply color tokens as backgrounds with bg--{token}](/framework/docs/3.1/background)
Next
[Rounded Control element rounding with predefined values](/framework/docs/3.1/rounded)
---
# Rounded
The Rounded system provides consistent border radius values for creating smooth corners on elements. It offers predefined sizes, corner-specific controls, and custom values to maintain visual consistency throughout your interface.
### 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
Predefined RoundedDesign System
...
...
...
...
...
...
...
...
...
...
...
#### Arbitrary
Use `rounded--[Npx]` syntax to specify
exact pixel values from **0px to 50px**. This works with all rounded utilities, but does not support responsive variants.
rounded--[0px]
rounded--[10px]
rounded--[30px]
rounded--[20px]
rounded--[40px]
rounded--[50px]
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}{-size}` where
corner can be tl (top-left), tr (top-right), br (bottom-right), or bl (bottom-left).
rounded-tl--large
rounded-tr--large
rounded-bl--large
rounded-br--large
Corner-Specific RoundingDesign System
#### Side Rounding
Round entire sides with `rounded-{side}{-size}` where
side can be t (top), r (right), b (bottom), or l (left).
rounded-t--large
rounded-r--large
rounded-b--large
rounded-l--large
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
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
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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--progress-bar-radius` | 10px | — | — | — |
| `--rounded-full` | 9999px | — | — | — |
| `--rounded-large` | calc(20px * var(--content-scale)) | — | — | — |
| `--rounded-medium` | calc(15px * var(--content-scale)) | — | — | — |
| `--rounded-none` | 0px | — | — | — |
| `--rounded-small` | calc(7px * var(--content-scale)) | — | — | — |
| `--rounded-xlarge` | calc(25px * var(--content-scale)) | — | — | — |
| `--rounded-xsmall` | calc(5px * var(--content-scale)) | — | — | — |
| `--rounded-xxlarge` | calc(30px * var(--content-scale)) | — | — | — |
| `--title-bar-border-radius` | 10px | calc(10px * var(--ui-scale)) | — | calc(10px * var(--ui-scale)) |
Previous
[Border Apply border patterns that create the illusion of different border intensities](/framework/docs/3.1/border)
Next
[Outline Pixel-perfect rounded borders using border-image for 1-bit displays](/framework/docs/3.1/outline)
---
# Outline
The Outline utility provides pixel-perfect rounded borders using CSS border-image with a 9-slice composite image. On 1-bit displays, it renders crisp, dithered corner patterns that scale with the element. On 2-bit and 4-bit displays, it falls back to standard CSS borders with border-radius.
### Basic Usage
The outline utility applies a pixel-perfect dotted rounded border to any element. On 1-bit displays,
it uses pure CSS gradients to place single-pixel dots at exact integer coordinates along
a rounded rectangle path. On 2-bit and 4-bit displays, it falls back to a standard CSS border
with border-radius.
#### Applying an Outline
Add the `outline` class to any element
to give it a pixel-perfect rounded border.
With outline
Without outline
Outline UtilityDesign System
Content with pixel-perfect rounded border
### How It Works
The outline utility uses 20 CSS background layers to place each dot at an exact integer pixel
coordinate. Edge dots use `repeating-linear-gradient` for a 1px dot every 4px. Corner dots use individual `linear-gradient` blocks sized to 1x1px
and positioned with pixel-precise offsets.
#### CSS Gradient Dots
No images are used. Each dot is computed mathematically by the CSS engine,
guaranteeing pixel-grid alignment at any element size. The border color comes from `--framework-border-strong`,
so dark mode works automatically without separate 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 ... */
}
### Bit-Depth Behavior
The outline utility adapts to different display bit-depths automatically. On 1-bit displays, it uses
CSS gradient dots for pixel-perfect rendering. On 2-bit and 4-bit displays, it falls back to
standard CSS borders with border-radius for smoother rendering.
#### 1-bit Displays
Uses pure CSS gradients to place sparse single-pixel dots at exact integer coordinates.
Dark mode works automatically via `--framework-border-strong` which inverts to white.
#### 2-bit and 4-bit Displays
Falls back to a standard 1px solid border with 10px border-radius for smoother rendering
that takes advantage of the additional grayscale capabilities.
/* 1-bit: CSS gradient dots (via outline-dots mixin) */
.outline::after {
@include outline-dots;
}
/* 2-bit and 4-bit: Falls back to CSS border */
.screen--2bit .outline::after,
.screen--4bit .outline::after {
background: none;
border: 1px solid var(--framework-border-strong);
border-radius: 10px;
}
### Screen Backdrop Modifier
For mashup layouts, the `screen--backdrop` modifier provides an alternative appearance where views sit on a patterned background instead of
having outlined borders.
#### Default vs Backdrop Mashups
By default, mashups use a white background with bordered views for a clean, separated look.
The `screen--backdrop` modifier changes this to a patterned background (1-bit) or solid gray background (2-bit/4-bit)
with plain white views on top.
Plugin A
Plugin B
...
...
...
...
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| `--rounded-full` | 9999px | — | — | — |
| `--rounded-large` | calc(20px * var(--content-scale)) | — | — | — |
| `--rounded-medium` | calc(15px * var(--content-scale)) | — | — | — |
| `--rounded-none` | 0px | — | — | — |
| `--rounded-small` | calc(7px * var(--content-scale)) | — | — | — |
| `--rounded-xlarge` | calc(25px * var(--content-scale)) | — | — | — |
| `--rounded-xsmall` | calc(5px * var(--content-scale)) | — | — | — |
| `--rounded-xxlarge` | calc(30px * var(--content-scale)) | — | — | — |
Previous
[Rounded Control element rounding with predefined values](/framework/docs/3.1/rounded)
Next
[Image Optimize images using dithering techniques for 1-bit rendering](/framework/docs/3.1/image)
---
# Image
Image creates the illusion of grayscale through carefully designed dither patterns. When rendered on 1-bit (black and white only) displays, these patterns create an illusion of different shades of gray by using specific arrangements of black and white pixels.
### Dithering
Use the class `image-dither` to dither an image.

Image
### Object Fit
Control how images are displayed when not shown in their original aspect ratio.
#### 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.

Fill

Contain

Cover
Object Fit Options
Previous
[Outline Pixel-perfect rounded borders using border-image for 1-bit displays](/framework/docs/3.1/outline)
Next
[Image Stroke Legible images when displayed on shaded backgrounds](/framework/docs/3.1/image_stroke)
---
# Image Stroke
The Image Stroke system allows you to add an outline to a vector or transparent raster images with customizable stroke width and color. This is useful for creating images that stand out against shaded backgrounds.
### Usage
The Image Stroke system includes preset size modifiers that allow you to quickly apply different stroke widths to your images. 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.
No Stroke
Small
Base
Default
Medium
Large
Extra Large
Image StrokePreset Sizes
### Stroke Colors
Use the black modifier for images on dark backgrounds.
No Stroke
Small
Base
Default
Medium
Large
Extra Large
Image StrokeColor Variants
Previous
[Image Optimize images using dithering techniques for 1-bit rendering](/framework/docs/3.1/image)
Next
[Scale Scale interface to affect content density and readability](/framework/docs/3.1/scale)
---
# Scale
The Scale system provides utility classes to scale the entire interface by adjusting the UI scale factor. This is useful for adapting content density for different viewing distances or user preferences.
### 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 is available on 4bit devices.
#### 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 Typography Separately
Use [Text Scale](/framework/docs/3.1/text_scale) when typography needs an additional factor without applying it to the rest of the interface.
### 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
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
Scale LevelSmall (87.5%)
#### Regular Scale (100%)
Default scale - this is 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
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
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
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
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.
#### Affected Properties
When you apply a scale modifier, it scales the following properties:
- Font sizes (all text elements)
- Line heights
- Component dimensions such as title bars and progress indicators
- Framework gaps and pixel-based spacing utilities
- Framework radii, text strokes, and image strokes
- Any 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.
### 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/8/16-bit |
| --- | --- | --- | --- | --- |
| `--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-large` | 16px | — | — | — |
| `--list-gap-small` | 8px | — | — | — |
| `--modifier-scale` | 1 | — | — | — |
| `--ui-scale` | calc(var(--device-ui-scale) * var(--modifier-scale)) | — | — | — |
Previous
[Image Stroke Legible images when displayed on shaded backgrounds](/framework/docs/3.1/image_stroke)
Next
[Colors Complete palette definition: grayscale, chromatic hues, and blend pairs](/framework/docs/3.1/colors)
---
# Colors
The Colors system defines the complete palette for the framework: grayscale, chromatic hues, blend pairs, 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 chromatic and blend palettes.
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 TRMNL's dark mode inverts the entire screen, except the images.
### 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.
### 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 can override via `--color-{role}` CSS variables.
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.1/background) page documents `bg--{token}`; the [Text Color](/framework/docs/3.1/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) |
**Grayscale:** black, gray-10 through gray-75, white. **Chromatic:** `{hue}` (pure color) or `{hue}-{step}` (e.g. red-50, blue-40). **Blend:** `{colorA}-{colorB}-{step}` (e.g. red-blue-50, green-yellow-25). **Semantic:** `primary`, `success`, `error`, `warning` (alias 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
### Active Collection Rows
Invert individual rows to make active resources scannable inside a longer collection.
The row background, text, item metadata, and other framework paint change together.
Desk M1
Occupied
Booked with TRMNL · 7:00 - 15:00
Desk M2
Occupied
laura@trmnl.com · Booked for the day
Desk M3
Available
Desk M4
Available
Desk S1
Available
Until 10:30
Desk S2
Available
Bookable desks
Desk M1
Occupied
Booked with TRMNL · 7:00 - 15:00
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 occupied rooms from available rooms without changing the grid structure.
Auxiliary meeting room
Available
Board room
Marketing Sync
11:00 - 12:30
Huddle Space Alpha
Occupied
11:15 - 11:45
Huddle Space Beta
Available
Meeting rooms
Auxiliary meeting room
Available
Board room
Marketing Sync
11:00 - 12:30
### State Transitions
Apply inverse to a larger status surface when a state transition should change the whole composition.
For example, an occupied meeting room can invert its schedule, dividers, and attendee details as one semantic state.
11:00 - 12:30
Marketing Sync
laura@trmnl.com & 5 more
Board room20 seats
11:00 - 12:30
Marketing Sync
dev@trmnl.top & 1 more
### Paint and Cascade Behavior
Inverse remaps framework background, text, border, and stroke paint for the element and its subtree, including the `--framework-*` surface tokens.
A utility class set directly on the element — for example `bg--white` — still overrides the inverse default.
See [Screen](/framework/docs/3.1/screen) for the screen-wide `screen--dark-mode` modifier.
Unlike the `invert` image filter, inverse remaps framework tokens instead of flipping pixels.
Inverse applies the opposite of the screen's current scheme: on a light screen the subtree paints dark, and inside a `screen--dark-mode` screen the subtree flips back to light. `dark:` utilities still key off `screen--dark-mode` and do not activate from inverse alone.
Previous
[Tokens Complete CSS variable reference with root defaults, density, and bit-depth overrides](/framework/docs/3.1/tokens)
Next
[Font Family Switch between Classic and TRMNL font bundles per device](/framework/docs/3.1/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.
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. Low-density displays use the selected pixel-font bundle.
High-density displays use **Inter Variable** regardless of bundle or bit depth.
Any Scale or Text Scale modifier other than Regular also resolves typography to Inter Variable,
because pixel bundles only render correctly at their native sizes.
In Framework 3.1, screens without a font-bundle class use **TRMNL** by default; 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 font--bold · Classic
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.
This is the implicit default for Framework 3.1 when no font-bundle class is present.
Add `screen--fonts-trmnl` when you want 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)
#### 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)
#### 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)
#### On-device preview
text--small · TRMNLtext--base · TRMNLtext--large · TRMNLtext--base font--bold · TRMNL
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.1/inverse)
Next
[Font Weight Toggle between regular and bold font weight independently of size](/framework/docs/3.1/font_weight)
---
# Font Weight
Utility classes for controlling font weight independently of size. Classic ships in a single weight, so font--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 `font--regular` and `font--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 `font--bold` has no visual effect on low-density Classic.
- **TRMNL** bundle: `font--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.
**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 |
| --- | --- | --- | --- | --- |
| `font--regular` | 400 | NicoPups / NicoClean / BlockKie | TRMNL12/16/21 Regular | Inter Variable @ 400 |
| `font--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 font--regulartext--small font--bold
text--base font--regulartext--base font--bold
text--large font--regulartext--large font--bold
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 font--regulartext--small font--bold
text--base font--regulartext--base font--bold
text--large font--regulartext--large font--bold
Font WeightTRMNL
Small regularSmall boldBase regularBase boldLarge regularLarge 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:font--bold` | Bold at medium breakpoint and up |
| Orientation | `portrait:font--regular` | Regular weight in portrait orientation |
| Bit-depth | `4bit:font--bold` | Bold on 4-bit displays only |
| Combined | `md:4bit:font--bold` | Bold at medium breakpoint on 4-bit displays |
Bold only on 4-bit displays
Bold at medium breakpoint and up
Previous
[Font Family Switch between Classic and TRMNL font bundles per device](/framework/docs/3.1/font_family)
Next
[Font Glyphs Browse every glyph available in each Framework font bundle](/framework/docs/3.1/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.
### Font selector
Classic bundle
Original TRMNL pixel fonts. Each family ships in a single weight - 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.1/font_weight)
Next
[Text Size Control text size with utility classes across all display types](/framework/docs/3.1/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. On low-density displays, the three smallest sizes use the active pixel-font
bundle (Classic NicoPups/NicoClean/BlockKie or TRMNL TRMNL12/16/21). On high-density displays,
every text size uses Inter Variable regardless of bundle. Sizes from xlarge onward use Inter Variable
on every display, and so does every size when a Scale or Text Scale modifier other than Regular
is active — pixel bundles only render correctly at their native sizes.
Text Size selects one typography role for an element. Use [Text Scale](/framework/docs/3.1/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 |
#### 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
Text SizeSmall
Regular textBold 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
Text SizeBase
Regular textBold 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
Text SizeLarge
Regular textBold 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
Text SizeXLarge
Regular textBold 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
Text SizeXXLarge
Regular textBold 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
Text SizeXXXLarge
Regular textBold 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
Previous
[Font Glyphs Browse every glyph available in each Framework font bundle](/framework/docs/3.1/font_glyphs)
Next
[Text Scale Scale framework typography independently from interface geometry](/framework/docs/3.1/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](/framework/docs/3.1/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.1/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 |
### 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
77°Temperature (9:28 AM
)
80°Feels Like
45%Humidity
SunnyRight Now
Partly cloudyJul 27
High (8)UV
70°Low
86°High
Light RainJul 28
Moderate (5)UV
65°Low
79°High
# 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.
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| `--content-scale` | 1 | — | — | — |
| `--device-ui-scale` | 1 | — | — | — |
| `--modifier-scale` | 1 | — | — | — |
| `--modifier-text-scale` | 1 | — | — | — |
| `--text-ui-scale` | 1 | — | — | — |
| `--ui-scale` | calc(var(--device-ui-scale) * var(--modifier-scale)) | — | — | — |
Previous
[Text Size Control text size with utility classes across all display types](/framework/docs/3.1/text_size)
Next
[Text Alignment Control text alignment with responsive breakpoint, orientation, and bit-depth variants](/framework/docs/3.1/text_alignment)
---
# Text Alignment
The Text Alignment system provides utility classes for controlling horizontal text alignment. Use left, center, right, and justify alignment 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.
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 at different screen widths.
Responsive alignment
Left by default, center on md+, right on lg+
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
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
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 framework typography independently from interface geometry](/framework/docs/3.1/text_scale)
Next
[Text Color Apply grayscale and chromatic color shades to text elements](/framework/docs/3.1/text_color)
---
# Text Color
The Text Color system creates the illusion of grayscale text through carefully designed dither patterns. When rendered on 1-bit (black and white only) displays, these patterns create an illusion of different shades of gray by using specific arrangements of black and white pixels. The shade scale matches the [Colors](/framework/docs/3.1/colors) palette.
### Usage
Use the `text--{shade}` utility
classes to apply these text color patterns to any element. Choose from sixteen values: black, gray-10 through gray-75,
and white. See the [Responsive Features](#responsive-text-color) section for responsive variants.
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
Text color shades
**Dark Mode Notice:** The color palette appears inverted because TRMNL's dark mode inverts the entire screen, except the images.
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
### 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 text (deprecated)
Gray 2 text (deprecated)
Gray 10 text (preferred)
Gray 20 text (preferred)
### Responsive Features
Text color classes support size-based and orientation-based responsive variants.
Bit-depth affects color rendering automatically based on the device (1-bit patterns, 2-bit patterns, 4-bit solid colors)—no bit-depth class prefixes are needed for colors.
#### Breakpoint Prefixes
Use breakpoint prefixes like `sm:`, `md:`, `lg:` to apply different colors at different screen widths.
Responsive color
Gray 50 by default, gray 30 on md+
Text ColorResponsive
Responsive colorProgressive 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 variantColor on md+ portrait
Text color classes support size-based and orientation-based variants only. Bit-depth affects color rendering automatically based on the device (1-bit patterns, 2-bit patterns, 4-bit solid colors)—no bit-depth class prefixes are needed for colors.
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| 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 | — | — | — |
Previous
[Text Alignment Control text alignment with responsive breakpoint, orientation, and bit-depth variants](/framework/docs/3.1/text_alignment)
Next
[Text Stroke Legible text when displayed on shaded backgrounds](/framework/docs/3.1/text_stroke)
---
# Text Stroke
The Text Stroke system allows you to add outlined text with customizable stroke width and color. This is useful for creating text that stands out against shaded backgrounds.
**Note:** Text Stroke works only on pure black or white text.
[Learn More](#browser-limitations)
### 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 white) |
| `text-stroke--{size}` | Stroke width: `small`, `medium`, `large`, `xlarge` |
| `text-stroke--{shade}` | Stroke color: `black`, `gray-10` … `gray-75`, `white`. See [Background](/framework/docs/3.1/background) for the shade scale. |
Outlined text
### Widths
The Text Stroke system includes preset size modifiers that allow you to quickly apply different stroke widths to your text. The default stroke is 3.5px white, 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
Text StrokePreset Sizes
AaAaAaAaAaAaAa
### Shades
Use the `text-stroke--{shade}` modifier to change the stroke color. Choose from sixteen values: black, gray-10 through gray-75, and white. For an overview of the shade scale and how it adapts across bit‑depths, see [Background](/framework/docs/3.1/background).
AaNo Stroke
AaSmall
AaBase
AaDefault
AaMedium
AaLarge
AaExtra Large
Text StrokeShades
AaAaAaAaAaAa
### Browser Limitations
**Text Stroke works only when the text itself is pure black or pure white.** This is due to how browsers render strokes relative to text fills.
We simulate grayscale text by applying hand-crafted bitmap patterns as a background and revealing them with `background-clip: text` (with transparent text color). This makes text appear gray, but under the hood the fill is not a solid color - it's a background image clipped to the text.
The CSS `paint-order` property cannot treat a background as a pass-through fill layer, so only `paint-order: stroke fill;` is effective when the fill is a solid color. Because clipped backgrounds are not considered a fill for paint-order, we cannot stroke around grayscale (background-clipped) text. Use black or white text when you need a stroke.
Previous
[Text Color Apply grayscale and chromatic color shades to text elements](/framework/docs/3.1/text_color)
Next
[Overflow Handle column items overflow](/framework/docs/3.1/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
OverflowGeneric Container
Title goes hereDescription 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.1/item) elements inside a single `.column` element, that's nested inside a `.columns` [Columns](/framework/docs/3.1/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
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
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
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
OverflowCounter Enabled
### Clamp-aware Overflow
The Clamp engine [Clamp](/framework/docs/3.1/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
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
Group HeadersSmart Distribution
TodayTomorrowThis 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
OverflowHarmonious Groups 3 Columns (2/3/2)
TodayTomorrowThis Week
### With dividers
When the Overflow engine hides items to fit the height or column budget, [Divider](/framework/docs/3.1/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.1/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
OverflowWith dividers
...
...
...
### Backwards compatibility
The Overflow engine [Overflow](/framework/docs/3.1/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.1/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. Responsive variants (e.g., `-sm`, `-md`) on the legacy element are honored when present; the base value is used for the engine.
- `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
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
[Text Stroke Legible text when displayed on shaded backgrounds](/framework/docs/3.1/text_stroke)
Next
[Table Overflow Handle table rows overflow](/framework/docs/3.1/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.1/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 |
| `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 |
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 |
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 `table-overflow-counter="false"` to clip the table at its height budget without showing the "and X more" row.
| | 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 |
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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--table-tbody-height` | 46px | — | — | — |
| `--table-thead-height` | 36px | — | — | — |
| Xsmall | | | | |
| `--table-xsmall-tbody-height` | 22px | — | — | — |
| `--table-xsmall-thead-height` | 18px | — | — | — |
| Small | | | | |
| `--table-small-tbody-height` | 31px | — | — | — |
| `--table-small-thead-height` | 24px | — | — | — |
| Large | | | | |
| `--table-large-tbody-height` | 56px | — | — | — |
| `--table-large-thead-height` | 44px | — | — | — |
| Xlarge | | | | |
| `--table-xlarge-tbody-height` | 72px | — | — | — |
| `--table-xlarge-thead-height` | 56px | — | — | — |
Previous
[Overflow Handle column items overflow](/framework/docs/3.1/overflow)
Next
[Clamp Manage text overflow with single and multi-line truncation](/framework/docs/3.1/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
ClampStandalone: Descriptions 1–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
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.1/table_overflow)
Next
[Format Value Format numbers and values with consistent styling](/framework/docs/3.1/format_value)
---
# Format Value
The Value Formatting system automatically formats numeric values to fit within their containers while maintaining readability. It supports various formatting options including abbreviations (K, M, B), dynamic precision adjustment, and currency values with proper symbol placement.
### Basic Usage
To enable automatic value formatting, add the `data-value-format="true"` attribute to your element.
2345678XLarge
2345678Regular
2345678Small
456789XLarge
456789Regular
456789Small
34562XLarge
34562Regular
34562Small
Value FormattingSize Comparison
234567845678934562
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
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)
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.1/clamp)
Next
[Fit Value Automatically resize numbers and values to fit within their containers](/framework/docs/3.1/fit_value)
---
# Fit Value
The Text Fitting system automatically adjusts the font size, weight, and line height of text elements so that they fit within their containers. This dynamic resizing is especially useful for responsive layouts where available space may vary, ensuring that content is always legible.
### Basic Usage
To enable automatic text resizing, add the `data-value-fit="true"` attribute to your element. The system will automatically adjust the element's font size and weight to fit the content
on a single line within the container width. This works 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
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
Fit ValueDunder Mifflin
Assistant to the Regional ManagerThreat Level MidnightWorld'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
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.1/format_value)
Next
[Content Limiter Change font size when content overflows to fit within the container](/framework/docs/3.1/content_limiter)
---
# Content Limiter
The Content Limiter system automatically restricts the height of content areas based on the view type. When content exceeds the height threshold, it applies smaller typography and truncates overflowing text using the Clamp Engine to fit the available space.
### 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.
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.
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.1/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.
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.1/fit_value)
Next
[Pixel Perfect Ensure text renders with crisp edges by aligning to the pixel grid](/framework/docs/3.1/pixel_perfect)
---
# Pixel Perfect
The Pixel Perfect system ensures text renders with crisp edges by aligning text precisely to the pixel grid. We use specialized pixel fonts designed to work at specific pixel sizes, producing the sharpest possible rendering and preventing sub-pixel rendering issues that cause text to appear blurry or inconsistently bold in final renders when converting layouts to a 1-bit color space 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.
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.1/content_limiter)
Next
[Framework Runtime How the runtime applies layout, clamping, overflow, and presentation adjustments at render time](/framework/docs/3.1/framework_runtime)
---
# Framework Runtime
Different devices have different, fixed amounts of screen space. The Framework Runtime fills that space the best way it can when a plugin layout renders. It follows a few simple rules and does the heavy, repetitive calculations for you, so the result is clean and reliable every time. You can inspect it from the “Framework Runtime” panel in the top navigation.
### What it does
At a high level, it measures the space you have and then plans columns, clamps text, formats and fits values, and adjusts gaps and index widths - so everything fits neatly without manual tweaking.
- Normalizes screen context (size, orientation, bit depth, scale)
- Maps legacy responsive classes to data attributes for consistency
- Formats values and fits numbers into their containers
- Adjusts gaps so columns/grids land on integer pixel widths
- Plans multi-column layouts, re-clamps text per column, and can add a trailing “and N more” label when enabled
- Applies standalone clamping where needed outside columns
- Limits overly tall content and schedules pixel-perfect text processing
### Runtime steps and stats
When the runtime executes, it performs these steps in order.
#### 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.1/clamp)
#### Overflow
Plans 1..N columns with off‑screen staging and commits the best fit, then re‑clamps per real column 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.1/overflow)
#### Value Formatting
Formats numbers to fit available space and abbreviates as needed (k, M, B).
- Respects `data-value-locale`
- Works with `data-fit-value` for auto‑sizing
Go to [Format Value](/framework/docs/3.1/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.1/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.1/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.1/columns)
#### Pixel‑Perfect Fonts
Wraps lines in spans and enforces even/odd widths for crisp rendering; scheduled in idle time.
- Skipped on higher bit‑depth modes
- Respects centered alignment
Go to [Pixel Perfect](/framework/docs/3.1/pixel_perfect)
#### Content Limiter
Constrains content height by view type or explicit threshold and flags small content.
- Override via `data-content-max-height`
- Adds `content--small` and `clamp--N` classes
Go to [Content Limiter](/framework/docs/3.1/content_limiter)
#### Index Widths
Ensures item index badges render at even widths to avoid artifacts.
- Observes width changes and adjusts without feedback loops
- Runs after column layouts commit to cover in‑column indices
Go to [Item](/framework/docs/3.1/item)
### Why this exists
Plugins need to fit source data into a static layout space that is device‑defined and varies by model, orientation, and density. While this resembles responsive web design, the runtime provides purpose‑built tools—overflow planning, per‑column clamping, integer pixel alignment, and value fitting—tailored specifically for TRMNL devices.
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| `--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` | calc(var(--device-ui-scale) * var(--modifier-scale)) | — | — | — |
Previous
[Pixel Perfect Ensure text renders with crisp edges by aligning to the pixel grid](/framework/docs/3.1/pixel_perfect)
Next
[Structure The framework's exact div hierarchy and how Screen, View, Layout, Title Bar, Columns, and Mashup work together](/framework/docs/3.1/structure)
---
# Structure
The framework structure is a fixed hierarchy that defines the display environment. Screen, View, Layout, Title Bar, Columns, and Mashup each have a specific role. 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. You only 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.1/screen)--portrait --no-bleed --dark-mode --og --v2 --backdrop
[Mashup](/framework/docs/3.1/mashup)--1Lx1R --1Tx1B --2x2 --1Lx2R --2Lx1R --2Tx1B --1Tx2B
[View](/framework/docs/3.1/view)--full --half_vertical --half_horizontal --quadrant
[Layout](/framework/docs/3.1/layout)--row --col
--left --center-x --right --top --center-y --bottom --center
--stretch --stretch-x --stretch-y
[Title Bar](/framework/docs/3.1/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.1/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.1/view)
#### Layout
Exactly one per View. The content container. Its direct children are typically Columns, Grid, or Flex. Use `layout--row`, `layout--col`, and alignment modifiers to arrange those children. See the Layout page's "What Goes Inside Layout" section for when to use each.
Go to [Layout](/framework/docs/3.1/layout)
#### Title Bar
Optional. Sibling to Layout within a View. Displays icon, title, and instance label.
Go to [Title Bar](/framework/docs/3.1/title_bar)
#### Columns
Use *inside* Layout for column-based content organization.
Go to [Columns](/framework/docs/3.1/columns)
#### Mashup
Wraps multiple Views and arranges them within the Screen (1Lx1R, 1Tx1B, 2x2, etc.).
Go to [Mashup](/framework/docs/3.1/mashup)
#### Single View
For a single plugin occupying the full screen:
Layout
PluginInstance
...
#### Mashup (Multiple Views)
For multiple plugins on one screen, wrap views in a [Mashup](/framework/docs/3.1/mashup). Each view has exactly one [Layout](/framework/docs/3.1/layout).
Plugin A
Plugin B
...
...
Previous
[Framework Runtime How the runtime applies layout, clamping, overflow, and presentation adjustments at render time](/framework/docs/3.1/framework_runtime)
Next
[Screen Device screen dimensions, orientation, and display properties](/framework/docs/3.1/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 component provides CSS variables that cascade throughout the framework. These variables automatically
recalculate 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 UI 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).
#### 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 the [Outline](/framework/docs/3.1/outline) utility for more details.
...
...
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| `--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` | calc(var(--device-ui-scale) * var(--modifier-scale)) | — | — | — |
Previous
[Structure The framework's exact div hierarchy and how Screen, View, Layout, Title Bar, Columns, and Mashup work together](/framework/docs/3.1/structure)
Next
[View Show your plugin in different sizes with Mashup view containers](/framework/docs/3.1/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. The view modifier sets each view's share of space; the Mashup modifier controls 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.1/layout) is the core component of every View [View](/framework/docs/3.1/view), providing a consistent container for your content.
Views can optionally include a Title Bar [Title Bar](/framework/docs/3.1/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.1/mashup) component.
#### With Layout and Title Bar
When combined with a title bar, it provides context and navigation options.
Layout
TitleInstance
TitleInstance
#### 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.1/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
[Screen Device screen dimensions, orientation, and display properties](/framework/docs/3.1/screen)
Next
[Layout Primary container for organizing plugin content](/framework/docs/3.1/layout)
---
# Layout
The Layout is the content container inside a View. There should be exactly one `layout` per `view`. Its height is calculated automatically based on the device type, orientation, and whether a title bar is present. It can arrange content horizontally (`layout--row`) or vertically (`layout--col`), with alignment and stretch modifiers. For organizing content inside it, use `flex`, `columns`, or `grid`.
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 direct children are usually Columns, Grid, or Flex.
#### 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.1/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.1/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.1/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. For example, you might 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
The Layout system provides two fundamental ways to organize content: horizontal and vertical arrangements.
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
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
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. The system provides both directional alignment (top/bottom/left/right) and centering options.
#### Horizontal Alignment
Use `layout--left`, `layout--center-x`, or `layout--right` to control horizontal alignment.
Left
LayoutLeft Alignment
Item 1
Item 2
Item 3
#### Vertical Alignment
Use `layout--top`, `layout--center-y`, or `layout--bottom` to control vertical alignment.
Top
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
LayoutCenter Alignment
Item 1
Item 2
Item 3
Item 1
Item 2
Item 3
### 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.
#### 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
Row LayoutFull Stretch
Item 1
Item 2
Item 3
Item 1
Item 2
Item 3
Row LayoutHorizontal Stretch
Item 1
Item 2
Item 3
Item 1
Item 2
Item 3
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
Column LayoutFull Stretch
#### 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
LayoutRow + Individual Stretch
Previous
[View Show your plugin in different sizes with Mashup view containers](/framework/docs/3.1/view)
Next
[Title Bar Standardized title bar with plugin information and instance details](/framework/docs/3.1/title_bar)
---
# Title Bar
The Title Bar component provides a consistent header for terminal-like interfaces, displaying application information such as icons, titles, and instance details.
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.1/title_bar) consists of three main elements: an icon [Image](/framework/docs/3.1/image), a title [Title](/framework/docs/3.1/title), and an optional instance label [Label](/framework/docs/3.1/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.1/title_bar) for the container.
Basic Title Bar
Basic Title Bar
#### Title Bar with Instance
Add an instance label using the `instance` class
to display additional context.
Title Bar with InstanceProduction
Title Bar with InstanceProduction
### Title Bar in Mashups
When the Title Bar is placed inside a [Mashup](/framework/docs/3.1/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
Plugin A
Plugin B
Plugin B
Plugin A
Plugin A
Plugin B
Plugin B
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--title-bar-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--title-bar-font-size` | 16px | 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 | 700 | — |
| `--title-bar-height` | 40px | calc(40px * var(--ui-scale)) | — | calc(40px * var(--ui-scale)) |
| `--title-bar-image-height` | 28px | calc(28px * var(--ui-scale)) | — | calc(28px * var(--ui-scale)) |
| `--title-bar-line-height` | 1 | 1 | calc(22px * var(--text-ui-scale)) | — |
| `--title-bar-padding-top` | 5px | calc(5px * var(--ui-scale)) | 0px | 0px |
| `--title-bar-text-stroke-width` | 3.5px | calc(3.5px * var(--ui-scale)) | calc(2px * var(--ui-scale)) | calc(2px * var(--ui-scale)) |
| Small | | | | |
| `--title-bar-small-font-size` | 16px | calc(16px * var(--text-ui-scale)) | calc(16px * var(--text-ui-scale)) | — |
| `--title-bar-small-height` | 32px | calc(32px * var(--ui-scale)) | — | calc(32px * var(--ui-scale)) |
| `--title-bar-small-image-height` | 24px | calc(24px * var(--ui-scale)) | — | calc(24px * var(--ui-scale)) |
Previous
[Layout Primary container for organizing plugin content](/framework/docs/3.1/layout)
Next
[Columns Implement zero-config column layouts for content organization](/framework/docs/3.1/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.1/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.1/grid). If you need flexible, content-sized row or column arrangements (toolbars, inline groups, etc.), use [Flex](/framework/docs/3.1/flex).
### Basic Column Layout
The basic column layout is flexible - you can add as many columns as needed depending on your content needs.
Column 1
Item
Item
Item
Item
Column 2
Item
Item
Column 3
Item
Columns
Content for column 1
Content for column 2
Content for column 3
Previous
[Title Bar Standardized title bar with plugin information and instance details](/framework/docs/3.1/title_bar)
Next
[Mashup Assemble multiple plugin views into a single interface](/framework/docs/3.1/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
Mashup modifiers control how [View](/framework/docs/3.1/view) instances are arranged within the screen, while each view's own modifier determines how much space it occupies.
The following layouts are available.
#### 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 grid arranges four plugins in a grid pattern.
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.1/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.
#### Available Modifiers
Combine one modifier from each group to place a cell. Column, row, and span values range from 1 to 3.
| 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
...
#### Spanning Cells and Title Bars
Add `mashup-cell--col-*` and `mashup-cell--row-*` to choose the starting tracks. Add the matching `*-span-*` modifiers to cover up to three tracks in either direction.
Add a [Title Bar](/framework/docs/3.1/title_bar) to label a cell's plugin. Place the `title_bar` as a sibling of `layout` inside the view. Every cell uses the compact title bar, whatever its size, and the layout above shrinks to make room for it.
Plugin A
Plugin A
Plugin B
Plugin B
Plugin C
Plugin C
...
...
...
...
...
...
Previous
[Columns Implement zero-config column layouts for content organization](/framework/docs/3.1/columns)
Next
[Title Style headings with consistent typography](/framework/docs/3.1/title)
---
# Title
The Title system provides consistent text headings with different size variants. It helps maintain visual hierarchy and readability throughout the interface.
### Size Variations
The Title system offers five size variants: small, base (default), large, xlarge, and xxlarge.
Small TitleBase TitleLarge TitleExtra Large TitleXXL Title
TitleSize Variations
Small TitleBase TitleBase TitleLarge TitleExtra Large TitleXXL TitleSmall 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 at different screen widths.
Responsive TitleSmall by default, xlarge on lg screens
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.
TitleOrientation
Orientation VariantLarge 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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--title-font-family` | "BlockKie" | "BlockKie" | "Inter Variable", Inter | — |
| `--title-font-size` | 26px | calc(26px * var(--text-ui-scale)) | calc(21px * var(--text-ui-scale)) | — |
| `--title-font-smoothing` | none | none | auto | — |
| `--title-font-weight` | 400 | 400 | 400 | — |
| `--title-line-height` | 1 | 1 | 1.2 | — |
| Small | | | | |
| `--title-small-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--title-small-font-size` | 16px | 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 | 700 | — |
| `--title-small-line-height` | 1 | 1 | 1.2 | — |
| Large | | | | |
| `--title-large-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--title-large-font-size` | 30px | — | calc(30px * var(--text-ui-scale)) | — |
| `--title-large-font-smoothing` | auto | — | auto | — |
| `--title-large-font-weight` | 425 | — | 425 | — |
| `--title-large-line-height` | 1.2 | — | 1.2 | — |
| Xlarge | | | | |
| `--title-xlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--title-xlarge-font-size` | 35px | — | calc(35px * var(--text-ui-scale)) | — |
| `--title-xlarge-font-smoothing` | auto | — | auto | — |
| `--title-xlarge-font-weight` | 400 | — | 400 | — |
| `--title-xlarge-line-height` | 1.2 | — | 1.2 | — |
| Xxlarge | | | | |
| `--title-xxlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--title-xxlarge-font-size` | 40px | — | calc(40px * var(--text-ui-scale)) | — |
| `--title-xxlarge-font-smoothing` | auto | — | auto | — |
| `--title-xxlarge-font-weight` | 375 | — | 375 | — |
| `--title-xxlarge-line-height` | 1.2 | — | 1.2 | — |
Previous
[Mashup Assemble multiple plugin views into a single interface](/framework/docs/3.1/mashup)
Next
[Value Display data values with consistent formatting](/framework/docs/3.1/value)
---
# Value
The Value system provides consistent text styling for displaying numerical and textual values, with various size options and support for tabular numbers. It ensures readability and visual hierarchy across different contexts.
### Size Variants
The Value system offers twelve size variants, from XXSmall to Peta.
#### XXSmall
The `value--xxsmall` class creates the smallest text size.
Example48,206.62
ValueXXSmall
Example48,206.62
#### XSmall
The `value--xsmall` class provides a size slightly larger than XXSmall.
Example48,206.62
ValueXSmall
Example48,206.62
#### Small
The `value--small` class creates a smaller text size.
Example48,206.62
ValueSmall
Example48,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
ValueBase
Example48,206.62Example48,206.62
#### Large
The `value--large` class creates larger text.
Example48,206.62
ValueLarge
Example48,206.62
#### XLarge
The `value--xlarge` class provides larger text.
Example48,206.62
ValueXLarge
Example48,206.62
#### XXLarge
The `value--xxlarge` class creates very large text.
Example48,206.62
ValueXXLarge
Example48,206.62
#### XXXLarge
The `value--xxxlarge` class provides very large text.
Example48,206.62
ValueXXXLarge
Example48,206.62
#### Mega
The `value--mega` class creates extremely large text.
42
ValueMega
42
#### Giga
The `value--giga` class provides massive text.
42
ValueGiga
42
#### Tera
The `value--tera` class creates colossal text.
42
ValueTera
42
#### Peta
The `value--peta` class provides the largest text.
42
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
ValueTabular Numbers
Regular: 48,206.62Tabular: 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 at different screen widths.
Responsive Value1,234.56
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
ValueOrientation
Orientation Variant42,000.00
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--value-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--value-font-size` | 38px | — | calc(38px * var(--text-ui-scale)) | — |
| `--value-font-smoothing` | auto | — | auto | — |
| `--value-font-weight` | 450 | — | 450 | — |
| `--value-line-height` | 42px | — | calc(42px * var(--text-ui-scale)) | — |
| Xxsmall | | | | |
| `--value-xxsmall-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--value-xxsmall-font-size` | 16px | 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 | 700 | — |
| `--value-xxsmall-line-height` | 16px | calc(16px * var(--text-ui-scale)) | calc(14px * var(--text-ui-scale)) | — |
| Xsmall | | | | |
| `--value-xsmall-font-size` | 20px | — | calc(20px * var(--text-ui-scale)) | — |
| `--value-xsmall-font-weight` | 600 | — | 600 | — |
| `--value-xsmall-line-height` | 24px | — | calc(24px * var(--text-ui-scale)) | — |
| Small | | | | |
| `--value-small-font-size` | 26px | — | calc(26px * var(--text-ui-scale)) | — |
| `--value-small-font-weight` | 500 | — | 475 | — |
| `--value-small-line-height` | 29px | — | calc(29px * var(--text-ui-scale)) | — |
| Large | | | | |
| `--value-large-font-size` | 58px | — | calc(58px * var(--text-ui-scale)) | — |
| `--value-large-font-weight` | 400 | — | 400 | — |
| `--value-large-line-height` | 70px | — | calc(70px * var(--text-ui-scale)) | — |
| Xlarge | | | | |
| `--value-xlarge-font-size` | 74px | — | calc(74px * var(--text-ui-scale)) | — |
| `--value-xlarge-font-weight` | 375 | — | 375 | — |
| `--value-xlarge-line-height` | 86px | — | calc(86px * var(--text-ui-scale)) | — |
| Xxlarge | | | | |
| `--value-xxlarge-font-size` | 96px | — | calc(96px * var(--text-ui-scale)) | — |
| `--value-xxlarge-font-weight` | 350 | — | 350 | — |
| `--value-xxlarge-line-height` | 108px | — | calc(108px * var(--text-ui-scale)) | — |
| Xxxlarge | | | | |
| `--value-xxxlarge-font-size` | 128px | — | calc(128px * var(--text-ui-scale)) | — |
| `--value-xxxlarge-font-weight` | 300 | — | 300 | — |
| `--value-xxxlarge-line-height` | 128px | — | calc(128px * var(--text-ui-scale)) | — |
| Mega | | | | |
| `--value-mega-font-size` | 170px | — | calc(170px * var(--text-ui-scale)) | — |
| `--value-mega-font-weight` | 275 | — | 275 | — |
| `--value-mega-line-height` | 180px | — | calc(180px * var(--text-ui-scale)) | — |
| Giga | | | | |
| `--value-giga-font-size` | 220px | — | calc(220px * var(--text-ui-scale)) | — |
| `--value-giga-font-weight` | 250 | — | 250 | — |
| `--value-giga-line-height` | 230px | — | calc(230px * var(--text-ui-scale)) | — |
| Tera | | | | |
| `--value-tera-font-size` | 290px | — | calc(290px * var(--text-ui-scale)) | — |
| `--value-tera-font-weight` | 225 | — | 225 | — |
| `--value-tera-line-height` | 300px | — | calc(300px * var(--text-ui-scale)) | — |
| Peta | | | | |
| `--value-peta-font-size` | 380px | — | calc(380px * var(--text-ui-scale)) | — |
| `--value-peta-font-weight` | 200 | — | 200 | — |
| `--value-peta-line-height` | 390px | — | calc(390px * var(--text-ui-scale)) | — |
Previous
[Title Style headings with consistent typography](/framework/docs/3.1/title)
Next
[Label Create clear labels for unified content identification](/framework/docs/3.1/label)
---
# Label
The Label system provides various styles for displaying text labels, with options for different visual treatments and sizes. The filled variant uses black (darkest) background; label--primary, label--success, etc. use semantic colors. Labels can be used to highlight text, show status, or create visual hierarchy in your interface.
### Size and Style Variants
Labels come in several style variants to suit different use cases. Each variant provides a distinct visual style
that can be combined with any size modifier.
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
LabelStyle Variants
Default LabelOutline LabelUnderline LabelGray LabelFilledPrimarySuccessErrorInverted (alias)Large Outline LabelXLarge Filled Label
#### Semantic variants
Use `label--primary`, `label--success`, `label--error`, `label--warning` for intent-based colors. `label--filled` uses black (darkest). Success and warning use black text; warning uses yellow background. See [Colors](/framework/docs/3.1/colors) for the semantic mapping.
FilledPrimarySuccessErrorWarning
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
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
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 at different screen widths.
Responsive LabelSmall by default, xlarge on lg screens
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.
LabelOrientation
Orientation VariantLarge 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)
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
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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--label-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--label-font-size` | 16px | calc(16px * var(--text-ui-scale)) | calc(16px * var(--text-ui-scale)) | — |
| `--label-font-smoothing` | none | none | auto | — |
| `--label-font-weight` | 400 | 400 | 500 | — |
| `--label-line-height` | 1.25 | 1.25 | 1.25 | — |
| Small | | | | |
| `--label-small-font-family` | "NicoPups" | "NicoPups" | "Inter Variable", Inter | — |
| `--label-small-font-size` | 16px | 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 | 500 | — |
| `--label-small-line-height` | 1 | 1 | 1 | — |
| Large | | | | |
| `--label-large-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--label-large-font-size` | 21px | — | calc(21px * var(--text-ui-scale)) | — |
| `--label-large-font-smoothing` | auto | — | auto | — |
| `--label-large-font-weight` | 500 | — | 500 | — |
| `--label-large-line-height` | 1.2 | — | 1.2 | — |
| Xlarge | | | | |
| `--label-xlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--label-xlarge-font-size` | 26px | — | calc(26px * var(--text-ui-scale)) | — |
| `--label-xlarge-font-smoothing` | auto | — | auto | — |
| `--label-xlarge-font-weight` | 475 | — | 475 | — |
| `--label-xlarge-line-height` | 1.2 | — | 1.2 | — |
| Xxlarge | | | | |
| `--label-xxlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--label-xxlarge-font-size` | 30px | — | calc(30px * var(--text-ui-scale)) | — |
| `--label-xxlarge-font-smoothing` | auto | — | auto | — |
| `--label-xxlarge-font-weight` | 450 | — | 450 | — |
| `--label-xxlarge-line-height` | 1.2 | — | 1.2 | — |
Previous
[Value Display data values with consistent formatting](/framework/docs/3.1/value)
Next
[Description Format descriptive text with standardized styles](/framework/docs/3.1/description)
---
# Description
The Description component provides a standardized way to display descriptive text content with consistent styling.
### Size Variants
Descriptions come in four size variants to suit different use cases. Each variant provides a distinct visual style
that can be used for various content hierarchies.
Base
Large
XLarge
XXLarge
Description
Description
Description
Description
DescriptionSize Variants
Base DescriptionLarge DescriptionXLarge DescriptionXXLarge 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
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
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 at different screen widths.
Responsive DescriptionBase by default, xlarge on lg screens
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.
DescriptionOrientation
Orientation VariantLarge 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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--description-font-family` | "NicoPups" | "NicoPups" | "Inter Variable", Inter | — |
| `--description-font-size` | 16px | calc(16px * var(--text-ui-scale)) | calc(13px * var(--text-ui-scale)) | — |
| `--description-font-smoothing` | none | none | auto | — |
| `--description-font-weight` | 400 | 400 | 400 | — |
| `--description-line-height` | 1 | 1 | 1.2 | — |
| Large | | | | |
| `--description-large-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--description-large-font-size` | 16px | 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 | 700 | — |
| `--description-large-line-height` | 1.25 | 1.25 | 1.2 | — |
| Xlarge | | | | |
| `--description-xlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--description-xlarge-font-size` | 21px | — | calc(21px * var(--text-ui-scale)) | — |
| `--description-xlarge-font-smoothing` | auto | — | auto | — |
| `--description-xlarge-font-weight` | 500 | — | 500 | — |
| `--description-xlarge-line-height` | 1.2 | — | 1.2 | — |
| Xxlarge | | | | |
| `--description-xxlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--description-xxlarge-font-size` | 24px | — | calc(24px * var(--text-ui-scale)) | — |
| `--description-xxlarge-font-smoothing` | auto | — | auto | — |
| `--description-xxlarge-font-weight` | 475 | — | 475 | — |
| `--description-xxlarge-line-height` | 1.2 | — | 1.2 | — |
Previous
[Label Create clear labels for unified content identification](/framework/docs/3.1/label)
Next
[Divider Create horizontal or vertical dividers between elements](/framework/docs/3.1/divider)
---
# Divider
The Divider element provides a simple, standalone way to create visual separations in your layouts. Dividers automatically adapt to their background color for optimal visibility across four background types: white, light, dark, and black.
### Usage
Use `divider` or `divider--h` for horizontal dividers,
and `divider--v` for vertical dividers.
Dividers automatically detect their background and adjust their appearance for optimal contrast.
#### Automatic Background Detection
By default, dividers automatically detect whether they're on a white, light, dark, or black background and adjust their appearance accordingly.
The system categorizes backgrounds into four types for optimal contrast:
- **White:** Very light backgrounds (gray-70 to gray-75 and pure white)
- **Light:** Light gray backgrounds (gray-50 to gray-65)
- **Dark:** Dark gray backgrounds (gray-30 to gray-45)
- **Black:** Very dark backgrounds (gray-10 to gray-25 and pure black)
White Background
Divider uses darkest style (level 7)
Light Background (gray-70)
Divider uses dark style (level 6)
Dark Background (gray-30)
Divider uses light style (level 3)
Black Background
Divider uses lightest style (level 1)
Auto Background Detection
#### Manual Background Control
You can manually specify the background type using `divider--on-white`, `divider--on-light`, `divider--on-dark`, or `divider--on-black` classes when automatic detection isn't suitable.
All variants on white
on-white (optimal)
on-light
on-dark
on-black (poor contrast)
All variants on black
on-white (poor contrast)
on-light
on-dark
on-black (optimal)
Manual Background Control
#### Vertical Dividers
Vertical dividers work the same way as horizontal dividers, with automatic background detection for all four background types.
Left SideWhite background
Right SideAuto-detected
Left SideBlack background
Right SideAuto-detected
Vertical Dividers
#### Common Usage Patterns
$1,234Revenue
42Orders
$29.38AOV
Section Separation
Previous
[Description Format descriptive text with standardized styles](/framework/docs/3.1/description)
Next
[Rich Text Display formatted paragraphs with alignment and size variants](/framework/docs/3.1/rich_text)
---
# Rich Text
The Rich Text component provides a flexible container for displaying text content with consistent styling and layout options. It's commonly used for paragraphs, articles, and other formatted text content that needs to maintain readability and visual hierarchy.
### 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.1/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 provides additional styling and formatting options specific to text.
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` |
This multi-level alignment system provides maximum flexibility for positioning both the component and its content independently.
### 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.
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.
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.
This is an example of center-aligned rich text content.
Centered text works well for quotes, headings, or highlighted information that needs visual emphasis.
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.
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.
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 both produce the same visual result.
Use `content--base` when you need to explicitly set the base size
in responsive contexts. All size variants support responsive prefixes like `sm:`, `md:`, `lg:`,
and `portrait:`.
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.
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.1/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.
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 (`w--[250px]`), 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 at different screen widths.
Responsive content
Base by default, xxlarge on lg screens
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.
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)
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
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.1/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.1/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.
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.1/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.1/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.
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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--richtext-content-max-width` | 640px | — | — | — |
| `--richtext-font-family` | "NicoClean" | "NicoClean" | "Inter Variable", Inter | — |
| `--richtext-font-size` | 16px | calc(16px * var(--text-ui-scale)) | calc(16px * var(--text-ui-scale)) | — |
| `--richtext-font-smoothing` | none | none | auto | — |
| `--richtext-font-weight` | 400 | 400 | 500 | — |
| `--richtext-line-height` | 22px | calc(22px * var(--text-ui-scale)) | calc(22px * var(--text-ui-scale)) | — |
| Small | | | | |
| `--richtext-small-font-family` | "NicoPups" | "NicoPups" | "Inter Variable", Inter | — |
| `--richtext-small-font-size` | 16px | 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 | 500 | — |
| `--richtext-small-line-height` | 16px | calc(16px * var(--text-ui-scale)) | calc(18px * var(--text-ui-scale)) | — |
| Large | | | | |
| `--richtext-large-font-family` | "BlockKie" | "BlockKie" | "Inter Variable", Inter | — |
| `--richtext-large-font-size` | 26px | 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 | 500 | — |
| `--richtext-large-line-height` | 1 | 1 | 1.2 | — |
| Xlarge | | | | |
| `--richtext-xlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--richtext-xlarge-font-size` | 30px | — | calc(30px * var(--text-ui-scale)) | — |
| `--richtext-xlarge-font-smoothing` | auto | — | auto | — |
| `--richtext-xlarge-font-weight` | 425 | — | 425 | — |
| `--richtext-xlarge-line-height` | 1.2 | — | 1.2 | — |
| Xxlarge | | | | |
| `--richtext-xxlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--richtext-xxlarge-font-size` | 35px | — | calc(35px * var(--text-ui-scale)) | — |
| `--richtext-xxlarge-font-smoothing` | auto | — | auto | — |
| `--richtext-xxlarge-font-weight` | 400 | — | 400 | — |
| `--richtext-xxlarge-line-height` | 1.2 | — | 1.2 | — |
| Xxxlarge | | | | |
| `--richtext-xxxlarge-font-family` | "Inter Variable", Inter | — | "Inter Variable", Inter | — |
| `--richtext-xxxlarge-font-size` | 40px | — | calc(40px * var(--text-ui-scale)) | — |
| `--richtext-xxxlarge-font-smoothing` | auto | — | auto | — |
| `--richtext-xxxlarge-font-weight` | 375 | — | 375 | — |
| `--richtext-xxxlarge-line-height` | 1.2 | — | 1.2 | — |
Previous
[Divider Create horizontal or vertical dividers between elements](/framework/docs/3.1/divider)
Next
[Item Build standardized list items and content blocks](/framework/docs/3.1/item)
---
# Item
The Item component provides a flexible container for displaying content with optional metadata and indexing. It's commonly used for lists, schedules, and other content that needs consistent formatting.
### 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
ItemWith Meta
Team MeetingWeekly team sync-up
9:00 AM - 10:00 AMConfirmed
#### With Meta Emphasis
Apply `item--emphasis-1`, `item--emphasis-2` or `item--emphasis-3` to progressively darken the meta bar and draw attention. Level 1 is the default styling.
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
ItemEmphasis Levels
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
#### 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
ItemWith Meta and Index
1
Team MeetingWeekly team sync-up
9:00 AM - 10:00 AMConfirmed
#### 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
ItemSimple
Team MeetingWeekly team sync-up
9:00 AM - 10:00 AMConfirmed
#### With Icon
Add an `icon` div between meta and content to display an icon alongside the item.
72°Temperature
12 mphWind Speed
6UV Index
SunnyToday
Partly CloudyTomorrow
RainyWednesday
ItemWith Icon
72°Temperature
### 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.1/gap) utility instead. The [Overflow](/framework/docs/3.1/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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--item-index-font-family` | "NicoPups" | "NicoPups" | "Inter Variable", Inter | — |
| `--item-index-font-size` | 16px | 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 | 600 | — |
| `--item-index-line-height` | 1 | 1 | 1 | — |
| `--item-meta-width` | 10px | calc(10px * var(--ui-scale)) | — | calc(10px * var(--ui-scale)) |
Previous
[Rich Text Display formatted paragraphs with alignment and size variants](/framework/docs/3.1/rich_text)
Next
[Table Create data tables optimized for 1-bit rendering](/framework/docs/3.1/table)
---
# Table
The Table system provides structured data presentation with consistent styling and various size options. It's designed to display information in a clear, scannable format while maintaining visual hierarchy.
### 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 |
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 |
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.
#### 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 |
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 |
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 |
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 |
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 |
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.1/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 |
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.1/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.1/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 |
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/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--table-tbody-height` | 46px | — | — | — |
| `--table-thead-height` | 36px | — | — | — |
| Xsmall | | | | |
| `--table-xsmall-tbody-height` | 22px | — | — | — |
| `--table-xsmall-thead-height` | 18px | — | — | — |
| Small | | | | |
| `--table-small-tbody-height` | 31px | — | — | — |
| `--table-small-thead-height` | 24px | — | — | — |
| Large | | | | |
| `--table-large-tbody-height` | 56px | — | — | — |
| `--table-large-thead-height` | 44px | — | — | — |
| Xlarge | | | | |
| `--table-xlarge-tbody-height` | 72px | — | — | — |
| `--table-xlarge-thead-height` | 56px | — | — | — |
Previous
[Item Build standardized list items and content blocks](/framework/docs/3.1/item)
Next
[Chart Visualize data optimized for 1-bit rendering](/framework/docs/3.1/chart)
---
# Chart
With careful, minimal styling choices, TRMNL can display a variety of numerical or time centric content as charts and graphs.
### Usage
Any CDN-enabled JavaScript library may be used to develop charting interfaces,
however the examples below leverage [Highcharts](https://highcharts.com) and [Chartkick](https://chartkick.com).
If you set the `height: null` within your highchart's settings, the chart will automatically expand to fill the available space.
Take care to disable animation effects, otherwise your chart may be only partially captured by TRMNL's screenshot rendering service.
#### Line Chart
Line charts effectively display trends over time. This example shows a simple line chart with customized styling to match the TRMNL aesthetic.
25,388Pageviews
4,771Visitors
2.23Mins on Page
ChartsLine Chart
25,388Pageviews
4,771Visitors
2.23Mins on Page
Simple Analyticstrmnl.com
#### Multi-Series Line Chart
For comparing data across multiple time periods or categories, multi-series line charts are ideal. This example demonstrates a comparison between current and previous period data with distinct styling for each series.
$85,240Total Sales
32Pending Orders
Jul 01 - Jul 15 Current
$128AOV
665Fulfilled Orders
Jun 15 - Jun 30 Previous
ChartsMulti-Series Line Chart
$85,240Total Sales
32Pending Orders
Jul 01 - Jul 15
Current
$128AOV
665Fulfilled Orders
Jun 15 - Jun 30
Previous
ChartsMulti-Series Line Chart
#### Bar Chart
Bar charts are ideal for comparing discrete categories side by side. This example displays four different metrics across multiple time periods.
$31,883Revenue
$22,910Expenses
$8,990Marketing
$14,930Operations
ChartsBar Chart
$31,883Revenue
$22,910Expenses
$8,990Marketing
$14,930Operations
#### Gauge Chart
Gauge charts can effectively display single metrics or scores. This example shows multiple gauges in a row with a main summary gauge,
perfect for displaying daily and weekly metrics like sleep quality scores.
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
18%REM Sleep
23%Deep Sleep
12mTime to Sleep
7h 32minSleep Duration
8Toss & Turns
0.5%Snoring
ChartsGauge Chart
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
18%REM Sleep
23%Deep Sleep
12mTime to Sleep
7h 32minSleep Duration
8Toss & Turns
0.5%Snoring
Previous
[Table Create data tables optimized for 1-bit rendering](/framework/docs/3.1/table)
Next
[Progress Display progress bars in different styles](/framework/docs/3.1/progress)
---
# Progress
The Progress component provides visual indicators for completion status and step-based processes. Optimized for ePaper displays with bitmap patterns for 1-bit displays and solid colors for 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 five sizes: xsmall, small, base (default), regular (default, no modifier), and large. Use the `fill` element with inline width styling to set the progress percentage. The `progress-bar--base` modifier explicitly sets the default/regular size and is useful for responsive layouts.
Xsmall Progress25%
Small Progress25%
Base Progress50%
Regular Progress50%
Large Progress75%
ProgressBar Sizes
Xsmall Progress25%
Small Progress25%
Base Progress50%
Regular Progress50%
Large Progress75%
#### Emphasis
Progress bars support three emphasis levels: default, emphasis-2, and emphasis-3 for different visual weights.
Default Emphasis60%
Emphasis 260%
Emphasis 360%
ProgressBar Emphasis
Default Emphasis60%
Emphasis 260%
Emphasis 360%
### 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 five sizes: xsmall, small, base (default), regular (default, no modifier), and large. Each size maintains the same dot states and functionality. The `progress-dots--base` modifier explicitly sets the default/regular size and is useful for responsive layouts.
Xsmall Progress
Small Progress
Base Progress
Regular Progress
Large Progress
ProgressDots Sizes
### Related Tokens
These tokens are automatically mapped to this page by token prefix.
| Token | 1-bit | 2-bit | Density 2x | 4/8/16-bit |
| --- | --- | --- | --- | --- |
| Base | | | | |
| `--progress-bar-height` | 24px | — | — | — |
| `--progress-bar-height-large` | 32px | — | — | — |
| `--progress-bar-height-small` | 12px | — | — | — |
| `--progress-bar-height-xsmall` | 6px | — | — | — |
| `--progress-dot-size` | 16px | — | — | — |
| `--progress-dot-size-large` | 20px | — | — | — |
| `--progress-dot-size-small` | 12px | — | — | — |
| `--progress-dot-size-xsmall` | 8px | — | — | — |
Previous
[Chart Visualize data optimized for 1-bit rendering](/framework/docs/3.1/chart)
---
# Is there a plugin for...
Public Plugins:
Community Recipes:
List of all plugins:
- [Exquisite Corpses](https://trmnl.com/recipes/241214)
- [IndyCar Calendar](https://trmnl.com/recipes/244852)
- [Mitglieder Deutscher Bundestag](https://trmnl.com/recipes/233024)
- [1-bit Jaws](https://trmnl.com/recipes/57642)
- [10 PRINT](https://trmnl.com/recipes/176517)
- [2. Bundesliga Spieltag](https://trmnl.com/recipes/178650)
- [72 Japanese Seasons](https://trmnl.com/recipes/103813)
- [A-League Men Ladder](https://trmnl.com/recipes/260919)
- [AA Daily Reflection](https://trmnl.com/recipes/260105)
- [ABC NEWS Australia](https://trmnl.com/recipes/74581)
- [ABC News](https://trmnl.com/recipes/252102)
- [ACTransit Stop](https://trmnl.com/recipes/183449)
- [ADHDinos](https://trmnl.com/recipes/248040)
- [AFL Ladder](https://trmnl.com/recipes/84638)
- [AI Leaderboard](https://trmnl.com/recipes/258521)
- [ATP/WTA Tennis Rankings](https://trmnl.com/recipes/194775)
- [AZ511](https://trmnl.com/recipes/254461)
- [Aare Guru](https://trmnl.com/recipes/118461)
- [Actual Budget](https://trmnl.com/recipes/111710)
- [Adafruit IO](https://trmnl.com/recipes/112643)
- [AdafruitIO](https://trmnl.com/recipes/41159)
- [Advanced RSS](https://trmnl.com/recipes/79118)
- [Advice Slip](https://trmnl.com/recipes/147316)
- [Air Gradient Air Quality Monitor](https://trmnl.com/recipes/359859)
- [Air Quality](https://trmnl.com/recipes/193113)
- [Air Quality Forecast](https://trmnl.com/recipes/23306)
- [Air Quality Monitor](https://trmnl.com/recipes/62233)
- [Alert.Swiss](https://trmnl.com/recipes/238152)
- [Alias](https://trmnl.com/integrations/alias)
- [All Your News](https://trmnl.com/recipes/182990)
- [Alpenglow](https://trmnl.com/integrations/alpenglow)
- [Amber Electric](https://trmnl.com/recipes/53435)
- [Amslertest for Macula Degeneration](https://trmnl.com/recipes/376628)
- [Anagram Puzzle](https://trmnl.com/recipes/34845)
- [Anime Character of the Day](https://trmnl.com/recipes/227462)
- [Anime of the Day](https://trmnl.com/recipes/48032)
- [Anon Opin](https://trmnl.com/recipes/147675)
- [AppSumo Deals](https://trmnl.com/recipes/179048)
- [Apple Calendar](https://trmnl.com/integrations/apple-calendar)
- [Apple Music](https://trmnl.com/integrations/apple-music)
- [Apple Note](https://trmnl.com/recipes/191420)
- [Apple Note Board](https://trmnl.com/recipes/96659)
- [Apple Photos](https://trmnl.com/integrations/apple-photos)
- [Apple Reminders](https://trmnl.com/recipes/17237)
- [Apple Reminders Companion](https://trmnl.com/recipes/336201)
- [Apple Shortcuts Reminders](https://trmnl.com/recipes/79716)
- [Apple Steps](https://trmnl.com/recipes/32162)
- [Applytics](https://trmnl.com/recipes/339013)
- [Architectural Styles](https://trmnl.com/recipes/228508)
- [Argentina's Central Bank Reserves](https://trmnl.com/recipes/231257)
- [Arlington Public School Status](https://trmnl.com/recipes/201396)
- [Arlington Public Schools Menus](https://trmnl.com/recipes/198409)
- [Art Store](https://trmnl.com/recipes/199611)
- [Art of the Day](https://trmnl.com/recipes/16765)
- [Asana](https://trmnl.com/integrations/asana)
- [Asfalt Alert (Dutch Traffic)](https://trmnl.com/recipes/115011)
- [Astrobin Astronomy Image of the Day](https://trmnl.com/recipes/283008)
- [Astronauts](https://trmnl.com/integrations/astronauts)
- [Astronomy Picture of the Day](https://trmnl.com/recipes/54706)
- [Atlas Quiz](https://trmnl.com/recipes/372558)
- [Atomic Habits Quotes](https://trmnl.com/integrations/atomic-habits-quotes)
- [Austrian Train Departures (OEBB)](https://trmnl.com/recipes/28271)
- [Autobahn Warnings](https://trmnl.com/recipes/232543)
- [Autumn](https://trmnl.com/recipes/119587)
- [Avalanche Forecast](https://trmnl.com/recipes/195454)
- [Aviation Telemetry](https://trmnl.com/recipes/354981)
- [Awair](https://trmnl.com/recipes/200956)
- [Azure DevOps - Pull Requests](https://trmnl.com/recipes/160336)
- [BART Commute](https://trmnl.com/recipes/181434)
- [BART Departures](https://trmnl.com/recipes/180202)
- [BARTstop](https://trmnl.com/recipes/180254)
- [BBC News - Latest News](https://trmnl.com/recipes/206768)
- [BBC World News](https://trmnl.com/recipes/79766)
- [BCycle](https://trmnl.com/recipes/100262)
- [BTC Price and Chart](https://trmnl.com/integrations/btc-price-and-chart)
- [BVG - Berlin Public Transport](https://trmnl.com/recipes/39029)
- [BVG departures](https://trmnl.com/recipes/113756)
- [Bandcamp Album of the Day](https://trmnl.com/recipes/260387)
- [Basecamp](https://trmnl.com/integrations/basecamp)
- [Basecamp Assignments](https://trmnl.com/recipes/249612)
- [Bauernkalender](https://trmnl.com/recipes/192909)
- [Bay Wheels](https://trmnl.com/recipes/185947)
- [Bears Football](https://trmnl.com/recipes/165579)
- [Been There: World Map](https://trmnl.com/recipes/254631)
- [Beetle Bailey | Comic Strip](https://trmnl.com/recipes/309719)
- [Bergfex Snow Report](https://trmnl.com/recipes/219253)
- [Better Battery](https://trmnl.com/recipes/217763)
- [Beware of Toddler Comic](https://trmnl.com/recipes/131756)
- [Bible Verse (ESV)](https://trmnl.com/recipes/96153)
- [Bible Verses](https://trmnl.com/integrations/bible-verses)
- [Bible in a Year](https://trmnl.com/recipes/244519)
- [Bijbelvers Dagelijkswoord](https://trmnl.com/recipes/142718)
- [Bike Stations](https://trmnl.com/recipes/25766)
- [Binary Clock](https://trmnl.com/recipes/11153)
- [Binary Clock](https://trmnl.com/recipes/135561)
- [Biorhythms](https://trmnl.com/integrations/biorhythms)
- [Birds 🐦](https://trmnl.com/recipes/248919)
- [Birthday Calendar](https://trmnl.com/recipes/225941)
- [Bitbucket Pull Requests](https://trmnl.com/recipes/219271)
- [Bitcoin Halving Countdown](https://trmnl.com/recipes/264832)
- [Bitcoin Market Overview](https://trmnl.com/recipes/242408)
- [Bitcoin Mempool](https://trmnl.com/recipes/167030)
- [Bitcoin Price Chart](https://trmnl.com/recipes/157962)
- [Bitcoin Wallet](https://trmnl.com/integrations/bitcoin-wallet-balance)
- [Bitpanda Portfolio](https://trmnl.com/integrations/bitpanda-portfolio)
- [Bluesky Profile](https://trmnl.com/recipes/85582)
- [Blunt Weather](https://trmnl.com/recipes/305453)
- [BoardGameGeek - Your Collection](https://trmnl.com/recipes/25265)
- [BoardGameGeek The Hotness &c.](https://trmnl.com/recipes/262487)
- [Book Club Planner](https://trmnl.com/recipes/126964)
- [Book Cover](https://trmnl.com/recipes/123042)
- [Book by its Cover](https://trmnl.com/recipes/127048)
- [Book of the day](https://trmnl.com/recipes/40821)
- [Bored Activities](https://trmnl.com/recipes/186137)
- [Bouldering Gym Capacity](https://trmnl.com/recipes/91950)
- [Brainless Tales](https://trmnl.com/recipes/181181)
- [Brandom](https://trmnl.com/recipes/96506)
- [Brewforge.sh](https://trmnl.com/integrations/brewforge-sh)
- [Brick Storage](https://trmnl.com/integrations/brick-storage)
- [Brighton buses](https://trmnl.com/recipes/112752)
- [Bring! Shopping List](https://trmnl.com/integrations/bring-shopping-list)
- [Brisbane Bin Collection](https://trmnl.com/recipes/240601)
- [Buffer](https://trmnl.com/recipes/376448)
- [Buienradar (Dutch Weather)](https://trmnl.com/recipes/21248)
- [Buienradar Station](https://trmnl.com/recipes/353457)
- [Buienradar image](https://trmnl.com/recipes/278318)
- [Bundesliga Spieltag](https://trmnl.com/recipes/137683)
- [Busdex](https://trmnl.com/recipes/268495)
- [Business Cat Comic](https://trmnl.com/recipes/172970)
- [Busy Buddy](https://trmnl.com/integrations/busy-buddy)
- [Börsihind.ee](https://trmnl.com/recipes/122983)
- [CISA Directives and Alerts](https://trmnl.com/recipes/258980)
- [CTRoads Traffic Advisories](https://trmnl.com/recipes/335490)
- [Caffeinator](https://trmnl.com/recipes/326998)
- [CalDAV](https://trmnl.com/integrations/caldav)
- [Calendar XL](https://trmnl.com/recipes/261373)
- [Calming Manatee](https://trmnl.com/recipes/298273)
- [Calvin and Hobbes Comics](https://trmnl.com/recipes/27184)
- [CamelCamelCamel](https://trmnl.com/recipes/121902)
- [Canadian Holidays](https://trmnl.com/recipes/176366)
- [Canvas](https://trmnl.com/integrations/canvas)
- [Carbon Proxy - Local LLM](https://trmnl.com/recipes/287762)
- [Cat Facts](https://trmnl.com/recipes/2734)
- [Cats 😸](https://trmnl.com/recipes/100133)
- [Cats, Dogs and Cat Facts](https://trmnl.com/recipes/40326)
- [Changelog](https://trmnl.com/integrations/changelog)
- [Channel News Asia - Latest News](https://trmnl.com/recipes/43101)
- [ChartMogul](https://trmnl.com/recipes/234571)
- [ChatGPT](https://trmnl.com/integrations/chatgpt)
- [Check X Order Queue](https://trmnl.com/recipes/260817)
- [Chess.com Ratings Chart](https://trmnl.com/recipes/115874)
- [Chess.com puzzle of the day](https://trmnl.com/recipes/109248)
- [Chinese Hanzi](https://trmnl.com/recipes/352027)
- [Chinese Proverbs (Translated)](https://trmnl.com/recipes/171219)
- [Chuck Norris Facts](https://trmnl.com/recipes/19481)
- [Cigar Quotes](https://trmnl.com/recipes/308201)
- [CitiBike](https://trmnl.com/recipes/97268)
- [Clash Royale Statistics](https://trmnl.com/recipes/80608)
- [Claude Code Dashboard](https://trmnl.com/recipes/277976)
- [Claude Code Dashboard](https://trmnl.com/recipes/278858)
- [Claude Usage](https://trmnl.com/recipes/263932)
- [ClickHole](https://trmnl.com/recipes/31807)
- [ClickUp Tasks](https://trmnl.com/recipes/48679)
- [Clicky](https://trmnl.com/integrations/clicky)
- [Climate Change: Global temperature anomalies](https://trmnl.com/recipes/238037)
- [Clock](https://trmnl.com/integrations/clock)
- [Close](https://trmnl.com/integrations/close)
- [Cocktail Canvas](https://trmnl.com/recipes/379405)
- [CodingLife](https://trmnl.com/recipes/91273)
- [Coffee Recipe](https://trmnl.com/recipes/252053)
- [CoinMarketCap](https://trmnl.com/integrations/coinmarketcap)
- [CoinSpot AUD](https://trmnl.com/recipes/101850)
- [Coinbase BTC Price](https://trmnl.com/recipes/13890)
- [Comic Book Covers](https://trmnl.com/recipes/186430)
- [Comic Library](https://trmnl.com/recipes/180935)
- [Community: Conway's Game of Life](https://trmnl.com/recipes/114863)
- [Compliments](https://trmnl.com/integrations/compliments)
- [ConvertKit](https://trmnl.com/integrations/convertkit-analytics)
- [Countdown](https://trmnl.com/recipes/16789)
- [Countdown](https://trmnl.com/recipes/188160)
- [Countdown with Icons](https://trmnl.com/recipes/152234)
- [Counter Strike 2](https://trmnl.com/recipes/221410)
- [Counter-Strike 2 Item Showcase](https://trmnl.com/recipes/180810)
- [Cozy Flashcards](https://trmnl.com/recipes/307338)
- [Cozy Scenes: Daily Comic Moments🐱](https://trmnl.com/recipes/284437)
- [Cozy Scenes: Daily Vibes](https://trmnl.com/recipes/270926)
- [Cozy Scenes: Inky Blobs](https://trmnl.com/recipes/285574)
- [Cozy Scenes: Side Quests (Task List RPG)📜🗡️](https://trmnl.com/recipes/283303)
- [Cozy Scenes: Space Time…ish](https://trmnl.com/recipes/288991)
- [Cozy Scenes: Waifu Weather 🌞](https://trmnl.com/recipes/280830)
- [Cozy Telegram Guestbook](https://trmnl.com/recipes/305779)
- [Cozy Trivia: The Office, Seinfeld & Friends](https://trmnl.com/recipes/296443)
- [Cracow Public Transport](https://trmnl.com/recipes/47552)
- [Crypto Fear & Greed Index](https://trmnl.com/recipes/19728)
- [Crypto Market Status](https://trmnl.com/recipes/59188)
- [Culture Series Ship Names](https://trmnl.com/recipes/44727)
- [Currency Index](https://trmnl.com/recipes/273095)
- [Current Reads](https://trmnl.com/integrations/current-reads)
- [Custom Cheatsheet](https://trmnl.com/recipes/197356)
- [Custom Next Holiday and Countdown](https://trmnl.com/recipes/257171)
- [Custom Text](https://trmnl.com/integrations/custom-text)
- [Custom Wisdom Tile](https://trmnl.com/recipes/110948)
- [Cut/daily Quote of the Day](https://trmnl.com/recipes/248986)
- [Cyanide and Happiness Comics](https://trmnl.com/recipes/30789)
- [DC Bus Stop Schedule](https://trmnl.com/recipes/65327)
- [DC Metro Station Schedule](https://trmnl.com/recipes/21537)
- [DEFAS Departures](https://trmnl.com/recipes/137663)
- [DIN Time 風](https://trmnl.com/recipes/131778)
- [DN Latest News](https://trmnl.com/recipes/176747)
- [DOOM](https://trmnl.com/recipes/348622)
- [DOTA Team Rankings](https://trmnl.com/recipes/185654)
- [DVB - Dresden Public Transport](https://trmnl.com/recipes/113464)
- [DWD](https://trmnl.com/recipes/83018)
- [DWD Bio Wetter](https://trmnl.com/recipes/32651)
- [DWD Pollen](https://trmnl.com/recipes/32444)
- [Dad Jokes](https://trmnl.com/recipes/619)
- [Dad Jokes by GroanDeck.com](https://trmnl.com/recipes/285134)
- [Dad Jokes via icanhazdadjoke](https://trmnl.com/recipes/168807)
- [Daily AI News](https://trmnl.com/recipes/214031)
- [Daily B.C. Comic](https://trmnl.com/recipes/248039)
- [Daily Conway's Game of Life](https://trmnl.com/recipes/226511)
- [Daily Corporate Bullshit](https://trmnl.com/recipes/226952)
- [Daily Devotional](https://trmnl.com/recipes/244233)
- [Daily Dewey](https://trmnl.com/recipes/127278)
- [Daily Dose - Local - JOTD & QOTD](https://trmnl.com/recipes/288134)
- [Daily Excuse](https://trmnl.com/recipes/286320)
- [Daily Film and TV Trivia](https://trmnl.com/recipes/144919)
- [Daily Finance Tip](https://trmnl.com/recipes/231754)
- [Daily Form: Workout of the Day](https://trmnl.com/recipes/263676)
- [Daily Horoscope](https://trmnl.com/recipes/30775)
- [Daily Luann Comic](https://trmnl.com/recipes/248034)
- [Daily New Yorker Cartoon](https://trmnl.com/recipes/131760)
- [Daily Pokémon](https://trmnl.com/recipes/115891)
- [Daily Quran](https://trmnl.com/recipes/76103)
- [Daily Rituals](https://trmnl.com/recipes/47433)
- [Daily Salary Earnings](https://trmnl.com/recipes/299995)
- [Daily Shell: Daily Unix Tips](https://trmnl.com/recipes/252227)
- [Daily Tarot](https://trmnl.com/recipes/35833)
- [Daily Tide Chart](https://trmnl.com/recipes/197206)
- [Daily Torah Quote](https://trmnl.com/recipes/140237)
- [Daily Torah Study](https://trmnl.com/recipes/151619)
- [Daily Torah Topic](https://trmnl.com/recipes/186781)
- [Daily Torah Wisdom](https://trmnl.com/recipes/195699)
- [Daily Weather](https://trmnl.com/recipes/150460)
- [Danish Power Prices](https://trmnl.com/recipes/240727)
- [Data Breaches](https://trmnl.com/recipes/183668)
- [Date + Weekday Indicator](https://trmnl.com/recipes/59988)
- [David Lynch Quotes](https://trmnl.com/recipes/376259)
- [Day Night Map](https://trmnl.com/recipes/127208)
- [Day of the Week](https://trmnl.com/recipes/244484)
- [DayGLANCE](https://trmnl.com/recipes/263917)
- [Days Left This Year](https://trmnl.com/integrations/days-left-year)
- [Days Left Until Retirement](https://trmnl.com/integrations/days-left-retirement)
- [Days Left Until...](https://trmnl.com/integrations/days-left-until)
- [Days Since...](https://trmnl.com/integrations/days-since)
- [Demotivational Quotes](https://trmnl.com/recipes/332609)
- [Dengonban](https://trmnl.com/recipes/42454)
- [Dependabot Alerts](https://trmnl.com/recipes/370766)
- [Desk Rhythm: Your Time-Management Companion](https://trmnl.com/recipes/263049)
- [Deutsche Bahn Abfahrten](https://trmnl.com/integrations/deutsche-bahn-abfahrten)
- [Deutsche Bahn Departures](https://trmnl.com/recipes/25192)
- [Dev.to Articles](https://trmnl.com/recipes/184240)
- [Developer Excuse](https://trmnl.com/recipes/53432)
- [DeviantArt](https://trmnl.com/recipes/246026)
- [Dexcom (Home Assistant)](https://trmnl.com/recipes/47133)
- [Dharma Quotes](https://trmnl.com/recipes/122917)
- [Dick Tracy | Comic Strip](https://trmnl.com/recipes/309512)
- [Die Losungen](https://trmnl.com/recipes/230750)
- [Digital Clock](https://trmnl.com/recipes/73403)
- [Digital Clock](https://trmnl.com/recipes/190574)
- [DigitalOcean Status](https://trmnl.com/recipes/227481)
- [Dilbert | Comic Strip](https://trmnl.com/recipes/310156)
- [Dinosaur Comics](https://trmnl.com/recipes/28743)
- [Discogs](https://trmnl.com/recipes/255730)
- [Discord Messages](https://trmnl.com/recipes/172622)
- [Divvy](https://trmnl.com/recipes/21568)
- [Docker Container Monitor](https://trmnl.com/recipes/162087)
- [Dog Facts](https://trmnl.com/recipes/2554)
- [Dogs 🐶](https://trmnl.com/recipes/149276)
- [Dolar Rates in Argentina](https://trmnl.com/recipes/224161)
- [Donations QR code](https://trmnl.com/recipes/278357)
- [Doonesbury | Comic Strip](https://trmnl.com/recipes/309473)
- [Dreamcast Title Screens](https://trmnl.com/recipes/222198)
- [Dropbox Photo Album](https://trmnl.com/integrations/dropbox-photo-album)
- [Duden Wort des Tages](https://trmnl.com/recipes/87629)
- [Duolingo](https://trmnl.com/recipes/152659)
- [Duolingo Streak](https://trmnl.com/recipes/57294)
- [Durham Waste Collection](https://trmnl.com/recipes/178528)
- [Dutch Public Transit Upcoming Departures](https://trmnl.com/recipes/143803)
- [Dynamic energy prices in Belgium](https://trmnl.com/recipes/148409)
- [E-sports upcoming tournaments](https://trmnl.com/recipes/181530)
- [EDHRec Top MTG Commanders](https://trmnl.com/recipes/131111)
- [EDTRMNL](https://trmnl.com/recipes/375783)
- [EPEX Nederland](https://trmnl.com/recipes/86393)
- [EPL Fixtures](https://trmnl.com/recipes/251081)
- [EPL Fixtures - My Team Only](https://trmnl.com/recipes/28435)
- [EToro Portfolio Viewer](https://trmnl.com/recipes/253167)
- [EVCC Dashboard](https://trmnl.com/recipes/240868)
- [Earth Observatory Image of the Day](https://trmnl.com/recipes/280791)
- [Earthquakes](https://trmnl.com/recipes/33312)
- [Ecowitt Weather Station](https://trmnl.com/recipes/48926)
- [Eight Sleep](https://trmnl.com/integrations/eight-sleep)
- [Electric Sheep](https://trmnl.com/recipes/268491)
- [Electricity prices (tado/awattar/smartENERGY)](https://trmnl.com/recipes/116993)
- [Elegant Countdown Countup Display](https://trmnl.com/recipes/327796)
- [Element of the Day](https://trmnl.com/recipes/226166)
- [Elprisetjustnu](https://trmnl.com/recipes/28620)
- [Email Messages (IMAP)](https://trmnl.com/recipes/198482)
- [Email Meter](https://trmnl.com/integrations/email-meter)
- [Environmental Sensor Graph](https://trmnl.com/recipes/256958)
- [Eredivisie Standings](https://trmnl.com/recipes/124580)
- [Esthio Schoolmenu](https://trmnl.com/recipes/250418)
- [Ethereum Wallet](https://trmnl.com/integrations/ethereum-wallet-balance)
- [Etsy](https://trmnl.com/integrations/etsy)
- [Europa League Table](https://trmnl.com/recipes/156818)
- [Exchange Rates](https://trmnl.com/recipes/142269)
- [Expecto (Pregnancy Guide)](https://trmnl.com/recipes/89136)
- [Extended Weather Dashboard](https://trmnl.com/recipes/214872)
- [Extra Fabulous Comics](https://trmnl.com/recipes/253160)
- [F1AcademyCalendar.com](https://trmnl.com/recipes/52893)
- [F2Calendar.com - Formula 2 Calendar](https://trmnl.com/recipes/52472)
- [F3Calendar.com - Formula 3 Calendar](https://trmnl.com/recipes/52907)
- [FIFA World Cup - Next Match](https://trmnl.com/recipes/326513)
- [FOXNews](https://trmnl.com/recipes/190653)
- [FPL top 5 standings](https://trmnl.com/recipes/184477)
- [FSHBWL Aquarium](https://trmnl.com/recipes/136156)
- [Far Side Comic](https://trmnl.com/recipes/122324)
- [Farcaster](https://trmnl.com/recipes/93503)
- [Fastmail Calendar](https://trmnl.com/integrations/fastmail-calendar)
- [Fathom Analytics](https://trmnl.com/recipes/89064)
- [Fera](https://trmnl.com/integrations/fera)
- [Ferengi Rules of Acquisition](https://trmnl.com/integrations/ferengi-rules-of-acquisition)
- [Fesshole](https://trmnl.com/recipes/32630)
- [Fifa World Cup 2026 - Team Tracker](https://trmnl.com/recipes/327669)
- [Financial Times (FT)](https://trmnl.com/recipes/133514)
- [Fish Tank](https://trmnl.com/recipes/224093)
- [FlashInvaders Status](https://trmnl.com/recipes/26983)
- [Flight Tracker](https://trmnl.com/integrations/flight-tracker)
- [Flip Clock](https://trmnl.com/recipes/174854)
- [Focus Timer](https://trmnl.com/recipes/241966)
- [Football - EFL Championship Standings](https://trmnl.com/recipes/35605)
- [Football - EPL Fixtures](https://trmnl.com/recipes/13395)
- [Football - EPL Results](https://trmnl.com/recipes/13442)
- [Football - EPL Standings](https://trmnl.com/recipes/15887)
- [Football - La Liga Standings](https://trmnl.com/recipes/275566)
- [Football - MLS Standings](https://trmnl.com/recipes/25478)
- [Formula 1 - Race and Standings](https://trmnl.com/recipes/251959)
- [Formula 1 Calendar & Race Times](https://trmnl.com/recipes/20507)
- [Formula 1 Constructor Standings](https://trmnl.com/recipes/30563)
- [Formula 1 Driver Standings](https://trmnl.com/recipes/30547)
- [Formula 1 Races](https://trmnl.com/recipes/11914)
- [FormulaECal.com - Formula E Calendar](https://trmnl.com/recipes/52910)
- [Fortnite Statistics](https://trmnl.com/recipes/186199)
- [Fortune Cookie of the Day](https://trmnl.com/recipes/201904)
- [Fox News - Political Cartoon of the Day](https://trmnl.com/recipes/162296)
- [FoxTrot Classics Comic](https://trmnl.com/recipes/122369)
- [Foxes in Love](https://trmnl.com/recipes/84949)
- [Frank Energie](https://trmnl.com/recipes/245095)
- [Fuel Prices Portugal](https://trmnl.com/recipes/181330)
- [Fun Daily Holidays](https://trmnl.com/integrations/fun-daily-holidays)
- [Fun Sort of Time](https://trmnl.com/recipes/182877)
- [Fun With Flags](https://trmnl.com/recipes/176791)
- [GG.Deals - Track your game discounts](https://trmnl.com/recipes/192524)
- [GO Transit Schedule](https://trmnl.com/recipes/226758)
- [GSOL](https://trmnl.com/recipes/43564)
- [GTA VI Countdown](https://trmnl.com/recipes/219410)
- [Gaffa](https://trmnl.com/integrations/gaffa)
- [Game Boy Title Screens](https://trmnl.com/recipes/202774)
- [Game Deals](https://trmnl.com/recipes/184803)
- [GameCube Title Screens](https://trmnl.com/recipes/202626)
- [GamesDoneQuick](https://trmnl.com/recipes/368523)
- [Garfield (Comic)](https://trmnl.com/recipes/31447)
- [Gemini](https://trmnl.com/recipes/50076)
- [Generative Art: Trees, Boxes, Circles, and Lines](https://trmnl.com/recipes/198750)
- [GeoGuess](https://trmnl.com/recipes/193220)
- [Geovelo](https://trmnl.com/recipes/72208)
- [German Film Guide](https://trmnl.com/recipes/130454)
- [Ghibli Backgrounds](https://trmnl.com/recipes/210390)
- [GitHub Commit Graph](https://trmnl.com/integrations/github-commit-graph)
- [GitHub Contributors](https://trmnl.com/integrations/github-contributor-summary)
- [GitHub Copilot Usage](https://trmnl.com/recipes/298373)
- [GitHub Issues](https://trmnl.com/recipes/53612)
- [GitHub Profile](https://trmnl.com/recipes/190117)
- [GitHub Pull Requests](https://trmnl.com/recipes/221833)
- [GitHub Trending Repos](https://trmnl.com/recipes/243710)
- [GitHub Workflow Runs](https://trmnl.com/recipes/161967)
- [GitLab Merge Requests](https://trmnl.com/recipes/76874)
- [Gita Verse of the Day](https://trmnl.com/recipes/261336)
- [Github Notifications](https://trmnl.com/recipes/179266)
- [Github Releases](https://trmnl.com/recipes/176802)
- [Github Repo Insights](https://trmnl.com/recipes/158645)
- [Github Review Requests](https://trmnl.com/recipes/105565)
- [Global Stock Market](https://trmnl.com/recipes/45484)
- [GoatCounter](https://trmnl.com/recipes/228787)
- [GoldenYears: Point & Care dashboard](https://trmnl.com/recipes/378692)
- [GoldenYears: Senior Care & Message Board](https://trmnl.com/recipes/351675)
- [Goodreads](https://trmnl.com/recipes/168273)
- [Goodreads RSS](https://trmnl.com/recipes/112256)
- [Google Analytics](https://trmnl.com/integrations/google-analytics)
- [Google Calendar](https://trmnl.com/integrations/google-calendar)
- [Google Doodle of the Day](https://trmnl.com/recipes/240618)
- [Google Drive (photos)](https://trmnl.com/integrations/google-drive-photos)
- [Google Health](https://trmnl.com/recipes/318970)
- [Google Photos](https://trmnl.com/recipes/227153)
- [Google Photos](https://trmnl.com/integrations/google-photos)
- [Google Photos Canvas](https://trmnl.com/recipes/230712)
- [Google Photos Public Album](https://trmnl.com/recipes/321608)
- [Google Sheet Quote](https://trmnl.com/recipes/121258)
- [Google Sheets Table (Finance)](https://trmnl.com/recipes/247022)
- [Google Tasks](https://trmnl.com/integrations/google-tasks)
- [Google Trends](https://trmnl.com/recipes/149848)
- [Government Alerts](https://trmnl.com/recipes/217791)
- [Grafana Dashboard](https://trmnl.com/recipes/105532)
- [Grafana Panel](https://trmnl.com/recipes/189121)
- [Gumroad](https://trmnl.com/integrations/gumroad-analytics)
- [HTM Tram/Bus Status](https://trmnl.com/recipes/148353)
- [HTTP Cats](https://trmnl.com/recipes/235410)
- [HTTP Dogs](https://trmnl.com/recipes/243773)
- [HTTP Pets](https://trmnl.com/recipes/323180)
- [HVV Transit](https://trmnl.com/recipes/187524)
- [Habitica Tasks](https://trmnl.com/recipes/123494)
- [Hacker News](https://trmnl.com/integrations/hacker-news)
- [Hagar The Horrible | Comic Strip](https://trmnl.com/recipes/309870)
- [Haiku Weather](https://trmnl.com/recipes/252404)
- [Ham Radio Solar Weather](https://trmnl.com/recipes/324600)
- [Hardcover](https://trmnl.com/recipes/63345)
- [Hardcover - Reading Buddy](https://trmnl.com/recipes/122776)
- [Harvest](https://trmnl.com/integrations/harvest)
- [Healthchecks.io](https://trmnl.com/recipes/64170)
- [Hello My Name Is](https://trmnl.com/recipes/36396)
- [Heyokyay](https://trmnl.com/recipes/243525)
- [Himawari Real-Time Image](https://trmnl.com/recipes/178288)
- [Hogwarts Randomizer](https://trmnl.com/recipes/97572)
- [Home Assistant Entity - Trend](https://trmnl.com/recipes/124881)
- [Home Assistant Entity Cards](https://trmnl.com/recipes/286869)
- [Home Assistant Entity Cards [Webhook]](https://trmnl.com/recipes/324788)
- [Home Assistant History Graph](https://trmnl.com/recipes/286742)
- [Home Assistant Screenshot](https://trmnl.com/integrations/home-assistant-screenshot)
- [Home Assistant To-Do List](https://trmnl.com/recipes/227107)
- [Home Assistant Weather Station](https://trmnl.com/recipes/46862)
- [Hong Kong Bus ETA](https://trmnl.com/recipes/14066)
- [Hong Kong Tunnel Tolls](https://trmnl.com/recipes/227860)
- [Hong Kong Weather](https://trmnl.com/recipes/376123)
- [Horizontal World Clock](https://trmnl.com/recipes/153664)
- [Hourglass](https://trmnl.com/recipes/249658)
- [Hourly Weather Forecast](https://trmnl.com/recipes/35788)
- [How Old Are You?](https://trmnl.com/recipes/236588)
- [Hoymiles Solar Panel](https://trmnl.com/recipes/140588)
- [HubSpot](https://trmnl.com/integrations/hubspot)
- [Hurricane Outlook](https://trmnl.com/recipes/137775)
- [Hydration Reminder](https://trmnl.com/recipes/220845)
- [I Ching - Daily Divination](https://trmnl.com/recipes/49685)
- [I'm in a Meeting!](https://trmnl.com/recipes/87731)
- [IPv6 Adoption](https://trmnl.com/recipes/345789)
- [ISS Location](https://trmnl.com/integrations/iss-location)
- [ISS Tracker Map](https://trmnl.com/recipes/135652)
- [ITranvias - Bus A Coruña](https://trmnl.com/recipes/312018)
- [Ice-Breaker Questions](https://trmnl.com/recipes/200507)
- [Image Display](https://trmnl.com/integrations/image-display)
- [Imgur Album](https://trmnl.com/recipes/87125)
- [Immich Random Image](https://trmnl.com/recipes/296218)
- [Indexed Blog](https://trmnl.com/recipes/238677)
- [Indian Premier League](https://trmnl.com/recipes/271323)
- [InfoQ Headlines](https://trmnl.com/recipes/77844)
- [International Flip Date Clock](https://trmnl.com/recipes/247379)
- [Is Mercury in Retrograde?](https://trmnl.com/recipes/221425)
- [Is it down?](https://trmnl.com/recipes/367860)
- [Is my metro commute screwed?](https://trmnl.com/integrations/is-my-metro-commute-screwed)
- [Islamic Prayer Times](https://trmnl.com/recipes/246906)
- [It has been [x] days since last y](https://trmnl.com/recipes/311239)
- [It's 5 O'Clock Somewhere](https://trmnl.com/recipes/253752)
- [It's a Plane](https://trmnl.com/recipes/122422)
- [JUDU - Vilnius Public Transport](https://trmnl.com/recipes/177713)
- [Japanese word of the day (with furigana)](https://trmnl.com/recipes/117962)
- [Jellyfin - Recent Activity](https://trmnl.com/recipes/16937)
- [Jenkins CI/CD](https://trmnl.com/recipes/336671)
- [Jira Issue Counts](https://trmnl.com/recipes/221333)
- [Jira Issues List](https://trmnl.com/recipes/216106)
- [Joulo - ERE Certificaten](https://trmnl.com/recipes/367355)
- [Journaling Prompt](https://trmnl.com/recipes/234315)
- [KKD Standings](https://trmnl.com/recipes/136209)
- [KSP Mission Outcome](https://trmnl.com/recipes/66356)
- [Kagi News](https://trmnl.com/recipes/45842)
- [KanIkEenKorteBroekAan.nl](https://trmnl.com/recipes/288182)
- [Kat Walkers Stats](https://trmnl.com/recipes/30257)
- [Kickstarter](https://trmnl.com/integrations/kickstarter)
- [King's Quest Scenes](https://trmnl.com/recipes/304932)
- [Kiosko Newspaper Frontpages](https://trmnl.com/recipes/291635)
- [Koto - Daily Japanese](https://trmnl.com/recipes/326131)
- [Kroki Diagram](https://trmnl.com/recipes/361513)
- [Kung Fu Panda Quotes](https://trmnl.com/recipes/240176)
- [LCARS Schematics](https://trmnl.com/integrations/lcars-schematics)
- [LDS Quotes](https://trmnl.com/recipes/23523)
- [LDS Scripture Stories](https://trmnl.com/recipes/378914)
- [LLM Leaderboard](https://trmnl.com/recipes/101445)
- [LOTR Fellowship Stills](https://trmnl.com/recipes/249533)
- [LOTR Movie Quotes](https://trmnl.com/recipes/43498)
- [LP Weather](https://trmnl.com/recipes/249564)
- [Laatste nieuws - NOS - RTL - AD - Nu.nl](https://trmnl.com/recipes/206736)
- [Language Learning](https://trmnl.com/integrations/language-learning)
- [Language Learning: Sentences](https://trmnl.com/recipes/177718)
- [Last 100 GitHub Workflows](https://trmnl.com/recipes/160135)
- [LastFM](https://trmnl.com/recipes/25815)
- [Latest Episodes from TWiT](https://trmnl.com/recipes/82367)
- [Latest Hardcover Quote](https://trmnl.com/recipes/84359)
- [Latest TRMNL recipes](https://trmnl.com/recipes/156769)
- [Launchpad - Upcoming Rocket Launches](https://trmnl.com/recipes/189917)
- [Lawn Mowing Direction](https://trmnl.com/recipes/92770)
- [Lego Sets](https://trmnl.com/recipes/184371)
- [LemonAid](https://trmnl.com/recipes/314690)
- [Lennys Podcast Insights](https://trmnl.com/recipes/242178)
- [Lexophile](https://trmnl.com/recipes/120897)
- [Lichess Daily Puzzle](https://trmnl.com/recipes/30458)
- [Lichess Puzzle](https://trmnl.com/recipes/63321)
- [Linear](https://trmnl.com/recipes/187006)
- [Lines - Time or Weather](https://trmnl.com/recipes/377363)
- [Linious Arts](https://trmnl.com/recipes/329568)
- [Literate Movies](https://trmnl.com/recipes/122948)
- [Literature Clock](https://trmnl.com/recipes/11141)
- [Litterbox Comics](https://trmnl.com/recipes/148700)
- [Little Better: Daily Challenges for a Better You](https://trmnl.com/recipes/262723)
- [Live CCTV Roulette](https://trmnl.com/recipes/269803)
- [Live Earth View](https://trmnl.com/recipes/114409)
- [Live Solar System](https://trmnl.com/recipes/207766)
- [Lobby](https://trmnl.com/integrations/lobby)
- [Lobsters](https://trmnl.com/recipes/212869)
- [Lojong Slogans](https://trmnl.com/recipes/11364)
- [Lokale bekendmakingen (NL: Local announcements)](https://trmnl.com/recipes/22748)
- [Lorcana Cards](https://trmnl.com/recipes/298482)
- [Love Is...](https://trmnl.com/recipes/293994)
- [Ludum Dare Games](https://trmnl.com/recipes/271931)
- [Lumon Wellness](https://trmnl.com/recipes/102880)
- [Lunar Calendar](https://trmnl.com/integrations/lunar-calendar)
- [Lunch Money](https://trmnl.com/integrations/lunch-money)
- [Lyrics](https://trmnl.com/recipes/226391)
- [MBTA Alerts](https://trmnl.com/recipes/93149)
- [MBTA Arrivals](https://trmnl.com/recipes/179242)
- [MEE6 Discord Leaderboard](https://trmnl.com/recipes/48539)
- [METAR](https://trmnl.com/recipes/21542)
- [MLB My Team Tracker](https://trmnl.com/recipes/51132)
- [MLB Season Standings](https://trmnl.com/recipes/273979)
- [MLB Standings](https://trmnl.com/recipes/274231)
- [MLB Team Dashboard](https://trmnl.com/recipes/272277)
- [MLB Team Scoreboard & Next Game](https://trmnl.com/recipes/294522)
- [MLS Standings by Conference](https://trmnl.com/recipes/324886)
- [MTA Station Train Times](https://trmnl.com/recipes/319820)
- [MTA Stringlines](https://trmnl.com/recipes/337953)
- [MTG Card of the Day](https://trmnl.com/recipes/127986)
- [MTG Daily Scry](https://trmnl.com/recipes/147727)
- [MTR Next Trains (Hong Kong)](https://trmnl.com/recipes/14045)
- [MVG - Munich Public Transport](https://trmnl.com/recipes/37373)
- [Magic 8 Ball](https://trmnl.com/recipes/22689)
- [Magic Draw](https://trmnl.com/recipes/89345)
- [Magic Hour](https://trmnl.com/recipes/329253)
- [Magic Plane of the Day](https://trmnl.com/recipes/129689)
- [Magic Price Tracker](https://trmnl.com/recipes/261552)
- [MagicList+](https://trmnl.com/integrations/magiclist)
- [Mailchimp](https://trmnl.com/integrations/mailchimp-analytics)
- [MakerWorld Trending Models](https://trmnl.com/recipes/272038)
- [Manchester Bikes](https://trmnl.com/recipes/127062)
- [Manga & Mawnha Tracker](https://trmnl.com/recipes/278680)
- [Manga of the Day](https://trmnl.com/recipes/66601)
- [March Madness Group Standings](https://trmnl.com/recipes/29987)
- [Mario Kart Track](https://trmnl.com/recipes/101176)
- [Marquee](https://trmnl.com/recipes/153733)
- [Marvel Character of the Day](https://trmnl.com/recipes/9379)
- [Mastodon](https://trmnl.com/recipes/89880)
- [Matomo](https://trmnl.com/recipes/307839)
- [Matomo Analytics](https://trmnl.com/recipes/97234)
- [Matrix](https://trmnl.com/recipes/16382)
- [Mawaqit](https://trmnl.com/recipes/105763)
- [Mawaqit](https://trmnl.com/integrations/mawaqit)
- [Max Payne Quotes](https://trmnl.com/recipes/224761)
- [Maze](https://trmnl.com/recipes/131035)
- [MealViewer (School Lunch)](https://trmnl.com/recipes/133879)
- [Mealie Shopping List](https://trmnl.com/recipes/159793)
- [Meesman Aandelen Wereldwijd (NL)](https://trmnl.com/recipes/188084)
- [Meh Deals](https://trmnl.com/recipes/262732)
- [Menu Pricelist](https://trmnl.com/recipes/135480)
- [Met.no Forecast](https://trmnl.com/recipes/132448)
- [Metal Price Tracker](https://trmnl.com/recipes/180898)
- [Meteoblue Weather](https://trmnl.com/integrations/meteoblue-weather)
- [Metro Nashville Police Dept. Active Dispatches](https://trmnl.com/recipes/90541)
- [Metro North Departures](https://trmnl.com/recipes/97591)
- [Metro North Trip Status](https://trmnl.com/recipes/225668)
- [Metro Transit](https://trmnl.com/recipes/95919)
- [MetroClock](https://trmnl.com/recipes/128829)
- [MetroDate](https://trmnl.com/recipes/128895)
- [Microsoft To Do](https://trmnl.com/integrations/microsoft-todo)
- [Mijn afvalwijzer](https://trmnl.com/recipes/200788)
- [Milestones - Event Countdown](https://trmnl.com/recipes/135996)
- [Minecraft Server](https://trmnl.com/recipes/175847)
- [Minecraft Server Statistics](https://trmnl.com/recipes/82497)
- [Minecraft Splashes](https://trmnl.com/recipes/245667)
- [Mini Muni San Francisco](https://trmnl.com/recipes/151924)
- [Miniflux Feed](https://trmnl.com/recipes/33478)
- [Mobiliteit.lu Checker](https://trmnl.com/recipes/129030)
- [Mod Analytics](https://trmnl.com/recipes/326235)
- [Modern Art of the Day](https://trmnl.com/recipes/184800)
- [Modrinth](https://trmnl.com/recipes/46866)
- [Mondrian](https://trmnl.com/integrations/mondrian)
- [Monero P2Pool Payouts](https://trmnl.com/recipes/337553)
- [Monkey Island Quotes](https://trmnl.com/recipes/106632)
- [MonkeyUser Comics](https://trmnl.com/recipes/230071)
- [Monkeytype](https://trmnl.com/recipes/184019)
- [Month Progress](https://trmnl.com/recipes/209701)
- [Monument of the day](https://trmnl.com/recipes/192148)
- [Motivational Quote](https://trmnl.com/integrations/motivational-quote)
- [MotoGP Drivers Standings](https://trmnl.com/recipes/372964)
- [MotoGPCal.com - MotoGP Calendar](https://trmnl.com/recipes/55415)
- [Movie Guesser](https://trmnl.com/recipes/178211)
- [Movie Quotes](https://trmnl.com/recipes/189976)
- [Movie Title Screens 🎬](https://trmnl.com/recipes/98891)
- [Mt. Baker Ski Area weather data](https://trmnl.com/recipes/267028)
- [Multi Date Countdown](https://trmnl.com/recipes/309987)
- [My Slack Messages](https://trmnl.com/recipes/128030)
- [Myers-Briggs Type](https://trmnl.com/recipes/272814)
- [N64 Title Screens](https://trmnl.com/recipes/223120)
- [NASA Image of the Day](https://trmnl.com/recipes/76645)
- [NASA Your Name In Landsat](https://trmnl.com/recipes/297167)
- [NASCAR Cup Series](https://trmnl.com/recipes/366969)
- [NBA Standings](https://trmnl.com/recipes/194456)
- [NBA Team Dashboard](https://trmnl.com/recipes/293160)
- [NBA Team Schedule](https://trmnl.com/recipes/224233)
- [NBL Ladder](https://trmnl.com/recipes/260913)
- [NCAA Men's Basketball Team Dashboard](https://trmnl.com/recipes/376118)
- [NES Title Screens 🕹️](https://trmnl.com/recipes/264734)
- [NFL Fantasy Pickems](https://trmnl.com/recipes/143537)
- [NFL Schedule](https://trmnl.com/recipes/142825)
- [NFL Standings By Division](https://trmnl.com/recipes/187644)
- [NFL Team Next Game](https://trmnl.com/recipes/161093)
- [NFL Team Tracker](https://trmnl.com/recipes/139791)
- [NHL - Next Edmonton Oilers Game](https://trmnl.com/recipes/32262)
- [NHL - Team's Next Game](https://trmnl.com/recipes/30044)
- [NHL Standings](https://trmnl.com/recipes/31670)
- [NL Energy Day Ahead Prices](https://trmnl.com/recipes/70055)
- [NMBS Departures](https://trmnl.com/recipes/22733)
- [NOAA Tides](https://trmnl.com/recipes/125365)
- [NOAA Tropical Weather Outlook (Hurricanes)](https://trmnl.com/recipes/178809)
- [NOS News Dutch headlines](https://trmnl.com/recipes/46612)
- [NPC for Open Legend RPG](https://trmnl.com/recipes/85403)
- [NPM Packages](https://trmnl.com/recipes/168898)
- [NPR News](https://trmnl.com/recipes/351866)
- [NRL Ladder](https://trmnl.com/recipes/260500)
- [NS Departures](https://trmnl.com/recipes/264343)
- [NS Disruptions](https://trmnl.com/recipes/55984)
- [NS Station vertrektijden](https://trmnl.com/recipes/127104)
- [NTS Radio: Live Now](https://trmnl.com/recipes/166082)
- [NWPS River Gauges](https://trmnl.com/recipes/101750)
- [NWS Radar (CONUS)](https://trmnl.com/recipes/123491)
- [NWS Severe Weather Alerts (USA)](https://trmnl.com/recipes/46722)
- [NYC 311](https://trmnl.com/recipes/87446)
- [NYC Ferry Departures](https://trmnl.com/recipes/106754)
- [NYC Metro Schedule](https://trmnl.com/integrations/nyc-metro-schedule)
- [NYC Subway Alerts](https://trmnl.com/recipes/81483)
- [NYC Subway Status](https://trmnl.com/recipes/130441)
- [NYT Games: Connections](https://trmnl.com/recipes/159089)
- [NYTimes Top Stories](https://trmnl.com/recipes/16262)
- [Nano Banana Dashboard](https://trmnl.com/integrations/nano-banana-dashboard)
- [Naolib - Départs à proximité](https://trmnl.com/recipes/256931)
- [Nashville Electric Service (NES)](https://trmnl.com/recipes/112378)
- [Nashville Fire Department Active Incidents](https://trmnl.com/recipes/89850)
- [Nashville Pollen & Air Quality](https://trmnl.com/recipes/93912)
- [Nashville Waste Collection](https://trmnl.com/recipes/240532)
- [Nashville WeGo Public Transit](https://trmnl.com/recipes/91940)
- [National Day Calendar](https://trmnl.com/recipes/9387)
- [National Rail Departures](https://trmnl.com/recipes/203604)
- [Naval Quotes](https://trmnl.com/recipes/250062)
- [Neocities Sites](https://trmnl.com/recipes/290436)
- [Netatmo Weather Station](https://trmnl.com/integrations/netatmo-weather-station)
- [Netflix Soon: Release Radar](https://trmnl.com/recipes/264056)
- [Network Devices v1.3](https://trmnl.com/recipes/189338)
- [New York Times Best Sellers](https://trmnl.com/recipes/121924)
- [Newgrounds](https://trmnl.com/recipes/29512)
- [Next Calendar Event/Room Reservation](https://trmnl.com/recipes/186929)
- [Next Game](https://trmnl.com/recipes/271063)
- [Next MLB Game](https://trmnl.com/recipes/57244)
- [Next Rocket Launch](https://trmnl.com/recipes/28575)
- [Nextcloud Calendar](https://trmnl.com/integrations/nextcloud-calendar)
- [Nextcloud Photos](https://trmnl.com/recipes/285470)
- [Night Sky](https://trmnl.com/recipes/290823)
- [Nightscout Glucose Trend](https://trmnl.com/recipes/30521)
- [Notable Local Bird Sightings (from eBird)](https://trmnl.com/recipes/124110)
- [Notifiarr](https://trmnl.com/recipes/122270)
- [Notion](https://trmnl.com/integrations/notion)
- [Nutrislice: School Lunch Menu](https://trmnl.com/recipes/299627)
- [OP3 Podcast Analytics](https://trmnl.com/recipes/153142)
- [OPDS Bookshelf](https://trmnl.com/recipes/122018)
- [ORF.at Newsfeed](https://trmnl.com/recipes/173288)
- [OSRS GE Price Trend](https://trmnl.com/recipes/265824)
- [OSRS HiScore Rank](https://trmnl.com/recipes/350066)
- [OSRS Hiscores](https://trmnl.com/recipes/209134)
- [Oblique Strategies](https://trmnl.com/recipes/17691)
- [Octopus Agile](https://trmnl.com/recipes/28219)
- [Off The Mark Comic](https://trmnl.com/recipes/183496)
- [Oil Price Tracker (WTI/Brent)](https://trmnl.com/recipes/375844)
- [Ollama](https://trmnl.com/recipes/193453)
- [Ollama AI Model Releases](https://trmnl.com/recipes/88231)
- [Olympics 2026: Medals per country](https://trmnl.com/recipes/241160)
- [Olympics 2026: Schedule](https://trmnl.com/recipes/241893)
- [One Album A Day](https://trmnl.com/recipes/154887)
- [One Forgotten Default](https://trmnl.com/recipes/227463)
- [One Headline News](https://trmnl.com/recipes/243967)
- [Only Good News](https://trmnl.com/integrations/only-good-news)
- [Open Library](https://trmnl.com/recipes/124200)
- [Open Trivia DB](https://trmnl.com/recipes/137500)
- [Open-Meteo Hourly Weather Forecast v1.6.0](https://trmnl.com/recipes/112422)
- [OpenWeatherMap Forecast](https://trmnl.com/recipes/41990)
- [Opening hours](https://trmnl.com/integrations/opening-hours)
- [Openrouter](https://trmnl.com/recipes/162051)
- [Outlook Calendar](https://trmnl.com/integrations/outlook-calendar)
- [PBS Kids](https://trmnl.com/recipes/110655)
- [PDC Darts: Order of Merit](https://trmnl.com/recipes/374447)
- [PGA Leaderboard](https://trmnl.com/recipes/279404)
- [POTUS Tracker](https://trmnl.com/recipes/29218)
- [PS2 Title Screens](https://trmnl.com/recipes/130437)
- [PSTMRK](https://trmnl.com/recipes/74331)
- [PagerDuty Incidents](https://trmnl.com/recipes/256937)
- [PagerDuty: On Call Today](https://trmnl.com/recipes/158498)
- [Paperboy - Hundreds of newspaper frontpages](https://trmnl.com/recipes/152705)
- [Parashat HaShavua (Torah Portion)](https://trmnl.com/recipes/110498)
- [Parcel](https://trmnl.com/integrations/parcel)
- [Park Buddies](https://trmnl.com/recipes/144856)
- [Parks Wait Time Display](https://trmnl.com/recipes/66418)
- [Paul's Recipe recipe](https://trmnl.com/recipes/236317)
- [Paymo Report](https://trmnl.com/recipes/77353)
- [Peanuts Begins | Comic Strip](https://trmnl.com/recipes/310000)
- [Peanuts Comics](https://trmnl.com/recipes/28496)
- [Pearls Before Swine | Comic Strip](https://trmnl.com/recipes/309763)
- [Pebble Appstore](https://trmnl.com/recipes/281436)
- [Pentagon Pizza Index](https://trmnl.com/recipes/218742)
- [Personal Steam Deals](https://trmnl.com/recipes/246965)
- [Pi-hole](https://trmnl.com/recipes/220520)
- [Pickles Comic](https://trmnl.com/recipes/122316)
- [Picture Guesser](https://trmnl.com/recipes/293405)
- [Pitchfork - Best New Albums](https://trmnl.com/recipes/84502)
- [Pitchfork - New Album Reviews](https://trmnl.com/recipes/231856)
- [Pitt Hour Clock](https://trmnl.com/recipes/250833)
- [Pixel Weather](https://trmnl.com/recipes/189394)
- [Pixelfed](https://trmnl.com/recipes/103958)
- [Pixelfed profile](https://trmnl.com/recipes/106505)
- [Place Facts](https://trmnl.com/integrations/place-facts)
- [Planet Finder](https://trmnl.com/recipes/348470)
- [Plant watering Schedule](https://trmnl.com/recipes/181032)
- [Planta Dashboard](https://trmnl.com/integrations/planta-dashboard)
- [Plausible](https://trmnl.com/recipes/158263)
- [PlayStation Title Screens](https://trmnl.com/recipes/222275)
- [Plex (Recently Played)](https://trmnl.com/recipes/107012)
- [Plex - Now Playing](https://trmnl.com/recipes/76059)
- [Poetry Today](https://trmnl.com/integrations/poetry-today)
- [Pokemon Cards](https://trmnl.com/recipes/297288)
- [Pokemon Go Updates](https://trmnl.com/recipes/211473)
- [Pokémon Team Analyzer](https://trmnl.com/recipes/380711)
- [Pollen Forecast (Europe)](https://trmnl.com/recipes/56803)
- [Pollen Forecast (US)](https://trmnl.com/integrations/pollen-forecast-us)
- [Pollen Information Austria](https://trmnl.com/recipes/255979)
- [Pollenradar (The Netherlands)](https://trmnl.com/recipes/31139)
- [Polymarket](https://trmnl.com/recipes/186483)
- [Polymarket](https://trmnl.com/integrations/polymarket)
- [Poorly Drawn Lines comic](https://trmnl.com/recipes/72396)
- [PostSecret](https://trmnl.com/recipes/31635)
- [Posthog Growth accounting](https://trmnl.com/recipes/112268)
- [Postmark](https://trmnl.com/integrations/postmark-analytics)
- [Potter Quotes](https://trmnl.com/recipes/258242)
- [Powerwall Energy Usage](https://trmnl.com/integrations/powerwall-energy-usage)
- [Pragmatic Programmer Tips](https://trmnl.com/recipes/145923)
- [Pregnancy Tracker](https://trmnl.com/recipes/249841)
- [Pretty Calendar](https://trmnl.com/recipes/210616)
- [Price Tags](https://trmnl.com/recipes/209398)
- [Private Plugin](https://trmnl.com/integrations/private-plugin)
- [Prochain Metro RER Paris](https://trmnl.com/recipes/38247)
- [Product Hunt](https://trmnl.com/integrations/product-hunt)
- [ProfitWell](https://trmnl.com/integrations/profit-well)
- [Progress Bar Calendar](https://trmnl.com/recipes/343035)
- [PromptInk](https://trmnl.com/recipes/221038)
- [Proton Calendar](https://trmnl.com/integrations/proton-calendar)
- [Public Recipe Stats](https://trmnl.com/recipes/170970)
- [Puget Sound Transit Nearby](https://trmnl.com/recipes/225183)
- [PurpleAir](https://trmnl.com/integrations/purpleair)
- [QR Code](https://trmnl.com/recipes/14428)
- [Quarter Progress](https://trmnl.com/recipes/212805)
- [Queensland Stop Timetable (Translink)](https://trmnl.com/recipes/233040)
- [Quoterism Random Quotes](https://trmnl.com/recipes/188221)
- [Quran Verses](https://trmnl.com/recipes/320310)
- [REENIO - booking system](https://trmnl.com/recipes/162137)
- [RMV Departures](https://trmnl.com/recipes/196089)
- [RSS Feed](https://trmnl.com/integrations/rss-feed)
- [RabbitAssets](https://trmnl.com/recipes/181797)
- [RabbitForex](https://trmnl.com/recipes/182766)
- [Rain Radar NL](https://trmnl.com/recipes/119118)
- [Raindrop - Hourly Rain Forecast (USA)](https://trmnl.com/recipes/35332)
- [Random Advice](https://trmnl.com/recipes/19328)
- [Random Board Game](https://trmnl.com/recipes/31666)
- [Random Coffee Cup](https://trmnl.com/recipes/82685)
- [Random Computer](https://trmnl.com/recipes/258479)
- [Random Cooking Recipes](https://trmnl.com/recipes/105468)
- [Random D&D Monster](https://trmnl.com/recipes/209012)
- [Random Duck](https://trmnl.com/recipes/58123)
- [Random Emoji](https://trmnl.com/recipes/84361)
- [Random Google Fonts](https://trmnl.com/recipes/163790)
- [Random Hardcover Quotes](https://trmnl.com/recipes/90802)
- [Random Image (Unsplash)](https://trmnl.com/recipes/23195)
- [Random Joke](https://trmnl.com/recipes/227561)
- [Random LEGO](https://trmnl.com/recipes/269418)
- [Random MDN Article](https://trmnl.com/recipes/153731)
- [Random Meal](https://trmnl.com/recipes/42499)
- [Random Meme](https://trmnl.com/recipes/236194)
- [Random Riddle](https://trmnl.com/recipes/22707)
- [Random Stock of the Day](https://trmnl.com/recipes/326338)
- [Random Toki Pona Word](https://trmnl.com/recipes/137228)
- [Random Unicode](https://trmnl.com/recipes/151050)
- [Random Zi](https://trmnl.com/recipes/156095)
- [Read It Later](https://trmnl.com/recipes/266399)
- [Readwise](https://trmnl.com/integrations/readwise)
- [Recent Earthquakes](https://trmnl.com/recipes/133626)
- [Recipe Leaderboard](https://trmnl.com/recipes/211002)
- [Recommend Me A Book](https://trmnl.com/recipes/297243)
- [Recurring Countdown (Payday Countdown)](https://trmnl.com/recipes/346244)
- [Recurring Items](https://trmnl.com/recipes/260208)
- [Recycle! - Belgium](https://trmnl.com/recipes/152152)
- [Reddit](https://trmnl.com/integrations/reddit)
- [Reddit Comics](https://trmnl.com/recipes/100794)
- [Reddit r/place](https://trmnl.com/recipes/182274)
- [Redirect](https://trmnl.com/integrations/redirect)
- [Relay for St Jude](https://trmnl.com/recipes/87177)
- [Retro Weather](https://trmnl.com/recipes/284828)
- [RetroAchievements: Recent Games](https://trmnl.com/recipes/176378)
- [RetroArchTRMNL](https://trmnl.com/recipes/374801)
- [RetroWall](https://trmnl.com/recipes/306354)
- [Rick and Morty](https://trmnl.com/recipes/227088)
- [Ride With GPS](https://trmnl.com/recipes/96060)
- [Robot Reflections](https://trmnl.com/recipes/178565)
- [RuneScape HiScore Rank](https://trmnl.com/recipes/346751)
- [Ruter / Entur Norway](https://trmnl.com/recipes/20164)
- [SAT GUS](https://trmnl.com/recipes/115686)
- [SBB Swiss Stationboard](https://trmnl.com/recipes/166116)
- [SBB/CFF Timetable](https://trmnl.com/recipes/32374)
- [SEQ Dam Levels](https://trmnl.com/recipes/263572)
- [SL - Stockholm Public Transport](https://trmnl.com/recipes/80473)
- [STRMNL](https://trmnl.com/integrations/strmnl)
- [SWU – Ulm/Neu-Ulm Public Transport](https://trmnl.com/recipes/52882)
- [Safely Endangered](https://trmnl.com/recipes/277420)
- [Salah Times](https://trmnl.com/integrations/salah-times)
- [Salesforce](https://trmnl.com/integrations/salesforce)
- [Sandwitches](https://trmnl.com/recipes/247547)
- [School Calendar for Zermelo](https://trmnl.com/recipes/238244)
- [Schwarzes Café](https://trmnl.com/recipes/337997)
- [Scooter finder](https://trmnl.com/recipes/136058)
- [Screensaver](https://trmnl.com/integrations/screensaver)
- [Screenshot](https://trmnl.com/integrations/screenshot)
- [SeatGeek](https://trmnl.com/recipes/120686)
- [Selected Nuclear Power Plant Outages](https://trmnl.com/recipes/280780)
- [Semaphore Flags](https://trmnl.com/recipes/249467)
- [Sensibo Home Temperature](https://trmnl.com/recipes/308167)
- [Sentry](https://trmnl.com/recipes/160968)
- [Sentry Issues](https://trmnl.com/recipes/159081)
- [Servarr Dashboard](https://trmnl.com/recipes/190336)
- [Shabbat Times](https://trmnl.com/recipes/115974)
- [Shakespeare Quotes](https://trmnl.com/recipes/369398)
- [Shan Shui Chinese Landscape](https://trmnl.com/recipes/14789)
- [Shodan - Random Image](https://trmnl.com/recipes/295402)
- [Shopify](https://trmnl.com/integrations/shopify)
- [Shopping List](https://trmnl.com/integrations/shopping-list)
- [Should I Deploy Today?](https://trmnl.com/recipes/29090)
- [Simple Analytics](https://trmnl.com/integrations/simple-analytics)
- [Simple Calendar](https://trmnl.com/recipes/11023)
- [Simple Date](https://trmnl.com/recipes/40143)
- [Simple Open Signage](https://trmnl.com/recipes/242183)
- [Simple Subscription Manager](https://trmnl.com/recipes/304437)
- [Simple To-do](https://trmnl.com/recipes/233168)
- [Simplenote Published Note](https://trmnl.com/recipes/236882)
- [Skeletor](https://trmnl.com/recipes/99970)
- [SkyWatch](https://trmnl.com/recipes/286469)
- [Sleeper One-on-One Matchups](https://trmnl.com/recipes/140718)
- [Sleeper: Matchups](https://trmnl.com/recipes/140922)
- [Sleeper: Standings](https://trmnl.com/recipes/141171)
- [Sleepy Time!](https://trmnl.com/recipes/190819)
- [Slickdeals Frontpage](https://trmnl.com/recipes/184875)
- [Small Seasons](https://trmnl.com/recipes/18876)
- [Snooker Scores](https://trmnl.com/recipes/198010)
- [Snow Report](https://trmnl.com/recipes/160226)
- [So Very British](https://trmnl.com/recipes/147175)
- [Soccer Matches](https://trmnl.com/recipes/275621)
- [Soccer Standings](https://trmnl.com/recipes/194354)
- [Social Media Followers](https://trmnl.com/recipes/199996)
- [Solana Wallet](https://trmnl.com/recipes/229540)
- [Solar Weather Activity](https://trmnl.com/recipes/190280)
- [Song of the Day - Random Song](https://trmnl.com/recipes/184682)
- [Sonos - Now Playing](https://trmnl.com/recipes/261848)
- [Sound Transit Link Light Rail](https://trmnl.com/recipes/48113)
- [Sound Transit Link Light Rail Dashboard](https://trmnl.com/recipes/179733)
- [Space Weather Prediction Center Alerts](https://trmnl.com/recipes/191916)
- [Spaceballs](https://trmnl.com/recipes/59980)
- [SparkFun: News](https://trmnl.com/recipes/179498)
- [Spelling Bee](https://trmnl.com/recipes/201905)
- [Spotify - Listening Stats](https://trmnl.com/recipes/164583)
- [Spotify Dashboard](https://trmnl.com/recipes/247528)
- [Spotify New Releases](https://trmnl.com/recipes/243523)
- [Spotify Stats](https://trmnl.com/recipes/245783)
- [Spotify Top Tracks](https://trmnl.com/integrations/spotify-top-tracks)
- [Spotify Weekly Rotation](https://trmnl.com/integrations/spotify-weekly-rotation)
- [Square POS](https://trmnl.com/integrations/square-pos)
- [St Albans Refuse Collections](https://trmnl.com/recipes/203404)
- [Stardew Valley Villagers](https://trmnl.com/recipes/160532)
- [Statuslanes](https://trmnl.com/recipes/181944)
- [Statuspage](https://trmnl.com/integrations/statuspage)
- [Steam Deals of the Day](https://trmnl.com/recipes/18131)
- [Steam Player Count](https://trmnl.com/recipes/181252)
- [Steam Top Sellers](https://trmnl.com/recipes/353722)
- [Steam recent Games](https://trmnl.com/recipes/234204)
- [Stock Portfolio](https://trmnl.com/recipes/237580)
- [Stock Price](https://trmnl.com/recipes/225680)
- [Stock Price](https://trmnl.com/integrations/stock-price)
- [StockChart](https://trmnl.com/recipes/353727)
- [Stoic Quotes](https://trmnl.com/recipes/19343)
- [String Theory](https://trmnl.com/recipes/374280)
- [Stripe](https://trmnl.com/recipes/89809)
- [Subbly](https://trmnl.com/recipes/5083)
- [Sudoku](https://trmnl.com/recipes/354989)
- [Sudoku of the day](https://trmnl.com/recipes/42282)
- [SunClock](https://trmnl.com/recipes/53263)
- [Sundial - 24hr clock](https://trmnl.com/recipes/202560)
- [Sunny 16](https://trmnl.com/recipes/23645)
- [Sunrise & Sunset](https://trmnl.com/recipes/217936)
- [Sunrise and Sunset](https://trmnl.com/recipes/29557)
- [Sunrise and Sunset Chart](https://trmnl.com/recipes/195274)
- [Super Mario Bros](https://trmnl.com/recipes/370586)
- [Super Netball Ladder](https://trmnl.com/recipes/320174)
- [Supercar Canvas](https://trmnl.com/recipes/385476)
- [Supercars Drivers](https://trmnl.com/recipes/260558)
- [Supercars Teams](https://trmnl.com/recipes/260533)
- [Sweep clock](https://trmnl.com/recipes/187386)
- [Swiss Transport Schedule](https://trmnl.com/recipes/26436)
- [Sydsvenskan Latest News](https://trmnl.com/recipes/176724)
- [TDOT SmartWay](https://trmnl.com/recipes/302448)
- [TFI Bus Departures](https://trmnl.com/recipes/361459)
- [TFL - Underground Line Status](https://trmnl.com/recipes/15955)
- [TMB Alerts](https://trmnl.com/recipes/247650)
- [TPLFVG](https://trmnl.com/recipes/53945)
- [TRMNL Battery](https://trmnl.com/recipes/190136)
- [TRMNL Battery Status](https://trmnl.com/recipes/29604)
- [TRMNL Blog](https://trmnl.com/recipes/313321)
- [TRMNL Bounties](https://trmnl.com/recipes/18156)
- [TRMNL Device Status](https://trmnl.com/recipes/195722)
- [TRMNL Framework Updates](https://trmnl.com/recipes/277426)
- [TRMNL Plugin Analytics](https://trmnl.com/recipes/286220)
- [TRMNL X Order Checker](https://trmnl.com/recipes/296981)
- [TTC Arrivals](https://trmnl.com/recipes/84336)
- [TV Quotes](https://trmnl.com/recipes/214847)
- [Tagesschau - Top News](https://trmnl.com/recipes/330325)
- [Tailscale](https://trmnl.com/recipes/108109)
- [Taiwan Power Generation](https://trmnl.com/recipes/107839)
- [Taiwan Reservoir Level](https://trmnl.com/recipes/94903)
- [Tatort](https://trmnl.com/recipes/261844)
- [Tautulli Now Playing](https://trmnl.com/recipes/117218)
- [Tear-off Calendar](https://trmnl.com/recipes/92229)
- [Tear-off Weather](https://trmnl.com/recipes/217346)
- [Techmeme Headlines](https://trmnl.com/recipes/18207)
- [Tedium: The Dull Side of the Internet](https://trmnl.com/recipes/303015)
- [Teehee - Jokes!](https://trmnl.com/recipes/265839)
- [Telescopius Highlights](https://trmnl.com/recipes/305596)
- [Temperature Graph](https://trmnl.com/recipes/141444)
- [Tempest Weather Station](https://trmnl.com/integrations/tempest-weather-station)
- [Tesla (via Teslamate)](https://trmnl.com/recipes/41258)
- [Tessie](https://trmnl.com/recipes/59557)
- [TfL Bus Stops - Realtime Bus Arrivals](https://trmnl.com/recipes/234101)
- [TfNSW Departures](https://trmnl.com/recipes/23278)
- [The Daily Sarcasm](https://trmnl.com/recipes/139729)
- [The Met Art Roulette](https://trmnl.com/recipes/363349)
- [The New York Times](https://trmnl.com/recipes/64780)
- [The Office](https://trmnl.com/integrations/the-office)
- [The Office Quotes: From the World's Best Boss](https://trmnl.com/recipes/298752)
- [The Onion - American Voices](https://trmnl.com/recipes/29537)
- [The Onion - Editorial Cartoon](https://trmnl.com/recipes/32609)
- [The Onion - Headlines](https://trmnl.com/recipes/29531)
- [The Rickies](https://trmnl.com/recipes/97896)
- [The Yetee Shirts of the Day](https://trmnl.com/recipes/83869)
- [Things To-Dos](https://trmnl.com/recipes/301969)
- [This Day in History](https://trmnl.com/recipes/9917)
- [This Week In Videogames](https://trmnl.com/recipes/336231)
- [Three Word Clock](https://trmnl.com/recipes/25196)
- [Tibber](https://trmnl.com/recipes/101453)
- [Tibber Dashboard For NL, DE, SE & NOR](https://trmnl.com/recipes/188591)
- [Tibber Norway](https://trmnl.com/recipes/135286)
- [TickTick](https://trmnl.com/integrations/ticktick)
- [Time Off Radar: Your Next Real Break](https://trmnl.com/recipes/266204)
- [Tiny Planets](https://trmnl.com/recipes/189748)
- [Today I Learned: Random Wikipedia](https://trmnl.com/recipes/276436)
- [Today in geek history](https://trmnl.com/recipes/184719)
- [Today's History in Panels](https://trmnl.com/recipes/184771)
- [Today's Mealie Meal Plan](https://trmnl.com/recipes/34578)
- [Today's Special](https://trmnl.com/recipes/95280)
- [Today: Beautiful Date & Countdown](https://trmnl.com/recipes/250879)
- [Today: Beautiful Weather](https://trmnl.com/recipes/270447)
- [Todo List](https://trmnl.com/integrations/todo-list)
- [Todoist](https://trmnl.com/integrations/todoist)
- [Token FOMO](https://trmnl.com/recipes/192381)
- [Token Meter](https://trmnl.com/integrations/token-meter)
- [Toki Pona Word of the Day](https://trmnl.com/recipes/36529)
- [Tokyo Metro Status](https://trmnl.com/recipes/176440)
- [Top Android Apps](https://trmnl.com/recipes/176578)
- [Top Cyber Threats Today](https://trmnl.com/recipes/259939)
- [Top Hacker News Story](https://trmnl.com/recipes/17494)
- [Top Severity CVEs Today](https://trmnl.com/recipes/153627)
- [Top iOS Apps](https://trmnl.com/recipes/176265)
- [Toronto Waste Collection](https://trmnl.com/recipes/102103)
- [Tour de France - Rankings & Jersey holders](https://trmnl.com/recipes/358042)
- [Tour de France Stages](https://trmnl.com/recipes/346857)
- [Trading212 Portfolio Viewer](https://trmnl.com/recipes/254858)
- [TradingView](https://trmnl.com/recipes/157210)
- [Trainboard](https://trmnl.com/integrations/trainboard)
- [Trakt.tv](https://trmnl.com/recipes/245666)
- [Trakt.tv](https://trmnl.com/integrations/trakt-tv)
- [TraktWastedTime](https://trmnl.com/recipes/252905)
- [Translink SEQ Network Status](https://trmnl.com/recipes/276612)
- [Transport Victoria](https://trmnl.com/integrations/transport-victoria)
- [Travel Time](https://trmnl.com/recipes/63494)
- [Trellis Dashboard](https://trmnl.com/recipes/133373)
- [Trello for Public Boards](https://trmnl.com/recipes/96319)
- [Trello for private boards](https://trmnl.com/integrations/trello-for-private-boards)
- [Trending Movie Trailers](https://trmnl.com/recipes/198072)
- [Trending Movies](https://trmnl.com/recipes/30888)
- [Trending Recipes](https://trmnl.com/recipes/241645)
- [Trending TV](https://trmnl.com/recipes/30916)
- [Triangles - Generated Art](https://trmnl.com/recipes/220720)
- [True or False?](https://trmnl.com/recipes/91747)
- [TrueNAS recommended versions](https://trmnl.com/recipes/261592)
- [Trump Quote](https://trmnl.com/recipes/113468)
- [Tsukuba Express Status](https://trmnl.com/recipes/179047)
- [Tweakers.net Dutch headlines unofficial (UPDATED)](https://trmnl.com/recipes/114675)
- [Twitch Dashboard](https://trmnl.com/recipes/243915)
- [Twonks Comics](https://trmnl.com/recipes/272414)
- [U.S. Gas Prices](https://trmnl.com/recipes/296696)
- [UK Bank Holidays](https://trmnl.com/recipes/311977)
- [UK Bus Departures (TransportAPI)](https://trmnl.com/recipes/221735)
- [UK Bus Departures (bustimes.org)](https://trmnl.com/recipes/222328)
- [UK Parliament Petitions](https://trmnl.com/recipes/228576)
- [UK River Levels: Live](https://trmnl.com/recipes/228203)
- [UK Tide Times](https://trmnl.com/recipes/113385)
- [UK Train Departures (via Realtime Trains)](https://trmnl.com/recipes/38770)
- [UK Trains](https://trmnl.com/recipes/232789)
- [US Citizenship Test](https://trmnl.com/recipes/367757)
- [US Debt](https://trmnl.com/recipes/56095)
- [US EV Fast Charger List (Recently Opened)](https://trmnl.com/recipes/342579)
- [US EV Fast Charger Stats](https://trmnl.com/recipes/328301)
- [US Stream Gauges](https://trmnl.com/recipes/198194)
- [US Weather Maps](https://trmnl.com/recipes/154046)
- [USGS Elevated Volcanoes](https://trmnl.com/recipes/178876)
- [USPTO New Trademarks](https://trmnl.com/recipes/264269)
- [Ultrahuman Ring](https://trmnl.com/recipes/260137)
- [Un-stats](https://trmnl.com/recipes/246002)
- [Unifi Dashboard](https://trmnl.com/recipes/292357)
- [Unifi Site Manager](https://trmnl.com/recipes/102852)
- [Unsplash](https://trmnl.com/integrations/unsplash)
- [Upcoming Holidays (for specific country)](https://trmnl.com/recipes/91928)
- [Upcoming Holidays Worldwide](https://trmnl.com/recipes/13875)
- [Upcoming Movies](https://trmnl.com/integrations/upcoming-movies)
- [Upcoming Tides (NOAA)](https://trmnl.com/recipes/98475)
- [Updown.io](https://trmnl.com/recipes/94944)
- [Uptime Kuma Statuspage](https://trmnl.com/recipes/159393)
- [UptimeRobot](https://trmnl.com/recipes/98765)
- [Urban Dictionary](https://trmnl.com/recipes/142466)
- [Urban Dictionary Top Word](https://trmnl.com/recipes/90146)
- [Useless Facts](https://trmnl.com/recipes/28722)
- [Velo Station Info Antwerpen](https://trmnl.com/recipes/151444)
- [Victron Power & Tanks](https://trmnl.com/recipes/361655)
- [Vienna Metro Departures (Wiener Linien)](https://trmnl.com/recipes/98534)
- [Vienna Public Transport Disruptions](https://trmnl.com/recipes/250757)
- [Visited Countries](https://trmnl.com/recipes/345771)
- [Visual Calendar](https://trmnl.com/recipes/218751)
- [Visual Child Clock](https://trmnl.com/recipes/90656)
- [Vlaamse Moppen](https://trmnl.com/recipes/281174)
- [Vocaloid Lyrics Quote](https://trmnl.com/recipes/150439)
- [Vox](https://trmnl.com/integrations/vox)
- [WNBA Team Dashboard](https://trmnl.com/recipes/376119)
- [Waifus](https://trmnl.com/recipes/246126)
- [WakaTime Dashboard](https://trmnl.com/recipes/123718)
- [WakaTime Stats](https://trmnl.com/recipes/241536)
- [War and Peas Comic](https://trmnl.com/recipes/213996)
- [Washington DC OPM Status](https://trmnl.com/recipes/203231)
- [Washington Real-time Travel Times, Cameras, Alerts](https://trmnl.com/recipes/227171)
- [Washington State Ferries](https://trmnl.com/recipes/299929)
- [Wassertemperatur Zürichsee](https://trmnl.com/recipes/201589)
- [Watchtower - Generated landscapes](https://trmnl.com/recipes/189444)
- [Wear Today](https://trmnl.com/recipes/316410)
- [Weather](https://trmnl.com/integrations/weather)
- [Weather BYOS (Open Meteo)](https://trmnl.com/recipes/221669)
- [Weather Chum](https://trmnl.com/recipes/49610)
- [Weather Circles](https://trmnl.com/recipes/341571)
- [Weather Dashboard](https://trmnl.com/recipes/191027)
- [Weather Glance](https://trmnl.com/recipes/181200)
- [Weather Underground](https://trmnl.com/integrations/weather-underground)
- [Weather XL](https://trmnl.com/recipes/194006)
- [Webhook Image](https://trmnl.com/integrations/webhook-image)
- [Webmentions](https://trmnl.com/recipes/203096)
- [Week Number](https://trmnl.com/recipes/216687)
- [Week Number](https://trmnl.com/integrations/week-number)
- [Weekend Countdown](https://trmnl.com/recipes/191614)
- [Weekend Movie Picker](https://trmnl.com/recipes/309303)
- [Weekly Calestenics](https://trmnl.com/recipes/113536)
- [Weekly Meal Planner](https://trmnl.com/recipes/30089)
- [Weeks of the Year](https://trmnl.com/recipes/209377)
- [Weird Patents](https://trmnl.com/recipes/232425)
- [What Country?](https://trmnl.com/recipes/234834)
- [What The Commit](https://trmnl.com/recipes/316017)
- [What to Wear Weather Forecast](https://trmnl.com/recipes/182853)
- [WhatPulse](https://trmnl.com/recipes/218535)
- [When is the next Apple Keynote?](https://trmnl.com/recipes/287989)
- [Who's That Pokémon?](https://trmnl.com/recipes/27251)
- [Wi-Fi/Wifi QR Code](https://trmnl.com/recipes/202908)
- [Wifi Signal Strength](https://trmnl.com/recipes/191294)
- [Wikipedia Articles](https://trmnl.com/integrations/wiki-random-article)
- [Wikipedia Feeds](https://trmnl.com/recipes/148054)
- [WildFires Tracker](https://trmnl.com/recipes/210345)
- [Wildlife Observations by iNaturalist](https://trmnl.com/recipes/290545)
- [Wildlife Sightings by Africam](https://trmnl.com/recipes/184141)
- [Wind Forecast](https://trmnl.com/recipes/345223)
- [Window Season](https://trmnl.com/recipes/304759)
- [Wingspan](https://trmnl.com/recipes/239594)
- [Winnie the Pooh Quotes](https://trmnl.com/recipes/23139)
- [Wisdom Project Quotes](https://trmnl.com/recipes/100671)
- [Wisdom from the Bible](https://trmnl.com/recipes/19339)
- [Withings](https://trmnl.com/integrations/withings)
- [WoW Character Dashboard](https://trmnl.com/recipes/377601)
- [Word Clock](https://trmnl.com/recipes/19925)
- [Word Clock Deutsch](https://trmnl.com/recipes/25205)
- [Word Clock English](https://trmnl.com/recipes/25190)
- [Word Clock Français](https://trmnl.com/recipes/25204)
- [Word Clock 中文](https://trmnl.com/recipes/187631)
- [Word Clock 日本語](https://trmnl.com/recipes/187661)
- [Word Search](https://trmnl.com/recipes/189996)
- [Word of the Day (8 languages)](https://trmnl.com/recipes/378295)
- [Word of the Day (Eng)](https://trmnl.com/recipes/33259)
- [Word of the Day (Intl)](https://trmnl.com/recipes/33136)
- [Work Chronicles Comic](https://trmnl.com/recipes/33461)
- [Work Nudge](https://trmnl.com/recipes/160878)
- [Workflowy](https://trmnl.com/integrations/workflowy)
- [World Clock](https://trmnl.com/recipes/27940)
- [World Clock](https://trmnl.com/recipes/66588)
- [World Cup 2026](https://trmnl.com/recipes/315593)
- [World Cup 2026 Bracket](https://trmnl.com/recipes/357839)
- [World Cup 26 Groups](https://trmnl.com/recipes/280421)
- [World Heritage Site of the Day](https://trmnl.com/recipes/260498)
- [World Population](https://trmnl.com/recipes/239154)
- [Wttr.in Weather](https://trmnl.com/recipes/149512)
- [XKCD](https://trmnl.com/recipes/18267)
- [XKCD Comic (with hover text)](https://trmnl.com/recipes/230540)
- [XWeather](https://trmnl.com/recipes/138269)
- [Year of the Day](https://trmnl.com/recipes/148248)
- [Yearly Cycle Clock](https://trmnl.com/recipes/219958)
- [Ynet Breaking News](https://trmnl.com/recipes/55524)
- [Yoda Says](https://trmnl.com/recipes/165968)
- [YouTube](https://trmnl.com/integrations/youtube-analytics)
- [YouTube Channel's Latest Video](https://trmnl.com/recipes/149148)
- [YouTube Creator Award](https://trmnl.com/recipes/198498)
- [Your Life in Weeks](https://trmnl.com/recipes/22415)
- [Youtube - Recent Uploads](https://trmnl.com/recipes/30043)
- [Zen Quotes](https://trmnl.com/recipes/19353)
- [Zmanim](https://trmnl.com/recipes/122757)
- [air-Q](https://trmnl.com/integrations/air-q)
- [badapple](https://trmnl.com/recipes/60000)
- [beehiiv](https://trmnl.com/integrations/beehiiv)
- [info.sh](https://trmnl.com/recipes/45647)
- [oghunt](https://trmnl.com/integrations/oghunt)
- [pISS Tank Watch](https://trmnl.com/recipes/215157)
- [poste](https://trmnl.com/integrations/poste)
- [¿Hay ahora fútbol?](https://trmnl.com/recipes/294074)
- [ÜSTRA Status: Aufzüge & Rolltreppen](https://trmnl.com/recipes/221170)
---
# Apple Photos
Connecting Apple Photos to TRMNL takes just a few moments and will not expose any pictures or data except without your explicit permission.
## **Step 1 - Visit the Apple Photos Plugin**
Inside TRMNL, navigate to Plugins > Apple Photos.
## **Step 2 - Connect Apple Photos**
This is easiest from the Apple Photos desktop application (requires a Mac computer), but is possible from a phone as well.
First, navigate to the Sharing section on the left side of the application.
Click the '+' symbol and create a Shared album, or skip below if you already have a Shared album you'd like to connect with TRMNL. Give your album a name.
Next, navigate to the top right of your album's content and click the icon with a generic avatar.
Check the "Public Website" box to enable URL-based sharing.
You may need to wait a few seconds for this album to be shared, then you can navigate back to the left-margin "Sharing" section, right-click (ctrl+click) the album name, and click Visit Website.
Finally, copy the resulting web page to your clipboard.
## **Step 3 - Add album to TRMNL**
Back inside TRMNL > Plugins > Apple Photos, paste this album URL and select your image handling settings.
## **Step 4 - You're Done!**
Based on your chosen refresh settings, you'll begin seeing photos from Apple very soon. Stay focused.
## **Troubleshooting**
If you provide an unrecognized URL, you may receive an erroring screen which says "*Please setup your album.*"
If you provide a public-friendly URL that allows others to view your photos, but it is not a "Shared" album (accessible within the Sharing section), you will receive an erroring screen which says "*That link isn't a shared album, enable Public Website for the album.*"
---
# Serverless
We welcome advanced community plugins. But sometimes they require authors to host or maintain their own middleman services.
Using TRMNL Serverless you can achieve the flexibility of 3rd party tools (e.g. Cloudflare Workers) without spinning up services or paying for subscriptions.
*The legacy version of this feature is called [Transformer](https://help.trmnl.com/en/articles/12996946-parsing-plugins-with-the-sandbox-runtime), and it will be replaced entirely by Serverless in the near future.*
## Quickstart
1. Visit [Plugins > Private Plugins > New](https://trmnl.com/plugin_settings/new?keyname=private_plugin), provide a name and click Save
2. Click Edit Markup, then click the Serverless tab
3. Select a programming language and build whatever you want*
**network access is included!*
Code provided in the Serverless text editor must include a `run` function that returns a hash / dictionary / JSON friendly object. For example...
**Ruby**
Includes `httparty` gem for network requests. Must `require` in your code.
require 'httparty'
def quotes
HTTParty.get('https://dummyjson.com/quotes?limit=10')['quotes']
end
def run(input)
{ input:, quotes: }
end
**Node**
Includes `fetch()` module for network requests.
async function run(input) {
const res = await fetch("https://dummyjson.com/quotes?limit=1");
const data = await res.json();
return { quote: data.quotes[0] };
}
**PHP**
Includes standard curl extension for network requests.
function run($input) {
$ch = curl_init("https://dummyjson.com/quotes?limit=1");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
unset($ch);
return ["quote" => $data["quotes"][0]];
}
**Python**
Includes `Requests` library for network request. Must `import` in your code.
import requests
def run(input):
data = requests.get("https://dummyjson.com/quotes?limit=1").json()
return {"quote": data["quotes"][0]}
## Accessing existing variables
Global variables like `#{{ trmnl }}` , as well as [custom form field](https://help.trmnl.com/en/articles/10513740-custom-plugin-form-builder) values, are available inside the parameter of your `run` function. We suggest `input` but it may be whatever you prefer.
## Frequently Asked Questions
*Is this supported by webhooks?*
No, private plugins with the "Webhook" strategy bypass the Serverless function. We're considering adding support for this in the near future.
*Can I refactor my existing (published) plugins to use this feature?*
Please don't, at least not until April 2026. Because this feature is in private beta, downstream installations won't have access to the runtime.
*Can I install additional packages into this VM?*
Yes, but it's currently a manual process. Send us a live chat or email with a particular package / gem / library you might need, and we can add it to the default image for your preferred language. For example, the Ruby image already has `HTTParty` included for simple network requests.
*What are the specs on this runtime?*
You get 128mb and 5 seconds to do your business.
## Troubleshooting
We strongly encourage you to enable [Debug Logs](https://help.trmnl.com/en/articles/11586187-debugging-private-plugins) from your private plugin settings page while developing Serverless plugins. This will return native errors, for example:
let data = await fetch('https://dummyjson.com/quotes?limit=5')
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
---
# Custom plugin form builder
If you're new to building plugins, see [Private Plugins](https://intercom.help/trmnl/en/articles/9510536-private-plugins) and [Public Plugins API Docs](https://docs.trmnl.com/go/plugin-marketplace/introduction).
*The form builder enables your plugin to collect information and preferences that enable functionality or adjust functionality of the plugin. **This is most valuable when you want to reuse the same private plugin or you want to publish it in the future for others to use.** Some common examples include: API key, latitude and longitude, layout preferences, and "selection" preference (e.g. select sports team).*
Below is a guide to adding user-facing form fields inside a Private Plugin or [Recipe](https://intercom.help/trmnl/en/articles/10122094-plugin-recipes)**.**
Prefer to build it with web forms? Try out our [visual form builder](https://usetrmnl.github.io/trmnl-form-builder/) to generate the YAML you need.
# How it works
**Step 1 - Plugin author (you) creates custom fields**
**Step 2 - Plugin user (including you) can interact with rendered fields**
**Step 3 - Provided values are accessible inside the Markup Editor**
#{{ trmnl.plugin_settings.custom_fields_values }}
# => { "password": "secret", "lookback_period": "7" }
These values are also accessible from the settings themselves, for example this field:
- keyname: api_key
field_type: string
name: API Key
description: Find this at my-server.com/settings
Can be leveraged in a Polling Header or Polling URL with just the `keyname`:
https://my-server.com/api/stats?access_token=#{{ api_key }}
For real-life examples, fork a few published Recipes:
# Supported field types
Where possible, TRMNL follows [HTML5 input types](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/HTML5_input_types) to leverage native browser validations and UX features. Our current selection includes:
- author_bio
- url
- string
- multi_string (store a string with multiple comma separated values)
- text (textarea)
- code (like textarea, but monospace font)
- number
- password
- date (M Y D)
- time (HH:SS)
- select, xhrSelect (single or multi)
- xhrSelectSearch
- plugin_instance_select
- time_zone
- copyable (produces 'click to copy' button)
- copyable_webhook_url (produces 'click to copy' with a plugin's UUID)
- boolean
- lat_lon
# Anatomy of a form field
The following key/value pairs are available to all field types:
- `keyname` - parameterized identifier, not user-facing, ex `user_email`
- `field_type` - one of the above "type" values
- `name` - field label
- `description` - secondary field label, HTML-friendly
- `description-LOCALE` - provides option to localize a `description` field in 1 or more [supported locales](https://github.com/usetrmnl/trmnl-i18n/tree/main/lib/trmnl/i18n/locales/web_ui) (ex: `de`, `es-ES`, `ja`, etc)
- `help_text` - tertiary field label, HTML-friendly (optional)
- `optional` - boolean; unless set to true, `required` will be added to HTML input (omit if field should be required)
- `default` - value if input is left blank; must be a lowercase, parameterized version of one of the provided `options` values or will not be pre-chosen on `select` field type. example: "Upcoming Movies" option => `upcoming_movies` as `default`
- `group` - used to combine multiple fields into a dropdown accordion for visual organization
The following key/value pairs are available for *some* field types:
- `rows` - number of visible rows on a `text` or `code` field
- `options` - collection of option values for the `select` field
- `value` - string to be added to a user's clipboard via `copyable` field
- `min` - minimum integer allowed in a `number` field
- `max` - maximum integer allowed in a `number` field
- `step` - allowed decimal place for a `number` field
- `maxlength` - maximum number of characters allowed in a `string` or `text` field
- `placeholder` - example value if input is cleared; supported by `url`, `string`, `text`, `number`, `code`
- `endpoint` - URL from which to return options that populate an `xhrSelect` or `xhrSelectSearch` field
- `http_verb` - HTTP method (GET, POST, etc) with which to make requests to `xhrSelect` and `xhrSelectSearch` fields; defaults to `POST`
When building a form field, only the following values are required:
- keyname, name, field_type
# Building form fields
Below are examples of every supported field type, which you may modify with optional parameters described above.
📌 **Important**
As the form field definition is YAML based, every field must be indented by 2 spaces, with the first line denoted with a leading hyphen (`-`), also called a [block sequence](https://www.tutorialspoint.com/yaml/yaml_collections_and_structures.htm).
## author_bio
This field operates like a README. It appears below your plugin's preview image on logged out + logged in pages. Here you can provide a description, verbose installation instructions, and more.
This field type also has specialized properties, rendered as clickable icons on your plugin as opposed to plain text links.
Optional special properties include:
- `email_address` (if you want users to contact you)
- `category` (up to 2x, comma-separated from approved list below)
- `github_url` (can point to your own profile or source code)
- `learn_more_url` (feel free to provide your own website, etc)
- `youtube_url` (if you made a demo video)
- keyname: doesnt_matter
name: About This Plugin
category: life,news
field_type: author_bio
description: Dad Jokes Daily™ was created by Abraham, father to many.
github_url: https://github.com/father-abraham
learn_more_url: https://www.bible.com/
email_address: fatherabe@hotmail.net
youtube_url: https://www.youtube.com/@useTRMNL
Categories increase your visibility in search results. Categories currently include `analytics`, `art`, `calendar`, `comics`, `crm`, `custom`, `discovery`, `ecommerce`, `education`, `email`, `entertainment`, `environment`, `finance`, `games`, `humor`, `images`, `kpi`, `life`, `marketing`, `nature`, `news`, `personal`, `productivity`, `programming`, `sales`, `sports`, `travel`. For an up-to-date list of categories, just poll the following URL from our Public API ([docs](https://trmnl.com/api-docs)):
https://trmnl.com/api/categories
**Note**: Recipe `author_bio` values are cached for ~2 hours. If you update a plugin's category, it will take up to 2 hours for it to appear in search results.
## url
- keyname: url
field_type: url
name: Web Address
description: URL of RSS Feed
placeholder: https://www.site.com/rss
help_text: Please include http:// or https://
## string
- keyname: api_key
field_type: string
name: API Key
description: Account API Key
placeholder: sa_api_key_qwerty
With maxlength attribute:
- keyname: nickname
field_type: string
name: Nickname
description: Shown in the corner of your screen.
maxlength: 12
placeholder: jimbob
help_text: Can be up to 12 characters long.
## multi_string
- keyname: device_ids
field_type: multi_string
name: Device Id(s)
description: List of device ids to fetch data from
placeholder: id_xxx
## text (textarea)
- keyname: prompt
field_type: text
name: Prompt
description: Enter your prompt for Chat GPT to respond.
placeholder: Tell me a Dad Joke under 200 words.
With multiple description translations:
- keyname: prompt
field_type: text
name: Prompt
description: Enter your prompt.
description-es-ES: Ingrese su mensaje
description-ko: 프롬프트를 입력하세요
placeholder: Tell me a Dad Joke under 200 words.
## number
- keyname: retirement_age
field_type: number
placeholder: 65
name: Retirement Age
description: At what age do you plan to retire?
With decimals allowed:
- keyname: height_in_feet
field_type: number
placeholder: 5
step: 0.1
name: Height
With minimum and maximum allowed values:
- keyname: posts_to_show
field_type: number
placeholder: 3
min: 1
max: 5
name: Number of Posts
description: How many stories would you like to see?
help_text: Maximum 5.
## password
- keyname: password
field_type: password
name: Password
description: Mobile app login password
placeholder: s3cret!
## code
- keyname: json_query
field_type: code
rows: 8
name: JSON Query
description: Provide a raw query
placeholder: |
{ "cannot": "spare a square" }
help_text: Learn how to do this here.
## date
- keyname: start_date
field_type: date
default: "2025-07-04" # YYYY-MM-DD; must be wrapped in quotes
name: Start Date
description: Provide your start date
When using the date field, the rendered form will display a date picker. However, the value inside the template object `#{{ trmnl.plugin_settings.custom_fields_values }}` will be in the `YYYY-MM-DD` format.
**💡 Hint**
You may also leverage `default: "today"` to pre-populate the current day.
## time
- keyname: scroll_time
field_type: time
name: Fixed start time?
description: By default, the week's earliest event time will be used.
help_text: Optional. Applies to week view only.
## select
**📌 Important**
If you'd like to use the options "Yes" and "No" in your field, and you'd like to set the default option to be "No," you must wrap the default in quotes as "no" or it will be considered truthy.
**Example:**
- keyname: include_description
field_type: select
name: Include Description
description: Select up to 3 statuses
options:
- "Yes"
- "No"
default: "no" # unless wrapped in quotes, value will be true
**select** (single, unified label/value option tags)
- keyname: filter_by
field_type: select
options:
- Upcoming # value will be 'upcoming'
- Now Playing # value will be 'now_playing'
default: now_playing
name: Filter By
description: Select a movie type
**select** (single, with separate label/value option tags)
- keyname: lookback_period
field_type: select
options:
- One Week: 7
- Two weeks: 14
default: 14 # should be set to the value, not the label
name: Lookback Period
**select** (multi)
- keyname: sales_pipeline
field_type: select
name: Pipeline Statuses
description: Select up to 3 statuses
options:
- 'Discovery'
- 'Pending'
- 'Closed-lost'
- 'Closed-won'
multiple: true
**💡 Hint**
When setting `multiple: true` , it's helpful to clarify how this works to end-users. Here is some recommended `help_text` and how it renders:
Use ⌘+click or ctrl+click to select and deselect multiple options.
## xhrSelect
- keyname: team
name: Baseball Team
field_type: xhrSelect
endpoint: https://trmnl.com/custom_plugin_example_xhr_select.json
The URL assigned to the `endpoint` key should respond to POST requests and return an array of options. For example:
# key/value, where key is user-facing label and value is stored in db
[{ 'Braves' => 123 }, { 'Yankees' => 456 }]
**ℹ️ Note**
The provided endpoint will be sent a POST request by default. Set `http_verb: ` to change it. Payload will include sibling settings (default + custom fields), which may be useful for providing a conditional dropdown user experience. You may also wish to disable the `X-CSRF-Token` header. To do so, set `exclude_csrf_token: true`.
### depends_on
xhrSelect fields have a special property available only to them called `depends_on` that creates chained dropdowns—where a child field's options depend on the parent field's selected value.
- keyname: team
name: Baseball Team
field_type: xhrSelect
endpoint: https://trmnl.com/custom_plugin_example_xhr_select.json
- keyname: player
name: Player
field_type: xhrSelect
depends_on: team
endpoint: https://trmnl.com/custom_plugin_example_xhr_select_#{{team}}.json
When the user selects "Yankees" (value: 456), the player endpoint becomes: .
Behavior:
- The child field shows "Select Team first" until a parent value is selected
- When the parent changes, the child automatically refreshes
- The parent field must appear before the child in your YAML
There are two ways to access the parent value:
1. URL Interpolation: Use #{{keyname}} in your endpoint URL. Ideal for static JSON files or third-party APIs:
1.
- keyname: player
depends_on: team
endpoint: https://example.com/teams/#{{team}}/players.json
2. Request Body: The parent's value is also included in the POST body as a sibling setting. Ideal when your backend needs to combine the parent value with stored credentials or make authenticated API calls:
1.
{
"function": "player",
"plugin_setting": {
"settings_custom_fields_values_team": 456,
...
}
}
ℹ️ **Note**
The key format is `settings_custom_fields_values_ + the parent's keyname`. So if your parent field has `keyname: team`, look for settings_custom_fields_values_team in the request body.
Your backend can read `params[:plugin_setting][:settings_team]` to fetch players for that team using your own API credentials.
💡 **Hint**
Both methods work simultaneously. You can interpolate the URL AND read from the body in the same request.
## xhrSelectSearch
An extension of `xhrSelect`, this field includes a dynamic search input and is a better choice for dropdowns with several options.
- keyname: project
name: Project
field_type: xhrSelectSearch
endpoint: https://trmnl.com/custom_plugin_example_xhr_select_search.json
ℹ️ **Note**
The provided endpoint will be sent a POST request by default. Set `http_verb: ` to change it. POST sends the full settings payload (default + custom fields) as JSON, with the search query inside a `query` body parameter. If you set `http_verb` to GET, only `function` and `query` are sent as URL parameters — sibling settings are left out of the query string so they never end up in your endpoint's access logs.
Example responses from our test endpoint:
# https://trmnl.com/custom_plugin_example_xhr_select_search.json
# query=null
[
{ 'id' => 'db-123', 'name' => 'Project Tasks' },
{ 'id' => 'db-456', 'name' => 'Team Goals' },
{ 'id' => 'db-789', 'name' => 'Family Goals' }
]
# query=goa
[
{ 'id' => 'db-456', 'name' => 'Team Goals' },
{ 'id' => 'db-789', 'name' => 'Family Goals' }
]
You may also wish to disable the `X-CSRF-Token` header. To do so, set `exclude_csrf_token: true`.
## plugin_instance_select
This field type creates a dropdown populated with the user's active plugin instances.
It enables templates using plugin_merge strategy to reference plugins via form field keynames (`#{{ my_weather.temp }}`) instead of hardcoded IDs (`#{{ weather_42.temp }}`). This is particularly useful for recipes where the actual plugin instance isn't known at template creation time.
ℹ️ **Note**
- Requires the recipe to be forked.
- Only plugins that are currently in a playlist or mashup are shown in the dropdown.
- This only works for the "plugin_merge" strategy - [learn more](https://docs.trmnl.com/go/private-api/plugin-data).
- keyname: weather_source
field_type: plugin_instance_select
name: Weather Data Source
plugin_keyname: weather
description: Select which Weather plugin instance to pull data from
help_text: Only your active Weather plugins are shown.
The selected value will be in the format `#{{ weather_source }}`
Accessing data:
Temperature: #{{ weather_source.temperature }}°
Humidity: #{{ weather_source.humitidy }}
## time_zone
- keyname: time_zone
field_type: time_zone
name: Time Zone
description: Where are you located?
## copyable
- keyname: shared_api_key
field_type: copyable
name: API Token
value: qwerty-fdsa-1234
description: Paste this into your Vandelay Industries account.
## copyable_webhook_url
- keyname: webhook_url
field_type: copyable_webhook_url
name: Webhook URL
description: Paste this into your Apple Shortcut
help_text: This will populate once the plugin has been created/saved.
## boolean
*Value should either be* `true` *or* `false`
- keyname: show_timestamps
field_type: boolean
default: true
name: Timestamps Enabled?
description: Shows a timestamp in the bottom corner.
## lat_lon
Lets user search any city, address, or postal code and pick a match from the autocomplete dropdown. TRMNL converts the friendly location to comma-separated `lat,lon` coordinates. User may also enter coordinates directly (e.g. 33.7490,-84.3880).
- keyname: lat_lon
field_type: lat_lon
name: Location
description: Search for a city or postal code, or provide a comma-separated lat/long.
**TIP**
To parse this in your Polling URL, for example, you can use Liquid `first` and `last` filters:
https://yoururl.com/?lat=#{{ lat_lon | split: ',' | first }}&lon=#{{ lat_lon | split: ',' | last }}
# Groups
If your plugin has several custom fields, you may want to separate them visually with groups. Below is how our calendar plugins organize options by type:
When clicked, each heading expands to show a handful of form fields. Encapsulate fields inside these accordions with the `group` parameter, for example:
- keyname: include_past_events
name: Include past events?
group: "Event Display"
field_type: select
options:
- 'Yes'
- 'No'
default: 'no'
# Conditional field validations
As described above, all fields are `required` unless `optional: true` is included in the field's YAML definition. Further, all fields are visible.
But there may be cases where Field B should only be required or visible if Field A has a given value, and vice versa. For this scenario we offer conditional validations.
To get started, attach `conditional_validation` to the (parent) field whose value (or lack thereof) should dictate the behavior of one or more other fields. Then nest one or more of the strategies below.
## Conditional visibility
**💡Use Case**
Show field B unless field A's value is `xyz`, but show field C (and hide field B) if field A's value is `abc`. Any combination is possible, including the inverse (hide Field B *unless* Field A's value is `abc`).
Here's an example from our native architecture, powering the Private Plugin editor with the `hidden` flag.
- keyname: strategy
field_type: select
options:
- Polling
- Webhook
- Static
default: polling
conditional_validation:
- when: polling
hidden:
- static_data
- webhook_url
- when: webhook
hidden:
- polling_url
- polling_verb
- polling_headers
- polling_body
- static_data
- when: static
hidden:
- polling_url
- polling_verb
- polling_headers
- polling_body
- webhook_url
The logic above will show or hide several fields based on the selected value of the "Strategy" dropdown.
**ℹ️ Note**
The property `when: xxx` means, "when this field has value `xxx`". Thus, when the Polling dropdown value is set to `webhook`, 4x `polling_*` fields are hidden in addition to the `static_data` field.
You can alternatively use `visible:` when a field should be hidden by *default* and revealed only for specific values of the controlling field. It is the mirror of `hidden:` — where `hidden:` lists fields to remove for a value, `visible:` lists fields to reveal for a value (and keeps them hidden for every other value).
This is ideal when a `select` has many options but only one (or a few) should expose an extra field — you write one block instead of listing the field under every other option.
- keyname: layout
field_type: select
options:
- Default
- ...many options...
- Advanced
conditional_validation:
- when: advanced
visible:
- advanced_settings_field
Here `advanced_settings_field` is hidden for every layout except `advanced`. To reveal a field for several values, add a `when` block per value, each listing the field under `visible:`.
Be sure to set `optional: true` on the conditionally *visible* field, otherwise it will silently fail the client side "required" check when the form is submitted and the field is in a hidden state.
**Note**: a field should use either `hidden:` or `visible:`, not both. If the same field appears in both, `visible:` wins (the field is treated as hidden-by-default).
## Conditional "required" state
**💡Use Case**
Make field B's value dependent on the value of field A.
Here's an example from our native Weather plugin, which has 2x data providers (WeatherAPI, Tempest) and 2x location-related fields. If a user selects the Tempest data provider, `lat_long` is required. But if a user selects the WeatherAPI data provider, the `location` field is required.
- keyname: data_provider
field_type: select
options:
- Tempest
- WeatherAPI
default: tempest
name: Data Provider
description: Provides underlying metrics and layout design.
conditional_validation:
- when: "tempest"
required: ["lat_lon"]
- when: "weatherapi"
required: ["location"]
- keyname: lat_lon
field_type: string
name: Latitude/Longitude (Tempest only)
placeholder: -28.001499,153.428467
description: Provide your location.
optional: true
- keyname: location
field_type: string
name: Location (WeatherAPI only)
placeholder: Atlanta, GA, USA
description: Choose a location
optional: true
This treatment produces the following user experience:
# Troubleshooting
## Form fields don't appear
If your YAML cannot be parsed for any reason, it will be ignored and no errors will be returned.
Check your YAML syntax inline with our syntax checker, located above the form fields input box. This will save you from losing your work due to incorrect formatting.
If you don't want to use our inline verifier, here's another free tool that you can use to parse your fields before click Save.
---
# AI Agent
Writing code is cool, but wouldn't it be cooler to type "*build me a plugin showing populations around the world*" and it Just Worked?
Now you can.
## Requirements
This feature is in *public beta*.
Agent currently requires an API Key from OpenRouter. You can load credits on their [billing portal](https://openrouter.ai/settings/credits).
*Alternatively, if you have a Claude API account with higher [rate limits](https://platform.claude.com/docs/en/api/rate-limits) you can [buy credits](https://platform.claude.com/settings/billing) directly and use an* *Anthropic [API Key](https://platform.claude.com/settings/keys)*.
Optionally, a free [Tavily](https://www.tavily.com/) API key can give your agent access to web search and web page fetching, enhancing your agents capabilities.
In the near future you'll be able to purchase credits directly from TRMNL.
## Quickstart
1. Visit your [TRMNL Account](https://trmnl.com/account)
2. Toggle Agent on the left side, click Save
3. Click the "Configure model, keys & usage" to open up the settings page.
4. Add the API keys mentioned in the requirements section and click Save
5. Visit [Plugins > Private Plugins > New](https://trmnl.com/plugin_settings/new?keyname=private_plugin) and click Save
6. Click Edit Markup, then click the Agent tab
7. Ask Agent to build whatever you want or to guide you would like to understand more of the TRMNL framework.
## How it works
TRMNL has developed a comprehensive system prompt written in plain English, which describe our platform's capabilities and design patterns in depth.
Whenever you interface with the Agent inside our private plugin interface, the agent has access to this context, which you may even ask it about.
In addition, the agent has been given access to a range of tools, including markup editing, settings updates, data refreshing, internet search, and a list of API endpoints to name a few. This empowers and grounds the agent in the "real world" to add value to your experience.
## MCP Server (Advanced)
Prefer building plugins from your own editor with Claude, Cursor, or another AI tool?
TRMNL exposes an MCP (Model Context Protocol) server that gives your local agent the same powers as our built-in Agent: reading and writing your plugin's markup, refreshing live data, taking screenshots, viewing merge variables and logs, searching recipes, and consulting our design system docs.
**Get your MCP API Key**
1. Open your private plugin's settings page (Plugins > Private Plugins > your plugin) and click Save.
2. On the right side, find the MCP API Key section and click Generate.
3. Copy the key — it starts with ps_mcp_. Each key is scoped to a single plugin, so your agent can only modify the plugin it belongs to.
**Claude Code (CLI)**
Run this in your terminal, substituting your key:
claude mcp add --transport http trmnl https://trmnl.com/mcp --header "Authorization: Bearer YOUR_MCP_API_KEY"
Start a Claude Code session and type `/mcp` — you should see trmnl listed as connected. Then just ask: "show me my plugin's markup," "refresh my data and screenshot the full layout," and so on.
By default this registers the server for your current project folder only; add `--scope user` to make it available everywhere. The Claude Code desktop app reads the same
configuration, so no extra setup is needed there.
**Claude Desktop (chat app)**
1. Go to Settings > Connectors and click "Add custom connector."
2. Name it TRMNL and paste this URL, substituting your key: `https://trmnl.com/mcp?api_key=YOUR_MCP_API_KEY`
3. Click Add, then start a new chat and confirm TRMNL appears in your connectors list.
Your key goes in the URL here because Claude Desktop's connector form doesn't reliably pass custom auth headers. If you'd rather keep the key in a header, use the config file instead: Settings > Developer > Edit Config, add the snippet below (requires Node.js 18+), then fully restart Claude Desktop.
{
"mcpServers": {
"trmnl": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://trmnl.com/mcp", "--header", "Authorization: Bearer YOUR_MCP_API_KEY"]
}
}
}
**Good to know**
- Treat your MCP API Key like a password. Revoke or regenerate it anytime from the same settings page.
- Requests are limited to 60 per minute per key.
- Other MCP clients (Cursor, VS Code, etc.) work too — point them at `https://trmnl.com/mcp` with your key in an `Authorization: Bearer` header, or in the `?api_key=` URL parameter if your client can't set headers.
## Frequently Asked Questions
*Which models does this feature support?*
Gemini, Codex, Claude Sonnet, Kimi, and even OSS models (e.g. Llama). Set a preferred model from your [Account](https://trmnl.com/account) > Agent configuration.
*How much does it cost to build a plugin?*
We're seeing an average of $1-3 per plugin.
For example, plugin can be created with a single sentence prompt: `make a daily deal plugin for meh.com`. After thinking and developing for a couple minutes, Agent required us to provide a free Meh.com API Key, then we requested that the daily deal item's image be included. Total cost: $2.20 USD.
*Can I publish plugins with this feature?*
Not currently. Our team reviews several plugin submissions per day and we need to scale internal tools before we can handle the inevitable Agent-driven volume bump.
*Can I use other llm models (including free ones)?*
Yes, even though Claude Sonnet 4.6 was used in developing this Agent, other state-of-the-art models like Gemeni 3.1 Pro are excellent replacements. Feel free to experiment with any [OpenRouter model ID](https://openrouter.ai/models). The free models on OpenRouter tend to be less capable and slower.
## Troubleshooting
Agent isn't perfect. Send us a live chat or email if (when) you discover bugs or quirks.
---
# GitHub Sync
In our web based plugin editor, clicking Save overwrites all markup and settings. The [inline undo button](https://help.trmnl.com/en/articles/14136474-plugin-markup-version-control) can help with accidental changes, but it's limited to your browser history.
For better versioning over time, git is the gold standard, and GitHub (90% uptime) is a great place to host plugin data.
## How it Works
1. Create a repo for your plugin
2. Connect your GitHub account (personal or an organization) to the repo
3. TRMNL syncs form fields, polling URLs, and all markup layouts automatically as individual commits, whenever you press Save
Syncing is automated from TRMNL → GitHub, and manual from GitHub → TRMNL.
[https://www.loom.com/embed/53cb321b4c324a71a20d510875ab6d23](https://www.loom.com/embed/53cb321b4c324a71a20d510875ab6d23)
## Get Started
Inside TRMNL > Plugins > [Private Plugins](https://trmnl.com/plugin_settings?keyname=private_plugin), scroll down the right side margin of any given settings instance.
Here you can click the GitHub icon to be walked through the syncing process and select (or create) a dedicated repository for your plugin's data.
You may disconnect at any time, and you may connect as many plugins as you'd like to their own repository.
## Frequently Asked Questions
**How often does TRMNL sync with GitHub?**
Whenever you click Save on a plugin settings form or the markup editor, the TRMNL Sync app will trigger a git commit. These are debounced by ~15 seconds, so multiple saves within that period will be combined in 1 commit.
**What happens if the GitHub repo is updated?**
The TRMNL Sync app subscribes to "push" events and will surface the option to import the latest changes from GitHub.
**How does this help with collaboration?**
Any [published or unlisted plugin](https://help.trmnl.com/en/articles/10122094-plugin-recipes) in the marketplace that is synced to a public GitHub repo will include a couple links which point to the repository homepage and Issues generator.
**Is this feature compatible with trmnlp?**
Yes, you can continue push/pull development with our [trmnlp local development suite](https://github.com/usetrmnl/trmnlp). The same repository you use for trmnlp source code can be connected with GitHub Sync, as the same archiver utility is used to parse the code.
**What if a TRMNL team member changes my plugin settings a synced repository?**
Sometimes while reviewing plugins that have been submitted for publication, we are granted permission by the author (you) to tweak their code or settings. In this case, a synced repository will showcase that commit with `[admin team]` appended to the commit message.
**Is it possible to sync multiple plugins to a single GitHub repository?**
We consider this a "power user" feature - please reach out to us if you need it.
---
# Plugin Markup Version Control
Want to sync directly with GitHub? [Go here](https://help.trmnl.com/en/articles/15977899-github-sync).
If you're like me, you've lost countless essays to computer crashes and have been smashing `cmd+s` every 8 seconds for the last 20 years.
TRMNL is here to alleviate that habit.
## Demo
Inside the [Markup Editor](https://markup), switch between versions with the left and right arrows.
A new version is created whenever you click "Save" or type `cmd+s` (Mac) or `ctrl+s` (Windows).
## **How it Works**
This feature lives inside your browser, leveraging the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API). If you goof up some code, our team will never know.
You may clear your history at any time, or jump between versions from the timestamped table.
---
# WPA2 Enterprise WiFi
This feature is available for TRMNL OG in firmware versions 1.7.1 - 1.7.7 and 1.8.6 onwards. TRMNL X support is pending.
[Firmware Releases](https://github.com/usetrmnl/trmnl-firmware/releases)
If your WiFi configuration requires a username and password to connect, we have a feature for you.
After [enabling WiFi pairing mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode), tap your network's name like you would on a computer.
TRMNL will attempt to detect if the WPA2 protocol is being used, and if so, generate additional form fields below the network name where you can provide those credentials.
---
# Turning Off the TRMNL X
To turn off the TRMNL X, see "turning the device off" in step 3 of [this article](https://help.trmnl.com/en/articles/11546838-how-to-unpair-re-pair-a-device?q=turn+off#h_3f09aaf420).
After turning off your X, it will display a welcome message with instructions to turn it back on.
---
# How to unpair / re-pair a device
If you're rocking a native TRMNL device (not BYOD, not Virtual), you can unlink it in 1 click. Then you need to perform a 'soft reset' on the device itself.
Here's how.
## **Step 1 - Visit the device settings**
Inside TRMNL, hover the top-right dropdown and click the gear icon beside the native device you want to unpair.
Scroll down to the Unlink Device section and click "Unlink."
After confirming the scary confirmation box you should see a success message:
## **Step 2 - Soft reset the device**
Now that the web application has said farewell, we need to instruct your physical device to consider itself reborn.
**## TRMNL OG**
Assuming your TRMNL is already on, hold the button on the back for 5-7 seconds, then let go. This should re-enter [WiFi pairing mode](https://help.usetrmnl.com/en/articles/11511577-enable-wifi-pairing-mode):
**## TRMNL X**
Press the left and right side of the touchbar for ~1 second, letting go when the screen flashes to enter [WiFi pairing mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode). You will confirm be holding in the center of the touchbar.
Next, connect to the WiFi network named TRMNL as the screen suggests. You can do this on a phone or computer.
But wait! Don't choose a WiFi network. We're resetting it aren't we?
Ignore the network list at the top, and tap the "Soft Reset" button near the bottom. If you don't see this button, tap "Advanced" and then "Soft Reset" on the next page.
Tap "yes" when it asks you to confirm.
## **Step 3 - Turning the Device Off**
**## TRMNL OG**
Within a few seconds, your TRMNL should re-enter WiFi pairing mode for a 2nd time. But this time, it will be as if it's a brand new device.
You can now turn the device OFF with the slider on the back (slide it down).
**## TRMNL X**
First, put your X in [WiFi pairing mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode#h_c231afff3a).
While in WiFi pairing mode, hold down on both the left and right sides of the touchbar, simultaneously, for 1-2 seconds.
Let go when your screen starts flashing. You will see a confirmation screen:
Hold the middle of the touch bar to confirm, letting it go when it starts flashing.
You may see a *Flashing Modem Firmware...* screen for ~1 minute:
After that completes it will show the Welcome screen:
## **Step 4 - You're done!**
Your previous user connection, API key, Friendly ID (6 digit code), mirroring settings, name, refresh rate, sleep settings, and so on, will all be cleared.
You may now [set up this device like usual](https://help.usetrmnl.com/en/articles/9416306-how-to-set-up-a-new-device), or turn it off and give it to someone else.
---
# Device Models
TRMNL has native devices and DIY / BYOD devices.
Native devices are sold on [our store](https://shop.trmnl.com/products/), while DIY devices are made from components found elsewhere or designed for other purposes (e.g. [Meta Portal](https://trmnl.com/blog/meta-portal/)).
Third party products like [Raspberry Pi](https://usetrmnl.com/blog/rpi-trmnl), [Kobo Readers](https://github.com/usetrmnl/trmnl-kobo), [Kindle](https://github.com/usetrmnl/trmnl-kindle), and [Android tablet](https://github.com/usetrmnl/trmnl-android) have something in common -- they can render a TRMNL dashboard, either from our web application or an open source [BYOS server client](https://docs.usetrmnl.com/go/diy/byos).
Here are some things to know as you set up a BYOD device, accessible from the [Device settings > Model picker](https://trmnl.com/devices/current/model/edit).
## **Official Models**
To support a device natively, we need a handful of attributes about its hardware. These are used as parameters inside the screen generation engine to ensure a perfect fit that leverages the best resolution and other capabilities.
For example, here's our Kindle 6th Gen Paperwhite adapter:
{
id: 5,
keyname: 'amazon_kindle_paperwhite_6th_gen',
name: 'Amazon Kindle PW 6th Gen',
width: 1024,
height: 768,
colour_depth: 8,
colours: 256,
scale_factor: 1,
rotate: 90,
offset_x: 0,
offset_y: 0,
format: 'png',
device_type: :kindle
}
View all existing model specifications via the public [Models API](https://trmnl.com/api/models), or browse a non-exhaustive list of quickstart guides (+ try it free) at [trmnl.com/byod](https://trmnl.com/byod).
## **Custom Models**
If you don't see a compatible dropdown option for your hardware, there are a couple options:
1. Send us a live chat or email and we'll add it to the official models dropdown picker.
2. Use the custom model builder. This means you don't have to wait for our team, but you may need to experiment with dimensions, color depth, and other parameters described previously.
## **BYOD device support is a labor of love**
We're committed to open sourcing as much of our platform as we can. Learn more about this commitment [here](https://trmnl.com/blog/the-unbrickable-pledge/), and see a summary of our ecosystem [here](https://trmnl.com/developers).
Supporting a growing library of 3rd party hardware has its challenges, and we ask that the community help us stay efficient + productive by:
- posting Issues on the various [GitHub repos](https://github.com/usetrmnl)
- joining our developer-only Discord > #byod / #byos channels
- volunteering to test out your used hardware on new builds
In exchange we offer [community recognition](https://trmnl.com/blog?tag=community), complimentary TRMNL devices, and other perks.
---
# Volume Pricing
Groups in need of multiple devices can save money with a bulk order.
Instead of predatory quoting based on perceived budgets, our rates are published below to demonstrate a TRMNL core value: transparency.
# Use cases
*Corporate gifting / customer rewards program*
**Give away** **TRMNLs** without any logistics hassle via our [Partners API](https://docs.trmnl.com/go/partners-api/introduction).
*Wholesalers / retailers*
**Resell TRMNLs** to your own customers, online or in person.
*In-house distribution*
**Equip your organization with TRMNLs** for personal/work reasons or infrastructure purposes like signage and meeting room companions.
# Pricing
All devices include an individually wrapped box. To build your quote, find the [retail price](https://shop.trmnl.com/collections/devices) of the devices you want and then follow the values below. Models may also be combined, so for example 5x OG and 5x Model X devices would qualify for a 10x+ device discount.
**Gifting (via Partners API)**
- $20 below retail price, paid up front in batches of 25+ quantity
- $10 below retail, invoiced weekly as devices are provisioned
- retail price, paid on-demand with no minimum quantity
- at-cost shipping ($7 /package USA, $12 /package international)
**Wholesale**
- MOQ 150x devices, $92 unit price
- MAP $20 below retail
- Free shipping to any single location
**Group discount**
- 10-20x devices @ $20 below retail
- 21-50x devices @ $30 below retail
- 51-99x devices @ $35 below retail
- 100-150x devices @ $37 below retail
- 151+ devices @ $40 below retail
- EDU or non-profit? Deduct another 10% (+ we accept tax-exempt forms)
- Free shipping to any single location or at-cost shipping ($7 /package USA, $12 /package international)
**Add-ons** (any use case)
- Charging Cable @ $2 per unit (retails for $5)
- Battery upgrade @ $2.50 per unit (retails for $10)
- Developer Edition upgrade @ $5 per unit (retails for $20)
# Customization
- Custom graphics on boot screen and loading icon @ $5 per unit
- Gift message on packing slip @ $2 per unit
# Next steps
To request an invoice, visit [trmnl.com/b2b](https://trmnl.com/b2b) and use the quote generator. Alternatively, email your requirements (color, quantity, add-ons) from the options above to . To discuss our (open source) self-hosted solutions, email .
---
# Mashups
TRMNL offers several layout combinations for your plugins. The default layout is full screen, illustrated by the top left icon in the graphic below.
If you have a TRMNL X or X-like device (reMarkable, E1003, etc) with a 10.3" screen, you may create even more layouts with [Fluid Mashups](https://trmnl.com/blog/fluid-mashups).
# Create a Mashup
When you connect a new plugin, or create a new plugin "instance" (for an existing plugin), **TRMNL adds a full screen layout to your Playlist automatically.**
**TIP:** You don't need to have the full version in your playlist for mashups to work for most* plugins, they are refreshed even if the full plugin is removed from your playlist (you'll see a message on the plugin's page saying "refreshing mashups" in that scenario).
* If you used the `install` option on a public recipe that, when clicked, brought you directly to your playlist and NOT the plugin editing page, that plugin has no customization (form fields), so if removed from the playlist, it will be removed overall. In this scenario, used the *hide* option to ignore the full version.
To instead leverage a Mashups layout, visit your [Playlists tab](https://trmnl.com/playlists) and hover your mouse over the "Add Plugin" dropdown.
Here you can select a layout, then begin building it out with a few clicks.
In the example above we chose the 50/50 split screen layout, which is the top-right icon in the graphic at the top of this guide.
Next, we can choose **any*** of our connected plugins from the "Section A" and "Section B" dropdowns.
As you can see, whatever plugin we provide in the Section A plugin will be mapped to the left pane layout, denoted by the 'A' inside the template.
Once you've chosen a plugin for each section, your Playlist will refresh.
# See your Mashup
After creating a mashup, it should then take just a few seconds for the Mashup content to be generated. So instead of waiting for your device to fetch this particular content, you can actually refresh your Playlists tab to see the preview image.
If you'd like to inspect your Mashup more closely before it appears on your device, simply click on the thumbnail.
Once it is updated, clicking on the mashup's image will "zoom" into it.
# Edit a Mashup
To update the contents of an individual item / section within a Mashup, click the tripe dots and choose Settings:
# Troubleshooting Mashups
If you've created a Mashup but still see orange boxes, try waiting a few more seconds for our queue to pick up your inner contents and generate a graphic.
If your Mashup contains Private Plugin or Recipes, and the content is duplicated between unique instances, check that the markup (HTML, CSS, JS) is containerized to have **[uniqueness](https://help.trmnl.com/en/articles/10347358-custom-plugin-filters)** across instances. For example, if you have a chart that binds to "#my-chart," then one of your Mashup sections will work, and the other will not.
If your Mashup has some working sections, and some blank sections, consider it's possible our system "timed out" while loading all the content and taking a snapshot for your device. If you have access to the markup and logic of the blank section, try making it smaller and faster. If the blank section is a native plugin, let us know.
---
# Home Assistant Integration
**Native support:** [Home Assistant Screenshot](https://help.trmnl.com/en/articles/13281388-home-assistant-screenshot)
Also see community plugins:
Home Assistant Official Integration (track battery in HA):
And these open source projects:
-
- [https://github.com/gitstua/trmnl-plugin-dev/tree/main/home-assistant-trmnl](https://github.com/gitstua/trmnl-plugin-dev/tree/main/home-assistant-trmnl#home-assistant-trmnl-plugin)
-
-
Finally, if you prefer to roll your own stack, combine the Home Assistant founder's "puppet" library with our Alias plugin:
-
-
---
# Registering your MAC address
If your device displays "MAC not registered" and points you to this help page, you've likely purchased hardware from somewhere *other* than TRMNL.
You will need to purchase a [BYOD license](https://shop.trmnl.com/products/byod) (Bring Your Own Device) to use **trmnl.com** as your server for your BYOD device. You are also welcome to run your own server ([BYOS](https://docs.trmnl.com/go/diy/byos)), no license required.
**If this is your first device being connected to TRMNL:**
[Claim your device](https://trmnl.com/claim-a-device) using your order number and name. Then [sign up](https://trmnl.com/signup) for an account.
**Once you have a trmnl.com account:**
With a BYOD license you can easily add your MAC address yourself. Hover over your BYOD device in the top-right of trmnl.com and click on the *[gear](https://trmnl.com/devices/current/developer/edit)* icon, selecting the *Developer Perks* section. Enter your device's MAC address and **save**.
More information: https://docs.trmnl.com/go/diy/byod#build-from-scratch-advanced
If you have further questions about using trmnl.com for your third-party hardware, send us a live chat or email .
---
# TRMNL Companion for iOS
Read more about this feature in [the announcement](https://trmnl.com/blog/companion-ios-app).
## Get the App
Download the app on your iPhone or iPad here:
## Feature Overview
1. Calendar Syncing
2. Apple Health
3. Apple Reminders
4. Apple Home
5. Shortcuts
Let's explore each one.
## Calendar Syncing
To make calendar sharing secure, Companion uses the EventKit library to sync events from multiple calendars (Apple, Outlook, etc) to your TRMNL account.
(*If you use Google Calendar, check out our [native Google Calendar plugin](https://help.trmnl.com/en/articles/9483539-google-calendar)*)
### Step 1 - create a Calendar plugin
Inside TRMNL, navigate to Plugins and create (or update) an existing Calendar* instance, setting the "Data Provider" value to "iPhone App" at the top of the settings form.
`* Apple, Outlook, Fastmail, Nextcloud, CalDAV`
### Step 2 - Sync your calendars
Inside the TRMNL Companion iOS App, pull down on the "Plugins" tab to refresh the list. Select the Calendar plugin instance(s) you set up in Step 1 and sync as many calendars as you'd like.
It's OK to mix and match providers. For example you could sync a personal Google Calendar and a work-only Outlook calendar. The calendars themselves will not be shared -- rather, their events will be pulled independently and synced with your TRMNL account.
Going forward, calendar events should automatically sync in the background.
## Apple Health / Reminders / Home
Syncing Apple data to TRMNL is easy.
### Step 1 - Create a Plugin
Inside the TRMNL web app, navigate to Plugins > [Private Plugin > New](https://trmnl.com/plugin_settings/new?keyname=private_plugin) and create a plugin with the "Webhook" strategy.
### Step 2 - Connect Data
Back inside TRMNL Companion, pull down from the top of your Plugins tab to refresh the list and select the plugin you just created.
Each category of data will appear in a dropdown menu, and you must enable permissions to share that type of data with TRMNL. Then you can toggle on/off data sources individually.
This data will appear inside "nodes" of your TRMNL plugin. For example all Reminder data will appear inside a `#{{ reminders }}` block and so on. Learn how to use this data [here](https://help.trmnl.com/en/articles/9510536-private-plugins) and [here](https://help.trmnl.com/en/articles/10671186-liquid-101).
## Apple Shortcuts
Shortcuts is a powerful "no code" utility built and maintained by Apple. This lets Apple users coordinate complex sequences of triggers and actions spanning many pieces of technology.
For example, with Shortcuts you can make a smart light turn on when you arrive home. Or draft a Note with the current timestamp whenever your back door is opened. The possibilities are endless.
With TRMNL Companion, these possibilities include creating and syncing custom plugins for your device.
### Step 1 - Create a Plugin
Inside the TRMNL web app, navigate to Plugins > [Private Plugin > New](https://trmnl.com/plugin_settings/new?keyname=private_plugin) and create a plugin with the "Webhook" strategy.
### Step 2 - Create a Shortcut
Inside your Apple product > Shortcuts, create a Shortcut with whatever inputs or triggers you'd like. Learn how to use Shortcuts generally [here](https://support.apple.com/guide/shortcuts/welcome/ios).
When you're ready to push that data to TRMNL, create a new step, search for TRMNL Companion, then choose "Send Data to Plugin."
Map your desired data inside the row labeled "JSON Data," then (optionally) customize the Key value "data" to something else.
After saving your Shortcut, tap the ▶ (play) icon to test it. After seeing a success message, head back to your plugin inside the TRMNL web app. Click "Edit Markup" in the top right, then find your data inside the "Your Variables" section below the markup preview area.
## Customize your sync experience
By default, TRMNL Companion will sync new calendar automatically in the background. But this may functionality may be paused if you "force close" the application.
If you'd like to increase the sync frequency, set up an Apple Shortcut as follows.
### Step 1 - Create a Shortcut
Open the Shortcuts app on your iPhone. Click the "+" symbol to add a new Shortcut. Search for "TRMNL" and select the TRMNL Companion app.
### Step 2 - Enable the Shortcut
After selecting the TRMNL Companion app, tap the Sync to TRMNL option. On the next screen we suggest disabling the "Show When Run" toggle.
If you'd like to test this out, tap the ▶ (play) icon in the bottom right corner.
### Step 2 - Automate the Shortcut
Tap the back button in the top left of your Shortcuts app to return to your home screen. Next, tap Automation in the bottom menu.
Here you can set up a schedule, combining a time of day with the Sync Shortcut we created a moment ago. Click the image below to see it expanded.
Note our recommendation to set "Run immediately" and disable "Notify when run," which will otherwise create a banner notification on your home screen.
# Troubleshooting
Please send feedback to .
# Security
TRMNL Companion uses native "Kit" modules by Apple. Each kit's data must be explicitly permitted by you. Downloading TRMNL Companion does not grant TRMNL personal information unless you login into your web account, approve permissions, then consent to additional data sharing.
Regarding calendar specifically, Companion uses the EventKit library to fetch **only the details need to populate a calendar** -- an event's title, description, start/end time, status, and calendar name. hackers are welcome to Man-In-The-Middle the network request:
{
"end" => "18:00",
"start" => "16:00",
"status" => "accepted",
"all_day" => false,
"calname" => "ryan@trmnl",
"summary" => "Support chats",
"end_full" => "2025-10-02T18:00:00.000Z",
"date_time" => "2025-10-02T16:00:00.000Z",
"start_full" => "2025-10-02T16:00:00.000Z",
"description" => "Respond to customers from my walking standup desk"
}
TRMNL Companion also only retrieves events from **the past 7 days, to the next 30 days**.
---
# Troubleshooting an Unresponsive Device
Quick Links:
- [TRMNL OG](#h_1267585565)
- [TRMNL X](#h_2efeaad9b6)
# TRMNL OG
## Charge the Device
Though it may seem silly, the best starting point is to make sure the device is **fully charged**: Plugged in until the green light on the back (TRMNL OG) turns off.
It's also a good practice to slide it into the **OFF** position during this charging cycle.
Slide the power switch to ON once it is charged.
For the rest of this article, if you reach a satisfactory result, please rate the article at the bottom accordingly.
If you do not, please continue with the next step(s) till you've exhausted all options.
### Only Works When Plugged In
If the device is responsive when plugged in, but it either:
- fails to respond to button presses when unplugged
- the green light never turns off
This indicates a problem with the internal battery. If you recently opened the unit, please double-check it is properly plugged into the PCB. If not, look for any signs of battery swelling or burn marks on the battery itself.
**Do not keep a faulty battery plugged in continuously!** [Contact support](#h_686ff24ec9) for further follow-up.
## Performing a Soft Reset
An unresponsive device may require a soft reset. There are two ways to trigger the reset:
### WiFi Captive Portal Button
- Hold the button on the back for 5-7 seconds, then release. This should enter the WiFi Captive Portal mode, like during [initial setup](https://help.trmnl.com/en/articles/9416306-how-to-set-up-a-new-device), broadcasting a network `TRMNL` to connect to via WiFi.
- Sign-in to the `TRMNL` SSID Network to display the captive portal.
- Press the **Soft Reset** button.
### Long Press to Soft Reset
Hold the button down on the back for 15-20 seconds to perform the soft reset.
## Reflashing the Device's Firmware
[Reflashing the device](https://help.trmnl.com/en/articles/11936721-put-your-trmnl-in-flashing-mode) overwrites the existing firmware via a USB-C cable and our [web flashing utility](https://trmnl.com/flash), which contains the necessary instructions.
Start with the most current firmware, and then step backwards and retest device responsiveness.
If your device was purchased before August 2025 and was working properly before suddenly becoming unresponsive after an update or charging, please refer to our [fw1.6.2 troubleshooting guide](https://help.trmnl.com/en/articles/11934315-fw1-6-2-troubleshooting) and follow up with support.
---
# TRMNL X
**We have an interactive troubleshooter for TRMNL X!**
*Please note that it can provide links back to sections of this article for visual references.*
## Soft Reset
If the dock is attached, remove it and invert it so the pins are pointing up. Place the inverted dock near the bottom-left corner of the **back** of the device and make a small circular motion (no need to touch, the magnet will do the work).
The front screen of the device should automatically change to mostly blank with the loading image in the bottom-right, indicating the reset worked, before loading a screen. **You can repeat these steps with no negative impact on the device.**
This soft reset will work to take the device out of boot loader mode, flashing mode, or during normal operation, as long as the device has battery charge.
## Charge the Device
If the device does not respond to a *soft reset*, it will need to be charged.
To do so, attach the dock with pins down to the center of the TRMNL logo glyph. It will be pulled into place by the magnets.
Attach a USB-C cable to the dock and to a power source. Your device should start charging immediately. **Note:** Some fast charging blocks do not work properly; you may need to connect the USB port of a PC or a basic USB->Wall Charger.
*If the device is online, you will see the charge state within your trmnl.com account, in the top-left. In v1.8.2+ of TRMNL X firmware, there will be a battery charging indicator visible on the bottom of the screen as well on any loading screen:*
The dock pins should align properly with the PCB inside the device, regardless of dock orientation. However, if not, see below.
## Getting a Good Connection
If charging is not working, there are two orientations that are successful. Thoughtfully adjusting the rotation of the dock can ensure a good connection for your unit. If there is an alignment issue of the PCB mounted inside your unit, then one of these two orientations will work, but the other may not.
- Arrow pointing vertically or horizontally, dock screws in an `x` pattern
- Arrow pointing at a 45° angle, dock screws in a `+` pattern
This rotation can continue all the way around to 90, 135, 180, etc. to find what works for your device without having to open the case and reseat the PCB.
If you are using a power+data USB-C cable (included with Clarity Kit), connecting the cable to a PC and using our [flashing guide](https://help.trmnl.com/en/articles/11936721-put-your-trmnl-in-flashing-mode) is another way to verify connectivity with the device showing up as `USB JTAG/serial...` on the list. **This method requires the battery to have charge.**
### Checking the Charging Dock Pins
A quality connection requires 4 pins for power and data. **Each dock is QA tested with the unit before being boxed for shipping**. However, pins can be compressed or damaged sometimes in shipping (continued improvements in packaging will minimize this).
**Pins in Good Condition**
*Here are examples of pins that need attention:*
**Pins not fully extended**
**Stuck Pin**
**Pin not "springing back"**
For these cases, you can exercise the pin manually to get the spring to properly extend again by pushing on it up and down.
On rare occasions, the spring is damaged and it can be gently pulled out for a good connection, but will not spring back (contact support for a replacement dock).
Additionally, one customer (of thousands) had a tight fit hole for a pin, causing it not to extend properly without slightly widening the hole. This was a manufacturing defect during injection molding that may not be caught in QA.
**Missing Pin(s) - Damaged in Shipment**
(contact support for a replacement dock)
**Pin Pressure Pads**
We add [these pads](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl#h_76db4d23e1) to the back of the dock's PCB to accomplish two tasks: Keep it from rattling and to apply additional pressure to ensure good pin connection. The are pressed down by bumps molded into the inside of the back cover.
If you cannot get your device out of the [setup/off screen](https://help.trmnl.com/en/articles/14553723-turning-off-the-trmnl-x) by connecting your charging dock and USB cable, you can test if additional pressure on the PCB manually makes a good connection (dock should be connected to the unit with USB power):
#### Glyph Sticker (last resort)
If the pins are all present, all fully extended, and you are still not getting a good connection, you could remove the glyph sticker on the back of the TRMNL X and retry your connection.
*This sticker isn't just aesthetic, it provides rotational friction between the dock and the device! This is relevant for magnetic mounting or wall mounting, not for using the kickstand.*
## Reconnecting to WiFi
If you need to connect to a new WiFi network, you can trigger the WiFi captive portal by simultaneously pressing the left and right side of the touch bar.
Then, hold the middle button to confirm on the next screen. See our [guide with video](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode).
## Manually Updating the Firmware
Please see our guide for putting the TRMNL X in [flashing mode](https://help.trmnl.com/en/articles/11936721-put-your-trmnl-in-flashing-mode). *The unit must have some battery charge to trigger the mode change.*
# Contact Support
If the unit continues to be unresponsive, please contact support using the online chat icon in the bottom-right of any page on trmnl.com or by emailing [support@trmnl.com](mailto:support@trmnl.com?subject=Unresponsive%20Device%20Troubleshooting).
---
# Home Assistant powered Calendar
Where feasible, TRMNL connects natively with popular calendars to minimize data exposure. Google and Microsoft Outlook are 2 examples, with more available through Apple Calendar and [TRMNL Companion](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).
Some calendars however, for example Proton, cannot be integrated as securely due to E2E (end to end) encryption. For this case we support the CalDAV / ICS protocol.
Unfortunately, ICS calendars share all events (past and future) to connecting platforms, with no ability to scope the data. That's where Home Assistant can help limit exposure.
### Prerequisites
- Home Assistant running on your local network
- A calendar that supports shareable ICS links
## Step 1 - Install CalDAV
Open Home Assistant -> Settings -> Integrations -> Install CalDAV.
Add ~any calendar you'd like, using app-specific passwords where possible.
## Step 2 - Install TRMNL
Add our native Home Assistant integration:
## Step 3 - You're Done!
Raw calendar data will only be available locally, with just the events' titles and date/time shared with TRMNL in screenshot format.
---
# How refresh rates work
As illustrated in our [architecture docs](https://docs.usetrmnl.com/go/how-it-works), TRMNL has 1 paradigm that underscores how content is displayed on your device. To summarize:
**Devices ping the server. Not the other way around**.
Practically this means:
- You cannot click a button on our website and 'force' new content to appear on your device. Your device must ask for it.
- Your device will not "know" when new content is available to be refreshed; The device must ask for its next screen, and the server will decide what to provide next.
Of course, it's natural to want to skip ahead to new content, or refresh a generated screen while building custom plugins. So that is possible, but you'll leverage both the web interface and your device’s skip button to do it.
# On-Demand Refresh
[On-demand refresh](https://help.trmnl.com/en/articles/15123293-on-demand-plugin-refresh) was enabled for all users by the end of May, 2026. It made the TRMNL refresh experience much closer to most user's common experience of how refresh works. To put it simply, **your device requests a screen and our servers anticipate that request and ensure data is fresh and a new screen is ready.**
When we say we *anticipate* the request, we mean exactly that. If we know the next request is coming in 15 minutes, we'll start getting things ready in enough time to have the screen available for your device's request.
There is a progressive set of factors that determine your refresh rate, where each one listed below acts as an override to the one prior.
## Device Refresh Rate
In your **[Device Settings](https://trmnl.com/devices/current/battery/edit)** in the top-right (gear icon), in the *Battery & Sleep* section, you can adjust your device's refresh rate. This is the time it will wait before moving to the next item in your playlist (or refreshing if there is only 1 item).
You can move the slider or click the time intervals below the slider to quickly set your refresh rate. This is a *fuzzy* value, **you cannot set this like a cron job to run every hour, at :00, for instance**. Due to batching, server load, WiFi connectivity, and various other factors, a refresh can vary a few seconds each time.
Also note that this can go as low as *5 mins*, but that may be restricted by your [Account Refresh Rate](#h_5d699ec185).
## Playlist Item Refresh Rate (Duration)
Within your [playlist scheduler](https://help.trmnl.com/en/articles/11663305-playlist-scheduler), you can set the *Duration* of an individual item on your playlist, whether a full screen plugin or a mashup.
As noted previously, this is an override to the Device Refresh Rate. If you have 4 items on your playlist that are always visible, at 15 minute refresh rate across all of them, your plugins would refresh every hour.
## Minimum Plugin Refresh Rate
The plugin settings page itself usually also offers a Minimum Plugin Refresh Rate or Max Refresh Rate. For a variety of reasons, you may not want to update your plugin as frequently as able, for instance if you were generating a custom AI "daily view" screen and each generation cost money.
If you had 1 plugin in your playlist, and your playlist was set to 15 minute refresh, but your plugin's Max Refresh Rate was set to *Every 30 mins*, then it would only update every *other* time the screen refreshed.
## Mashup Minimum Refresh Rate
If you have a mashup, the minimum refresh rate is the minimum of all the plugins in that mashup.
## Account Refresh Rate
By default, accounts have a 15 minute minimum refresh rate. With [TRMNL+](https://help.trmnl.com/en/articles/11861887-trmnl-faq), this can be as low as 5 minutes. That means that no matter what, if a playlist is set to refresh every 5 minutes, and there is only 1 plugin, it will not get a new screen generated but every 3rd time.
Want to see all the details about what our server did and what your device asked for? Check out your [Activity Logs](https://trmnl.com/devices/current/activity_logs) in your device's settings.
## Additional Reasons for Skipping Refresh
### Manual Screen Request
If you manually tap a touchbar or press a button to [request the *next* screen](https://help.trmnl.com/en/articles/12410440-how-to-change-screens) in your playlist, the device receives the current screen stored in our system, because we cannot *anticipate* that you were going to manually make that request. Only natural refreshes are anticipated. In this scenario, you would need to use the **Force Refresh** option in the plugin's settings page prior to the manual request, for instance if testing a new plugin setting change.
### Data Unchanged
If the underlying data for a plugin that is not Static data or has a different exception in our system, when the screen generation process occurs, if the data has not changed then the screen will not be generated. This includes situations where a plugin may have set special features in their markup to define when to [skip screen generation](https://help.trmnl.com/en/articles/13615138-skipping-screens-within-plugin-markup).
---
As of **June 2026, the below summary is no longer accurate.** It is kept here for historical purposes to capture the methods and decisions TRMNL made to launch it's first product, the TRMNL OG.
# Asynchronous Refresh
Below are more details about how TRMNL refresh rates work.
1. [Plugin refresh rates](#h_64269842e4)
2. [Overriding plugin refresh rates](#h_6bc016b817)
3. [Device refresh rates](#h_854b46ae51)
4. [Mashup refresh rates](#h_8943c22098)
5. [Lazy refreshing device content](#h_7094fc0e29)
6. [Determining "when" (hh:ss) a refresh occurs](#h_ead6e5ebd0)
## Plugin refresh rates
Every native plugin has a default refresh rate. For example our "Days left this year" refreshes just 1x per day, because it would be pointless to re-generate the same screen intra-day.
Meanwhile our GitHub Commit Graph updates every 30 minutes. The random Wikipedia article generator refreshes every 4 hours. And our Product Hunt plugin refreshes hourly.
These are just defaults, however. You are welcome to override them on your own account, on a per-plugin basis.
## Overriding a plugin's refresh rate
After setting up a plugin in your TRMNL account, you'll notice a refresh rate dropdown on the right side of the settings page:
Depending on which plugin you're editing, this dropdown will have different options.
When it's time to generate new screen content, **TRMNL will follow whichever refresh rate is faster** -- yours, or the system default.
However, **you cannot refresh faster than the system allows**. So for example, since Shopify has a max speed of every 10 mins, you can refresh every 10, 15, etc minutes. But since our RSS plugin refreshes at a max speed of 60 mins, you cannot refresh faster than 60 minutes.
We are open to decreasing refresh intervals on a case by case basis. These limitations are simply in place to predict our server resource requirements and maintain free, lifetime usage for all of your TRMNL devices.
## Device (playlist) refresh rates
Adjacent to your plugin refresh rates is your device's refresh rate. This is simply how often your TRMNL wakes up, requests new content, renders it, then goes back to sleep.
You may configure your device's refresh (fetch) rate from either the Playlists or Devices tab. If you edit from the Playlists tab, you get even more control, because you can change this interval throughout the day.
For example, you may want to switch between your calendar and the weather every 30 minutes in the morning, but then switch between reddit, your glucose meter, and stock quotes every 5 minutes throughout the afternoon.
It's important to note that the screen generation process, governed by your plugin settings, happens *independently* of your device wakeup / fetch interval. In other words, **TRMNL does not sync content + generate graphics on-demand when your device pings the server**. This would be too compute intensive, drain your device battery (to stay awake for fresh content), and have unexpected results in the case that new content doesn't exist or the 3rd party plugin is non-responsive.
## Lazy refreshing device content
Above we describe how plugin content may be requested every *N* minutes, based on your preferences on the plugin settings screen.
However it is possible (and common) that when TRMNL requests new data from a plugin, the content has not changed since our last request.
For example, if your connect Google Calendar and set it to refresh every 15 minutes, TRMNL will sync new calendar events every 15 minutes. But if the events returned to us have the same title, description, start/end date, and so on, TRMNL will not generate a new graphic for your device.
Here's an example of that in action.
This is due to TRMNL's timer-based content generation strategy. Every minute, TRMNL loops through every plugin that is due to be refreshed and attempts to create new screens.
This screen creation attempt has 2 steps:
1. **sync** new content
2. **render** new content
When TRMNL syncs content, but it matches the previously synced content (from 15+ minutes ago), the system stops and does not re-*render* (generate a screen) the content.
This saves compute power, without changing your device experience. You will still see the calendar graphic if it's up next on your Playlist, but the image itself may have been generated 15+ minutes ago.
Thus on your plugin settings screen might look like this:
Here we have a 1 hour refresh set on our Shopify plugin, but the "Refreshed" timestamp shows 6 hours ago. That implies we have not received a new order in 6 hours. But rest assured, TRMNL looks for new orders every hour based on our preferences.
## **Mashup refresh rates**
A Mashup is any combination of 2 or more plugins on a single screen, using one of these layouts (excluding the top left option):
Since Mashups are composed of several underlying Playlist items (plugin settings), we sync new data for each of your plugin settings when generating Mashup screens.
Mashups update at the speed of the fastest underlying plugin. So if you have a 3x quadrant Mashup, where the underlying plugins refresh every 15 minutes, hourly, 1x/day, then your entire Mashup will update every 15 minutes.
## **Determining "when" a refresh occurs**
New screens are generated relative to the moment in time that you establish a refresh rate, except 1x / day plugins.
1x / day plugins always refreshes at 00:15 local time. 1x / day mashups always refreshes at 00:30 local time.
If you enable Google Calendar at 14:00 (2p) local time, and the default refresh rate is 1x per hour, then it will refresh daily at 15:00 (3p) your local time.
If you later change your Google Calendar plugin to refresh 4x /day, and you make this change at 18:00 (6p) local time, then new screens will be generated at 18:00, 00:00, 06:00, and 12:00 in your local time.
You may easily manipulate this refresh interval to a more predictable moment, for example every 15 minutes at HH:15, HH:30, and so on, by clicking "Force Refresh" on the right side of your plugin settings screen, at that moment.
Just note that over time, your plugin's refresh interval will slip by a few seconds, possibly every day. This is due to fluctuations in our background queue's number of jobs, server deployments that pause the queue, and so on.
## **In summary**
We're linking this help guide from a few places in-app to clarify how refreshing works, but the main points are:
1. TRMNL's website does not 'ping' your device, only your device can ping the TRMNL website
2. Your plugin <> device refresh intervals, when combined with our lazy loading generation strategy, may seem to indicate that plugins are not generating on time. But TRMNL does follow your timing preferences, with exceptions mentioned above.
If you have additional questions, send us an email or live chat at any time.
Stay focused.
---
# Special Functions
TRMNL devices have a few interactive elements, but they're not obvious. Get started by selecting your device below.
**## TRMNL OG (7.5" screen)**
The back of your OG TRMNL device has an on/off switch, USB-C input, and multi functional "boot" button.
The boot button does 3 things by default:
1. Skip to the next screen in your playlist (click)
2. Reset WiFi credentials (hold for 6-7 seconds)
3. Soft reset your device (hold for 16-18 seconds)
But you can program a 4th function, which we call Special Functions.
# Determining your special function action
If your device is running Firmware 1.6.1 or newer, special functions are enacted by a "medium press" button hold of 1-2s, then release.
If your device is running Firmware released before August 2025 (1.4.X, 1.5.X), special functions are enacted by a double-click on the boot button.
**## TRMNL X (10.3" screen)**
Your model X has a touch bar that supports several different gestures. Over time this collection will grow, but as of May 2026 they are as follows:
- Tap the middle => skips to the next screen in your Playlist
- Tap or Swipe* left side => goes back a screen, reading from device memory
- Tap or Swipe right side => goes forward a screen, reads from device memory (requires a previous "Left" swipe, otherwise reloads current screen)
`*` Tapping and Swiping are 2 flavors of same gesture but all devices default to "Tap" mode. You can change to "Swipe" mode here:
[https://trmnl.com/devices/current/controls/edit](https://trmnl.com/devices/current/controls/edit)
*Devices running firmware version <= 1.8.2 may also "Hold" the middle of the touchbar for 1-2 seconds to enable or disable OTG, which is the ability to power other devices from your X. Note that while in OTG mode, your X will not charge. This feature was removed in firmware version 1.8.4+ (it will return in the future under different controls).*
You can also [enter pairing mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode) with the following gesture:
[https://www.youtube.com/embed/aXiA_liuCvI?rel=0](https://www.youtube.com/embed/aXiA_liuCvI?rel=0)
If videos aren't your thing:
1. Hold both ends until screen blinks
2. Tap the middle to cancel, or hold the middle to enter pairing mode
To turn off your X device, first enter WiFi pairing mode, then repeat the same gesture (hold left/right ends of touchbar) and confirm you wish to turn off the device. You will see a welcome screen when this is complete.
# Setting a Special Function
Visit your [Device settings page](https://trmnl.com/devices/current/edit) and select the [Controls](https://trmnl.com/devices/current/controls/edit) section.
There are several options, which we'll explain below.
As of February 2026, Special Functions are not yet available on the X model device. They will be soon however, and you'll be able to set a unique gesture per function versus restricted to 1.
# Identify
Triggers a simple identification screen to help you determine which device is which.
This is helpful for customers with multiple devices who may not give them descriptive names, or who have their devices in the same room with a similar case color.
# Sleep
Puts your device to sleep for 8 hours. In the future you may be able to customize this sleep period length.
# Add WiFi
Instructs your device's firmware to activate the "TRMNL" WiFi portal so that you can connect to a new network, without resetting your existing credentials.
This works even on captive-portal / WPA-Enterprise networks. [See details here](https://intercom.help/trmnl/en/articles/11663377-setting-up-a-trmnl-on-tricky-wi-fi-situations).
# Restart Playlist
Begins showing content from your Playlist at the top/first position. Think of this as an enhancement to the single-click "skip" feature, as it skips multiple screens all at once.
# Rewind
Goes back to the previously viewed screen. Helpful if you noticed something interesting just as your TRMNL device began showing new content.
# Send to me
Sends yourself an email with the latest (current) device screen content. Helpful if you want to save/bookmark something for later but don't want to search for it in your Playlist history.
# Guest Mode
Switches your screen to any item of your choice. Helpful if strangers are nearby and you'd rather show the weather (for example) than a personal calendar.
This interface will appear once you set the Special Function dropdown to Guest Mode. Set an item from your playlist as well as a duration to ensure sensitive information does not re-appear until guests are gone.
---
# How to Change Screens
# TRMNL (OG)
The button on the back of the TRMNL (OG) device can serve many functions, but the most prominent is to force the device to refresh and *advance to the next screen in your [playlist](https://help.trmnl.com/en/articles/11663305-playlist-scheduler)*.
Pressing the button wakes up your device and instructs it to connect to its server to fetch the next screen update.
This button has additional functions outlined in our [special functions](https://help.trmnl.com/en/articles/9672080-special-functions) article.
# TRMNL X
TRMNL X may be designed as an ambient dashboard device, but that doesn't mean you can't give it a little push (literally!) if you want to advance to the next screen in your [playlist](https://help.trmnl.com/en/articles/11663305-playlist-scheduler).
To get the next image, tap the center of the touchbar on the bottom:
## Touchbar Extra Functionality
TRMNL X will store recent screens in memory, allowing you to go "back" and "forward" through *saved* screens, no network request required.
### Touchbar Mode
By default, TRMNL X devices ship in **Tap** mode. However, in your Device Settings > Controls you can change this gesture from tap to **Swipe**.
### Touchbar Tap
Touching the left-side of the touchbar will go back to the previous screen.
Touching the right-side of the touchbar will go forward through saved screens.
### Touchbar Swipe
You can swipe left to go back and swipe right to go forward.
## Other Touchbar Functions
- [Enter WiFi Pairing Mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode#h_c231afff3a)
- [Turn the Device Off](https://help.trmnl.com/en/articles/11546838-how-to-unpair-re-pair-a-device?q=turn+off#h_8e3490343a)
## Recalibrating the Touchbar
If a "hold for 2s" feature like the *Other Touchbar Functions* above is triggering instantly, or any long-press function is treated like a quick press, these are signs you need to perform a re-calibration.
To recalibrate the touchbar, perform a [soft reboot](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device#h_ee5e7cb9ad). Ensure you are not actively touching the touchbar during this process! The screen will flash a few times. Once completed, the touchbar will respond as expected.
---
# Parsing plugins with the Sandbox Runtime
[Private plugins](https://help.trmnl.com/en/articles/9510536-private-plugins) enforce a maximum 100 kilobyte blob of data from external resources. Endpoints that respond with a larger payload will be rejected.
But it's not always possible to reduce a payload with request parameters alone. Introducing the **transformer sandbox runtime**.
In May 2026, **transformer** was superseded with **[serverless](https://help.trmnl.com/en/articles/14130649-serverless).** Same functionality, but more language support.
# Example use case
Create a private plugin that sometimes yields a large JSON object. As is, this plugin will soon go into a degraded state when the response is too large (> 100 kb).
Visit the Markup Editor > Serverless tab and filter the payload with JavaScript (Node v22), returning a new object.
Wrap your *node* logic with the `transform()` function like so:
function transform(input) {
return {
// parsed payload goes here
}
}
The `input` variable contains the complete original payload.
Trigger a force refresh from the plugin settings screen, then find your new payload object back inside the Markup Editor.
# Benefits of using this sandbox
In addition to making otherwise disallowed plugin endpoints possible, the `transformer()` strategy can enhance your developer experience overall.
## Cleaner code
If your plugin needs just a few bits of data but you find yourself traversing deeply nested nodes to find it, surfacing those values as top level JSON objects can simplify your plugin's markup.
## Deeper functionality
TRMNL is committed to making [Liquid templating](https://help.trmnl.com/en/articles/10671186-liquid-101) a formidable tool in plugin development. But let's not pretend JavaScript isn't a lot more powerful. As plugins become more advanced we hope to reduce the learning curve with data parsing inside this sandbox versus alongside your markup.
# Usage notes
- The Node.js runtime lives inside an [isolated-vm](https://github.com/laverdet/isolated-vm).
- Execution timeout is capped at 1 second.
- Internet access is disallowed -- sorry, no Underscore.js.
# Troubleshooting
*How to access other variables, e.g. `##{{ trmnl }}` form field values?*
The `trmnl` global variable node is available as `input.trmnl`. To leverage a custom form field value with keyname `genre`, for example:
function transform(input) {
let userFilter = input.trmnl.plugin_settings.custom_fields_values.genre;
return {
// parsed payload that leverages userFilter goes here
}
}
*How can I debug my JavaScript?*
We suggest retrieving a (large, realistic) payload independently of the TRMNL platform, then writing a parser separately. Enable [debug logs](https://help.trmnl.com/en/articles/11586187-debugging-private-plugins) for lightweight hints at what may be causing issues, for example a missing semi-colon. In the future we hope to expose more native JavaScript exceptions.
# Usage examples
## Limiting the Size of an Array of Objects
**Data**
Inside *Your Variables* you have `##{{ actions }}` (seen below), a top-level key in the JSON you are *polling*; it is an array of objects. There are other top-level keys in the JSON as well, but we only want to *Transform* this one and leave the rest untouched.
[
{
actionId: 1000
...
},
{
actionId: 1001
...
},
...
]
**Transform**
- `data` is the name of the new object that will be present after the transform, aka `##{{ data }}`.
- `...input` makes sure to include all other key:value pairs.
- `input.actions.slice(0, 30)` returns only the first 30 entries in the array.
function transform(input) {
return {
data: {
...input,
actions: input.actions.slice(0, 30)
}
};
}
## Removing a Property
**Data**
We want to *Transform* by removing the top-level property `synonyms`.
{
id: 12345,
word: "payload",
definition: {...},
examples: [{...},{...},{...}],
synonyms: [{...},{...},{...}],
metadata: {...}
}
**Transform**
- `data` is the name of the new object that will be present after the transform, aka `##{{ data }}`.
- `synonyms` is extracted as its own variable, which is ignored.
- `...newInput` variable contains the remaining properties.
function transform(input) {
const {synonyms, ...newInput} = input;
return {
data: newInput
};
}
# What's next?
JavaScript is the *lingua franca* of web development. Still you may prefer Ruby, its little brother Python, or Java.
TRMNL is open to extending this sandbox to more runtimes (languages) to make plugin development feel native to developers of all technical backgrounds.
---
# TRMNL Mounting Options
## TRMNL (OG)
While the TRMNL (OG) has a built-in triangle cutout for easy wall mounting with a nail or a [3M Sawtooth Picture Hanger Strip](https://www.command.com/3M/en_US/p/d/b10014175/), the power of TRMNL is the ability to go anywhere a WiFi signal will reach and provide months of uninterrupted value.
To assist with that, the community has designed a [variety of mounts](https://github.com/usetrmnl/mounts). If you do not have access to a 3d printer, check your local library or maker space for a free or low-cost option!
Of course, you could also do more direct modifications, such as gluing magnets onto the back of the device to attach the device to a refrigerator. The TRMNL (OG) case is a soft-touch ABS plastic, so a **CA (Cyanoacrylate) Glue**, more commonly known as "Super Glue", is a great option for attaching magnets permanently.
## TRMNL X
The TRMNL X introduces even more flexibility with mounting solutions designed to fit any space. For those seeking a minimalist, low-profile setup, the device can be paired with an adhesive magnetic or metal sticker. This allows the X to snap directly onto the surface for an almost completely flush finish. *For thinner plates meant for phones, you **must** double-stack them to create enough ferrous material for our strong magnet to hold!*
- [Amazon.com](https://www.amazon.com/dp/B00O2HYV7K) - (customer/TRMNL tested) 2 Rectangle Metal Plates stacked (discard 2 small circle plates)
- [Amazon.eu](https://amzn.eu/d/08Itom5T) - ([customer found](https://www.reddit.com/r/trmnl/comments/1uc5jby/comment/ot2w4qk/?share_id=3eAeyD8bhix9TZRP6_bmQ)) 3 double-stacks next to each other (6 of 10 in package used)
- [Amazon](https://www.amazon.com/dp/B0GYNNND64) - ([customer found](https://www.reddit.com/r/trmnl/comments/1uc5jby/comment/ot8ozgf/?share_id=vEjkkS4BFpplo56g15lhz)) Single thick disk; package of 4, best for multiple devices
Alternatively, for a more permanent installation, a dedicated U-mount is available that can be screwed directly to the wall. The X Dock snaps securely into this mount, providing a sturdy 20mm standoff.
---
# Proton Calendar
Connecting your Proton Calendar to TRMNL takes just a few seconds.
## **Step 1 - Visit the Proton Calendar Plugin**
Inside TRMNL, navigate to Plugins > Proton Calendar.
## **Step 2 - Connect Proton Calendar**
**Note**: *Enabling this plugin requires either a) publishing your Proton calendar to an anonymized "ICS" style format, or b) using the [TRMNL Companion iOS App](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).*
**Option A** (published ICS link)
This approach is similar to publishing an unlisted YouTube video or "view with link" permission Google Docs file. If your calendar has sensitive data you may limit the published permissions to only expose timestamps, not event details.
Inside your Proton Calendar, follow [these steps from the official website](https://proton.me/support/share-calendar-via-link) to create a shareable URL. Copy this URL to your clipboard.
**Option B** (iPhone / iPad app)
Download and install the [TRMNL Companion iOS App](https://apps.apple.com/us/app/trmnl-companion/id6752111280) to your phone or iPad, then move ahead to Step 3.
## **Step 3 - Configure Plugin**
Back inside TRMNL, paste your published calendar link into the ICS input field (Option A only). Your full URL should begin with "https". You can attempt to visit it in your web browser -- it should download an ".ics" file to your computer which you can safely delete.
Next you may set a handful of layout and other preferences.
**Time Format**
24 hours vs 12 hour (AM/PM) style.
**Include Description**
If "Yes," a truncated preview of the event's description field will be shown beneath the event title. Note that on the "default" Layout (explained below), setting this option to "No" will ~double the number of events shown thanks to saved space.
**First Day of Week**
Applies only to the "month" view. Tis allows you to set which day of the week is represented by the first column, furthest to the left. Defaults to Sunday.
**Ignored Phrases**
Supports a list of words that will be used to ignore events based on title or description content.
**Layout**
- Default (upcoming events for today, tomorrow, next day)
- Week (5-7 days upcoming)
- Month (this calendar month)
Note that only "Week" and "Month" layouts support multi-day events, but every layout supports all-day events.
**Example** - Week layout with multi-day event ("Michael OOO")
**Example** - Default layout with all-day event ("Michael OOO")
**Example** - Month layout with multi-day event:
## **Step 4 - You're Done!**
Based on your device's refresh settings, you'll begin seeing Proton Calendar events very soon.
Note: while we fetch data according to your refresh interval, TRMNL only generates new screen renders if the data fetched contains changes. Thus setting your layout to "Default" (which shows 2-3 days) but updating an event 4+ days in the future will not trigger a screen refresh.
Stay focused.
---
# Apple Calendar
Connecting your Apple Calendar to TRMNL takes just a few seconds.
## **Step 1 - Visit the Apple Calendar Plugin**
Inside TRMNL, navigate to Plugins > Apple Calendar.
## **Step 2 - Connect Apple Calendar**
**Note**: *You may enable this plugin either by a) publishing your Apple calendar to an anonymized "ICS" style format, or b) using the [TRMNL Companion iOS App](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).*
**Option A** (published ICS link)
From an iPhone or iPad (if you don't use a Mac computer):
1. Open the Calendar app
2. Tape Calendars, then tap the (i) next to the iCloud calendar you want to share
3. Turn on Public Calendar, then tap Share Link to copy or send the URL
([source](https://support.apple.com/guide/iphone/share-icloud-calendars-iph7613c4fb/ios))
From a Mac computer, open your Apple Calendar client.
Ctrl-click on the calendar and check the Public Calendar box. After a moment (you may need to navigate away, then re-open this pane) a URL will appear below.
Copy this long URL value to your clipboard.
**Option B** (iPhone / iPad app)
Download and install the [TRMNL Companion iOS App](https://apps.apple.com/us/app/trmnl-companion/id6752111280) to your phone or iPad, then move ahead to Step 3.
## **Step 3 - Configure Plugin**
Back inside TRMNL, paste your published calendar link into the ICS input field. Important: replace the "webcal" text with "https" so your full URL looks like `https://p44-caldav.icloud.com/published....`.
Next you may set a few preferences.
**Time Format**
24 hours vs 12 hour (AM/PM) style.
**Include Description**
If "Yes," a truncated preview of the event's description field will be shown beneath the event title. Note that on the "default" Layout (explained below), setting this option to "No" will ~double the number of events shown thanks to saved space.
**First Day of Week**
Applies only to the "month" view. Similar to native Outlook Calendar (month view), this allows you to set which day of the week is represented by the first column, furthest to the left. Defaults to Sunday.
**Ignored Phrases**
Supports a list of words that will be used to ignore events based on title or description content.
**Layout**
- Default (upcoming events for today, tomorrow, next day)
- Week (5-7 days upcoming)
- Month (this calendar month)
Note that only "Week" and "Month" layouts support multi-day events, but every layout supports all-day events.
**Example** - Week layout with multi-day event ("Michael OOO")
**Example** - Default layout with all-day event ("Michael OOO")
**Example** - Month layout with multi-day event:
## **Step 4 - You're Done!**
Based on your device's refresh settings, you'll begin seeing Apple Calendar events very soon.
Note: while we fetch data according to your refresh interval, TRMNL only generates new screen renders if the data fetched contains changes. Thus setting your layout to "Default" (which shows 2-3 days) but updating an event 4+ days in the future will not trigger a screen refresh.
Stay focused.
---
# Fastmail Calendar
Connecting your Fastmail Calendar to TRMNL takes just a few seconds.
## **Step 1 - Visit the Fastmail Calendar Plugin**
Inside TRMNL, navigate to Plugins > Fastmail Calendar.
## **Step 2 - Connect Fastmail Calendar**
**Note**: *You may enable this plugin either by a) publishing your Fastmail calendar to an anonymized "ICS" style format, or b) using the [TRMNL Companion iOS App](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).*
**Option A** (published ICS link)
Inside Fastmail, navigate to Settings > Calendar. Beside the calendar you wish to integrate with TRMNL, click the Edit & Share button.
In the **Publish** section and click the toggle button next to Full event details or Free/busy information, depending on what information you wish to share. ([source](https://www.fastmail.help/hc/en-us/articles/360060590793-Calendar-sharing))
Click "Copy to clipboard" below this long URL.
**Option B** (iPhone / iPad app)
Download and install the [TRMNL Companion iOS App](https://apps.apple.com/us/app/trmnl-companion/id6752111280) to your phone or iPad, then move ahead to Step 3.
## **Step 3 - Configure Plugin**
Back inside TRMNL, paste your published calendar link into the ICS input field. Important: replace the "webcal" text with "https" so your full URL looks like `https://user.fm/calendar/v1-....`.
Next you may set a few preferences.
**Time Format**
24 hours vs 12 hour (AM/PM) style.
**Include Description**
If "Yes," a truncated preview of the event's description field will be shown beneath the event title. Note that on the "default" Layout (explained below), setting this option to "No" will ~double the number of events shown thanks to saved space.
**First Day of Week**
Applies only to the "month" view. Tis allows you to set which day of the week is represented by the first column, furthest to the left. Defaults to Sunday.
**Ignored Phrases**
Supports a list of words that will be used to ignore events based on title or description content.
**Layout**
- Default (upcoming events for today, tomorrow, next day)
- Week (5-7 days upcoming)
- Month (this calendar month)
Note that only "Week" and "Month" layouts support multi-day events, but every layout supports all-day events.
**Example** - Week layout with multi-day event ("Michael OOO")
**Example** - Default layout with all-day event ("Michael OOO")
**Example** - Month layout with multi-day event:
## **Step 4 - You're Done!**
Based on your device's refresh settings, you'll begin seeing Fastmail Calendar events very soon.
Note: while we fetch data according to your refresh interval, TRMNL only generates new screen renders if the data fetched contains changes. Thus setting your layout to "Default" (which shows 2-3 days) but updating an event 4+ days in the future will not trigger a screen refresh.
Stay focused.
---
# Outlook Calendar
Connecting your Outlook 365 Calendar to TRMNL takes just a few seconds.
## **Step 1 - Visit the Outlook Calendar Plugin**
Inside TRMNL, navigate to Plugins > Outlook Calendar.
## **Step 2 - Connect Outlook Calendar**
**Note**: *you may enable this plugin either by a) directly connecting your Outlook Calendar, b) publishing your Outlook calendar to an anonymized "ICS" style format, or c) using the [TRMNL Companion iOS App](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).*
**Option A** (native integration)
Follow the steps on the plugin screen (Step 3) to connect your calendar.
**Option B** (published ICS link)
First log in to your Outlook email at [outlook.office365.com](https://outlook.office365.com). Visit your calendar on the left side navigation, then click the settings gear cog in the top right corner.
Click the "Shared calendars" link, then underneath the "Publish a calendar" heading you can select a calendar + set permissions.
Click Publish, then click to copy the 2nd link labeled ICS.
If you don't see the ability to Share or Publish a calendar, first make sure that calendar sharing is enabled at the administrator level. The following view is accessible from your Office 356 admin area:
After confirming that users may share their calendars, retry the beginning of Step 2 above.
**Option C** (iOS / iPad app)
Download and install the [TRMNL Companion iOS App](https://apps.apple.com/us/app/trmnl-companion/id6752111280) to your phone or iPad, then move ahead to Step 3.
## **Step 3 - Configure Plugin**
Back inside TRMNL, paste your published calendar link into the ICS input field (Option A only).
Next you may set a few preferences.
**Time Format**
24 hours vs 12 hour (AM/PM) style.
**Include Description**
If "Yes," a truncated preview of the event's description field will be shown beneath the event title. Note that on the "default" Layout (explained below), setting this option to "No" will ~double the number of events shown thanks to saved space.
**First Day of Week**
Applies only to the "month" view. Similar to native Outlook Calendar (month view), this allows you to set which day of the week is represented by the first column, furthest to the left. Defaults to Sunday.
**Ignored Phrases**
Supports a list of words that will be used to ignore events based on title or description content.
**Layout**
- Default (upcoming events for today, tomorrow, next day)
- Week (5-7 days upcoming)
- Month (this calendar month)
Note that only "Week" and "Month" layouts support multi-day events, but every layout supports all-day events.
**Example** - Week layout with multi-day event ("Michael OOO")
**Example** - Default layout with all-day event ("Michael OOO")
**Example** - Month layout with multi-day event:
## **Step 4 - You're Done!**
Based on your device's refresh settings, you'll begin seeing Outlook Calendar events very soon.
Note: while we fetch data according to your refresh interval, TRMNL only generates new screen renders if the data fetched contains changes. Thus setting your layout to "Default" (which shows 2-3 days) but updating an event 4+ days in the future will not trigger a screen refresh.
Stay focused.
---
# Nextcloud Calendar
Connecting your Nextcloud Calendar to TRMNL takes just a few seconds.
## **Step 1 - Visit the Nextcloud Calendar Plugin**
Inside TRMNL, navigate to Plugins > Nextcloud Calendar.
## **Step 2 - Connect Nextcloud Calendar**
**Note**: *Enabling this plugin requires either a) publishing your Nextcloud calendar to an anonymized "ICS" style format, or b) using the [TRMNL Companion iOS App](https://help.trmnl.com/en/articles/12294875-trmnl-companion-for-ios).*
**Option A**
This approach is similar to publishing an unlisted YouTube video or "view with link" permission Google Docs file. If your calendar has sensitive data you may limit the published permissions to only expose timestamps, not event details.
Inside your Nextcloud Calendar, open the share menu and click the "+" next to the "Share link" option ([source](https://docs.nextcloud.com/server/19/user_manual/pim/calendar.html#publishing-a-calendar)). Copy this URL to your clipboard.
**Option B**
Download and install the [TRMNL Companion iOS App](https://apps.apple.com/us/app/trmnl-companion/id6752111280) to your phone or iPad, then move ahead to Step 3.
## **Step 3 - Configure Plugin**
Back inside TRMNL, paste your published calendar link into the ICS input field. Your full URL should begin with "https". You can attempt to visit it in your web browser -- it should download an ".ics" file to your computer which you can safely delete.
Next you may set a few preferences.
**Time Format**
24 hours vs 12 hour (AM/PM) style.
**Include Description**
If "Yes," a truncated preview of the event's description field will be shown beneath the event title. Note that on the "default" Layout (explained below), setting this option to "No" will ~double the number of events shown thanks to saved space.
**First Day of Week**
Applies only to the "month" view. Tis allows you to set which day of the week is represented by the first column, furthest to the left. Defaults to Sunday.
**Ignored Phrases**
Supports a list of words that will be used to ignore events based on title or description content.
**Layout**
- Default (upcoming events for today, tomorrow, next day)
- Week (5-7 days upcoming)
- Month (this calendar month)
Note that only "Week" and "Month" layouts support multi-day events, but every layout supports all-day events.
**Example** - Week layout with multi-day event ("Michael OOO")
**Example** - Default layout with all-day event ("Michael OOO")
**Example** - Month layout with multi-day event:
## **Step 4 - You're Done!**
Based on your device's refresh settings, you'll begin seeing Nextcloud Calendar events very soon.
Note: while we fetch data according to your refresh interval, TRMNL only generates new screen renders if the data fetched contains changes. Thus setting your layout to "Default" (which shows 2-3 days) but updating an event 4+ days in the future will not trigger a screen refresh.
Stay focused.
---
# Replacement parts
We agree with iFixIt - **you have the right to repair**. Below are a few ways we can help you maintain a functional device, forever.
**Quick Links:**
- [TRMNL OG](#h_7a9101347e) (7.5")
- [TRMNL X](#h_075d03a2aa) (10.3")
- [Other Accessories](#h_c93a1ccd53)
If you know what you're doing, shop for replacement parts here:
# TRMNL OG
## EPD (screen)
We'll sell you one for $50 (including shipping), or you can get [this Waveshare one](https://amazon.com/dp/B075R69T93/) in US Store or this [UK Version](https://www.amazon.co.uk/dp/B0BD5NQPH4). You can order directly from our shop (for [OG](https://shop.trmnl.com/products/replacement-og-epd) or for [BWRY](https://shop.trmnl.com/products/replacement-bwry-epd)) and see our [disassembly](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl) + [assembly](https://help.trmnl.com/en/articles/10003236-how-to-assemble-your-trmnl) guides.
## Case enclosure
Have access to a 3D printer? Check out the open source designs [here](https://github.com/usetrmnl/mounts/tree/main/resources/og_model).
Alternatively we'll send you a native case for $15 + shipping. Email and see our [disassembly](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl) + [assembly](https://help.trmnl.com/en/articles/10003236-how-to-assemble-your-trmnl) guides.
## Battery
TRMNL (OG) maximum space for the battery is **62x31x10mm** and uses a Molex Picoblade (1.25mm pin) configuration. it should be installed **close to the PCB and furthest from the kickstand**.
### **FITS INSIDE TRMNL OG and OG BWRY**
Unfortunately we can't send "loose" batteries, but [this one](https://www.amazon.com/dp/B0BHQWMKVN/) (1000mAh) will fit and has been purchased/tested by TRMNL previously.
#### Additional US Battery Options
This 2000mA option would be fit and work properly: [Amazon - B0DTHG757X](https://www.amazon.com/MakerFocus-Rechargeable-Protection-Insulated-Development/dp/B0DTHG757X)
Here is one more that we have not purchased but hardware team validated (based on listing) as compatible on June 26, 2026: [2000mAh #103450](https://www.amazon.com/Qimoo-103450-3-7V-2000mAh-Battery/dp/B0FT3CZ6N1?th=1).
#### **DOES NOT FIT INTERNALLY**
Alternatively [this option](https://www.amazon.com/dp/B0DXSJLHRB) provides 3x the power (3000mAh), but does not fit in the native TRMNL enclosure. If you prefer a longer battery life, consider pairing the larger battery with one of our many [3D printable case designs](https://github.com/usetrmnl/mounts). Watch [this assembly video](https://www.youtube.com/watch?v=Z64FDqIaKpg) for installation steps.
### Additional EU Battery Options
This 1200mAh option would fit (it's less wide, so it fits despite not having the dimensions mentioned above) and work properly: ****
This large capacity version would be a very tight fit, it already has a proper connector, a large capacity, and an integrated protection board ****
## PCB (main board)
We'll send you one for $20 (including shipping). Just order directly from our store (for [OG](https://shop.trmnl.com/products/replacement-og-pcb) or for [BWRY](https://shop.trmnl.com/products/replacement-bwry-pcb)) and watch [this assembly video](https://www.youtube.com/watch?v=Z64FDqIaKpg) for installation steps. We will also flash the latest firmware on your behalf, or you can do so in 1 click [here](https://trmnl.com/flash).
## Kickstand
We'll send you a new one for free, plus a little help for postage. Email and [follow these steps](https://help.trmnl.com/en/articles/11360753-how-to-replace-your-kickstand) to replace it.
## PCB Screws
Our OG model uses `M3-0.5 x 4mm` screws to attach the PCB to the back enclosure. Get more with the same specs [here](https://www.amazon.com/dp/B0C1SGKGWZ).
# TRMNL X
## EPD
We can sell you a replacement screen for $70 [right in our shop](https://shop.trmnl.com/products/replacement-x-epd). Feel free to follow our [re-assembly guides](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl), or contact support if you want to ship the device back to our warehouse so we can do the repair for you.
## PCB Screws
All screws are torx, stainless steel, magnetic
- **Case**: `M2x8` conical head x12
- **Charging Dock**: `M2x5` conical head x4
- **Kickstand**: `M2.5x10` flat-head x2
- **PCB** (internal): `M2.5x4` flat-head x5
***Note: The above image has imperfectly aligned screws which can lead to dock connectivity issues.***
# Other accessories
**USB-C**
Any cable with power + data should work, but we can sell you one for $5:
**Microfiber cloth / Anti-glare film**
We'll send you a new one for free, plus a little help for postage. Email .
---
# Gaffa
Gaffa has partnered with TRMNL to give users 6 months of **free** usage. Thereafter, your personal API key can be obtained from: [https://gaffa.dev](https://gaffa.dev/?campaign=trmnl&utm_source=trmnl&utm_medium=referral&utm_campaign=trmnl-integration&utm_content=homepage).
Keeping focused and up to date with the news takes just a few seconds. To see how this work, check out the video below:
[https://www.youtube.com/embed/eIGvUttZ_P8?rel=0](https://www.youtube.com/embed/eIGvUttZ_P8?rel=0)
## **Step 1 - Visit the Gaffa Plugin**
Inside TRMNL, navigate to Plugins > Gaffa.
## **Step 2 - Configure Plugin**
- News Website URL - Paste the URL of the site you have chosen.
- Custom Prompt - The instructions that the Gaffa AI will use to summarize the news.
- API key (Optional) - After the 6 months trial period, your personal API key can be obtained from: [https://gaffa.dev](https://gaffa.dev/?campaign=trmnl&utm_source=trmnl&utm_medium=referral&utm_campaign=trmnl-integration&utm_content=homepage).
## **Step 3 - You're Done!**
Give TRMNL and Gaffa a couple of minutes to display your extracted data on the Plugin configuration page and then your TRMNL. You'll begin seeing news content very soon.
---
# Home Assistant Screenshot
This plugin is a companion to the TRMNL Home Assistant [add-on](https://github.com/usetrmnl/trmnl-home-assistant) that captures screenshots of your dashboards, applies dithering algorithms optimized for e-paper screens, and sends them to TRMNL.
## Step 1 - Add the Plugin
Inside TRMNL, navigate to Plugins > Home Assistant Screenshot.
## Step 2 - Copy Your Webhook URL
After saving, you'll see a unique Webhook URL in your plugin settings.
This URL is private to you. Treat it like a password. Anyone with this URL can update your display.
## Step 3 - Installing TRMNL HA
### OPTION 1 - Add-on store
Navigate to the TRMNL Home Assistant [add-on](https://github.com/usetrmnl/trmnl-home-assistant) and click:
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fusetrmnl%2Ftrmnl-home-assistant)
OR
In Home Assistant, go to Settings > Add-ons > Add-on Store.
Click the three-dot menu, select "Repositories", and add:
Then find "TRMNL HA" in the list and click Install.
### OPTION 2 - Stand-alone
If you run Home Assistant in Docker (HA Container), you don't have the add-on store. Run TRMNL HA as a separate container. See step 4 for details on your the HOME_ASSISTANT_URL and ACCES_TOKEN values.
docker run -d --name trmnl-ha \
-e HOME_ASSISTANT_URL=http://YOUR_IP:8123 \
-e ACCESS_TOKEN=your_token \
-p 10000:10000 \
ghcr.io/usetrmnl/trmnl-ha-amd64:latest
Note: For ARM64 (Pi 4/5, Apple Silicon), use this image url: `ghcr.io/usetrmnl/trmnl-ha-aarch64:latest`
Access the Web UI at `http://your-server:10000`
Full Docker setup:
## Step 4 - Configure
Go to Settings > Add-ons > TRMNL HA > Configuration.
Create a Long-Lived Access Token (Profile > Long-Lived Access Tokens) and paste it in the "access_token" field. Leave home_assistant_url as the default `http://homeassistant:8123` unless you have connection issues. If it fails, use your device's IP address (e.g., `http://192.168.1.100:8123`) as the default won't work from inside containers.
Save and start the add-on.
Add HA to the sidebar by using the dedicated toggle.
## Step 5 - Configure
Open "TRMNL HA" from the sidebar, create a schedule, pick your device preset using the preview to confirm it looks good on device, and paste your Webhook URL into the allocated field.
## Step 6 - You're Done!
Based on your schedule and device refresh settings, you'll see your dashboard on the display very soon.
Note: Use "Send Now" to test immediately. Device presets auto-configure the right size and dithering for your display.
## Troubleshooting
**Display not updating?**
Check that your schedule is enabled and the webhook URL is correct. Click "Send Now" to test. Also check add-on logs at Settings > Add-ons > TRMNL HA > Log
**Screenshot looks wrong?**
Try increasing the "Wait" time for complex dashboards. Use a high-contrast theme like "Graphite". Fine-tune the image generation.
**HA connection failing?**
Verify your access token and home assistant url is correct. The Web UI shows connection status with details.
**Need more help?**
Full docs:
---
# Developer Edition Features
From plugin development, API access, device logs, to Discord access and early release firmware, there are multiple benefits to supporting TRMNL with a developer edition license.
This feature was announced on November 4, 2024:
# What is Developer Edition?
Developer Edition is an optional add-on for devices that can be purchased [individually](https://shop.trmnl.com/products/developer-edition), as part of a bundle ([Clarity Kit](https://shop.trmnl.com/products/clarity-kit)), or in-app from your [device settings](https://trmnl.com/devices/current/edit) page.
**Developer Edition licenses are linked to devices, not users.** For more information on deciding if and how many licenses you may want, see [this article](https://help.trmnl.com/en/articles/11629486-calculating-byod-and-dev-edition-add-ons).
Here are some benefits of adding Developer Edition to your device:
- [Private Plugin Development](https://help.trmnl.com/en/articles/9510536-private-plugins)
- [Publishing Community Recipes](https://help.trmnl.com/en/articles/10122094-plugin-recipes)
- API Keys
- Programmatic [Screen Manipulation](https://docs.trmnl.com/go/private-api/screens?q=API)
- [Mirror to iPad](https://help.trmnl.com/en/articles/11647459-trmnl-for-ipad) and Android
- Device Logs and Render [Debug Logs](https://help.trmnl.com/en/articles/11586187-debugging-private-plugins)
- Discord Community
- Beta release option (Staging) for new Firmware
- Supporting TRMNL's developer community
# Developer Edition vs BYOD License
Developer Edition is an add-on for TRMNL native devices. BYOD licenses are for DIY (BYOD) devices and include the same features as Developer Edition, plus access to the web platform itself.
**You do not need** BYOD + Developer Edition.
---
# Device battery FAQ
While logged into TRMNL you can see WiFi + battery strength from your device picker or [all devices](https://usetrmnl.com/devices) view:
Calculating remaining battery power is a simple equation, but it becomes less reliable as energy is drained and playlist refresh rates change.
## **Low battery notification**
When your TRMNL device has < 10% energy remaining, you'll be notified by the following screen on your display:
In the future you may be able to customize this screen. ;)
## **Charging your TRMNL**
Select your device...
**## OG**
The OG model has 2 battery options, 1800mAh (default) and 2500mAh (upgraded).
Assuming 0% energy:
- 1800mAh - 3.5 hours to full charge
- 2500mAh - 5 hours to full charge
While charging, an LED on your device (above the USB-C port) will be green. When the device finishes charging, the LED will turn off.
**## Model X**
The X model has 2 battery options, 6000mAh (default) and 12000mAh (upgraded).
Assuming 0% energy:
- 6000mAh - 5 hours to full charge
- 12000mAh - 10 hours to full charge
While charging, a "now charging" indicator will appear in the web interface and a charging icon will show on the loading screen:
## **Percent charged versus voltage**
Select your device...
**## OG**
The `X%` charge value in your TRMNL account is approximated as follows:
def percent_charged
self.battery_voltage ||= 100
pct_charged = ((battery_voltage - 3) / 0.012).round(2)
case pct_charged
when 88..1000000 # 4.08v follows a full charge
100.0
when 85..88
95.0
when 83..85
90.0
when 10..83
pct_charged.round(2)
when -1000000..10
1.0
end
end
If you instead prefer to know your device's last reported voltage, just hover the battery icon(s) for a read out:
**## Model X**
The Model X has a "gas gauge" chip built into the device, which reports charge levels down to 1/10th of 1%.
This value, e.g. "42.4" or "95.3," is reported to TRMNL's web app whenever your device wakes up to request new content.
Examine your precise battery charge amount by visiting your device's battery settings page:
## **Understanding power consumption**
One refresh cycle takes 32.8mA over a period of 24s. In this time the device will ping the TRMNL server, download an image, and refresh the display.
If there is no need to download and refresh the display with new image content -- for example if an image is not returned, or the returned image matches what is currently displayed -- power consumption will be around 20% of the above calculation.
For more comprehensive details on TRMNL device power consumption, see here:
## **Determining your battery type**
TRMNL devices offer multiple battery size options. If you are not sure which one came in your device, or simply want to verify:
**## OG**
[Disassemble your device](https://help.usetrmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl) (unless it is a clear case)
1. Compare your battery's shape and size to the photo below
2. For further confirmation, detach the battery (will require new tape) and look on the under side, which will show the mAh value
Top: 1800mAh
Bottom: 2500mAh
**## Model X**
Your Model X has 1 or 2 batteries, both of the same size and shape and power (6000mAh).
If you have a clear enclosure, count them from looking through the back. Otherwise, you are welcome to disassemble your TRMNL with the included T6 screwdriver, or ask our support team to check the battery count being reported to our database by your device's firmware.
---
# Put your TRMNL in "flashing mode"
For a TRMNL PCB to accept data transfer via a USB-C cable, it needs to be in "boot mode," also sometimes called flashing mode.
**Quick Links:**
- [TRMNL OG](#h_152eb7260b)
- [TRMNL X](#h_96e07a072e)
- [Seeed Studio](#h_61ce847adc)
# TRMNL OG
If you have a 7.5" native TRMNL device...
## **Basic method**
1. Plug a data enabled USB-C cable into your TRMNL.
2. Turn off the TRMNL.
3. While pressing down on the circular ("boot") button below the power switch, turn the device back on.
4. Let go of the boot button.
## **Stubborn method** (if Basic does not work)
1. Turn off the device.
2. Hold down on the circular ("boot") button below the power switch.
3. While pressing down, plug a data enabled USB-C cable into your TRMNL.
4. Keep pressing the boot button and turn the device back on.
5. Let go of the boot button.
If the above methods both fail, turn off your device for 10-15 minutes and try again. There may be a static charge or heat buildup that needs to dissipate.
---
# TRMNL X
This model has 2x modes that bypass the firmware, "[reset](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device#h_ee5e7cb9ad)" and "flashing."
## Flashing / Boot Mode
Rotate your charging dock so the flat (back) side is facing the back of your TRMNL. Then, "hover" the dock from the bottom left corner to the middle, and plug it back in.
A USB-C cable should also be attached if you are flashing new firmware from or your own development environment.
Here's a couple angles in action. First, from above:
[https://www.youtube.com/embed/D7p5Yk-fOuA?rel=0](https://www.youtube.com/embed/D7p5Yk-fOuA?rel=0)
Next, from the side:
[https://www.youtube.com/embed/fF1LuqHe9t4?rel=0](https://www.youtube.com/embed/fF1LuqHe9t4?rel=0)
Complete this motion at a 2-3 second speed. If you do it too quickly, each of the 2x reset points may not recognize the magnet inside your dock.
**QUICK TIP:** The touchbar is disabled in flashing mode, so you can confirm it is done correctly by pressing the touchbar. If the screen reacts, you are NOT in flashing mode. *Please note that the touchbar will **also not** react when [powered off](https://help.trmnl.com/en/articles/14553723-turning-off-the-trmnl-x) or on the loading screen (glyph showing in the bottom right).*
**Reminder**: You will then need to connect your USB-C cable to the dock and the dock to the back of your device so that the pins connect to the TRMNL X so it can be [flashed](https://trmnl.com/flash) from your computer.
If the device is not recognized (`USB JTAG/serial...`) when you click **Connect** on the flashing page, it is *not* in Boot Mode or does not have a good connection through the dock.
- **We have a guide for [how to get a good connection](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device#h_d6c1eff6f6).**
- **Soft Reset / Restart device:** [Perform a soft reset](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device#h_ee5e7cb9adhttps://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device#h_ee5e7cb9ad) and then repeat the boot mode steps.
---
# Seeed Studio
Check out [our Seeed guide](https://help.trmnl.com/en/articles/12408721-seeed-studio-devices-xiao-diy-kit-and-reterminal) for steps to understand the buttons and how to enter flashing mode on your DIY Kit, E1001, E1002, and other devices.
---
# How to Disassemble your TRMNL
# TRMNL OG / BWRY
This video walks through taking apart your device in under 1 minute. We recommend first watching the entire video, then trying it yourself.
You may also get tips from our other guide: [how to assemble your TRMNL](https://help.trmnl.com/en/articles/10003236-how-to-assemble-your-trmnl).
[https://www.youtube.com/embed/uvF9No5Z1_A?rel=0](https://www.youtube.com/embed/uvF9No5Z1_A?rel=0)
For detailed photos and next steps see our guide on [replacing internal components](https://help.trmnl.com/en/articles/12082769-how-to-replace-your-internal-components).
# TRMNL X
Below are steps to take apart your X for the purposes of replacing a screen or magnet. You may also want to check out our [assembly video](https://www.youtube.com/watch?v=RdfMNQTyQQ8).
### Before we begin
First [turn off the device](https://help.trmnl.com/en/articles/14553723-turning-off-the-trmnl-x).
When you see a welcome screen, you're ready.
## Take apart the main enclosure
Flip over the device and use the included screwdriver from your X packaging to remove the 12 screws.
Put these screws at least a few inches away from your device before opening it up, as the interior N52 magnet is strong enough to create a little dance show on your table top.
Flip your device back over, so that the screen is facing you, then turn it 90 degrees counter-clockwise. The top of the screen will be to your left.
Now, slowly open the device like a book, leaving the bottom of the front enclosure touching (in contact with) the bottom of the back enclosure.
Note how the bottom edge of the front + back enclosures must stay very close together, as there are 2x ribbon cables connecting the screen and touchbar to the main PCB.
It's likely that 1 of the 2 silicone gaskets will detach from the front enclosure. This is fine, we'll fix it later.
### PCB Screws
With your device open, our first step is to remove the 5x screws from the main PCB in the center of your back enclosure.
Here again, use the screwdriver included in your X packaging.
With all 5x screws lose, place them to the side in a fresh pile as they are a different length than the 12x case screws we removed earlier. You should now be able to lift up the PCB.
Your main board (PCB) is tethered in either 5 or 6 locations:
- Touchbar - right side, narrow white ribbon
- Screen - right side, wide yellow/orange ribbon
- Main Battery - center, above the "T" in TRMNL
- Extra Battery (Optional) - center, above the "R" in TRMNL
- WiFi Antenna - center, above the "M" and "L" in TRMNL
### Removing the Batteries
Before continuing we need to remove the batteries.
One at a time, firmly grip the top and bottom of the battery wires as close to the board as possible. You may lift the board 1-2 inches to make this step easier.
For future reference, the "MAIN BAT" (main battery) is the one directly to the left of your main board, and the "EXTRA BAT" (extra battery) is the one at the bottom of this image.
With both batteries removed we're ready to make a repair.
## Replace your screen (EPD)
Put your finger behind the corner of your display, front enclosure, and push the display upwards. In the orientation depicted below you'd be pushing from the top right, underneath the screen.
Let the screen drive the display board into a similarly flipped position, ending like this.
Let's zoom in to better understand what we're working with.
Detach the screen from your main PCB by putting your finger underneath the black gate and pushing upward. You will feel a light click.
Once the gate is open, the ribbon should fall out without any pull force.
## Connecting your New Screen
Put your new screen in the same position as the old one -- with the display itself facing upwards (towards you) and the ribbon on the far right side.
Firmly grip the ribbon cable with your thumb + index finger and insert it into the main PCB cable connector.
The most important thing to note at this step is ensuring the ribbon is inserted directly perpendicular (90 degrees) to the cable connector gate.
While holding the ribbon in place, use your other hand to click the gate connector back down over the cable.
Skip ahead to "Put together the main enclosure" below to complete your repair.
## Replace your Magnet
With your main board free to move, use either your finger nails or pair of tweezers (any thin instrument will suffice) to pry the back enclosure magnet from the donut shaped hole in the middle.
Once the magnet is removed, note the correct polarity for re-assembly.
The north pole (painted red) should be face down towards your table top.
If your magnet does not have a red marking, verify the correct orientation by ensuring that the side faces down is attracted to the side of your charging dock with pogo pins.
Your new magnet should already have silicone attached on both sides. If it doesn't, detach and re-use the silicone from the old magnet.
## Put together the main enclosure
After replacing your screen or magnet, re-connect the batteries. You may have 1 or 2 pending the options you chose at purchase.
The battery directly to the left of the main PCB is should be connected to the "MAIN BAT" bottom-most port in the photo below.
Each battery has 4x wires. The red wire should be on the same side as the WiFi antenna. In the photo above that means on the "top" side, and the black wire should face the batteries, on the "bottom" side.
### Reattach PCB and Alignment
It's time to re-attach the main PCB. There are 5x screws, let's align just the bottom left hole and add a screw.
Don't tighten this all the way - you may need to pivot (rotate) the board to align the other 4x holes. This is important for ensuring your charging dock's pogo pins make good contact with the opposite side of the PCB.
Next we need to re-position the screen and potentially re-insert the silicone gasket on the front half of the enclosure.
Gently grip the far right end of your touchbar (opposite of the white ribbon cable) and move it above + behind the screen.
### Fixing the Silicone Gasket
Now let's fix the silicone gasket if it fell out during the previous steps.
There are 2x gaskets, inner + outer, with slightly different lengths. If both of your gaskets fell out during the steps above, be sure to use the longer one on the outer edge and the shorter one for the inner edge as depicted here.
Apply minimal vertical (not lateral / sideways) pressure to insert your gasket back into the groove along the inner edge of your front enclosure.
If you stretch the gasket it will be too long and the extra slack won't have a home. Just push the gasket straight down into the groove, working your way around the perimeter.
Your final corner may be minimally stretched to create a tension fit.
### Reinstalling the Touchbar
Now, reposition your back and front enclosures side by side, like a book as in the earlier steps of this guide.
First re-install the touchbar by flipping it over from the left to the right side.
There are notches in the touchbar which will fit around the "ribs" of the front enclosure's bottom edge, depicted in the center above.
Carefully flip over your screen so that its bottom (white) edge sits on top of your touchbar.
## Adjusting the EPD and Ribbons
Do not attempt to close your device yet - this will crack your screen. Gently lift the screen's ribbon cable from underneath the center of its length to help the screen "sit" inside the enclosure, beside the 2x ribs and 2x threaded inserts on the front enclosure.
### Closing the Case
Begin slowly closing the case by lifting the back enclosure (left side above) as if you were closing an open book. The screen should naturally adjust to be sitting *beside* the 2x ribs and threaded insert standoffs.
Here's another angle. Note the visibility of the front enclosure ribs near the center of the image.
And here's a close-up... this step is very important but we're almost done. ;)
The same elements should be visible on all 4x sides of your front enclosure. You will see ribs and threaded insert standoffs beside your screen. If you do not, re-position the screen to fit snugly in the center. Otherwise it will crack.
Finally, inspect your closed device by ensuring no ribbon cables are pinched along the bottom edge. A small seam gap is OK and will be resolved by screws.
One screw at a time, connect the back enclosure to the front enclosure, starting along the bottom.
As you turn each screw, press gently beside it to help guide the 2x enclosures together and prevent stripping from uneven planes. Work your way around the enclosure until all 12x screws have been re-attached.
## Turn device back on
After assembling your device you should see the same welcome screen we started with. If it is not responsive after connecting the charging dock with power, perform a [soft reset](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device).
## Charging Dock
The charging dock contains a magnet and a PCB with pins and USB connection. It uses 4 screws to secure it, all accessible by the included screwdriver. If you need to access the device, hold it in your other hand so that your fingers ensure that unscrewing it does not apply pressure on the pins (*e.g.*, do not lay it pin down).
Here is the view with the cover removed:
Additional view with it opened like a book:
---
# How to replace your internal components
Swapping out an internal part like a PCB is a simple process, but should be done carefully to not damage other components in your TRMNL.
TRMNLs are built to last, but e-ink displays are fragile. So whatever you do, be very careful when handling the display module, especially when adding pressure.
# TRMNL X
Please see our [detailed guide](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl).
# TRMNL OG
First, turn off the device. Then [open it up](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl) (video guide), laying it flat like this:
## Removing the PCB
Next, use a small Phillips screwdriver to remove the 3x screws on your PCB. A flat-head also works, but you'll need to be careful to avoid stripping.
After removing all 3 screws, slowly lift the PCB board and detach the battery by pulling straight outward on both wires (red/black).
## Changing the battery
*If you are changing your battery*, be mindful that there is adhesive holding it in place, so carefully pry it up slowly using a flat, rounded-edge tool (puncturing the battery is hazardous!).
Then, continue to [reattach the wires once replaced and rescrew the PCB to the case](#h_53502e31e5).
## Disconnecting the PCB and the Screen
We're almost done. Use a fingernail or screwdriver to gently "open" the black gate, located along the back edge of where the ribbon cable attaches to your board. The gate, when closed, is parallel to the board.
This will allow the ribbon to slide out of the board with almost no effort. Notice that the open gate is now perpendicular to the board.
Here's another angle.
You may now replace the board or the screen.
## Replacing the Screen
If replacing the screen (EPD), you want to flex the **case** out of the way, not the screen, to remove it from its held position. The EPD should never have pressure placed on it at a single point, or it will cause it to break. Imagine a cupcake or loaf of bread in a pan; you don't want to squish it, so you adjust the *container* (the case), not the *object* (screen).
## Reattaching the Screen to the PCB
When reattaching the screen to the new or old PCB, you need to insert the ribbon cable again with the black gate open (perpendicular). When inserting the ribbon cable into your new PCB, be sure **not to insert it diagonally**.
Here's another example, with the board inserted at a diagonal angle. **This will not function.**
Insert the ribbon cable **straight**, but note that the gold section of the ribbon cable will *not* be completely covered by the white/black connector.
Seeing a narrow, even-width section of exposed gold pins is the expected installation. You can now lower the black gate to lock the cable in place.
## Reattaching the Battery
Lower the board back into place, re-attaching the battery. The red wire should be on the side of the USB-C cable.
Finally, add back the 3x screws. We recommend starting with this one first (farthest from battery connector), as it will help "seat" the PCB into the tight tolerances of the enclosure.
[Close your case](https://help.trmnl.com/en/articles/10003236-how-to-assemble-your-trmnl) (video guide) to complete this procedure. Turn the device back on, then re-pair WiFi and [add the device to your account](https://help.trmnl.com/en/articles/9416306-how-to-set-up-a-new-device) as if it were brand new.
---
# Understanding Color Palettes
TRMNL e-ink screens show images in shades of gray. Originally, only black and white were supported, but firmware upgrades have unlocked the ability to display 4 shades on both first-party and BYOD hardware. TRMNL X will display 16 shades, and some BYOD screens even go up to 256 grays.
Bigger palettes looks nicer, and are generally preferable. They allow the jaggy edges of fonts to be anti-aliased into smooth curves. More levels of information hierarchy can be represented. And of course, photos and screenshots look closer to their originals.
These advantages come with some tradeoffs. Here are some things to consider when deciding what color palette to use.
## Smaller Palette (e.g. Black & White)
- Pro: Smaller file size, which is quicker to download, prolonging battery life
- Pro: Faster transitions between screens
- Con: Simpler, less contrasty images
## Larger Palette (e.g. 4 grays, 16 grays)
- Pro: More appealing images
- Con: Larger file size, which takes longer to download, shortening battery life
- Con: Slower, more noticeable transitions between screens
## Changing the Palette
The preferred color palette can be adjusted per device, and also per playlist item for ultimate control. The change will take place the next time the plugin is refreshed.
For example, you might set the device to B&W for the highest performance most of the time, but override it to 4 shades of gray so your family photo looks its best.
To change the default color palette for the device, go to device settings and select "Color Palette".
To change the specific color palette for a playlist item, go to the Playlist, click the *three dots*, and hover over the *Presentation* option.
---
# Skipping Screens within Plugin Markup
Sometimes a plugin isn't relevant, such as in the off-season for your favorite sports team, or because it's not the right day of the week. Other times, you may want to keep content stale on purpose.
If this is strictly a day of the week or hour of the day schedule, then you could use our [playlist scheduler](https://help.trmnl.com/en/articles/11663305-playlist-scheduler) to set things up. But if you want to control it from the plugin level, there are two special variables available in markup.
## `TRMNL_SKIP_SCREEN_GENERATION`
**Use Case:** When no news is good news.
{% if condition %}
{% endif %}
*Screen generation* is based on your plugin's set refresh rate. When it is time to refresh your plugin's content, our worker servers will look/refresh your data source and then look at your markup. If the `TRMNL_SKIP_SCREEN_GENERATION` variable is set to `true`, the worker will skip generating a new screen.
This setting does not affect whether a plugin is shown on a playlist, unless you enable the [playlist](https://trmnl.com/playlist) feature "Smart Playlist":
## `TRMNL_SKIP_DISPLAY`
{% if condition %}
{% endif %}
If set to `true`, `TRMNL_SKIP_DISPLAY` will cause the plugin to be skipped on your playlist when it's requested by your device.
💡Tip: You can also return `TRMNL_SKIP_DISPLAY = true` as a top level property of your polling payload response, and it will have the same effect as setting `window.TRMNL_SKIP_DISPLAY = true` in markup.
{
"TRMNL_SKIP_DISPLAY": true,
"other_data": "..."
}
`TRMNL_SKIP_DISPLAY` should **not** be used in published recipes, as user's have no indication that the plugin's markup caused it to be skipped in their playlist and can lead to confusion. Use in private plugins is the most appropriate.
---
# How to Assemble your TRMNL
**## TRMNL OG**
This video walks through putting your OG device back together in under 1 minute. We recommend first watching the entire video, then trying it yourself.
You may also get tips from our other guide: [how to disassemble your TRMNL](https://help.usetrmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl).
### Full TRMNL OG assembly from scratch
[https://www.youtube.com/embed/Z64FDqIaKpg?rel=0](https://www.youtube.com/embed/Z64FDqIaKpg?rel=0)
## Partial TRMNL OG assembly
[https://www.youtube.com/embed/t6HDGpmyN2I?rel=0](https://www.youtube.com/embed/t6HDGpmyN2I?rel=0)
**## TRMNL X**
For a general overview with audio commentary, see our full assembly guide video below.
[https://www.youtube.com/embed/RdfMNQTyQQ8?rel=0](https://www.youtube.com/embed/RdfMNQTyQQ8?rel=0)
To perform a specific repair, see the [disassembly guide](https://help.trmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl).
---
# Playlist Scheduler
First some terminology:
- **Plugins** are content shown on your device
- **Playlists** are that content's schedule
This guide will walk you through the Playlist editor. To learn more about building plugins, [go here](https://help.trmnl.com/en/articles/10546870-compare-custom-plugin-types).
# Playlist Items
A playlist item is a single piece of content that represents a single plugin or [Mashup](https://help.trmnl.com/en/articles/10168132-mashups).
From left to right:
- The icon, decided by its author (TRMNL or a community member)
- A thumbnail of this item's most recent render
- Title (e.g. "The Office"), item type, and schedule(s)
- A Badge indicating it is a TRMNL created plugin and the name of the integration
- A visual indicator for the schedule (when it will be displayed)
- Triple dots to access different options
- Badge indicating the the plugin is actively being shown on the device or other special cases (*e.g.,* Hidden)
- "Hamburger" icon to drag/drop the ordering of this item relative to others, for example Calendar-Weather-News, or Weather-News-Calendar
The tripe dots provide access to these options:
- Preview the screen generated
- plugin settings (not available for installed community recipes that do not provide further settings via form fields)
- Duplicate the item, for instance if you want to show more frequently in the rotation
- Adjust the [color palette](https://help.trmnl.com/en/articles/12985974-understanding-color-palettes) (not available for some devices with a fixed palette like BWRY)
- show/hide this item temporarily (ignored by your device, not disconnected from your account)
- manage the item's schedule and to adjust this item's Duration (defaults to Device Refresh Rate); also accessible by clicking on the visual noted above
- completely remove this item from your Playlist
# Playlist Scheduling
You can dictate the timing, duration, and visibility of each playlist item on its own unique schedule - even specify its own color palette.
Click "Adjust Schedule" behind the ( i ) icon to see how it works.
## Default duration and schedule
If you don't make any changes, each item on your playlist will display 7 days per week for a length of time equal to [your device's](https://trmnl.com/devices/current/battery/edit) refresh rate. The slower your refresh rate, the longer your battery lasts ([more details](https://help.trmnl.com/en/articles/10556850-device-battery-faq)).
If you are OK with seeing the same content every day of the week, in the ordering you specifiy via the drag/drop functionality in the playlist, no action is required.
## Custom duration and scheduling
Click "Set a custom schedule" to expand the editor.
In this example we've updated our Work Calendar playlist item to show for 30 minutes, only during the M-F work week, from 08:00-12:30.
## Multiple schedules
Click "+ And during" to set additional schedules for this same item.
In this example we've extended our Work Calendar item to also appear on Sunday afternoons from 15:00-21:00, which could be helpful for planning the next week.
## Priority / Importance
Below your schedules is a toggle, Important. If you enable this, other items that match the set schedules will be ignored unless they are also set as Important.
This can be helpful for users with many different playlist items, who want to ensure that high priority content (like upcoming appointments) is not "buried" by other matching items.
You may tag as many items "important" as you want. Just note that if all your items are marked important, then your playlist will behave as if none of your items are marked as important.
## Copy schedule from
For common use cases like "hide my work calendar on the weekends" there are a few default options in the "Copy schedule from" dropdown:
As you build out custom schedules, for example to see [XKCD comics](https://trmnl.com/recipes/18267) when they're released on M/W/F, you can adopt existing items' schedules into new items, reducing the setup time to make things exactly how you want them.
## Color Palette
Our e-ink screens can display different numbers of gray shades, from simple black and white up to 16 or more levels, depending on your device. Larger palettes produce smoother fonts, clearer hierarchy, and better-looking photos, but they require bigger files, slower transitions, and more battery. Smaller palettes are faster and more efficient, but visually simpler.
You can set the desired [Color Palette](https://help.trmnl.com/en/articles/12985974-understanding-color-palettes) per playlist item to balance performance and image quality for each use case.
## Time Travel
On the playlist page, the *time travel* feature allows you to adjust the day and time and see how your playlist schedules react. **No items or settings are changed using this feature**. It's a great way to verify all your hard work is behaving as expected, without having to just "wait".
## Multi-Select
At the top of the Playlist you can enable multi-select. Checking any item on the right-side will enable the new action menu at the bottom.
---
# Custom plugin filters
Below is a growing list of custom (fancy) mutations available to Private Plugins, powered by the open source [Liquid library](https://github.com/Shopify/liquid).
Each filter is itself open-sourced in the trmnl-liquid repository:
# Getting started with Liquid
To get started quickly with `#{{ liquid }}` flavor variable notation, create a Private Plugin inside TRMNL, visit the Markup Editor, then follow along our Liquid 101 guide:
To understand how Liquid words under the hood, check out the official docs:
To parse and re-format Liquid-injected data, see the "Filters" section of this cheat sheet:
# TRMNL custom filters
Given our web application is built in Ruby/Rails, we're able to extend the default Liquid library with custom filters using the following strategy:
We currently offer the following extensions. Email or hop inside our Developer-only Discord > #ideas channel to request more.
## **data**
# convert a variable to JSON
#{{ my_var | json }} => {"foo":"bar"}
# parse a nested json string
# let data = {"data" => {"foo" => "bar", "stuff" => "{\"num\":5}"}}
{% assign parsed = data.stuff | parse_json %}
#{{ parsed_stuff.num }}
# => 5
## **collections**
Requires an array of data, accessible from the Merge Variables dropdown.
# let people = [{ "name": "Ryan", "age": 35 }, { "name": "Sara", "age": 29 }, { "name": "Jimbob", "age": 29 }]
# group_by
#{{ people | group_by: "age" }}
# => {35 => [{"name" => "Ryan", "age" => 35}], 29 => [{"name" => "Sara", "age" => 29}, {"name" => "Jimbob", "age" => 29}]}
# find_by ,
#{{ people | find_by: "name", "Ryan" }}
# => {"age" => 35, "name" => "Ryan"}
# find_by , ,
#{{ people | find_by: "name", "Toby", "Not Found" }}
# where_exp
# let numbers = [1, 2, 3, 4, 5]
#{{ numbers | where_exp: "n", "n >= 3" }}
# => [3, 4, 5]
# let people = [{name: "Ryan", age: 35}, {name: "Liz", age: 25}]
#{{ people | where_exp: "person", "person.age == 'Ryan' or person.age < 26" }}
Manipulate a comma-separated value string into an array, then **sample** a single value randomly. Useful for providing a list of values in a Form Field and selecting one randomly to use in the Polling URL.
# let isbns = "9781473205321,9781473205338,9781473200203, 9781473200104"
#{{ isbns | split: ',' | sample }}
# removing whitespace
# let isbns = "9781473205321, 9781473205338, 9781473200203, 9781473200104"
#{{ isbns | split: ',' | sample | strip }}
## **numbers**
#{{ 1234 | number_with_delimiter }} # => 1,234
# custom delimiter (default is comma)
#{{ 1234 | number_with_delimiter: '.' }} # => 1.234
#{{ 1000000 | number_with_delimiter: ' ' }} # => 1 000 000
# optional 2nd arg separates whole number from fractional value
#{{ 1234.57 | number_with_delimiter: ' ', ',' }} # => 1 234,57
# currency handling
#{{ 10420 | number_to_currency }} # => $10,420.00
#{{ 152350.69 | number_to_currency: '£' }} # => £152,350.69
# optional 2nd + 3rd args delimit + separate
#{{ 1234.57 | number_to_currency: '£', '.', ',' }} # => £1.234,57
# currency handling with locale instead of currency unit
#{{ 1234.57 | number_to_currency: 'sv' }} # => 1234.57 kr
#{{ 1234.57 | number_to_currency: 'sv', '.', ',' }} # => 1.234,57 kr
#{{ 1234.57 | number_to_currency: 'en-GB' }} # => £1234.57
Note that named parameters are not supported by Liquid, so you must supply all preceding arguments in the case that you want to override a 2nd+ parameter default.
## **string, markup, HTML**
# pluralize + inflect/humanize
#{{ "book" | pluralize: 1 }} # => 1 book
#{{ "book" | pluralize: 2 }} # => 2 books
#{{ "octopus" | pluralize: 3 }} # => 3 octopi
#{{ "person" | pluralize: 4 }} # => 4 people
# convert markdown into HTML
{% assign markdown = "This is *crazy* and [here's a link](https://somewhere.com)." %}
#{{ markdown | markdown_to_html }} # => This is crazy and here's a link.
# optional - pass strip_html to convert to plain text
#{{ markdown | markdown_to_html | strip_html }} # => This is crazy and here's a link.
## **dates**
For general tips to manipulate dates with native Liquid, see our [Advanced Liquid](https://help.trmnl.com/en/articles/10693981-advanced-liquid) guide. The custom date filters below may be combined built-in Liquid filters like `plus` and `minus` to add or subtract minutes, hours, time zone offsets, and beyond.
#{{ 7 | days_ago }} => yyyy-mm-dd representing 7 days before now
# oridinalize a day, e.g. 1 => 1st, 30 => 30th
# accepts a date string with strftime argument that includes <>, which will be substituted by the ordinal itself
#{{ "2025-10-02" | ordinalize: "%A, %B <>, %Y" }} # => Thursday, October 2nd, 2025
#{{ "2025-12-31 16:50:38 -0400" | ordinalize: "%A, %b <>" }} # => Wednesday, Dec 31st
## **uniqueness**
The `append_random` filter ensures that multiple, linked HTML elements (like a `
` and JS object) don't override another instance of this plugin within a mashup layout.
{% assign chart_id = "chart-" | append_random %} # => "chart-q7x1"
Within your layout's context, any usage of `#{{ chart_id }}` will yield the same generated value. This is useful for binding elements such as:
Highcharts.chart("#{{ chart_id }}")
## **localization**
First, `l_date` localizes a date (object or string) to a friendlier format with [strftime syntax](https://www.foragoodstrftime.com/).
#{{ '2025-01-11' | l_date: '%y %b' }} # => 25 Jan
# with a specific locale, ex: Korean
#{{ '2025-01-11' | l_date: '%y %b', 'ko' }} # => 25 1월
# with the user's locale; good for published Plugin Recipes
#{{ '2025-01-11' | l_date: '%y %b', trmnl.user.locale }} # => 25 1월
Next, `l_word` translates common words to another language. Contribute more [here](https://github.com/usetrmnl/localizations).
#{{ "today" | l_word: 'es-ES' }}
# => hoy
{% assign day = "tomorrow" %}
#{{ day | l_word: trmnl.user.locale }}
# => 내일
## **QR codes**
Convert a string (or URL) to a scannable QR code that automatically scales inside its container:
#{{ "Hello World" | qr_code }}
#{{ "https://some-long-url.com/help.asp-net" | qr_code }}
For backwards compatibility, you can use a fixed width/height. To enable this functionality:
# A larger module_size, lower error correction "level", and fixed size
#{{ "https://somewhere.com" | qr_code: 18, "", "fixed" }}
Want to style your QR code? TRMNL automatically adds class `qr-code`. Here's an example that puts your QR code in the top right corner of the screen:
#{{ "https://trmnl.ink" | qr_code: 3 }}
Note: the `qr_code` filter relies on the RQRCode library:
[https://github.com/whomwah/rqrcode](https://github.com/whomwah/rqrcode?tab=readme-ov-file)
# Troubleshooting
Combining filters with our Framework handlers, for example [Value Formatting](https://trmnl.com/framework/format_value), may produce unexpected results. For example:
#{{ 10420.00 | number_to_currency }}
Will yield $10,420 without `.00` trailing decimal places, because `value--tnums` attempts to simplify whole numbers.
# Adding more custom filters
Want to write code? Submit a pull request to [trmnl-liquid](https://github.com/usetrmnl/trmnl-liquid/), putting your work [here](https://github.com/usetrmnl/trmnl-liquid/blob/main/lib/trmnl/liquid/filters.rb) and a simple test case [here](https://github.com/usetrmnl/trmnl-liquid/blob/main/spec/trmnl/liquid/filters_spec.rb).
Don't want to write code? Email or hop inside our Developer-only Discord > #Plugins channel to request additional filters or arguments.
---
# On-demand plugin refresh
This improvement was released to all users on May 22, 2026.
On-demand refresh ("just-in-time") is a new plugin sync strategy that ensures your TRMNL always displays the latest information. For a deeper dive, check out [how refresh rates work](https://help.trmnl.com/en/articles/10113695-how-refresh-rates-work).
## How it Works
The concept is simple. **Plugins are automatically refreshed right before they are shown on the TRMNL.**
That's it. That's the whole shebang. The cadence of data sync is determined entirely by the device.
The upshot of this approach is that your TRMNL always displays the latest information, not from 15 minutes or 4 hours ago.
## Compared To Before
Previously, the *plugin refresh rate* determined how often your data would be updated, regardless of what was happening with your device. Content would be updated on regularly-scheduled intervals (for the nerds: a cron job).
Now that setting has been repurposed to have a new meaning: it's the *minimum plugin refresh rate*. The plugin will refresh as often as needed to display fresh data your device, but never faster than your preference.
## Limitations
On-demand refresh works best when the TRMNL naturally advances to the next screen. If you use the button or touch bar to navigate through the playlist out of order, those screens may not have the latest content.
## A Note to Plugin Authors
If your plugin fetches data from an API, this change may have an impact on the frequency and cadence of API requests. Our metrics indicate that this should not result in a significant change to the number of overall requests, but the usage patterns may vary more widely between plugin installs.
---
# International Customs / Duty Fees
TRMNL holds [several EC licenses](https://help.trmnl.com/en/articles/11327036-hardware-specs-for-custom-authorities) and is permitted to sell in many countries around the world.
All devices are assembled + shipped from our warehouses in Atlanta, Georgia (USA) and Berlin, Germany.
# Estimating duty fees
Orders to USA are shipped from our American warehouse and recipients pay $0 Duty fees as well as free shipping.
Orders to Europe are sent from our Berlin warehouse and EU residents enjoy $0 Duty fees as well as free shipping.
A handful of other destinations also do not charge Duty fees:
- Australia
- European Union
- Hong Kong
- New Zealand
- Philippines
- (+ several more)
But residents in *some* countries are not so lucky. Here are estimated fees (USD) in a handful of popular TRMNL customer locations.
Please note that fees below are mostly for single OG devices. We are collecting more data as we ship the new X device across the globe.
- Canada, $13.50 pre April 2025 (May 2025: $33.51 CAD in Ontario; $38.85 CAD in Québec; $59 CAD in Calgary; $70 CAD in Montreal)
- United Kingdom: from [$0](https://www.reddit.com/r/trmnl/comments/1k0j76z/comment/mnf32wg/) to 41 GBP (for TRMNL X, including FedEx fee)
- Israel, 145 ILS ($45)
- India, 7800₹ ($82 for an OG)
- United Arab Emirates (UAE): Imports are subject to local charges upon delivery (typically 5% VAT and around 5% customs duty on electronics). The recipient is responsible as the Importer of Record, and these fees are collected by the courier.
The exact amount paid at arrival time may be lower or higher pending the quantity of devices you ordered and if you included add-ons.
For reference, TRMNL devices ship with description "[Case Color] E-ink display," category "Accessory - with battery," and HS Code 85171400.
Finally, when you receive a notice from your local courier with a commercial invoice and "Declared value," note that this declared value is the total of the goods -- not the Duty fee.
# Optimizing duty fees
We don't include the value of digital products in your order. For example, if you purchase the Developer Edition add-on, this is a software upgrade and will reduce your Customs value.
# Paying duty fees
Some customers may prefer to pre-pay their Duty fee to avoid the hassle of making a separate payment in-person at a local post office.
But this is not always an optimal decision, as some couriers charge a large "courier handling fee" of $7-28 for this pre-pay privilege.
Other customers prefer to pre-pay due to their in-office job or travel schedule. In our experience, post offices typically hold packages for days or weeks before returning them to TRMNL. Additionally, some local couriers allow Duty fees to be paid online, or they include a payment slip during delivery and allow the recipient to pay fees later.
---
# Tuning a washed out display
Do you have ghosting (ghost images of previous screen) on your TRMNL X?
[See below.](#h_931074218d)
The [no flicker + grayscale](https://trmnl.com/blog/no-more-flicker) feature release exposed underlying issues with some customer displays.
This was resolved by our factory for upcoming components, but previously affected customers may experience occasional washed out images:
# Tuning TRMNL OG
This guide provides a quick method to adjust your display's tuning and resolve the washed out effect without new hardware. An alternative to this guide is [enabling Maximize Compatibility](https://help.trmnl.com/en/articles/11934315-fw1-6-2-troubleshooting).
## Step 1 - Flash the newest firmware release
Visit [trmnl.com/flash](https://trmnl.com/flash) and select your device, then the latest firmware (1.7.x or higher), following instructions to add this build to your device.
**Note**: the "temp profile" feature is currently only available for TRMNL OG devices.
## Step 2 - Find profiles in your device settings
Go to your Device settings, click "Firmware" and toggle between multiple profiles.
## Step 3 - Test each profile
We suggest first trying Profile A, then B, and so on. You may want to take a photo of each result under good lighting conditions, preferably with your exposure settings locked.
To test a profile:
1. Select and save the profile from your Device settings
2. Install our [Temperature Profile Test Image plugin](https://trmnl.com/recipes/211135) and add it to your playlist multiple times. Drag and drop them in your Playlist to be the "next" items. Do this by ordering it after the row with a green "Displayed now" badge (example below)
3. Click the button on the back of your device to force-skip to the next screen
4. Observe results
Repeat Steps 1-4 for each profile until you find one that looks best on your device. Send us a live chat to share before/after photos, which we'll use to generate additional profiles.
# **TRMNL X**: Decreasing Previous Image Visibility on Current Image (Ghosting)
As of firmware v1.8.4, TRMNL X can now use the Temperature Profile feature to increase the screen flashing to ensure the e-ink gets pushed properly and there is no "ghost" image of the previous screen.
**Device Settings > Firmware**, select either Temperature Profile A or B (they both do the same thing), instead of Default to increase the screen "wipes" and click **Save**.
---
# Finding your TRMNL Mac Address
Every TRMNL has 2 immutable characteristics -- a Mac Address, and a Serial.
Mac Addresses are generated by our manufacturer, and [Serial values](https://help.usetrmnl.com/en/articles/11510216-finding-your-trmnl-serial) are printed directly on the PCB (circuit board).
In some cases, TRMNL may need to know one of these values to help debug a connection issue. But instead of [disassembling your device](https://help.usetrmnl.com/en/articles/10003228-how-to-disassemble-your-trmnl), we suggest looking up your Mac Address instead.
## **Option A - WiFi Router settings**
Pending your local network setup, you may have access to a wireless router settings interface or mesh network mobile app. This app will present a list of devices like your phone, television, computers, and so on.
If you have access to such an interface, look for a device whose name starts with "ESP_", followed by a few random characters. Select that device and you should see a "MAC" value such as `AB:4D:CA:FF:Q1`.
## **Option B - WiFi Portal Footer**
Put your TRMNL in [pairing mode](https://help.usetrmnl.com/en/articles/11511577-enable-wifi-pairing-mode).
Connect to the WiFi network named "TRMNL," and you should see a screen like this:
### TRMNL X
TRMNL X has both 2.4Ghz and 5Ghz WiFi antenna modules, with separate MAC addresses. They are visible as of firmware v1.8.2+ at the bottom of your captive portal.
### **Using Firmware 1.5.11 (released July 11, 2025)**
If you're on the latest firmware (devices update automatically), the bottom of your WiFi portal will include the MAC by default:
### **Using Firmware 1.5.10 or lower**
To expose your MACc, input your WiFi network name (SSID) + password and tap Connect. For 1-2 seconds, your MAC will appear at the bottom like so:
This screen disappears quickly, so it's easiest if you take a screenshot when you see the grey bar. You can do this with your phone's screen grab shortcut or on your computer, if you access this WiFi portal from a web browser.
---
# Compare custom plugin types
From easiest to hardest:
| **Difficulty** | **Public** | **Server required?** | **OAuth required?** | **Language** |
| --- | --- | --- | --- | --- |
| **Private Plugin** | no | no | no | [Liquid](https://docs.usetrmnl.com/go/private-plugins/templates) |
| **Recipe** | yes | no | no | [Liquid](https://docs.usetrmnl.com/go/private-plugins/templates) |
| **Screenshot** | no | yes | no | any |
| **3rd Party** | yes | yes | yes | any |
| **BYOS** | no | yes | no | any |
## Private Plugin
The simplest way to write a custom plugin is as a [Private Plugin](https://intercom.help/trmnl/en/articles/9510536-private-plugins). Feed it data via HTTP polling or webhooks, even [Oauth2](https://trmnl.com/blog/oauth2-plugins). Design a beautiful dashboard with (or without) our [Framework UI](https://trmnl.com/framework/) and we handle the rest.
Private plugins can remain private forever, shared offline via [exporting](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins), or "Unlisted" for with a hidden URL.
## Recipe
Recipes are simply Private Plugins that have been approved by the TRMNL team to be listed publicly.
As recipe master, any changes you make will automatically propagate to others who have installed your recipe.
Learn more about [building and submitting Recipes](https://intercom.help/trmnl/en/articles/10122094-plugin-recipes).
## Screenshot
Another way to get custom content to a TRMNL is to leverage the [Screenshot plugin](https://intercom.help/trmnl/en/articles/10302121-screenshot).
Point this plugin to any website that looks decent in your TRMNL device's screen dimensions and TRMNL will periodically poll it to generate images. Common use cases for this plugin include publicly posted schedules, PDFs, school lunch menus, or even news sites.
## Third Party Plugin
A third party plugin is the most flexible - and the most complex - custom plugin type.
You provide a web application that connects to TRMNL via our simplified OAuth2 authentication flow, and in return get per-user settings management, and full control over generated markup. TRMNL servers will periodically poll your application to generate images for the device.
While third party require the most effort, they have a couple unique benefits:
- User's full name + email is shared with you (for building a newsletter, etc)
- You can control how often you allow data to be refreshed, keeping your server and API costs under control, by simply throttling inbound markup requests
Learn how to [build and submit a public plugin](https://docs.trmnl.com/go/plugin-marketplace/introduction).
See an [example in Python](https://github.com/Omeryl/trmnl-plugin-example).
## BYOS
For complete control over your TRMNL device you can [Bring Your Own Server](https://docs.trmnl.com/go/diy/byos). This capability bypasses all plugin functionality provided above.
---
# Seeed Studio Devices: Xiao, DIY Kit, and reTerminal
# Hardware Troubleshooting
If you purchased a device from Seeed Studio and are experiencing issues getting it up and running, please see the following Wiki pages provided by Seeed Studio or reach out to their support to troubleshoot hardware problems:
- [XIAO 7.5" ePaper Panel](https://wiki.seeedstudio.com/xiao_075inch_epaper_panel/)
- [Seed Studio 7.5" DIY Kit](https://wiki.seeedstudio.com/trmnl_7inch5_diy_kit_main_page/)
- [reTerminal E Series](https://wiki.seeedstudio.com/reterminal_e10xx_main_page/) (*e.g.*, E1001, E1002)
Our help articles are designed for the TRMNL hardware, so they may refer to buttons or switches that differ from Seeed Studio devices. Please refer to the Wiki documentation above for hardware button functionality.
## Green LED Blinking
A green LED blinking means you haven't connected the battery. If green LED is on, it means the battery is charging. If it's not on, it means it's fully charged.
## Enable Pairing Mode to add a new Wi-Fi network (Network Reset)
- On XIAO 7.5" ePaper Panel: press the Reset button, release it, then press and hold the Boot button for 5 seconds
- On Seeed Studio 7.5 DIY Kit: press and hold Key 3 for about 5 seconds
- On reTerminal E Series (*e.g.*, E100*x*): press and hold both navigation buttons (left and right) simultaneously for 2 seconds
## Can not discover device on my network
*This is a tip provided by a customer.*
With the battery attached, the Seed Studio (OG) DIY Kit goes to sleep before it can be found on my router. What I ended up doing was disconnecting the battery and finally erasing the flash memory.
# Using Seeed Studio Software
Seeed Studio provides various software with their products, detailing on the product page where you made your purchase. This includes tools for Home Assistant. You do not need to connect to TRMNL to use these features.
# Connecting to TRMNL (BYOD License)
You will need to purchase a [BYOD license](https://shop.trmnl.com/products/byod) to use **trmnl.com** as your server for your Seeed device. After purchasing your license:
1. Create a BYOD device:
2. Visit your [device settings page](https://trmnl.com/devices/current/edit) to select your [Device Model](https://help.trmnl.com/en/articles/11547008-device-model-faq) and set your DIY device's **MAC address** in the *[Developer Perks](https://trmnl.com/devices/current/developer/edit)* section.
When you first boot your Seeed device, it may display a message regarding **"Need to register your MAC Address."**
*This should be completed following the steps above.*
# Connecting to Your Server (BYOS)
You are welcome to [Bring Your Own Server](https://docs.trmnl.com/go/diy/byos) (BYOS), no license required, for any TRMNL compatible device. If using Terminus, you can also refer to our [Terminus setup guide](https://help.trmnl.com/en/articles/12263392-connect-your-device-to-terminus-byos).
# Contacting TRMNL Support
Our team is happy to assist you with our platform or any issues getting your BYOD license properly connected to your trmnl.com account. Use the online chat or email [support@trmnl.com](mailto:support@trmnl.com?subject=Seeed%20Studio%20BYOD%20Assistance) for assistance.
---
# Eight Sleep
Connecting your Eight Sleep account to TRMNL takes just a few seconds.
## **Step 1 - Visit the Eight Sleep Plugin**
Inside TRMNL, navigate to Plugins > Eight Sleep.
## **Step 2 - Configure Plugin**
In the settings form below, provide the same email and password you use to log into the Eight Sleep mobile application.
**Note**: Eight Sleep does not provide an official API for 3rd party integrations. We use your mobile app credentials to "spoof" a login and retrieve the same dashboard data available inside the native Eight Sleep application.
## **Step 3 - You're Done!**
Based on your refresh settings, you'll begin seeing a dashboard of your previous day + last week's sleep and recovery metrics very soon.
Stay focused.
---
# How to set up a new device
[Deutsch](https://help.trmnl.com/en/articles/12593804-so-richtest-du-dein-neues-gerat-ein)
Congrats on getting a TRMNL! We're stoked to have you.
***Your time is valuable***
If the steps below are confusing or not aligned with your experience, [find your MAC address](https://help.trmnl.com/en/articles/10614205-finding-your-trmnl-mac-address) and send us a live chat (click the bottom right icon).
# Step 1 - Set up your device
Click on your model below for detailed instructions.
**Quick Links:**
- [TRMNL OG (7.5")](#h_5fee15b4bf)
- [TRMNL X (10.3")](#h_d8288edec7)
## TRMNL OG (7.5" screen)
Welcome aboard! TRMNL's OG device is our bestseller. Some may even call it a classic. If your box includes a clear film accessory, [see here](https://intercom.help/trmnl/en/articles/10217443-applying-the-screen-anti-glare-protector). Otherwise, let's begin.
### Turn on TRMNL
Flip the power switch to ON from the back of your TRMNL device. After it wakes up and blinks a few times, your screen might look something like this:
If nothing happens when you turn on the device, the battery may be drained. In this case, plug TRMNL in with the included USB-C cable (if part of your order) and restart your device using the OFF/ON switch on the back.
### Connect to WiFi
As the device screen says, use your phone or a nearby computer to find and connect to the WiFi network named "TRMNL," which will broadcast from your turned on device.
After connecting, a WiFi portal will appear like the middle screenshot above.
Wait a moment while TRMNL scans for available networks, or provide credentials into the SSID/Password box directly if your home/office network doesn't appear or is hidden. Finally, click Connect to share these credentials with your TRMNL device.
(*Your WiFi credentials are [never](https://docs.trmnl.com/go/how-it-works#opinionated-device-less-than-greater-than-server-relationship) sent to our servers*)
Having trouble on corporate or enterprise networks? [See this guide](https://intercom.help/trmnl/en/articles/11663377-setting-up-a-trmnl-on-tricky-wi-fi-situations).
**Troubleshooting**
For suggestions to fix a few rare issues, see our [troubleshooting guide](https://help.trmnl.com/en/articles/10193157-device-wifi-troubleshooting).
### Register your Device
After adding your WiFi credentials, TRMNL will restart and show a setup message. If your screen is blurry or you can't read the 6 digit ID, use [Find My Device](https://trmnl.com/find-my-device) to recover it instantly.
If you *already have* a TRMNL account, [Add a New Device](https://trmnl.com/devices/new) with this Friendly ID.
If you *don't* have a TRMNL account (different from your store profile), register at and provide this Friendly ID in the required field.
After signing up, your device should begin working within moments. TRMNL adds a default plugin to all new accounts, which you're welcome to delete from your Plugins tab inside the web application.
---
## TRMNL X (10.3" screen)
Welcome aboard! The X is our latest release, incorporating feature requests and key learnings from thousands of OG model owners. Let's begin.
### Turn on TRMNL
Your device should have a welcome screen, likely prettier than this one.
Remove the circular charging dock from your box and attach it to the back of your display. It has a strong magnet and should snap on without effort.
Next, plug a USB-C cable into the dock. Plug the other end of your cable into any power source. You may also attach the kickstand, in which case the USB port is behind the kickstand's hinge.
Within a few seconds your device should boot up. If nothing happens, try [rotating your dock](https://help.trmnl.com/en/articles/12407673-troubleshooting-an-unresponsive-device) 45 degrees and wait a few more seconds.
### Connect to WiFi
Your device should be showing a screen like this. As it reads, use your phone or a nearby computer to find and connect to the WiFi network named "TRMNL."
After connecting to the TRMNL network you'll see a sequence of screens like this.
Wait a moment while TRMNL scans for available networks, or provide credentials into the SSID/Password box directly if your home/office network doesn't appear or is hidden. Click Connect to share these credentials with your TRMNL device.
(*Your WiFi credentials are [never](https://docs.trmnl.com/go/how-it-works#opinionated-device-less-than-greater-than-server-relationship) sent to our servers*)
Having trouble on corporate or enterprise networks? [See this guide](https://intercom.help/trmnl/en/articles/11663377-setting-up-a-trmnl-on-tricky-wi-fi-situations).
**Troubleshooting**
For suggestions to fix a few rare issues, see our [troubleshooting guide](https://help.trmnl.com/en/articles/10193157-device-wifi-troubleshooting).
### Register your Device
After adding your WiFi credentials, TRMNL will restart and show a setup message. If your screen is blurry or you can't read the 6 digit ID, use [Find My Device](https://trmnl.com/find-my-device) to recover it instantly.
If you *already have* a TRMNL account, [Add a New Device](https://trmnl.com/devices/new) with this Friendly ID.
If you *don't* have a TRMNL account (different from your store profile), register at and provide this Friendly ID in the required field.
After signing up, your device should begin working within moments. TRMNL adds a default plugin to all new accounts, which you're welcome to delete from your Plugins tab inside the web application.
# Step 2 - Connect Plugins
While logged into TRMNL, visit the [Plugin directory](https://trmnl.com/plugins) to add content to your device.
Your TRMNL will rotate through these plugins based on your [Playlist Schedule](https://help.trmnl.com/en/articles/11663305-playlist-scheduler) and [refresh intervals](https://help.trmnl.com/en/articles/10113695-how-refresh-rates-work). You can also force-skip to new screens at any time.
How to force skip:
- TRMNL OG - click the button on the back of your device
- TRMNL X - tap anywhere on the touch bar (best results in the middle)
Additional device button features can be configured as [special functions](https://help.trmnl.com/en/articles/9672080-special-functions).
# Step 3 - Enjoy!
TRMNL keeps you focused but informed, aware but not distracted, and in sync, async.
Feel free to modify the refresh rate, enable sleep mode, and more from the [Devices > Edit](https://trmnl.com/devices/current/edit) page in your TRMNL account.
If you have questions about the setup process, send us a live chat (bottom right corner of this page).
---
***Unhappy with this article?*** *Let us know what you were searching for and why this article missed the mark for you. Email [support@trmnl.com](mailto:support@trmnl.com?subject=How%20to%20set%up%20a%20new%20device%20Article%20Feedback) and let us know (use the link to automatically include the article title)!*
---
# Image Display
For a more dialed-in experience than the [Screenshot](https://help.trmnl.com/en/articles/10302121-screenshot) plugin, our Image Display plugin lets you generate custom graphics that TRMNLs convert to e-ink friendly screens.
# Use cases
- Family photo album at `mysite.com/hidden/family.jpg?auth=qwerty`
- Home Assistant dashboard
- Deer cam CCTV
To get started with Image Display, navigate to Plugins > Image Display and input a URL to your image, which can be in any format or size.
# Image dimensions
Image Display accepts images of any size and reformats them to fit your device's screen. For best results, match your source image to the target's **aspect ratio** — the plugin uses cover-fit, so mismatched ratios will be center-cropped.
**Single plugin (full screen):**
| **Device** | **Resolution** | **Aspect ratio** |
| --- | --- | --- |
| TRMNL OG | 800×480 | 5:3 |
| TRMNL X | 1872×1404 | 4:3 |
**Inside a Mashup:** the slot aspect ratio depends on the layout, not the device. Match the ratio for your slot:
| **Slot type** | **Aspect ratio** |
| --- | --- |
| `half_vertical` (left/right half) | ~2:3 |
| `half_horizontal` (top/bottom half) | ~8:3 |
| `quadrant` (one of four) | ~4:3 |
For crisp dithering, supply images at or above the slot's native pixel dimensions. Undersized images will appear blurry.
# Where to host images
We've heard mixed reports of sites like imgur.com being unreliable. Potentially they use anti-scraping tech that treats TRMNL like a robot. We don't deserve that.
So you're welcome to point to Google Drive instead. Simply upload an image, share it to "Anyone with the link," then copy + paste the link like so:
Here's the above example if you'd like to try it:
https://drive.google.com/file/d/164qYbGtnzQq_PDLE-eB6cu4a6mIT8DzN/view?usp=sharing
You can also use Dropbox to host your images. When entering a Dropbox link, you'll need to change `dl=1` or `dl=0` to `raw=1`. Otherwise, the link points to a preview webpage rather than the actual image file, which won't work with the plugin. For example:
https://www.dropbox.com/scl/fi/your-file-id/image.png?rlkey=your-key&raw=1
Read more about forcing downloads from Dropbox here:
# Ensuring content changes
TRMNL syncs plugin content according to your plugin's refresh interval, for example every 15 minutes. But we only generate new screen renders if the content changes. Learn more about how this works [here](https://help.trmnl.com/en/articles/10113695-how-refresh-rates-work).
For most plugins, this is straightforward as the underlying data will surely be different if something happens. But with images, we use the headers `etag` with `If-None-Match` and `last-modified` with `If-Modified-Since` to determine if we need to process that image. Adjust your image endpoint to support these headers.
Alternatively you can use our [Alias plugin](https://help.trmnl.com/en/articles/10701448-alias-plugin) and TRMNL will serve your generated image directly. Just note that this method requires you to format images to [our exact specifications](https://docs.trmnl.com/go/imagemagick-guide), while Image Display does this for you automatically.
---
# Redirect Plugin
Setting up the Redirect plugin lets you configure endpoints that respond to your TRMNL device as if it were the core web application.
# Use Cases
1. You already use the [Alias plugin](https://help.trmnl.com/en/articles/10701448-alias-plugin), but want to avoid screen flicker if the content hasn't changed.
2. You want a more dynamic rotation of content. Instead of the fixed minutes interval from your [Playlist > Playlist Group](https://trmnl.com/playlists) preferences, you may instruct a device to wait 3 mins, or 20 mins (for example) based on the content being returned.
3. Your server's content is uploaded to remote locations, where the URL is not predictable. Either because it is hashed (e.g. S3 bucket) or otherwise unique. The [Alias plugin](https://help.trmnl.com/en/articles/10701448-alias-plugin) requires a static endpoint, whereas Redirect allows you to pass back a different URL on each request.
# Step 1 - Configure Plugin
From the Plugins directory, select the Redirect plugin. Provide a URL endpoint from your server. This Web Address must be publicly accessible on the web.
TRMNL will ping your server to get the response data. Your server should respond with the below JSON data. Please keep in mind that TRMNL has strict timeout of 2 seconds, before sending a failure response.
{ filename: 'get-coffee', url: 'https://192.168.2.1:4567/get-coffee.bmp', refresh_rate: 3000 }
Note that the `url` response param can be anywhere, including a local network.
**Filename**
The filename attribute is used as the "diff" parameter. If the filename between requests does not change, your device will not refresh the screen, thus avoiding unnecessary screen flicker and saving battery power.
**URL**
Make sure this points to an image that is 1 bit bitmap(bmp3) or 1 bit PNG, 800x480 pixels. [See here](https://docs.trmnl.com/go/imagemagick-guide) for a hint on generating this format with ImageMagick. Your image may be at a local destination, for example `192.168.5.3/my/image.png`.
**Refresh Rate**
The number of seconds your device should sleep before waking up again to request new content. The fastest you can go is 1x/minute (subject to change).
# Step 2 - You're Done!
Based on your device's refresh settings, TRMNL will begin forwarding your content requests to the Web Address specified above.
Stay focused.
Credits:
Redirect by Alex Bickov from [Noun Project](https://thenounproject.com/browse/icons/term/redirect/) (CC BY 3.0)
# Troubleshooting
**Request origins**
TRMNL operates a network of worker boxes for plugin processing, including Hetzner servers in the EU. If your JSON URL endpoint expects only requests from a specific region, it may be [blocking our attempt to fetch the JSON](https://help.trmnl.com/en/articles/12386583-plugin-not-receiving-data-from-polling-url) endpoint's 3x response body payload params.
**Image metadata**
Most issues with this plugin are due to the image formatting not matching [our specifications](https://docs.trmnl.com/go/imagemagick-guide) exactly. For example, the OG TRMNL device model expects 800x480 pixel images, not 1600x960 or some other equivalent ratio.
**Image response headers**
There have been reports that your image's endpoint must respond with a `content-length` header; the value should be an integer. This is a requirement we are [actively investigating](https://github.com/usetrmnl/firmware/issues/156) and intend to remove. If you are generating images on the fly, you may not have this value by default because the content is still streaming. In this case, use a dummy value.
---
# Alias Plugin
Users who can generate screens themselves, can make use of our Alias plugin.
As the name indicates, this plugin simply passes along your own (hosted) image to your TRMNL device. This may sound similar to our Image Display plugin, but with one key difference: Alias lets you encrypt + decrypt the content, making it impossible for TRMNL to know what's being displayed.
# How it works
TRMNL expects image content to be accessible over the internet / home-network. This image content may then be plugged into the TRMNL web application in either plain text or encrypted format.
The diagram below explains how each of the Alias plugin strategies differ.
# Getting started
To set up an Alias plugin, first provide the absolute URL of your image.
Make sure the image is 1 bit bitmap(bmp3), 1-bit PNG, or 2-bit PNG (OG model, firmware v1.6.x or higher), 800x480 pixels. [See here](https://docs.trmnl.com/go/imagemagick-guide) for a hint on generating this format with ImageMagick.
**Type - Plain Text**
When your TRMNL device requests content from your Alias plugin instance, TRMNL will respond with the data you provided in the TRMNL web application. For example, as per the screenshot above.
**Type - Encrypted**
For an alias with type Encrypted, encrypt your image_url using <**TBD**> algorithm.
Next, provide a decryption key to your TRMNL device via the captive WiFi portal. You can instantiate WiFi pairing mode by holding the boot button on the back of your TRMNL for 5-6 seconds, then letting go, while the device is turned on.
Your TRMNL device will store this decryption key onboard, and never share it over the internet.
# Putting it all together
The Alias plugin provides a secure way to generate and consume content without sharing any information with the TRMNL servers, while still leveraging the convenience and power of the TRMNL web application.
# Troubleshooting
**Request origins**
Whatever the exact URL is that can be reached by your local network, use that in the URL field. If you're running a server at , provide that full URL. It does not have to be `https`.
**Image metadata**
Most issues with this plugin are due to the image formatting not matching [our specifications](https://docs.trmnl.com/go/imagemagick-guide) exactly. For example, the OG TRMNL device model expects 800x480 pixel images, not 1600x960 or some other equivalent ratio.
**Image response headers**
There have been reports that your image's endpoint must respond with a `content-length` header; the value should be an integer. This is a requirement we are [actively investigating](https://github.com/usetrmnl/firmware/issues/156) and intend to remove. If you are generating images on the fly, you may not have this value by default because the content is still streaming. In this case, use a dummy value.
**Caching**
At the bottom of your Alias plugin settings, there is a section called **Enable cache?**:
If set to **no**, your device will always re-draw the image URL, even if its filename matches the last rendered filename (*e.g.*, the image is rewritten at the source using the same filename).
---
# Device / WiFi troubleshooting
Below is a work-in-progress collection of potential issues + fixes to ensure your TRMNL device remains connected.
# Your time is valuable
If the steps below are confusing or not aligned with your experience, [find your MAC address](https://help.trmnl.com/en/articles/10614205-finding-your-trmnl-mac-address) and send us a live chat (click the bottom right icon). You can also try our [Find My Device](https://trmnl.com/find-my-device) tool to resolve issues without waiting on our team.
# Initial Setup Issues
If you've just unboxed your TRMNL, see the following tips.
### WiFi portal closing unexpectedly
On your phone or computer, click "forget network" (or similar feature), then restart your device and re-connect to TRMNL WiFi.
**Using a VPN?**
This should be fine, but we suggest disabling your VPN during initial device setup.
### WiFi network not listed
Like many IoT devices, TRMNL requires a 2.4ghz network. If you only have 5ghz enabled on your router, you can usually enable dual band with no changes required for your existing devices.
It's ideal to have a separate, dedicated 2.4ghz network versus a "dual band" network, because routers with dual-band enabled will often instruct devices to use the 5ghz band, which is incompatible with most IoT devices.
### My network has a captive portal
If you're setting up TRMNL in a building with "hotel style" WiFi, whereby you must input credentials inside a captive portal popup following network connection, TRMNL must be accompanied by a middleware device.
[Check out our article on setting up your TRMNL through a captive portal with the help of a travel router](https://intercom.help/trmnl/en/articles/11663377-setting-up-a-trmnl-on-tricky-wi-fi-situations).
Universities may offer a device network if you register the MAC address first. For example, [Carnegie-Mellon University](https://www.cmu.edu/computing/services/endpoint/network-access/wireless/how-to/cmudevice.html).
**WiFi network connects, then portal appears again**
If you're able to connect to the TRMNL network, input your local network SSID + Password, then click "Connect," but the screen just refreshes and goes back into pairing mode, email your device's MAC to or start a live chat with us for further debugging. You can find your MAC [here](https://help.trmnl.com/en/articles/10614205-finding-your-trmnl-mac-address).
### Wifi network not connecting
If you're inputting your home or office network's WiFi credentials into the [TRMNL WiFi portal](https://intercom.help/trmnl/en/articles/9416306-how-to-set-up-a-new-device), but the device is not connecting or showing an error screen, check out the security settings on your router. While TRMNL should work with WPA, WPA2, and WPA3, some users have reported that only WPA3 (or WPA3 Personal) connections were successful for their device. We have seen issues with "WPA2/WPA3 Personal."
Another thing to check is whether loads on your local network, incognito. Just use your phone (on WiFi) or computer and visit the URL. If it doesn't load, your router or ISP is blocking the endpoint that our Firmware pings for screen content. **Note: do not use ** to operate your device. It will not work.
In this case, you can simply modify the firmware to ping our actual URL, , by choosing "Custom Server" from the WiFi setup step and inputting "".
To reproduce this setup step, hold the circular button on the back of your TRMNL for 5-7 seconds, then let go. You may also need to turn the device off, wait 5 seconds, then turn it back on (after performing a hold-reset).
**I have T-Mobile internet / phone**
For some reason, we recently learned T-Mobile might block `tmrnl.app` from device access. If you have access to a VPN, try running your router traffic through it temporarily to see if that brings your TRMNL online. This is not a long-term solution, but will help us debug further and offer alternative solutions when you send us a live chat or email.
**Channels and Bandwidth**
The ESP32 is most stable on channels **1–11** in the 2.4 GHz band. Unusual bandwidth settings (like 40 MHz or dynamic channel selection) can cause erratic behavior.
**AP (Client) Isolation**
Some routers enable “AP Isolation” or “Client Isolation,” which prevents devices on the same Wi-Fi network from communicating with each other. **Disable AP Isolation in your router’s advanced wireless settings.**
**Static IP Address**
As of firmware 1.7.5, you can now set a [static IP address](https://help.trmnl.com/en/articles/13673576-device-static-ips) for your device. This can help if your DHCP server has re-used an IP with firewall restrictions/customization that is interfering with your device's access.
# Configuring Your Access Point or Router
Using your phone's hotspot feature is a way to isolate the device from your home network and confirm it is operating properly, because hotspots are much less complex than home or commercial access points or routers.
First, we'll perform a **soft reset** to erase all saved SSIDs/networks.
- Hold the button on the back of the TRMNL for 5-7 seconds to re-enter WiFi pairing mode
- Connect to TRMNL network and in the captive portal click ADVANCED > Soft Reset (this clears all SSIDs from memory)
If you are unable to get the device responding properly after trying multiple times to re-enter WiFi pairing mode, you can also do a [firmware flash](https://help.trmnl.com/en/articles/10271569-manually-flash-firmware) (cable required) which we consider a **hard reset**.
- Reconnect to TRMNL network and this time, instead of connecting to your regular WiFi, put your cell phone in hotspot mode and connect it to that.
Here are some instructions:
- For Android:
- Make sure your hotspot is configured in 2.4Ghz mode!
Android Settings > network > hotpsot > wifi hotspot > extend compatibility
- For iOS:
- Make sure you use "Maximum Compatibility Mode"!
If everything works fine, congratulations, you've done the hardest troubleshooting step, you've isolated the problem!
## Using AI to Configure Your Local Network
With a few exceptions, this guide focuses on broad issues that affect multiple hardware network devices. To configure **your specific hardware router or access point**, you can leverage your favorite LLM (AI) using the following phrase:
"*How do I configure my BRAND AND MODEL NAME to be compatible with IoT devices using only 2.4Ghz*."
Insert your network device's name/model, such as `ASUS WRT600N`.
# Ongoing Issues
If you've already set up your TRMNL but are experiencing new issues, see the following tips. We will update this as often as we detect a new pattern.
For live help, open the chat widget in the bottom right corner and ping us in real time. Be ready to share your router brand / model or other home networking settings to help us help you.
### Firmware update looping / slow
Our firmware packages are only around 1.4mb, however it may take a few minutes for the package to be installed on your device following a download. We do not recommend clicking the boot button to 'skip' this update, or turning your device off/on.
If you prefer to disable firmware updates, simply visit [Devices > Edit](https://trmnl.com/devices) > scroll down and uncheck the "OTA Updates" feture. Note that this requires the Developer edition perk, but if you don't have that enabled then you can send us a live chat and we'll do it on your behalf.
### Intermittent content vs error screen
If your TRMNL sometimes displays content as expected, but at other times shows an error screen, it's possible the buffer needs to be reset.
Simply turn off your device for 5-7 seconds (this length of time is necessary), then turn it back on.
### Error screen: "wifi not connected" or "content malformed"
Suggestion: If you have ad blockers installed at the router-level, try whitelisting the domain "". This is the endpoint where the device firmware fetches new content.
Suggestion: Set your Device's refresh rate (visit [Devices > Edit](https://trmnl.com/devices) > *Battery & Sleep* section) to 5 minutes, then click the button on the back of your device 1x. This should sync your new refresh rate. Then, wait 5 minutes for the device to perform a full refresh cycle (sleep, wake, request content).
Suggestion: To help us confirm if this is a local network issue (vs device / firmware related), try connecting your TRMNL to your phone's hotspot. If you're using an iPhone > Personal HotSpot, make sure to toggle on "Maximize Compatibility" (beneath your editable WiFi Password field).
### Device error log - wifi connection failed, current WL Status: 6
If you have Developer Edition unlocked, you can view device logs via > *Developer Perks* section > scroll down to Logs.
If the error code is "WL Status: 6", check out your router's Channel settings. One user reported that their 2.4ghz band was set to Channel 9 (auto), but changing to Channel 1 resolved their intermittent disconnections.
### WPA2/WPA3 Encryption
When using the TRMNL (OG) model, if your router configuration offers *WPA2/WPA3-PSK and SAE encryption*, you will need to switch to ***WPA2-PSK only***.
### Maybe it's a DNS Problem?
See our [DNS article](https://help.trmnl.com/en/articles/12386067-dns-and-network-requests-for-firewall-whitelists).
## TP-LINK user?
Even after setting up distinct 2.4Ghz and 5Ghz SSIDs, if you still struggle to connect your TRMNL device use the "guest network" feature on the router with "WPA2/WPA3-Personal" security.
## Eero mesh customer?
Disable connection steering for your TRMNL to prevent 5ghz or faraway access point connections upon device wakeup.
You can also [temporarily hide the 5Ghz band](https://support.eero.com/hc/en-us/articles/360049983772-How-Do-I-Temporarily-Hide-the-5GHz-Band-on-My-eero-Network) during setup.
## Orbi mesh customer?
Your standard setup may include a dual-band 2.4 / 5ghz network. However some users have reported that they needed to enable a 2.4ghz-only band for their TRMNL connection to remain stable.
## Fritz! Fritzbox (Fritz!OS 8)?
In this update, `PMF` (Protected Management Frames) was automatically enabled, causing issues with TRMNL connectivity. Disabling this feature on the 2.4Ghz band can resolve the issue. There may also been some additional features set at factory based on [this article](https://fritz.com/en/apps/knowledge-base/FRITZ-Box-7520/3481_Does-the-FRITZ-Box-support-WPA3).
## Ubiquiti / Unifi customer?
Our friends at HostiFi wrote [this article](https://support.hostifi.com/en/articles/7019454-unifi-recommend-settings-for-iot-devices) with tips to ensure iOT devices like TRMNL play nicely with your network.
Without a proper setup, your router's default feature "Band Steering" will direct TRMNL devices to leverage the 5ghz band which will not work, even if you connected to 2.4ghz during setup.
Below are some router settings from 2 users who fixed their connectivity issues. They have a 2.4ghz-only SSID with multiple IoT devices.
Setup A:
Setup B:
If some of the above parameters don't work, try setting just your router's "Minimum Data Rate Control" setting to a minimum of 12mbps on the 2.4ghz network.
If you've tried everything above, and are using WPA2, try changing to WPA3 Personal.
### Intrusion Prevention Detection
One user had 4 OG units, and Unifi chose to single out one unit that caused failure after WiFi setup (no further connectivity to trmnl.app):
"Unifi flagged the device as a threat, via its Intrusion Prevention Detection ([IPS](https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Intrusion-Detection-and-Prevention-IDS-IPS)). It allowed the device to connect to the network but dropped all packets."
## Congestion and Compatibility
Two things to look at:
- Ensure you are not heavily overlapping with a neighbour's channel, which can cause issues if it's massively congested.
- Also, in the *Settings* -> *WiFi,* if you click on your network, you can see there is an *advanced* section below where you can change the compatibility.
Unify likes to up this so it's not as compatible with low-speed devices like the ESP's. You can manually alter this to **low density**.
You can make a 2.4Ghz only network and also an IOT network where the settings are lower density, so it will go as low as 1 Mbps.
## Does the device support WiFi feature XXX?
TRMNL (OG) uses an [ESP32-C3's wifi module](https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-guides/wifi-security.html).
# What else?
In general, device connectivity issues are a mix of local networking / router quirks and some ongoing compatibility improvements being made to our open source firmware:
So if your device is having issues, rest assured that we can probably fix it with a little bit of back and forth debugging, and your hardware is in good condition otherwise.
(*As of February 2026 we've shipped 30,000+ devices. Only 1x had a broken WiFi receiver on the board, which we immediately replaced at no cost to the customer.*)
---
***Unhappy with this article?*** *Let us know what you were searching for and why this article missed the mark for you. Email [support@trmnl.com](mailto:support@trmnl.com?subject=Device%20WiFi%20troubleshooting%20Article%20Feedback) and let us know (use the link to automatically include the article title)!*
---
# Connect your Device to Terminus (BYOS)
## Step 0 - What is Terminus
[Terminus](https://github.com/usetrmnl/byos_hanami) is the TRMNL-maintained [Bring Your Own Server](https://docs.trmnl.com/go/diy/byos) (BYOS) solution for self-hosting. It offers a convenient one-line script for installing with Docker in your environment as well as other options outlined in the [GitHub Repository](https://github.com/usetrmnl/byos_hanami).
You can follow these steps with any BYOS solution.
## Step 1 - Turn your Device On
Flip the power switch to ON from the back of your TRMNL device (TRMNL OG) or attach the charging dock to the back of the unit (TRMNL X). After it wakes up and blinks a few times, your screen might look something like this:
If nothing happens when you turn on the device, the battery may be drained. In this case, plug TRMNL in with the included USB-C cable (if part of your order).
Restart your device using the OFF/ON switch on the back (TRMNL OG).
## Step 2 - Connect to WiFi
As the device screen says, use your phone or a nearby computer to find and connect to the WiFi network which includes the name "TRMNL" which will broadcast from your device.
After connecting, a WiFi portal will appear (your WiFi credentials are [never](https://docs.trmnl.com/go/how-it-works#opinionated-device-less-than-greater-than-server-relationship) sent to our servers).
Click **Advanced** > **Custom Server** > **Yes** to expose the URI for your BYOS server.
Add the full path to your server as set up and displayed in the Terminus Dashboard. Do not include a trailing slash.
# bad
http://192.68.0.2:2300/
# good
http://192.168.0.2:2300
Once entered, click **Back to Wi-Fi**, select your SSID, and enter your network password. Finally, click **Connect** to complete setup by sending these credentials to your device.
**Troubleshooting**
For suggestions to fix a few rare issues, see our [troubleshooting guide](https://help.trmnl.com/en/articles/10193157-device-wifi-troubleshooting).
## Step 3 - Device Registration
If all information is correct, your device will register with Terminus and display the following message, or something similar, on the screen:
Congratulations, you're ready to configure the device in Terminus!
## Switching From BYOS to TRMNL core
If you have configured your device as above, but want to switch back to trmnl.com as your server, you will need to follow a few steps.
1. [Enter WiFi Pairing mode](https://help.trmnl.com/en/articles/11511577-enable-wifi-pairing-mode) again.
2. Reconnect to the TRMNL SSID and, under **Advanced**, perform a *Soft Reset.*
3. When the device restarts, follow the normal [setup guide](https://help.trmnl.com/en/articles/9416306-how-to-set-up-a-new-device).
**Note:** If the portal still shows an API Server URL, you should clear that field completely before clicking **Connect**. When blank, it defaults to TRMNL's server.
### Note about Ngrok
About 1 year ago Ngrok added a splash page to the ~1st HTTP request sent to their server (per Origin IP). so it's necessary to follow the instructions to add a skip header or click through on the page themselves in a web browser, then retry the API requests.
---
# Portrait mode
With the release of the TRMNL X, we officially support portrait mode for our native plugins and require compatibility for all community recipes released since January 17, 2026, however hundreds of plugins published prior were updated to support the layout.
To change your device's orientation, go to your **Device Settings** > **Color & Rotation** > **Screen Rotation** ([quick link for your current device](https://trmnl.com/devices/current/palette/edit))
Change the setting to **Portrait** and *save*.
Once changed, the backend screen generator still needs to generate screens of your playlist items in the new orientation. This will happen automatically based on the plugin's refresh rate, or for private plugins and installed recipes you can use the **Force Refresh** feature on the plugin's settings page to expedite the process.
### I Don't See the Screen Rotation Option
If it is not present, your device does not currently support portrait mode. This is most common with smaller screen sizes, like the TRMNL OG or smaller.
## Using CSS to mimic portrait mode
This section is being kept for reference only and is **NOT** recommended.
First, build a plugin. This can be a Recipe, private plugin, or third party instance. Learn more about each type [here](https://help.usetrmnl.com/en/articles/10546870-compare-custom-plugin-types).
Next, inside your markup, simply add the `transform` CSS declaration like so:
transform: rotate(90deg);
Here's some working sample code:
First Time Around
First Time Around is a one-disk DVD by Randy Bachman and Burton Cummings recorded in 2006 at CBC Studios in Toronto, Canada, by CBC. It was originally shown on CBC in April 2006, but was later released as a DVD with extended footage of the concert. The concert has 20 tracks of songs by Bachman-Turner Overdrive, Burton Cummings, The Guess Who and cover versions of artists such as Sting and Jimi Hendrix. It was originally shown on CBC in April 2006, but was later released as a DVD with extended footage of the concert. The concert has 20 tracks of songs by Bachman-Turner Overdrive, Burton Cummings, The Guess Who and cover versions of artists such as Sting and Jimi Hendrix Sting and Jimi Hendrix. It was originally shown on CBC in April 2006, but was later released as a DVD with extended footage of the concert. The concert has 20 tracks of songs by Bachman-Turner Overdrive, Burton Cummings, The Guess Who and cover versions of artists such as Sting and Jimi Hendrix Sting and Jimi Hendrix. It was originally shown on CBC in April 2006, but was later released as a DVD with extended footage of the concert. The concert has 20 tracks of songs by Bachman-Turner Overdrive, Burton Cummings, The Guess Who and cover versions of artists such as Sting and Jimi Hendrix.
Which looks like this in TRMNL's live preview markup editor:
And here's the completed render:
---
# Enable WiFi Pairing Mode
TRMNL supports multiple WiFi credential pairs, all of which is stored locally on your device and never sent to the cloud or your [BYOS server clients](https://docs.usetrmnl.com/go/diy/byos).
If you change network settings or moved your TRMNL between locations, you might need to update the WiFi configuration.
Select your device for instructions to reset WiFi.
Quick Links:
- [TRMNL OG](#h_26e0b8775e)
- [TRMNL X](#h_c231afff3a)
# TRMNL OG (7.5" device)
Resetting WiFi on the OG is a simple process.
## Manually Enter Pairing Mode
With your TRMNL device already turned on, hold the boot button on the back of your TRMNL for 6-8 seconds, then let go and it should enter WiFi pairing mode. You can also try holding the button for 15-20 seconds if this shorter value doesn't work.
## Set a Macro for Pairing Mode
If you anticipate frequent WiFi or location changes for your TRMNL, consider setting a [special function](https://help.usetrmnl.com/en/articles/9672080-special-functions) so make your device enters WiFi pairing whenever you medium-press (1-2s hold) on the boot button.
## Default approach to Pairing Mode
If your device has become unresponsive, turn it off for 10 seconds to reset the buffer. Then turn it back on. If the network SSID is not found, it should re-enter pairing mode.
---
# TRMNL X (10.3" device)
See this video or follow the steps below it.
## Manually Enter Pairing Mode
[https://www.youtube.com/embed/aXiA_liuCvI?rel=0](https://www.youtube.com/embed/aXiA_liuCvI?rel=0)
### Steps from the video
Hold both the left and the right side of the touchbar until the screen blinks:
Tap the middle to cancel, or hold the middle to enter pairing mode:
---
# Creating Inline Images for Plugins
The [Framework](https://trmnl.com/framework) [Title Bar](https://trmnl.com/framework/title_bar) component commonly includes a small icon to represent the plugin, especially on smaller mashup views. This is the most common place SVG image files are used to display simple graphics or iconography in plugins, but the following steps can be used for other SVGs in your plugin.
To see a clear example of converting an SVG network call (...) to an inline SVG within a plugin, [this commit](https://github.com/stephenyeargin/trmnl-adafruit-io/commit/a555db36633a718ffe0f178b3c8023ca0992d45c) by community member Stephen Yeargin is invaluable.
# Converting an Image File to SVG
If you aren't already using an SVG file for your icon, some tools can help you do the conversion.
For PNG, [pngtosvg.com](https://www.pngtosvg.com/) is a great option. Depending on your image, you'll want to adjust the *Colors* and *Simplify* values to get the best results, but here are some examples:
- Complex full color image: *Colors* to `1` and *Simplify* to `10`
- Simple, one-color (non-black) image: *Colors* to `2` and *Simplify* to `0`
For JPG, you may need to do more preparation of your JPG, but there are numerous converters available.
# Converting an SVG to Inline Code
The tool [svg-2-code](https://nikitahl.github.io/svg-2-code/) by nikitahl is a good option. The output `