Security
Text, attributes and URLs each get the right escaping. A javascript: URL renders as about:blank#blocked. If escaping cannot make a value safe, it throws.
import { html, attrs } from '@itsy/html';
type Link = { href: string; label: string; active?: boolean };
const Link = ({ href, label, active = false }: Link) =>
html`<a ${attrs({ href, class: ['link', { active }], aria: { current: active ? 'page' : null } })}>${label}</a>`;
const Menu = (title: string, links: Link[]) => html`
<nav ${attrs({ aria: { label: title }, hidden: !links.length })}>
<h2>${title}</h2>
${links.map(Link)}
</nav>`;
Menu('Docs & more', [
{ href: '/guide', label: 'Guide', active: true },
{ href: 'javascript:alert(1)', label: '<script>' },
]);<nav aria-label="Docs & more"> <h2>Docs & more</h2> <a href="/guide" class="link active" aria-current="page">Guide</a><a href="about:blank#blocked" class="link"><script></a> </nav>