og:image must be an absolute URL — Facebook, Slack and LinkedIn won’t resolve a relative path.
Icons and Open Graph/Twitter tags for link previews.
favicon
open graph
og
twitter card
social
Semantic Elements
Landmark & Sectioning Elements
<header>Site or section header</header><nav>Primary navigation</nav><main> <article>Self-contained, independently distributable content</article> <section>Thematic grouping, usually with a heading</section></main><aside>Tangentially related content</aside><footer>Site or section footer</footer>
No role attribute needed — but the mapping has conditions: <header>/<footer> only become banner/contentinfo landmarks when they’re not nested inside <article>, <section>, <aside> or <nav>, and <section> only becomes a region landmark once it has an accessible name (a heading, or aria-label). Use <section> only when the content has its own heading; otherwise prefer <div>.
Structural elements that replace generic <div> soup.
header
nav
main
article
section
aside
footer
landmarks
Text-Level Semantics
<mark>Highlighted</mark> search term.<time datetime="2026-08-26">August 26, 2026</time><abbr title="Cascading Style Sheets">CSS</abbr><details> <summary>Click to expand</summary> Revealed content, no JavaScript required.</details>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> to search.
Preview
Highlighted search term.
CSS
Click to expand
Revealed content, no JavaScript required.
Press Ctrl + K to search.
Inline elements that carry real meaning, not just styling.
email/url/tel/number get built-in validation and the right on-screen keyboard on mobile. date swaps in a picker instead of a keyboard; range and color can never be invalid, since any value in their range is valid.
Preview
Built-in inputs for email, dates, color and more — with native UI and mobile keyboards.
pattern is implicitly anchored — no need to wrap it in ^…$. Prefer :user-invalid/:user-valid over plain :invalid/:valid for styling: the plain versions paint an empty required field red before the user has typed anything. Combine with the Constraint Validation API (el.checkValidity()) for custom error UI.
Constrain and validate form input without JavaScript.
Merging cells, and keeping the header visible on a long, scrolling table.
colspan / rowspan
Merges a cell across multiple columns or rows
headers
On a <td>, explicitly associates it with one or more <th> ids for complex tables
colspan
rowspan
headers
sticky
Media & Embeds
Responsive Images with <picture>
<!-- above the fold: load eagerly and with priority, not lazily --><picture> <source srcset="hero-wide.avif" media="(min-width: 800px)" type="image/avif" /> <source srcset="hero.avif" type="image/avif" /> <img src="hero.jpg" alt="Description of the image" width="1600" height="900" fetchpriority="high" decoding="async" /></picture><!-- further down the page: lazy-load is the right call --><img src="photo-800.jpg" srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w" sizes="(min-width: 900px) 800px, 100vw" width="800" height="533" loading="lazy" alt="Description"/>
width/height (or aspect-ratio in CSS) reserve space before the image loads, preventing layout shift. loading="lazy" on a hero image is a common LCP mistake — reserve it for images below the fold.
Serve different image files by viewport size or format support.
srcset
Candidate image files, each tagged with a width or pixel density
sizes
The rendered width of the image at each breakpoint
loading
"lazy" defers off-screen images — never use it on an above-the-fold hero
fetchpriority
"high" tells the browser to fetch a critical image (like a hero) sooner
picture
srcset
sizes
source
images
<video> & <audio>
<video controls muted autoplay playsinline poster="poster.jpg" width="640"> <source src="clip.webm" type="video/webm" /> <source src="clip.mp4" type="video/mp4" /> <track kind="captions" src="captions.vtt" srclang="en" label="English" default /> Your browser doesn't support video playback.</video><audio controls src="track.mp3"></audio>
Native media playback with captions and multiple source formats.
controls
Shows the browser-native play/pause/volume UI
muted
Starts silent — required for autoplay in most browsers
playsinline
Plays inline on mobile instead of forcing fullscreen
poster
Image shown before playback starts
track default
Pre-selects that track — it does not force captions on for the viewer
video
audio
controls
track
captions
Global Attributes
Attributes Available on Every Element
<div id="unique" class="a b c" data-state="open" data-index="2"></div><div tabindex="0">Focusable via keyboard</div><div contenteditable="true">Editable in place</div><div hidden>Not rendered, not in the accessibility tree</div><div draggable="true">Drag me</div>
Read data-* attributes in JS via el.dataset.state / el.dataset.index.
id, data-*, tabindex and friends — usable on any HTML element.
Rule of thumb: prefer a native element (<button>, <nav>) over ARIA-on-a-<div> whenever one exists. role="status" already implies aria-live="polite" — adding it explicitly is redundant, not wrong, just unnecessary.
Fill accessibility gaps when semantic HTML alone is not enough.
aria
role
aria-label
aria-hidden
accessibility
Browser APIs
Web Storage
// Survives across tabs and browser restartslocalStorage.setItem('theme', 'dark');localStorage.getItem('theme'); // "dark"localStorage.removeItem('theme');// Cleared when the tab closessessionStorage.setItem('draft', JSON.stringify({ title: '' }));// React to changes from other tabswindow.addEventListener('storage', (e) => { console.log(e.key, e.oldValue, e.newValue);});
Persist data client-side without a server round trip.