Web development cheat sheetHTML5 logo

HTML cheat sheet

HTML on one page: document skeleton, semantic elements, links, images, tables, forms and input types, meta tags and ARIA, checked against the HTML standard.

Last updated

HTML is the structure of every web page: which part is a heading, a link, a form or a table. The reference below is grouped by what you are trying to do, and the filter box searches all of it at once. Type input for every form control, or aria for the accessibility attributes.

Every snippet is checked against the HTML Living Standard, the current and only version of HTML, and passed the Nu Html Checker, the validator behind validator.w3.org/nu. Anything that reached every major browser after 2022 says when in the notes column. Names like cat.jpg, menu and example.com are placeholders for your own. To make the page do something, the JavaScript cheat sheet covers selecting these elements, listening for events and reading form values. To change how they look, the CSS cheat sheet covers selectors, layout and colour.

Document skeleton

Every page starts from the same dozen lines. The first worked example below puts them together; these are the pieces and what each one is for.

TaskCodeNotes
Declare modern HTML<!doctype html>The first line. Without it, browsers render in quirks mode. Case does not matter
Root element with the page language<html lang="en">Sets the screen reader voice, hyphenation and translation. en-GB for British English
Metadata container<head> ... </head>Title, meta, link, style and script tags. Nothing in it is shown on the page
Character encoding<meta charset="utf-8">First in head. It must be within the first 1024 bytes of the file
Fit the screen on phones<meta name="viewport" content="width=device-width, initial-scale=1">Without it, phones draw a 980px desktop page and zoom out
Page title<title>HTML cheat sheet - coding.kitty</title>The tab text and the headline in search results. Required, one per page
Link a stylesheet<link rel="stylesheet" href="styles.css">
CSS inside the page<style>body { margin: 0; }</style>In head. A linked file can be cached across pages
Load a script after the HTML is parsed<script src="app.js" defer></script>Put it in head. Deferred scripts run in order, once the page is parsed
Load a JavaScript module<script type="module" src="main.js"></script>Deferred automatically, and can use import
Load a script that nothing depends on<script src="analytics.js" async></script>Runs as soon as it arrives, in any order
Visible content<body> ... </body>Everything the reader sees goes here
Comment<!-- Not shown on the page -->Still visible in View Source, so never put secrets in one
Message when JavaScript is off<noscript>This page needs JavaScript.</noscript>
Markup to copy later with JavaScript<template id="row"><li class="item"></li></template>Parsed but not shown. template.content.cloneNode(true) makes a copy
Reserved characters in text&lt; &gt; &amp; &quot;For < > & and a double quote. In ordinary text only < and & need escaping
Space that never breaks a line10&nbsp;kgKeeps the number and unit together
Any character by name or number&copy; &#169; &#xA9;All three are ©. With utf-8 you can also type the character itself

Attributes any element takes

TaskCodeNotes
Unique name for links, labels and scripts<h2 id="pricing">Pricing</h2>Unique on the page. href="#pricing" links straight to it
Hook for CSS<p class="note warning">Mind the gap</p>Space-separated, as many as you like
Inline style<p style="color: tomato">Hot</p>Beats the stylesheet, so it is hard to override. Use sparingly
Custom data for scripts<li data-user-id="42">Ada</li>el.dataset.userId in JavaScript. Always a string
Tooltip<span title="Last saved at 14:02">Saved</span>Mouse only: touch and keyboard users never see it, so it is not a label
Hide from everyone<p hidden>Shown after you sign in</p>Screen readers skip it too. A CSS display rule overrides it
Language of one phrase<p>She said <span lang="fr">bonjour</span>.</p>So a screen reader switches voice for it
Right-to-left text<p dir="rtl" lang="ar">مرحبا</p>dir="auto" guesses from the text. Use it for user content
Let people edit the text<div contenteditable="plaintext-only">Edit me</div>plaintext-only drops formatting on paste, in every major browser since 2025. "true" keeps it
Keep a name out of machine translation<span translate="no">coding.kitty</span>For brand names and code
Turn off spell check<input name="username" spellcheck="false">For usernames, codes and anything that is not prose

Text elements

Pick the element for what the text means, then style it with CSS. The default look is only a hint.

