contenteditable, template & Web Components
Three powerful but often misunderstood native HTML features: `contenteditable` makes any element an in-place editor, `<template>` holds inert HTML ready to clone, and Web Components let you define your own reusable custom elements with encapsulated styles.
What these three features do
The contenteditable attribute makes any HTML element directly editable by the user in the browser -- not just <input> and <textarea>. Setting contenteditable="true" on a <div> or <p> gives it the same cursor, selection, and paste behaviour as a text input. The input event fires on every change, and el.textContent or el.innerHTML reads the current value. The "plaintext-only" value (Chromium-only) strips HTML formatting on paste, which is often what you want for editors that should not accept rich text.
The <template> element holds HTML markup that is completely inert: the browser parses it but does not render it and does not execute scripts or load images within it. The content is accessible as a DocumentFragment via templateEl.content. Clone it with .cloneNode(true) to produce a live DOM subtree you can modify and insert. This is the native alternative to building HTML strings in JavaScript.
Web Components is the umbrella term for three browser APIs: Custom Elements (define <my-card> as a class), Shadow DOM (an encapsulated DOM tree with scoped styles attached to any element), and HTML Templates (the <template> element above). Together they let you write self-contained, reusable components in plain HTML/JS/CSS with no framework required. Custom elements are registered with customElements.define('my-card', MyCard) where MyCard extends HTMLElement.
When to use each one
contenteditable is the right tool for in-place editing UIs: comments that can be edited after posting, rich-text areas, inline renaming, and note-taking apps. It is not a replacement for <textarea> in forms: contenteditable elements do not participate in form submission (no name/value), do not support maxlength, and do not benefit from built-in browser spell-check in the same way. For simple text fields, use <textarea>.
<template> shines when the same chunk of HTML needs to be stamped out multiple times: table rows populated from JSON, notification toasts, card grids. Instead of building HTML strings (innerHTML = '<div class...'), which is an XSS risk and forces the browser to re-parse HTML, define the structure in <template> and clone it. Web Components use <template> to define their shadow DOM content declaratively.
How to use them
For contenteditable: <div contenteditable="true" role="textbox" aria-multiline="true" aria-label="Note"></div>. Listen for input events to read changes. Add a CSS placeholder: [contenteditable]:empty::before { content: attr(data-placeholder); color: #aaa; }. Set spellcheck="true" explicitly if you want browser spell-check. Use "plaintext-only" in Chromium to strip paste formatting.
For <template>: define <template id="card-tpl"><div class="card"><h3 class="card-title"></h3></div></template>. Clone and populate: const clone = document.getElementById('card-tpl').content.cloneNode(true); clone.querySelector('.card-title').textContent = 'Hello'; document.body.appendChild(clone);.
For a custom element: class MyCard extends HTMLElement { connectedCallback() { this.innerHTML = '<p>Hello</p>'; } } customElements.define('my-card', MyCard);. Use it in HTML as <my-card></my-card>. For Shadow DOM, call this.attachShadow({ mode: 'open' }) in the constructor and append children to this.shadowRoot.
Try it
Type into a live contenteditable box, then stamp out cards from a <template> clone.
Type freely below. The element has contenteditable="true" -- the browser handles cursor, selection, and paste.
Characters
0
Words
0
el.isContentEditable
true
isEmpty
true
<div
contenteditable="true"
role="textbox"
aria-multiline="true"
aria-label="Note"
spellcheck="true"
>
</div>
/* CSS placeholder */
[contenteditable]:empty::before {
content: attr(data-placeholder);
color: #aaa;
}Check yourself
Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.
Remember this
contenteditablemakes any element editable in-place. It is not a form control -- use<textarea>for forms.<template>holds inert HTML. Clone with.content.cloneNode(true)to produce a live DOM subtree.- Custom elements extend
HTMLElementand are registered withcustomElements.define('tag-name', Class). connectedCallbackruns when the element enters the DOM;attributeChangedCallbackruns on observed attribute changes.
Done with this concept?
Mark it complete to track your progress. No login needed.