Install
$ agentstack add skill-oaustegard-claude-skills-composing-html ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
composing-html
Produce single-file HTML artifacts without hand-writing the page chrome. The composer supplies `, , inlined CSS, base.js`, design tokens, masthead, and colophon. You supply a title and the body content.
The product is the chrome and inventory below — primitives you can drop into any artifact without re-deriving what a card, badge, or eyebrow looks like. Templates are shortcuts on top of this, useful when the same artifact shape repeats; see [Templates](#templates-shortcuts-for-repeat-structure) near the end.
Default workflow: freeform
freeform gives you the whole chrome with one content slot — body_html — for the page body. Reach for it first. Reach for a template only when the structure repeats across artifacts (see [Templates](#templates-shortcuts-for-repeat-structure) near the end).
There are two ways to invoke it. Use the --set flow for anything with a substantial body — it sidesteps the JSON-string escaping that bites heredoc-style spec writing (newlines, quotes, ` # required keys + skeleton
- write spec.json
- python scripts/build.py build --spec spec.json --out artifact.html
For templates with typed slots (`pr_review.findings[]`, `slide_deck.slides[]`,
`status_report.metrics[]`), the spec file is the right shape — the template
reasons over the structure. For `freeform`, the spec is mostly a thin config
wrapper around one HTML string; the `--set` flow above is usually less
friction.
You can mix both: small `spec.json` for metadata, `--set body_html=@body.html`
for the heavy bit. `--set` overrides any matching field from `--spec`.
### Pitfall: don't inline multi-line HTML into a JSON heredoc
`cat > spec.json \n..." } EOF` does not
produce valid JSON — JSON strings can't contain raw newlines or unescaped
quotes. Either:
- use `--set body_html=@body.html` (recommended), or
- assemble the spec in Python with `json.dump(spec, f)` so escaping is automatic.
## Inventory
Everything in this section is loaded into every artifact via inlined CSS and
`base.js`. Use these tokens and classes inside `body_html` (or any
template's `*_html` field) without re-declaring them.
### Color tokens
| Token | Hex | Use |
|---|---|---|
| `--ivory` | `#FAF9F5` | Page background |
| `--paper` | `#FFFFFF` | Card background |
| `--slate` | `#141413` | Headings, inverted background |
| `--clay` | `#D97757` | Brand accent (lines, primary actions) |
| `--clay-d` | `#B85C3E` | Hover/dark variant |
| `--oat` | `#E3DACC` | Soft contrast surface |
| `--olive` | `#788C5D` | Success, secondary accent |
| `--rust` | `#B04A3F` | Errors, destructive |
| `--moss` | `#4A6B3A` | Success text |
| `--g100` … `--g700` | grays | Surfaces, borders, body text |
Semantic aliases: `--ok`, `--warn`, `--err`, `--info`.
### Type stacks
- `--serif` — display headings (h1, h2, big numerics).
- `--sans` — body text (default).
- `--mono` — code, eyebrows, badges, captions.
### Geometry
`--radius-sm` (6px) · `--radius` (10px) · `--radius-lg` (16px) ·
`--border` · `--border-soft` · `--shadow-card` · `--shadow-pop`.
### Layout primitives
- `.page` — main column (1080px max). Variants: `.page--wide` (1280px),
`.page--narrow` (720px). Set via the `page_class` spec key.
- `.masthead` — header strip with `.eyebrow` + `` + `.subtitle`
(auto-rendered from `title`/`subtitle`/`eyebrow` unless `show_masthead`
is false).
- `.grid .grid--2|3|4|auto` — responsive CSS grid.
- `.stack`, `.row` — vertical / horizontal flex.
- `.card`, `.card--soft`, `.card--elev` — content containers.
- `.rule` — `` underline below ``.
- `.colophon` — footer strip (auto-added by composer).
### Components
- **Eyebrow**: `SECTION` — small all-caps label
with a leading clay rule.
- **Badge**: `v1.0`.
- **Kbd**: `⌘K`.
- **Bullets**: `…` — clay dots.
- **Code**: inline `` and block ``. Block code gets a
`copy` button automatically via `base.js`.
- **Details**: native `……` styled.
### Tabs
```html
Tab A
Tab B
…
…
base.js wires this automatically and selects the first tab by default.
Drag-to-reorder
…
…
Optional cross-zone drops: add data-zone="" to each container.
Live parameter bindings
.box { width: var(--bind-size, 50px); }
The CSS custom property --bind- is updated on every input event, and any [data-out=""] element receives the formatted value.
Output rules
Spend output tokens on content, not chrome:
- Never write `
,,,, or`. The
composer adds all of them. If you find yourself writing a complete page, you missed the skill.
- Don't restate design tokens. Reuse the inventory above —
var(--clay),
.card, .badge--warn, .bullets, etc. are already loaded. Don't hardcode hex/rgb() colours, inline font-family/font-size, or reference tokens that aren't in the palette.
body_htmlis HTML, not a JSON dialect. Write `,`,
`` directly. No translation layer.
- Anything in an
_htmlfield is inserted verbatim — escape any
user-supplied content yourself. All other string values are HTML-escaped automatically.
- One artifact per build. Browser tabs are free.
Checking output
After building, lint the artifact before presenting it:
python scripts/build.py check artifact.html
The checker is deterministic — no model call, stdlib only. It doesn't grade taste (the fixed chrome already prevents the usual AI tells); it flags content that breaks out of the design system or wires base.js hooks to nothing — the failure modes the chrome can't prevent on its own:
| rule | catches | severity | |---|---|---| | chrome-leak | // (and top-level /) in body_html | error | | undefined-token | var(--typo) — a token not in the palette or declared here | error | | broken-tabs | data-target with no matching .tab-panel[data-id] | error | | hardcoded-color | #hex / rgb() literals instead of palette tokens | warn | | inline-typography | font-family / font-size overriding the type stacks | warn | | undefined-token for --bind-* | (allowed — created by data-bind) | — | | nested-card | .card inside .card | warn | | broken-bind | data-bind with no consumer, or orphan data-out | warn | | broken-sortable | data-sortable with no draggable children | warn | | heading-skip | heading levels that jump (h1 → h3) | warn | | img-no-alt | ` without an alt` attribute | warn |
Exit code is non-zero when any error-severity rule fires. The output rules above carry ` anchors tying each guidance line to its check, so the teaching and the enforcement stay in sync. Full-artifact vs body fragment is auto-detected; force with --full / --fragment. --json` emits machine-readable findings. Contrast ratios are intentionally not checked — the token pairs are pre-vetted and regex can't judge author-introduced pairs without false positives.
Iteration
Edit the spec, re-run build, open in a browser. If a layout pattern repeats across multiple artifacts, that's when a template earns its keep — otherwise stay in freeform.
Templates: shortcuts for repeat structure
When the same artifact shape recurs (status reports week after week, PR reviews across many PRs, slide decks with consistent navigation), a template's fixed slot map is worth the translation cost. It enforces cross-artifact consistency and skips the layout decisions you'd otherwise re-derive each time.
Use a template only when:
- You're producing the same artifact shape repeatedly.
- The repeat structure justifies a fixed slot map.
- Cross-artifact consistency matters more than per-artifact flexibility.
Otherwise: freeform.
1. python scripts/build.py list # all templates, one-line summaries
2. python scripts/build.py describe # required keys + JSON skeleton
3. write spec.json # only your content + parameters
4. python scripts/build.py build --spec spec.json --out artifact.html
describe prints a valid-JSON starter skeleton you can edit in place. For worked examples, see references/templates.md — but only after picking a template; reading it cold wastes context.
For templates with prose-heavy *_html slots (e.g. summary_html, intro_html, details_html), the same --set KEY=@FILE mechanism from the freeform workflow applies — load the prose from a .html file rather than escaping it into the JSON spec.
There are 21 templates, grouped into 9 categories plus freeform:
report.*— statusreport, incidentreportreview.*— prreview, codewalkthrough, module_mapeditor.*— triageboard, flageditor, prompt_tunerdeck.*— slide_deck (arrow-key + space navigation)design.*— designsystem, componentvariantsexploration.*— comparisongrid, designdirections, implementation_planresearch.*— featureexplainer, conceptexplainerdiagram.*— svgfiguresheet, flowchartprototype.*— animationsandbox, clickflow
Some templates with prose-heavy slots take raw HTML in keys ending with _html (e.g. summary_html, intro_html, details_html). Same rules as freeform.body_html: use the inventory above, escape user-supplied content.
Tests
tests/test_smoke.py covers every template with a representative spec plus explicit security regressions (table escaping, script-tag breakout in prompt_tuner, attribute injection in flag_editor, CSS-color injection, spec mutation in module_map). tests/test_checker.py covers the check linter — one assertion per rule (fires on the violation, silent on the clean case). Run with:
python composing-html/tests/test_smoke.py # no pytest required
python composing-html/tests/test_checker.py # no pytest required
python -m pytest composing-html/tests -q # if pytest is available
When adding or changing a template, add a spec entry and any regression asserts before merging. When adding a checker rule, add it to both scripts/checker.py and a `` anchor in the relevant guidance line, plus a test assertion.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: oaustegard
- Source: oaustegard/claude-skills
- License: MIT
- Homepage: https://austegard.com/ai-tools/claude-skill-releases.html
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.