TaskCodeNotes
Headings<h1>Page title</h1> <h2>Section</h2> <h3>Subsection</h3>h1 to h6. One h1 per page by convention, and never skip a level on the way down
Paragraph<p>One idea per paragraph.</p>
Important text<strong>Do not unplug</strong>Bold by default. b is bold with no extra meaning, for keywords
Stress that changes the meaning<p>I <em>really</em> mean it.</p>Italic by default. i is for terms, ship names and foreign words
Highlight<mark>matched text</mark>Relevant to what the reader is doing, such as search hits
Fine print<small>Prices include VAT.</small>Side comments and legal text, not just smaller text
Show an edit<del>£20</del> <ins>£15</ins>s is for text that is no longer accurate, such as an old price
Inline code<code>npm install</code>
Keyboard keys<kbd>Ctrl</kbd> + <kbd>C</kbd>
Program output<samp>File not found</samp>
Block of code<pre><code>if (a &lt; b) swap(a, b);</code></pre>pre keeps spaces and line breaks. Escape < and & inside
Long quotation<blockquote cite="https://example.com/talk"><p>Make it work, then make it fast.</p></blockquote>cite is not shown. Credit the source in visible text too
Inline quotation<q>Stay curious</q>The browser adds the quotation marks
Title of a work<cite>The Pragmatic Programmer</cite>A book, film or article. Not a person's name
Abbreviation<abbr title="Cascading Style Sheets">CSS</abbr>The title is a mouse-only hint. Spell it out in the text the first time
Define a term<p><dfn>Hoisting</dfn> moves declarations to the top of their scope.</p>
Date or time a machine can read<time datetime="2026-09-27">27 September</time>Also datetime="14:30" or "2026-09-27T14:30Z"
Subscript and superscriptH<sub>2</sub>O E = mc<sup>2</sup>
Line break inside a paragraph<p>221B Baker Street<br>London</p>For addresses and poems. Use CSS margin for spacing
Where a long word may wrapexample.com/<wbr>very/<wbr>long/<wbr>pathNo hyphen is added
Change of topic<hr>Drawn as a line, but it means a thematic break
Generic wrappers<div class="card"><span class="price">£15</span></div>div for blocks, span inline. No meaning, so use them when nothing else fits

Semantic layout elements

These say what a part of the page is for. Screen readers list the landmarks among them so people can jump straight to one, and search engines use them to find the main content. They look like a plain div until you style them.

TaskCodeNotes
Site or article header<header><a href="/">coding.kitty</a></header>A banner landmark when it belongs to the page, not to an article or section
Block of navigation links<nav aria-label="Main"><a href="/blog">Blog</a></nav>For the main groups of links. Label each nav when there is more than one
The main content<main id="main"><h1>Page title</h1></main>One visible main per page. Not inside header, nav, article, aside or footer
A piece that stands on its own<article><h2>Post title</h2><p>First paragraph.</p></article>A blog post, a comment, a product card
Section of related content<section><h2>Features</h2><p>Details.</p></section>Give it a heading. Without one, a div is usually the right element
Side content<aside><h2>Related</h2></aside>Sidebars, callouts and pull quotes
Footer<footer><p>&copy; 2026 coding.kitty</p></footer>Copyright, contact and secondary links
Search area<search><form action="/search"><input type="search" name="q" aria-label="Search"></form></search>A search landmark with no role needed. In every major browser since 2023
Image or code with a caption<figure><img src="chart.png" alt="Sales doubled in 2026"><figcaption>Sales by year</figcaption></figure>figcaption is the first or last child
Contact details<address><a href="mailto:hi@example.com">hi@example.com</a></address>For the page or article author. Not for any postal address

Built-in interactive elements

Open and close things without writing any JavaScript. Each one handles the keyboard and screen reader behaviour for you.

TaskCodeNotes
Show and hide a section<details><summary>Show answer</summary><p>42</p></details>Add open to start expanded
Accordion where one stays open<details name="faq"><summary>Question</summary><p>Answer</p></details>details with the same name close each other. In every major browser since 2024
Modal dialog<dialog id="confirm"><p>Delete it?</p><form method="dialog"><button>Cancel</button></form></dialog>Open it with dialog.showModal(). A button in a form with method="dialog" closes it
Open a dialog without JavaScript<button commandfor="confirm" command="show-modal">Delete</button>Invoker commands, in every major browser since late 2025. command="close" closes it
Popover<button popovertarget="tips">Tips</button><div id="tips" popover>Press / to search</div>Sits on top of everything. Esc or a click outside closes it. Since 2024
Popover that only closes itself<div id="toast" popover="manual">Saved</div>No light dismiss. Close it with hidePopover() or a second button
TaskCodeNotes
Link to a page on your site<a href="/about">About us</a>The text should make sense on its own. Not "click here"
Link to another site<a href="https://developer.mozilla.org">MDN Web Docs</a>
Link to a section of this page<a href="#pricing">Pricing</a>Jumps to the element with id="pricing"
Link to a section of another page<a href="/docs#install">Install</a>
Open in a new tab<a href="https://example.com" target="_blank">Example (opens in a new tab)</a>Implies rel="noopener" in every current browser. Say that it opens a new tab
Do not send the referring URL<a href="https://example.com" rel="noreferrer">Example</a>Also implies noopener
Email link<a href="mailto:hi@example.com?subject=Hello%20there">Email us</a>Encode spaces in the subject as %20
Phone link<a href="tel:+442071234567">020 7123 4567</a>Full international number in href, any format in the text
Download instead of open<a href="/report.pdf" download>Download the report (PDF, 2 MB)</a>Same-origin files only. download="name.pdf" renames it
Paid or untrusted link<a href="https://shop.example.com" rel="sponsored">Our sponsor</a>rel="nofollow" for links you do not vouch for, "ugc" for links users posted

