A tagged template is a function that receives a template literal split into its static parts and its interpolated values — html`<p>${name}</p>` calls html(['<p>', '</p>'], name). That split is exactly what you need to build HTML safely: the static parts are trusted markup you wrote, but the ${…} values are (potentially) untrusted data, so you escape them before stitching everything together. This is the core of lit-html, htm, and every "html in JS without a framework" library.
Implement the html tag. Join the literal parts with the escaped values, parse the result into a DocumentFragment via a <template>, and return it — so an injected <script> becomes harmless text, not a live element.
function html(strings, ...values) {
// returns a DocumentFragment
}
const name = 'Ada';
html`<p>Hello ${name}</p>`; // <p>Hello Ada</p>
html`<div>${'<img src=x onerror=hack()>'}</div>`;
// the value is escaped -> <div><img …></div> (text, not an <img>)
strings (the literal parts) are trusted HTML; the values are untrusted and must be HTML-escaped. Escape only the values.&, <, >, ", ' → their entities. An array value is escaped element-wise and joined.strings[0] + escape(values[0]) + strings[1] + escape(values[1]) + ….<template>'s innerHTML and return template.content (a DocumentFragment you can insert anywhere).Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.