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.
Searches the task, the command and the third column. Press / from anywhere on the page.
197 commands
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.
| Task | Code | Notes |
|---|---|---|
| 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 | < > & " | For < > & and a double quote. In ordinary text only < and & need escaping |
| Space that never breaks a line | 10 kg | Keeps the number and unit together |
| Any character by name or number | © © © | All three are ©. With utf-8 you can also type the character itself |
Attributes any element takes
| Task | Code | Notes |
|---|---|---|
| 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.
| Task | Code | Notes |
|---|---|---|
| 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 < 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 superscript | H<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 wrap | example.com/<wbr>very/<wbr>long/<wbr>path | No 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.
| Task | Code | Notes |
|---|---|---|
| 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>© 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.
| Task | Code | Notes |
|---|---|---|
| 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 |
Links
| Task | Code | Notes |
|---|---|---|
| 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
| Task | Code | Notes |
|---|---|---|
| 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
| Task | Code | Notes |
|---|---|---|
| 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.
| Task | Code | Notes |
|---|---|---|
| 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
| Task | Code | Notes |
|---|---|---|
| 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.
| Type | Code | What you get |
|---|---|---|
| text | <input type="text" name="name"> | The default when type is missing |
<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.
| Task | Code | Notes |
|---|---|---|
| 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.
| Task | Code | Notes |
|---|---|---|
| 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>© 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 right | What actually happens | Do this instead |
|---|---|---|
<img src="logo.png"> | Screen readers read out the file name | alt="coding.kitty", or alt="" for decoration |
<button> for a JavaScript action in a form | Submits 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 action | Jumps to the top of the page and reads as a link | A <button>. Links go somewhere, buttons do something |
| A placeholder as the only label | Gone once someone types, and often too faint to read | A <label> above the field |
type="number" for a phone or card number | Drops leading zeros and adds spinner arrows | type="tel", or inputmode="numeric" |
| An unticked checkbox | Sends nothing, not "off" or "false" | Treat a missing field as false on the server |
A disabled field with a value you need | Not sent with the form | readonly |
pattern="[\w-]+" | An invalid pattern with the v flag browsers use, so it is ignored | Escape the dash: pattern="[\w\-]+". The Regex cheat sheet explains why |
<h4> because it is the right size | Breaks the heading outline screen reader users navigate by | The level the structure needs, sized with CSS |
<br><br> between paragraphs | Empty lines read out, and spacing you cannot restyle | Separate <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> inside | Invalid, and screen readers miscount the items | Only <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 it | Put it on a button, link, landmark or input, or use visible text |
loading="lazy" on the top image | Delays the biggest image on the page | Leave it off above the fold, add fetchpriority="high" |
An <img> with no width and height | The page jumps when the image arrives | Set both. CSS can still resize it |
<meta name="keywords"> | Ignored by Google and Bing | A clear title and description |
Two elements with the same id | Labels, links and getElementById find only the first | Make 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.