Images and media

TaskCodeNotes
Image<img src="cat.jpg" alt="Ginger cat asleep on a keyboard" width="800" height="600">width and height reserve the space, so the page does not jump when it loads. CSS can still resize it
Decorative image<img src="divider.svg" alt="">Empty alt: screen readers skip it. With no alt at all, many read out the file name
Load only when scrolled near<img src="team.jpg" alt="The team at the 2026 meetup" loading="lazy" width="800" height="600">Not for images in the first screen: it delays them
Load the main image first<img src="hero.jpg" alt="Laptop covered in cat stickers" fetchpriority="high" width="1200" height="600">For the largest image in the first screen. In every major browser since 2024
Same image at several sizes<img src="cat-800.jpg" srcset="cat-400.jpg 400w, cat-800.jpg 800w, cat-1600.jpg 1600w" sizes="(min-width: 768px) 50vw, 100vw" alt="Ginger cat" width="800" height="600">The browser picks a file from the widths in srcset and the display width in sizes
Sharper on high-density screens<img src="logo.png" srcset="logo@2x.png 2x" alt="coding.kitty" width="200" height="50">For images shown at a fixed size
Newer format with a fallback<picture><source srcset="cat.avif" type="image/avif"><img src="cat.jpg" alt="Ginger cat" width="800" height="600"></picture>The first source the browser supports wins. The img is required, and holds the alt. AVIF is in every major browser since 2024
Different crop on small screens<picture><source media="(max-width: 600px)" srcset="cat-square.jpg"><img src="cat-wide.jpg" alt="Ginger cat"></picture>
Video with controls<video src="intro.mp4" controls width="640" height="360" poster="intro.jpg"></video>poster is shown before it plays
Background video that autoplays<video src="loop.mp4" autoplay muted loop playsinline></video>Browsers only autoplay muted video. playsinline stops iPhones going full screen
Captions<track kind="captions" src="talk.en.vtt" srclang="en" label="English" default>Inside the video. A WebVTT file. For deaf viewers and anyone with the sound off
Offer two formats<video controls><source src="clip.webm" type="video/webm"><source src="clip.mp4" type="video/mp4"></video>
Audio<audio src="episode.mp3" controls></audio>
Embed another page<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID" title="Intro video" width="560" height="315" loading="lazy" allowfullscreen></iframe>title is how screen readers name the frame
Embed something you do not trust<iframe src="widget.html" title="Weather widget" sandbox="allow-scripts"></iframe>sandbox blocks scripts, forms and popups, except what you list
Inline SVG icon<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true"><path d="M12 2 2 22h20z"/></svg>CSS can style it. aria-hidden when it is decoration

Lists

TaskCodeNotes
Bulleted list<ul><li>Milk</li><li>Eggs</li></ul>When the order does not matter
Numbered list<ol><li>Preheat the oven</li><li>Bake</li></ol>When the order matters
Start from another number<ol start="5"><li>Fifth</li><li>Sixth</li></ol>
Count down<ol reversed><li>Three</li><li>Two</li><li>One</li></ol>
Letters or Roman numerals<ol type="a"><li>First</li><li>Second</li></ol>Also type="A", "i" and "I". For looks alone, list-style-type in CSS
Set one item's number<ol><li value="10">Tenth</li><li>Eleventh</li></ol>The items after it count on from there
Nested list<ul><li>Fruit<ul><li>Apple</li><li>Pear</li></ul></li></ul>The inner list goes inside an li, not between two
Terms and descriptions<dl><dt>HTML</dt><dd>Structure</dd><dt>CSS</dt><dd>Presentation</dd></dl>Also for key and value pairs, such as product specs. A div may wrap each pair

Tables

Tables are for data with rows and columns, never for layout. The header cells are what let a screen reader say which column and row a value belongs to.

