@itsy/html
v1.1.0
Published
tiny isomorphic HTML renderer — tagged templates in, escaped strings out; context-aware escaping, URL scheme guard, one attribute helper, no hydration
Maintainers
Readme
@itsy/html
Tagged-template HTML renderer, zero dependencies, just enough features.
Documentation · Playground · Error codes
pnpm add @itsy/htmlimport { html, attrs } from '@itsy/html';
const Link = ({ href, label, active }: LinkData) =>
html`<a href="${href}" class="link ${active && 'is-active'}">${label}</a>`;
const Menu = ({ groups }: MenuData) => html`
<nav ${attrs({ 'aria-label': 'Main', hidden: groups.length === 0 })}>
${groups.map(
(g) => html`
<h2>${g.title}</h2>
<ul>${g.links.map((l) => html`<li>${Link(l)}</li>`)}</ul>`,
)}
</nav>`;
res.send(Menu(data).markup); // server
el.innerHTML = Menu(data).markup; // client: same function, same string, no hydrationWhy
Most template renderers escape every value the same way, wherever it lands, and none of them look
at URLs. One escape cannot be right everywhere: text needs entities, an unquoted attribute needs
quotes before anything else helps, and javascript: in an href contains nothing worth escaping
and still runs.
This one reads the static markup around each value, works out which context the value landed in,
and applies what that context needs. Where no escaping would make a context safe — inside a tag,
inside <script>, in any on* attribute — it throws instead of guessing. The result is an Html
wrapper, so a template nested in another is not escaped twice, and the same function renders on a
server and in a browser.
Features
- Escaping by context — text, a quoted attribute, a URL, the inside of a tag, a script body and a comment are six different jobs, and the renderer picks per value.
- A URL scheme guard — anything outside
http https mailto tel data blobrenders asabout:blank#blocked, however it is spelled. - A markup check in development — unclosed tags, stray end tags and nesting a browser would rewrite, reported the first time a template runs.
attrs()andcx()— booleans, class lists, style objects,ariaanddatagroups, with the URL guard applied to attributes that hold URLs.frame()— a whole document, with a head that merges so a page can override one entry of a shared layout without reordering the rest.check()— the same checks over a rendered page, plus duplicate ids, id references pointing nowhere, and any URL the guard blocked. 28 bytes in production.- Twenty-five accessibility rules, running inside
check()by default, in the same pass and the same list: the unlabelled icon button, the misspelledaria-attribute, the image with noalt, the role that does not exist.{ a11y: { without: [...] } }turns any of them off, with the names type-checked. They cost nothing — the production build compiles them away with the rest ofcheck(). - Custom rules —
check(page, { rules })runs a project's rules in that same pass, so house style and design-system constraints report like everything else, and ship like everything else: not at all.
Before starting
[!WARNING]
Htmlis an object, not a primitive.typeofreports'object', an empty one is truthy, and any framework that serializes objects will JSON-encode it rather than send the markup. Useview.markupat that boundary.
[!WARNING] Prettier and oxfmt reformat the HTML inside
htmltemplates by default.<br>becomes<br />, which the markup check then reports. Set"embeddedLanguageFormatting": "off".
Size
Minified and brotli-compressed, with the listed imports and nothing else.
| import | | production | development |
| ------------------- | -------------------------- | ----------- | ----------- |
| @itsy/html | html, attrs, raw | 1.79 kB | 5.79 kB |
| @itsy/html/attrs | attrs, cx | 929 B | — |
| @itsy/html/check | check | 28 B | 8.91 kB |
| @itsy/html/frame | frame, head, element | 2.6 kB | 6.64 kB |
| @itsy/html/util | all seven helpers | 1.21 kB | — |
| @itsy/html/create | createHtml | 1.84 kB | — |
The import map has every entry point and what each one exports.
For coding agents
AGENTS.md is a task-oriented map of the library, and it ships inside the package.
The site also publishes llms.txt and llms-full.txt.
License
MIT
