<template> & <slot> Tags in HTML

Quick facts

Category<template>: metadata, flow, phrasing and script-supporting content. <slot>: flow and phrasing content
Content model<template>: nothing as visible children — its contents live in a separate document fragment (.content). <slot>: transparent
Tag omissionNeither the start nor the end tag can be omitted, for either element
Permitted parents<template>: almost anywhere, including <head>, tables and lists. <slot>: any element that accepts phrasing content
DOM interfacesHTMLTemplateElement, HTMLSlotElement

Syntax

<template id="row"> <li class="item"></li> </template> <script> const copy = document.getElementById("row").content.cloneNode(true); copy.querySelector("li").textContent = "New item"; document.querySelector("ul").append(copy); </script>

Live example

Edit the code below and watch the preview update instantly.

index.html
Preview

Attributes

<template>

AttributeDescription
shadowrootmodeopen or closed: turns the template into a declarative shadow root for its parent, with no JavaScript
shadowrootdelegatesfocus / shadowrootclonable / shadowrootserializableExtra options for that declarative shadow root

<slot>

AttributeDescription
nameA named slot is filled by page content with a matching slot="name" attribute; an unnamed slot takes everything else

Common examples

Rendering a list from data

Clone the template for each item, fill it in, then add it to the page.

<ul id="list"></ul> <template id="row"><li><strong class="name"></strong> – <span class="price"></span></li></template> <script> const items = [{ name: "Tea", price: "$3" }, { name: "Coffee", price: "$4" }]; for (const it of items) { const li = row.content.cloneNode(true); li.querySelector(".name").textContent = it.name; li.querySelector(".price").textContent = it.price; list.append(li); } </script>

Table rows

Templates are handy for <table> rows, because a <tr> can’t live inside a hidden <div>.

<table><tbody id="tb"></tbody></table> <template id="tr"><tr><td></td><td></td></tr></template>

A web component with slots

In a custom element’s shadow DOM, <slot> marks where the page’s own content is placed.

<template id="card-tpl"> <style>.box { border: 1px solid #ccc; padding: 1rem; }</style> <div class="box"><slot name="title"></slot><slot></slot></div> </template> <my-card><h3 slot="title">Hello</h3><p>Body text</p></my-card> <script> customElements.define("my-card", class extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }).append(document.getElementById("card-tpl").content.cloneNode(true)); } }); </script>

<template> vs building HTML strings

A common shortcut is to build HTML in JavaScript with strings and innerHTML. It works, but if any data comes from users it can open the door to script injection (XSS), and long strings of HTML in JavaScript are hard to read.

With <template>, the markup stays in your HTML where it belongs, and you fill it in with textContent, which always treats data as plain text. It’s safer and easier to maintain, and cloning a template is fast.

<template> + textContentinnerHTML strings
Markup lives inHTMLJavaScript
Safe with user dataYesOnly if you escape it
ReadabilityGoodGets messy

Common mistakes to avoid

  • Appending the template itself. Clone template.content with cloneNode(true); the template element stays hidden.
  • Querying the template content with document.querySelector. Its content isn’t in the page yet; search inside the clone instead.
  • Putting important content only in templates. Search engines may not see it until JavaScript renders it.
  • Filling templates with innerHTML and user data. Use textContent to avoid injection.

Accessibility & SEO tips

  • Make sure content created from templates still uses proper elements — real buttons, links and headings — so it’s accessible.
  • When you add content dynamically, consider an aria-live region so screen reader users hear about updates.
  • For SEO-critical lists such as products or articles, render them in the HTML on the server rather than only through templates.

Browser support

Supported in all modern browsers, including Chrome, Edge, Firefox, Safari and their mobile versions. It was introduced with HTML5, and only very old browsers such as Internet Explorer 8 needed workarounds. and shadow DOM are supported in all current browsers.

Related tags

Frequently asked questions

Is the content of a <template> shown on the page?
No. Browsers parse it but don't display it, don't load its images and don't run its scripts until JavaScript clones it into the page.
Why use <template> instead of a hidden <div>?
A hidden <div> is still part of the page: its images load, its scripts run, and its IDs and form fields can clash with the real ones. A <template> stays completely inert until you use it.
Is text inside <template> indexed by search engines?
Generally no, because it isn't rendered. Don't put important content only inside a template; keep it in normal HTML so it can rank.
How do I use a <template> with JavaScript?
Get the template, clone its content with template.content.cloneNode(true), fill in the clone with textContent, and add it to the page with append().
Can scripts inside a <template> run?
Not while they are in the template. Scripts and images inside it are inactive until the content is cloned and added to the page.
What is <slot> used for?
Inside a web component's shadow DOM, a <slot> marks where the content written between the custom element's tags will appear. Named slots place specific pieces in specific spots.