TaskCodeNotes
Smallest table<table><tr><th>Name</th><th>Age</th></tr><tr><td>Ada</td><td>36</td></tr></table>The browser adds the tbody for you
Caption<caption>Monthly sales, 2026</caption>The first thing in the table. Its name for screen readers
Header rows<thead><tr><th scope="col">Month</th><th scope="col">Sales</th></tr></thead>
Body rows<tbody><tr><th scope="row">January</th><td>120</td></tr></tbody>Several tbody elements can split the rows into groups
Totals row<tfoot><tr><th scope="row">Total</th><td>1,440</td></tr></tfoot>
Header for a column<th scope="col">Price</th>scope says which cells the header labels
Header for a row<th scope="row">January</th>
Cell across two columns<td colspan="2">Closed</td>
Cell down three rows<td rowspan="3">Weekend</td>Leave that cell out of the next two rows
Style whole columns<colgroup><col><col class="highlight"></colgroup>After the caption. Only border, background, width and visibility apply
Let a wide table scroll<div class="table-scroll" tabindex="0" role="region" aria-label="Monthly sales"><table>...</table></div>Give the div overflow-x: auto in CSS. tabindex lets keyboard users scroll it

Forms

TaskCodeNotes
Form that posts to the server<form action="/subscribe" method="post"> ... </form>The default, method="get", puts the values in the URL. Right for search
Label a field<label for="email">Email</label> <input id="email" name="email" type="email">for matches the input's id. Clicking the label focuses the field
Label by wrapping<label><input type="checkbox" name="terms"> I agree to the terms</label>No id needed
Name the value sent<input name="city">Sent as city=... A field with no name is not sent at all
Start with a value<input name="city" value="London">
Required field<input name="email" type="email" required>Blocks submitting and shows the browser's message
Hint inside the field<input name="q" placeholder="e.g. Toronto">Not a label: it disappears as soon as someone types
Length limits<input name="username" minlength="3" maxlength="20">maxlength stops typing. minlength is checked on submit
Number limits<input type="number" name="qty" min="1" max="10" step="1">Also min and max on date and time fields
Match a pattern<input name="ref" pattern="[A-Z]{3}-[0-9]{4}" title="Three capitals, a dash, four digits">Must match the whole value. Most browsers add the title to the error message
Autofill hint<input name="email" type="email" autocomplete="email">Also name, tel, street-address, postal-code, one-time-code. Password managers use it
Password on a sign-up form<input type="password" name="password" autocomplete="new-password">current-password on a sign-in form
Shown but not editable<input name="order" value="A-102" readonly>Still sent with the form
Switched off<input name="coupon" disabled>Not focusable and not sent with the form
Focus when the page loads<input name="q" autofocus>Once per page at most. It skips screen reader users past everything above it
Multi-line text<textarea name="message" rows="4" maxlength="500"></textarea>Starting text goes between the tags. textarea has no value attribute
Drop-down<select name="size"><option value="s">Small</option><option value="m" selected>Medium</option></select>
Drop-down that forces a choice<select name="country" required><option value="">Choose a country</option><option value="gb">United Kingdom</option></select>With required, the empty first option cannot be submitted
Group the options<optgroup label="Europe"><option value="fr">France</option></optgroup>
Pick several<select name="tags" multiple><option>html</option><option>css</option></select>Ctrl or Cmd to pick more than one. Checkboxes are often easier
Suggestions while typing<input name="browser" list="browsers"><datalist id="browsers"><option value="Firefox"><option value="Safari"></datalist>Free text is still allowed
Group related fields<fieldset><legend>Delivery</legend><label><input type="radio" name="speed" value="standard" checked> Standard</label><label><input type="radio" name="speed" value="express"> Express</label></fieldset>The legend is read with each field. Always use one for radio buttons
Submit button<button type="submit">Send</button>submit is the default type inside a form
Button that does not submit<button type="button">Preview</button>For JavaScript actions inside a form
Submit without validating<button type="submit" formnovalidate>Save draft</button>
Switch off browser validation<form action="/signup" method="post" novalidate> ... </form>When your own JavaScript does the checking
Upload files<form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="photo"><button>Upload</button></form>Without that enctype, only the file name is sent
Field that sits outside its form<input name="note" form="checkout">form takes the form's id
Show a calculated result<output name="total" for="qty price">£0.00</output>for lists the ids of the fields it depends on. Screen readers announce changes
Progress bar<progress value="70" max="100">70%</progress>Leave out value for a busy bar with no known end
Gauge<meter value="0.8" high="0.75">80%</meter>A measure within a range, such as disk space. Not for progress. low, high and optimum set the colour

Input types

One element, many controls. The type sets the keyboard on phones, the built-in validation and the picker. A browser that does not know a type shows a text box, so every one is safe to use.

