trmnl
GitHub为TRMNL电子墨水屏设备生成HTML内容。通过Webhook API发送符合TRMNL框架规范的布局与样式,支持仪表盘、通知及终端显示更新,需处理Payload大小限制及设备配置。
Trigger Scenarios
Install
npx skills add viralcode/openwhale --skill trmnl -g -y
SKILL.md
Frontmatter
{
"name": "trmnl",
"description": "Generate content for TRMNL e-ink display devices using the TRMNL CSS framework. Use when the user wants to display information on their TRMNL device, send messages to an e-ink display, create dashboard content, show notifications, or update their terminal display. Supports rich layouts with the TRMNL framework (flexbox, grid, tables, progress bars, typography utilities) and sends content via webhook API."
}
TRMNL Content Generator
Generate HTML content for TRMNL e-ink display devices.
Quick Start Workflow
- Check for
$TRMNL_WEBHOOKenvironment variable - If missing, prompt user for webhook URL
- Ask user to verify TRMNL display markup is set to:
<div>{{content}}</div> - Confirm device type (default: TRMNL OG, 2-bit, 800x480)
- Read relevant reference docs based on content needs
- Generate HTML using TRMNL framework classes
- Send via POST to webhook (use temp file method)
- Send minimal confirmation only - Do NOT echo content back to chat
Device & Setup
Default target: TRMNL OG (7.5" e-ink, 800x480px, 2-bit)
Display markup required:
<div>{{content}}</div>
Webhook:
export TRMNL_WEBHOOK="https://trmnl.com/api/custom_plugins/{uuid}"
Sending content (temp file method):
cat > /tmp/trmnl.json << 'EOF'
{"merge_variables":{"content":"<div class=\"layout\">HTML</div>"}}
EOF
curl "$TRMNL_WEBHOOK" -H "Content-Type: application/json" -d @/tmp/trmnl.json -X POST
Webhook Limits
| Tier | Payload Size | Rate Limit |
|---|---|---|
| Free | 2 KB (2,048 bytes) | 12 requests/hour |
| TRMNL+ | 5 KB (5,120 bytes) | 30 requests/hour |
Check payload size before sending:
python3 scripts/check_payload.py /tmp/trmnl.json
Tips to reduce payload size:
- Minify HTML (remove unnecessary whitespace)
- Use framework classes instead of inline styles
- Use short class names the framework provides
- Remove comments from HTML
- Use
deep_mergestrategy for incremental updates
Reference Documentation
Read these files as needed:
| File | When to Read |
|---|---|
references/patterns.md |
Start here - Common plugin patterns from official examples |
references/framework-overview.md |
Device specs, e-ink constraints, responsive prefixes |
references/css-utilities.md |
Colors, typography, sizing, spacing utilities |
references/layout-systems.md |
Flexbox, grid, overflow/clamp engines |
references/components.md |
Title bar, dividers, items, tables, charts |
references/webhook-api.md |
Payload format, rate limits, troubleshooting |
assets/anti-patterns.md |
Common mistakes to avoid |
assets/good-examples/ |
HTML reference implementations |
Scripts:
| Script | Purpose |
|---|---|
scripts/check_payload.py |
Verify payload size before sending (run on /tmp/trmnl.json) |
Standard Plugin Structure
Every plugin follows this pattern:
<div class="layout layout--col gap--space-between">
<!-- Content sections separated by dividers -->
</div>
<div class="title_bar">
<img class="image" src="icon.svg">
<span class="title">Plugin Name</span>
<span class="instance">Context</span>
</div>
layout+layout--col= vertical flex containergap--space-between= push sections to edgestitle_bar= always at bottom, outside layoutdivider= separate major sections- CRITICAL: Only ONE
.layoutelement per view (no nesting)
Grid System (10-Column)
Column spans should sum to 10:
<div class="grid">
<div class="col--span-3">30%</div>
<div class="col--span-7">70%</div>
</div>
Simple equal columns: grid--cols-2, grid--cols-3, etc.
Item Component
Standard data display pattern:
<div class="item">
<div class="meta"><span class="index">1</span></div>
<div class="content">
<span class="value value--xlarge value--tnums">$159,022</span>
<span class="label">Total Sales</span>
</div>
</div>
Value Typography
Always use value--tnums for numbers.
| Class | Usage |
|---|---|
value--xxxlarge |
Hero KPIs |
value--xxlarge |
Large prices |
value--xlarge |
Secondary metrics |
value--small |
Tertiary data |
value--tnums |
Always for numbers |
Auto-fit: <span class="value" data-fit-value="true">...</span>
Columns (for Lists)
<div class="columns">
<div class="column" data-overflow="true" data-overflow-counter="true">
<span class="label label--medium group-header">Section</span>
<div class="item">...</div>
</div>
</div>
Grayscale Dithering
Use dithered classes, not inline gray colors:
bg--black,bg--gray-60,bg--gray-30,bg--gray-10,bg--whitetext--black,text--gray-50
Data Attributes
| Attribute | Purpose |
|---|---|
data-fit-value="true" |
Auto-resize text to fit |
data-value-format="true" |
Auto-format numbers (locale-aware) |
data-clamp="N" |
Limit to N lines |
data-overflow="true" |
Enable overflow management |
data-overflow-counter="true" |
Show "and X more" |
data-overflow-max-cols="N" |
Max columns for overflow |
data-content-limiter="true" |
Auto-adjust text size |
data-pixel-perfect="true" |
Crisp text rendering |
data-table-limit="true" |
Table overflow with "and X more" |
Label & Title Variants
<span class="label label--small">Small</span>
<span class="label label--medium">Medium</span>
<span class="label label--underline">Underlined</span>
<span class="label label--gray">Muted/completed</span>
<span class="title title--small">Compact title</span>
Gap Utilities
gap--space-between | gap--xxlarge | gap--xlarge | gap--large | gap--medium | gap | gap--small | gap--xsmall | gap--none
Typography Guidelines
Recommended: Georgia serif font for e-ink readability.
Content-aware sizing:
- Short content = bigger fonts (24-28px body)
- Long content = smaller fonts (20px body)
- Headings: 36-48px
User Experience
Critical: Do NOT echo content back to chat. Just confirm "Sent to TRMNL".
Anti-Patterns
- Tiny fonts for short content
- Center-aligning columns with different lengths (use
layout--start) - Spoiling content in chat confirmation
- Missing
value--tnumson numbers - Missing
title_bar - Not using
data-fit-valueon primary metrics - Skipping
data-overflowon variable lists - Using inline gray colors instead of
bg--gray-* - Forgetting dividers between sections
Best Practices
- Verify
<div>{{content}}</div>display markup - Use
layout+title_barstructure - Always
value--tnumsfor numbers - Use
data-fit-valueon primary metrics - Use
data-overflowon variable lists - Use
itemcomponent pattern - Use
dividerbetween sections - Use
bg--gray-*dithered classes - Content-aware font sizing
- Top-align columns (
layout--start) - Temp file method for curl
- Minimal confirmations
Mashup Layouts (Multi-Plugin)
For dashboard views with multiple plugins:
| Layout | Description |
|---|---|
mashup--1Lx1R |
2 columns (50/50) |
mashup--1Tx1B |
2 rows (50/50) |
mashup--2x2 |
4 quadrants |
See references/layout-systems.md for all 7 layouts.
Troubleshooting
| Problem | Solution |
|---|---|
| Webhook fails | Verify URL, check rate limits (12/hour free) |
| Content missing | Check display markup is <div>{{content}}</div> |
| Payload too large | Run scripts/check_payload.py, keep under 2KB (free) or 5KB (TRMNL+) |
| Numbers misaligned | Add value--tnums |
| Text overflow | Use data-clamp or data-overflow |
| Columns misaligned | Use layout--start not layout--center |
| Multiple layouts error | Keep only ONE .layout element per view |
| Nested content fails | Use .richtext for nested/formatted content |
Version History
- 238a4b1 Current 2026-08-20 08:22


