--- name: inline-widget description: "Inline HTML widgets: charts, dashboards, data tables rendered directly in the chat via ShowWidget" --- # Inline Widget Render interactive HTML/SVG widgets directly inside the chat conversation using `ShowWidget`. Widgets appear inline between text — no sandbox, no preview URL, no side panel. They run JavaScript, so lean into making them **interactive and explorable** where it helps — something the user can sort, filter, toggle, and hover over, not just a static picture. ## When to Use - User wants a **quick visualization** embedded in the conversation (chart, metric card, data table) - The visualization is **self-contained** — all data is embedded in the HTML, no server needed - User wants **interactivity** within the chat: buttons, toggles, hover effects, animated charts - The output is a **single view** — not a multi-page app or dashboard that needs routing **Use `interactive-dashboard` instead if:** User needs a multi-page web app, server-side data, live data refresh, or complex interactivity requiring React/FastAPI. Read `.agents/skills/ui-design/SKILL.md` for design quality — its color discipline, chart restraint, and anti-slop principles apply here too. Its font pairings and type scale, though, are for full documents; a widget sits on the chat surface, so use the host-font typography rules below instead. ## ShowWidget API ``` ShowWidget(html: str, title: str | None = None, data_files: list[str] | None = None) ``` - `html`: Raw HTML fragment — no ``, ``, ``, or `` tags - `title`: Optional metadata (not displayed to user) - `data_files`: Optional list of sandbox file paths to make available as `window.__WIDGET_DATA__` The HTML is rendered in a sandboxed iframe with: - **CDN libraries**: `cdnjs.cloudflare.com`, `cdn.jsdelivr.net`, `unpkg.com`, `esm.sh` - **CSS theme variables**: automatically injected (see Theme section) - **`sendPrompt('text')`**: global function to trigger follow-up chat messages - **`window.__WIDGET_DATA__`**: dict of filename→content for files passed via `data_files` - **No network to non-CDN origins**: `fetch()` / `XMLHttpRequest` to arbitrary URLs are blocked by CSP — only CDN domains (cdnjs, jsdelivr, unpkg, esm.sh) are allowed. Use `data_files` for sandbox files, or embed small data directly in HTML ## Layout Rules (CRITICAL) The widget sits directly on the chat surface inside a transparent iframe. Follow these rules for seamless integration: ### Outer Element — Transparent Shell The **outermost HTML element** must have: - **NO background** (or `background: transparent`) - **NO border** - **NO border-radius** - **NO box-shadow** - **NO padding** — add padding on inner sections only ```html
...inner card content...
...content...
``` ### Inner Elements — Use Theme Variables Inner cards, sections, and components should use CSS variables for styling, always in the fallback form: ```css /* Card */ background: var(--color-bg-card, #ffffff); border: 0.5px solid var(--color-border-muted, #e4e1dc); border-radius: 8px; padding: 16px; /* Metric card */ background: var(--color-bg-subtle, #f4f2ee); border: 0.5px solid var(--color-border-muted, #e4e1dc); border-radius: 8px; ``` In chat the variables are always injected, so the fallback never shows there — it is what renders when the widget is saved as a workspace `.html` file and previewed before theme injection or opened standalone. Use the light literals from the html-report skill's table. ### Positioning - **NO `position: fixed`** — breaks iframe auto-sizing (elements collapse to 0 height) - Use `position: relative` for chart containers - No nested scrolling — the iframe auto-sizes to fit all content ## Theme Variables These CSS variables are automatically injected and resolve correctly in both light and dark mode: | Variable | Purpose | |----------|---------| | `--color-bg-page` | Page background | | `--color-bg-card` | Card/panel background | | `--color-bg-elevated` | Elevated surface | | `--color-bg-subtle` | Subtle/muted background | | `--color-bg-hover` | Hover state background | | `--color-text-primary` | Primary text | | `--color-text-secondary` | Secondary/muted text | | `--color-text-tertiary` | Hint/label text | | `--color-border-muted` | Default border (use with 0.5px) | | `--color-accent-primary` | Brand/accent color | | `--color-profit` | Positive/gain (green) | | `--color-loss` | Negative/loss (red) | | `--color-warning` | Warning (amber) | | `--color-info` | Info (blue) | | `--color-success` | Success (green) | **Never hardcode colors** like `#333` or `rgb(...)` for text, backgrounds, or borders — they break in dark mode. Author every color as `var(--color-x, #lightLiteral)`: the injected variable always wins in chat, and the fallback keeps the widget legible on its secondary surfaces. The only exception is chart canvas colors — Chart.js canvas cannot read CSS variables; resolve them via `getComputedStyle` with a literal fallback. ## Charts (Chart.js) Load Chart.js from CDN and follow these rules: ```html
``` **Key rules:** - Set height on the **wrapper div**, never on the canvas - Always use `responsive: true, maintainAspectRatio: false` - Use UMD build from CDN (sets `window.Chart` global) - Read CSS variables via `getComputedStyle` with a literal fallback (`pick()`) for chart colors ## Typography - Font: inherited from host (`-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`) - Weight: favor **400** (regular) and **500** (medium); use 600 sparingly for a key figure or heading. Avoid 700 — it reads heavy on the chat surface - Heading sizes: h1 = 22px, h2 = 18px, h3 = 16px (all weight 500) - Body: 14-16px, weight 400 - Use **sentence case** — no Title Case or ALL CAPS (except short metric labels) ## Interactivity ### Make it explorable A widget runs JavaScript, so prefer something the user can **poke at**, not just read. When the data supports it, reach for: - **Sortable / filterable tables** — click a header to sort by P&L or weight; filter to a sector or watchlist. - **Series & metric toggles** — show/hide chart series, switch price ↔ % change, flip timeframe (1M / 6M / 1Y). - **Hover detail** — tooltips on chart points and table rows that surface the underlying numbers. - **What-if inputs** — a slider or field that recomputes a figure live (drag a growth rate, watch the projection update). - **Tabs / segmented views** — split a dense widget (Overview / Holdings / Performance) so the reader drills in. It all runs **client-side over the embedded data** — never `fetch()` a non-CDN origin (CSP blocks it; use `data_files` for anything large). Keep a meaningful default state so the widget reads correctly before any interaction. Use `sendPrompt()` only when the next step genuinely belongs back in the chat (a new query, a deeper analysis) — handle exploration the widget can do itself in-place. ### sendPrompt() Call `sendPrompt('text')` from buttons to trigger a follow-up chat message: ```html ``` Add a **↗ arrow** on buttons that call `sendPrompt()` to signal they trigger a chat action. ### Refresh / Animation `setInterval` and `requestAnimationFrame` work normally for animations and live tickers: ```javascript setInterval(function() { // Update prices, rotate data, animate updateDisplay(); }, 3000); ``` ## File Data Use `data_files` to load data from sandbox files instead of inlining everything in the HTML string. This is especially useful for larger datasets. ### Workflow 1. Generate data files via Python 2. Pass file paths to `ShowWidget` via `data_files` 3. Access data in the widget via `window.__WIDGET_DATA__["filename"]` ```python # Step 1: Generate data import json data = {"labels": ["Q1", "Q2", "Q3"], "values": [100, 150, 200]} with open("work//chart_data.json", "w") as f: json.dump(data, f) # Step 2: Agent calls ShowWidget with data_files ShowWidget( html='
...
', data_files=["work//chart_data.json"] ) ``` ### Widget access ```javascript // Text files (json, csv, txt, etc.) — returned as strings var data = JSON.parse(window.__WIDGET_DATA__["chart_data.json"]); var csvText = window.__WIDGET_DATA__["results.csv"]; // Binary files (png, jpg, etc.) — returned as data URLs document.getElementById("img").src = window.__WIDGET_DATA__["chart.png"]; ``` ### Supported file types - **Text** (returned as strings): `.json`, `.csv`, `.txt`, `.html`, `.xml`, `.svg`, `.md`, `.yaml`, `.yml`, `.tsv`, `.geojson`, `.topojson` - **Binary** (returned as data URLs): `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico` ### Size limits Total inline data is capped at 500KB across all files. Keep datasets concise — aggregate or sample large files before passing them. ## Blocked Patterns The following will cause `ShowWidget` to **reject** your HTML with an error. Fix and retry: | Pattern | Why blocked | |---------|-------------| | `new ResizeObserver(...)` | Host handles iframe sizing — your observer creates infinite resize loops | | `parent.postMessage(...)` | Use `sendPrompt()` instead — direct postMessage bypasses the bridge | | `window.top.*` / `window.parent.*` | Sandboxed iframe — parent access is blocked | | `position: fixed` | Breaks iframe auto-sizing | | Background/border on outermost element | Breaks seamless integration with chat surface | ## Design Patterns ### Metric Cards Row ```html
Revenue
$2.4M
+12.5%
``` ### Data Table ```html
Symbol Price
AAPL $213.18
``` ### Section with Chart ```html
Performance
```