TypeCodeWhat you get
text<input type="text" name="name">The default when type is missing
email<input type="email" name="email">Checks for an @ and a domain, and shows the @ key on phones. multiple allows a comma-separated list
password<input type="password" name="password" autocomplete="current-password">Hides what is typed. autocomplete lets password managers fill it
number<input type="number" name="qty" min="1" max="99">Spinner arrows and a number keyboard. For quantities, not card or phone numbers
Digits that are not a quantity<input type="text" name="card" inputmode="numeric" autocomplete="cc-number">Number keyboard, keeps leading zeros, no spinner
tel<input type="tel" name="phone" autocomplete="tel">Phone keypad. No format check, because formats vary by country
url<input type="url" name="website">Needs the scheme: https://example.com passes, example.com does not
search<input type="search" name="q">A clear button in some browsers and a search key on phones
date<input type="date" name="birthday" min="1900-01-01">The value is always YYYY-MM-DD, whatever the display format
time<input type="time" name="alarm" step="900">step is in seconds. 900 is 15 minutes
datetime-local<input type="datetime-local" name="starts">Date and time with no time zone. Value like 2026-09-27T14:30
checkbox<input type="checkbox" name="newsletter" value="yes" checked>Sent only when ticked. The value is "on" if you do not set one
radio<input type="radio" name="size" value="m">Radios with the same name are one group, and only one can be picked
range<input type="range" name="volume" min="0" max="10" value="5">A slider with no number shown. Pair it with an output
color<input type="color" name="accent" value="#f5c518">A colour picker. The value is a lowercase #rrggbb hex
file<input type="file" name="docs" accept=".pdf,.docx" multiple>accept only filters the picker. Check the file type on the server
Photo from the camera<input type="file" name="photo" accept="image/*" capture="environment">Opens the rear camera on phones. capture="user" for the front one
hidden<input type="hidden" name="order_id" value="1042">Sent but not shown. Anyone can still read and change it
submit<input type="submit" value="Send">The older form of button type="submit". A button can hold icons and markup

Meta and SEO tags

All of these go in the head. Title and description decide how the page looks in search results, the og tags decide how a shared link looks in chat apps and social feeds.

TaskCodeNotes
Search result description<meta name="description" content="Every HTML tag and attribute worth knowing, on one page.">Often shown under the title in results. Aim for 110 to 160 characters
The one URL to index<link rel="canonical" href="https://example.com/html-cheat-sheet">For when the same page is reachable at several URLs. Use the full URL
Keep a page out of search results<meta name="robots" content="noindex">The page must stay crawlable, or the tag is never seen. robots.txt stops crawling, not indexing
Link preview title<meta property="og:title" content="HTML cheat sheet">Open Graph. Read by Facebook, LinkedIn, Slack, Discord, WhatsApp and iMessage
Link preview description<meta property="og:description" content="Every HTML tag worth knowing, on one page.">
Link preview image<meta property="og:image" content="https://example.com/og/html.png">Full URL. 1200 x 630 pixels is the usual size
Link preview URL and type<meta property="og:url" content="https://example.com/html"> <meta property="og:type" content="website">article for posts
Large preview on X<meta name="twitter:card" content="summary_large_image">X falls back to the og tags for the title, description and image
Favicon<link rel="icon" href="/favicon.ico" sizes="32x32"> <link rel="icon" href="/icon.svg" type="image/svg+xml">The browser picks the best one it supports
Home screen icon on iPhone<link rel="apple-touch-icon" href="/apple-touch-icon.png">A 180 x 180 PNG
Installable web app details<link rel="manifest" href="/site.webmanifest">A JSON file with the app's name, icons and colours
Colour of the browser bar<meta name="theme-color" content="#f5c518">Not every browser uses it. Add media="(prefers-color-scheme: dark)" for a dark version
Support light and dark mode<meta name="color-scheme" content="light dark">Form controls, scrollbars and the default background follow the system theme
Other language versions<link rel="alternate" hreflang="fr" href="https://example.com/fr/">One per language, the page's own included, plus hreflang="x-default"
Structured data<script type="application/ld+json">{"@context": "https://schema.org", "@type": "Article", "headline": "HTML cheat sheet"}</script>JSON-LD, for rich results. Can also go in body
Connect early to another server<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>For a server the page will certainly use soon
Fetch a critical file early<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>Fonts need crossorigin, even from your own site, or they download twice

Accessibility attributes

Native elements do most of the work: a button is focusable, works with Enter and Space and is announced as a button. ARIA only changes what assistive technology is told, never how an element behaves, so reach for it when no element does the job.

TaskCodeNotes
Describe an image<img src="chart.png" alt="Sales doubled between 2024 and 2026">Say what it shows or does. Not "image of"
Name an icon-only button<button type="button" aria-label="Close menu">✕</button>Replaces the text content as the name screen readers read
Name something after its heading<dialog aria-labelledby="dialog-title"><h2 id="dialog-title">Delete file?</h2></dialog>Points at the id of visible text. Several ids allowed, space-separated
Link a hint to a field<label for="pw">Password</label> <input id="pw" type="password" name="pw" autocomplete="new-password" aria-describedby="pw-hint"><p id="pw-hint">At least 12 characters.</p>Read after the field's name
Mark a field as wrong<label for="email">Email</label> <input id="email" type="email" name="email" aria-invalid="true" aria-describedby="email-error"><p id="email-error">Enter an email like name@example.com</p>Set it once the value has been checked, not before anyone types
Hide decoration from screen readers<span aria-hidden="true">★</span>Never on anything focusable
Text only screen readers read<span class="visually-hidden">(opens in a new tab)</span>The class clips it with CSS. The rule is in the first worked example
Current page in the navigation<a href="/blog" aria-current="page">Blog</a>Also aria-current="step" and "date"
Button that shows and hides a panel<button type="button" aria-expanded="false" aria-controls="menu">Menu</button>Update aria-expanded from JavaScript. details and popover do it for you
Toggle button<button type="button" aria-pressed="false">Mute</button>Keep the label the same and flip aria-pressed
Announce updates<div aria-live="polite">3 results</div>The container must be on the page before its text changes
Announce a status<div role="status">Saved</div>Same as aria-live="polite". Waits until the screen reader is quiet
Announce an error straight away<div role="alert">Payment failed</div>Interrupts whatever is being read. For urgent messages only
Tell two navs apart<nav aria-label="Breadcrumb"><a href="/">Home</a></nav>Also for sections, regions and asides that repeat
Focusable from script only<h2 id="results" tabindex="-1">12 results</h2>Then results.focus() to move focus there. Never a positive tabindex: it breaks the tab order
Block part of the page<div inert>Page behind the drawer</div>Nothing inside can be focused, clicked or read out. Since 2023
Skip link<a href="#main" class="skip-link">Skip to content</a>First thing in body. CSS hides it until it has focus
Name an SVG image<svg role="img" aria-label="Pie chart: 60% cats" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>

A page skeleton, start to finish

A complete page with the head most sites need, the landmarks screen readers jump between, a skip link, and the visually-hidden class the accessibility table refers to.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Cat care basics - Whisker Weekly</title>
    <meta name="description" content="How much to feed a cat, how often to take it to the vet and which plants to keep out of reach, from a vet with twenty years of practice.">
    <link rel="canonical" href="https://example.com/cat-care">
 
    <meta property="og:title" content="Cat care basics">
    <meta property="og:description" content="Feeding, vet visits and the plants to keep out of reach.">
    <meta property="og:image" content="https://example.com/og/cat-care.png">
    <meta property="og:url" content="https://example.com/cat-care">
    <meta property="og:type" content="article">
    <meta name="twitter:card" content="summary_large_image">
 
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="stylesheet" href="/styles.css">
    <script src="/app.js" defer></script>
 
    <style>
      .visually-hidden {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
      }
      .skip-link:not(:focus) {
        position: absolute;
        left: -9999px;
      }
    </style>
  </head>
  <body>
    <a class="skip-link" href="#main">Skip to content</a>
 
    <header>
      <a href="/">Whisker Weekly</a>
      <nav aria-label="Main">
        <ul>
          <li><a href="/" aria-current="page">Home</a></li>
          <li><a href="/guides">Guides</a></li>
          <li><a href="/about">About</a></li>
        </ul>
      </nav>
    </header>
 
    <main id="main">
      <article>
        <h1>Cat care basics</h1>
        <p>By <a href="/about">Dr Priya Shah</a>, <time datetime="2026-09-27">27 September 2026</time></p>
 
        <h2>Feeding</h2>
        <p>An adult cat needs roughly <strong>200 to 250 calories a day</strong>.</p>
 
        <h2>Plants to keep out of reach</h2>
        <ul>
          <li>Lilies, which can cause kidney failure</li>
          <li>Poinsettia</li>
        </ul>
      </article>
 
      <aside>
        <h2>Related guides</h2>
        <a href="/guides/kittens">Your first kitten</a>
      </aside>
    </main>
 
    <footer>
      <p><small>&copy; 2026 Whisker Weekly</small></p>
      <a href="https://example.com/newsletter" target="_blank">
        Newsletter <span class="visually-hidden">(opens in a new tab)</span>
      </a>
    </footer>
  </body>
</html>

Nothing here needs a class to explain its purpose. header, nav, main, aside and footer become landmarks, so a screen reader user can jump from the skip link straight into the article, and the headings form an outline they can move through one level at a time. The visually-hidden rule keeps the text in the page for screen readers while taking it out of sight, which display: none and hidden would not. To add structured data, put JSON-LD in a <script type="application/ld+json"> from the meta table, and check the JSON parses with the JSON formatter before it ships.

A form, start to finish

A sign-up form using only what the browser gives you: labels, the right input types, built-in validation and a hint linked to its field.

<form action="/signup" method="post">
  <label for="name">Name</label>
  <input id="name" name="name" type="text" autocomplete="name" required>
 
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required>
 
  <label for="password">Password</label>
  <input id="password" name="password" type="password" autocomplete="new-password"
         minlength="12" required aria-describedby="password-hint">
  <p id="password-hint">At least 12 characters.</p>
 
  <fieldset>
    <legend>Plan</legend>
    <label><input type="radio" name="plan" value="free" checked> Free</label>
    <label><input type="radio" name="plan" value="pro"> Pro, £5 a month</label>
  </fieldset>
 
  <label for="country">Country</label>
  <select id="country" name="country" autocomplete="country" required>
    <option value="">Choose a country</option>
    <option value="gb">United Kingdom</option>
    <option value="ie">Ireland</option>
    <option value="us">United States</option>
  </select>
 
  <label>
    <input type="checkbox" name="newsletter" value="yes">
    Send me the monthly newsletter
  </label>
 
  <button type="submit">Create account</button>
</form>

Submitting it empty stops at the first missing field and shows the browser's own message, with no JavaScript. On a phone, the email field brings up the @ key and the password manager offers a strong password because of autocomplete="new-password". If nobody ticks the newsletter box, the form sends no newsletter field at all, so the server should treat a missing field as "no". The server has to check everything again anyway: anyone can edit the HTML in their browser, or send the request without it.

An accessible data table, start to finish

A table with a caption, column and row headers, and a totals row, wrapped so it scrolls on its own on a narrow screen instead of making the whole page scroll.

<div class="table-scroll" tabindex="0" role="region" aria-labelledby="sales-caption">
  <table>
    <caption id="sales-caption">Books sold per quarter, 2026</caption>
    <thead>
      <tr>
        <th scope="col">Title</th>
        <th scope="col">Q1</th>
        <th scope="col">Q2</th>
        <th scope="col">Q3</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">Learn HTML</th>
        <td>120</td>
        <td>145</td>
        <td>160</td>
      </tr>
      <tr>
        <th scope="row">Learn CSS</th>
        <td>95</td>
        <td>110</td>
        <td>130</td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <th scope="row">Total</th>
        <td>215</td>
        <td>255</td>
        <td>290</td>
      </tr>
    </tfoot>
  </table>
</div>

With the headers marked up, a screen reader can read the row and column headers along with each cell, so moving to 145 tells the listener it is Learn HTML in Q2 and nobody has to count columns. The tabindex="0" on the wrapper lets keyboard users focus it and scroll with the arrow keys, and the role and label tell screen readers what they have landed on. The scrolling itself is one line of CSS: .table-scroll { overflow-x: auto; }, one of the overflow rows in the CSS cheat sheet.

Responsive images, start to finish

The same photo at three sizes, a modern format with a fallback, and the attributes that stop the page jumping while it loads.

<picture>
  <source
    type="image/avif"
    srcset="/img/cat-400.avif 400w, /img/cat-800.avif 800w, /img/cat-1600.avif 1600w"
    sizes="(min-width: 768px) 50vw, 100vw">
  <img
    src="/img/cat-800.jpg"
    srcset="/img/cat-400.jpg 400w, /img/cat-800.jpg 800w, /img/cat-1600.jpg 1600w"
    sizes="(min-width: 768px) 50vw, 100vw"
    alt="Ginger cat asleep on a laptop keyboard"
    width="800"
    height="600"
    loading="lazy">
</picture>

sizes tells the browser the image will be half the viewport wide on screens 768px and up and the full width below that. With that, and the widths in srcset, it downloads the smallest file that is still sharp: a phone 400px wide at 2x density takes the 800w file, not the 1600w one. Browsers that support AVIF take the source; the rest use the img, which also carries the alt, width and height for both. Drop loading="lazy" and add fetchpriority="high" if this is the first thing on the page.

Gotchas

The mistakes almost everyone makes in their first month of HTML.

Looks rightWhat actually happensDo this instead
<img src="logo.png">Screen readers read out the file namealt="coding.kitty", or alt="" for decoration
<button> for a JavaScript action in a formSubmits the form and reloads the page<button type="button">
<div onclick="save()">Save</div>Cannot be reached with Tab or pressed with Enter<button type="button">
<a href="#" onclick="..."> for an actionJumps to the top of the page and reads as a linkA <button>. Links go somewhere, buttons do something
A placeholder as the only labelGone once someone types, and often too faint to readA <label> above the field
type="number" for a phone or card numberDrops leading zeros and adds spinner arrowstype="tel", or inputmode="numeric"
An unticked checkboxSends nothing, not "off" or "false"Treat a missing field as false on the server
A disabled field with a value you needNot sent with the formreadonly
pattern="[\w-]+"An invalid pattern with the v flag browsers use, so it is ignoredEscape the dash: pattern="[\w\-]+". The Regex cheat sheet explains why
<h4> because it is the right sizeBreaks the heading outline screen reader users navigate byThe level the structure needs, sized with CSS
<br><br> between paragraphsEmpty lines read out, and spacing you cannot restyleSeparate <p> elements and CSS margin
<p><div>Text</div></p>The <p> closes before the <div>, and </p> adds a second, empty <p>A <div>, or keep <p> to text and inline elements
<div />The slash is ignored, so the div stays open and swallows what follows<div></div>. Only void elements like <br> close themselves
<ul> with a <div> insideInvalid, and screen readers miscount the itemsOnly <li> directly inside <ul> and <ol>
aria-label on a plain <div> or <span>Not allowed on elements with no role, and most screen readers ignore itPut it on a button, link, landmark or input, or use visible text
loading="lazy" on the top imageDelays the biggest image on the pageLeave it off above the fold, add fetchpriority="high"
An <img> with no width and heightThe page jumps when the image arrivesSet both. CSS can still resize it
<meta name="keywords">Ignored by Google and BingA clear title and description
Two elements with the same idLabels, links and getElementById find only the firstMake each id unique on the page

Common questions

Which version of HTML does this cheat sheet cover?

The HTML Living Standard, which is the only current version. It is maintained by the WHATWG and updated continuously rather than in numbered releases, so there is no HTML6 on the way. HTML5 is still the name most people use for it. Every snippet here was checked with the Nu Html Checker, the validator behind validator.w3.org/nu, and anything that reached every major browser after 2022 says when in the notes column.

Is HTML a programming language?

No. HTML is a markup language: it describes what each part of a page is, such as a heading, a link or a form, and has no variables, loops or logic. CSS decides how it looks and JavaScript makes it behave. That does not make it trivial. Getting the elements right is what makes a page work with a keyboard, a screen reader and a search engine.

What is semantic HTML and why does it matter?

Semantic HTML means choosing elements for what the content is rather than how it should look: a button for an action, nav for navigation, h2 for a section heading, table for tabular data. Browsers, screen readers and search engines read that meaning. A real button is focusable and works with Enter and Space for free; a div styled to look like one does neither until you write the code yourself.

What is the difference between div and section?

A div means nothing: it is a box for styling or scripting. A section is a thematic part of a document that would appear in an outline, and it should have a heading. If you are only adding a wrapper for a flex or grid layout, use a div. If the part has its own heading and would make sense as an entry in a table of contents, use a section. For content that stands alone, like a blog post or a comment, use article.

What is the difference between strong and b, or em and i?

strong marks text as important and em marks stress that changes the meaning of a sentence. b and i only set text apart without adding importance: b for keywords or product names, i for technical terms, thoughts, ship names and phrases in another language. The default styles are the same, bold and italic, so choose by meaning and change the look with CSS.

When should I use ARIA attributes?

Only when no HTML element does the job. The first rule of ARIA is to use a native element instead: a button rather than a div with role="button", a details element rather than a hand-built disclosure. ARIA changes what a screen reader announces, not how the element behaves, so role="button" on a div still needs tabindex and keyboard handling written by you. The attributes that are genuinely useful day to day are aria-label for icon-only buttons, aria-describedby for hints and errors, aria-expanded, aria-current and aria-live.

Which meta tags matter for SEO?

The title element and the meta description, which search engines use for the result's headline and often its snippet, a canonical link when the same page is reachable at more than one URL, and the viewport tag, because search engines index the mobile version of a page. Add robots noindex only to pages you want kept out. The Open Graph tags do not affect ranking but decide how the link looks when shared. The keywords meta tag is ignored by Google and Bing.

Do I still need rel="noopener" on links with target="_blank"?

No. The HTML standard now says target="_blank" implies noopener, and every current browser does it, so the new tab cannot reach back to your page through window.opener. Adding rel="noopener" does no harm and still protects very old browsers. rel="noreferrer" is different: it also stops the new site seeing which page the visitor came from.

Do I need to close tags like br and img?

No. br, img, input, meta, link, hr and the other void elements have no content and no closing tag. Writing br with a slash before the bracket is allowed and ignored. On any other element the slash is also ignored, so a div written that way is not closed and swallows everything after it. Close every non-void element with its end tag.

How do I check that my HTML is valid?

Paste the page or its URL into the Nu Html Checker at validator.w3.org/nu. It catches unclosed tags, misplaced elements, duplicate ids and labels pointing at nothing. Validity is not the same as accessibility, so also run the accessibility audit in Lighthouse in Chrome DevTools, or the axe browser extension, and try the page with only a keyboard.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.