TS-39: HTML
HTML was simple once. In its early days, the language consisted of a few dozen tags and a handful of attributes, and offered little interactivity or multimedia. Since then, the web has grown from a network of interconnected text documents into an application runtime, and HTML has grown with it. It is now just one of several core languages that make up the web platform, alongside CSS, JavaScript, and data interchange formats such as XML and JSON, plus adjacent formats and protocols (SVG, WebM, WebP, WOFF, RSS, Atom, and more). These are specified across hundreds of documents produced by multiple standards bodies, including the World Wide Web Consortium (W3C), the Web Hypertext Application Technology Working Group (WHATWG), the Internet Engineering Task Force (IETF), and the International Organization for Standardization (ISO).
Not all of modern HTML is useful, and not all of it is safe to use. Some elements and attributes are rarely used
and add little value. Some are redundant with other standards. For example, the <nav> element from HTML5, the
role="navigation" attribute from WAI-ARIA, and typeof="SiteNavigationElement" from the RDFa implementation of
Schema.org, all serve the same purpose. And some things are not supported consistently across target browsers and
platforms, so cannot be relied upon in production.
This technical standard defines a working standard for HTML. Think of it as a practical subset of the full language, filtered down to the markup that is useful, non-redundant, and reliably supported, for use in production code. It covers document structure and the allowed-element subset, the document head, text content, tables, hyperlinks, forms and buttons, images and SVG, audio/video and embedded content, scripting conventions, metadata schemas, internationalization, and accessibility. For CSS, see TS-40: CSS; for the web platform’s scripting APIs, see TS-37: Web platform APIs; for broader web-GUI concerns (performance, fonts) beyond markup itself, see TS-18: Web GUIs.
- Fundamentals
- Document head
- Text content
- Tables
- Hyperlinks
- Forms
- Buttons
- Images
- Vector graphics
- Audio, video, and embedded content
- Scripting and templates
- Semantics and metadata
- Accessibility
- Keyboard navigation
- Modals and focus management
- Accesskey
- Visibility
- WAI-ARIA
- Redundant ARIA
- ARIA roles
- ARIA states and properties
- Live regions
- Applications versus documents
- Presentational and separator roles
- Labelling and describing
- Tooltips
- Alerts and dialogs
- Custom buttons
- Forms accessibility
- Search
- Custom input controls
- Drag-and-drop
- Grids
- Tabs and accordions
- Trees
- Menus
- Other live-region components
- Landmark roles
- Best practices
- Keyboard interaction patterns
- Testing
- Accessibility checklist
- References
Fundamentals
Encoding and doctype
Every document MUST be served as UTF-8. The server response MUST include Content-Type: text/html;
charset=utf-8, and the document itself MUST additionally declare its encoding as the first child of
<head>:
<meta charset="utf-8">
The <meta> declaration is not redundant with the HTTP header. It is what lets the document render correctly
when opened as a local file, or served by infrastructure that does not set the header, so it MUST be present
regardless of what the server sends.
The document type declaration MUST be <!DOCTYPE html>, and it MUST be the very first thing in the file —
column 1, line 1, with nothing before it, not even a blank line or a comment. Any content before the doctype
forces the browser into quirks mode, which changes box-model and layout behavior unpredictably.
Source order and document structure
HTML’s primary role is to convey the structure and meaning of content, not its presentation. Source order matters independently of how a page is styled, because it is what a screen reader, a search engine crawler, or a browser with CSS disabled sees. Stripped of all styling, a page SHOULD still read coherently from top to bottom: heading, then the content it introduces; navigation grouped and labeled as navigation; the main content in one identifiable block.
Sectioning elements MUST be used wherever they apply, in preference to a generic <div>:
<header>for introductory content — typically a page or section’s heading, logo, and top-level navigation.<nav>for a block of primarily navigational links. A<nav>SHOULD carry a heading (which MAY be visually hidden) where a page has more than one, so that a screen reader user can distinguish "primary navigation" from "pagination" or "breadcrumbs" without inspecting each one’s content.<main>for the document’s primary content, unique per page.<article>for a self-contained piece of content that would make sense distributed or syndicated on its own — a blog post, a comment, a product card.<section>for a thematic grouping of content that is not self-contained enough to be an<article>. A<section>SHOULD contain a heading; a generic grouping with no heading and no distinct semantic role is a<div>, not a<section>.<aside>for content tangentially related to the surrounding content — a pull quote, a related-links box, a sidebar.<footer>for closing content — typically metadata about its nearest ancestor sectioning element, such as authorship, copyright, or related links.
<figure> is not limited to images — see Figures — and is the correct wrapper for any content referenced
from the surrounding prose that could be moved without breaking the flow of that prose: a code sample, a
table, a video, or a quotation, each with an optional <figcaption>.
A generic <div> or <span> remains the right choice where none of the above apply: a purely presentational
wrapper introduced for styling or scripting, with no semantic role of its own.
Allowed elements
This standard defines a working subset of HTML: elements that are useful, non-redundant with another element already in the set, and reliably supported. An element not in this list MUST NOT be used, even where it is valid HTML, unless this standard is extended to permit it.
- Sectioning and grouping
html,head,body,header,nav,main,article,section,aside,footer,div,figure,figcaption,hr.- Text content
h1–h3,p,ul,ol,li,dl,dt,dd,pre,blockquote,address.- Inline text
a,b,bdi,bdo,br,code,data,em,i,mark,small,span,strong,sub,sup,time.- Tabular data
table,caption,thead,tbody,tfoot,tr,th,td.- Forms
form,fieldset,legend,label,input,textarea,select,option,optgroup,button.- Media and embedded content
img,svg(and its own conforming child elements — see SVG as the default vector format),audio,video,source,track,iframe,embed,picture.- Scripting and metadata
script,noscript,template,meta,link,title,style,base.
This list deliberately excludes elements that are deprecated (acronym, applet, center, font,
marquee, strike, tt), that have unreliable or inconsistent browser support for their intended behavior
(dialog, menu, menuitem, keygen), or that this standard judges not worth the added complexity for a
working-standard subset (abbr, dfn, ins, del, s, u, kbd, samp, var, wbr, meter,
progress, details, summary, area, map). Two exclusions are worth calling out specifically because
earlier drafts of this standard’s source material disagreed on them:
<dl>/<dt>/<dd>is permitted. It is the correct element for genuinely paired term/description content — a glossary, a metadata list, a FAQ — and excluding it in favor of<ul>throws away real semantics for no benefit. It MUST NOT be used as a general-purpose layout list; use<ul>or<ol>for anything that is not a term/description pairing.<b>,<i>, and<small>are permitted, each for a specific semantic meaning that is not covered by<strong>or<em>— see Inline text elements. They MUST NOT be used purely for visual styling; that is what CSS is for.
<caption> is likewise permitted — see Tables.
Coding style
- Documents MUST be well-formed and MUST validate against the W3C HTML validator.
- Tag names and attribute names MUST be lowercase.
- Attribute values MUST use double quotes. Single quotes are reserved for JSON embedded in an attribute value, so that the JSON’s own double quotes do not need escaping.
- Boolean attributes (
disabled,checked,required, and similar) MUST be written valueless —<input disabled>, not<input disabled="disabled">. - Every element MUST be explicitly closed. Void elements (
br,img,input,hr,meta,link) MUST use the explicit self-closing form with a single preceding space:<br />. <html>,<head>, and<body>MUST always be written explicitly, even though HTML’s parsing rules would infer them.- Indentation MUST use four spaces, never tabs. Each block-level element MUST start on its own line.
- Attributes on the same element SHOULD be written in alphabetical order, to make a long attribute list scannable and diff-friendly.
classMUST be used exclusively as a CSS styling hook. It MUST NOT be queried by JavaScript to select elements — see Data attributes for the attribute JavaScript MUST use instead.idis reserved for hash-fragment targets and for DOM relationships that require a unique reference (for/id,aria-labelledby, and similar). AnidMUST be lowercase, hyphen-delimited ASCII, and SHOULD stay under around 15 characters. Anidthat exists purely to express a relationship between two elements SHOULD be prefixed with the node name of the element that owns it, e.g.input-email,select-country.- The literal characters
&,<, and>MUST be escaped as&,<, and>wherever they appear in text content or attribute values, other than inside markup itself. Inside a double-quoted attribute value, a literal"MUST be escaped as". Any other special character SHOULD be written as its literal UTF-8 character, not as a numeric or named entity — the file’s UTF-8 encoding makes entities unnecessary for anything beyond&/</>/". The exception is a zero-width space (), which MAY be inserted before punctuation in a long unbroken string (a URL or an email address) to give the browser a legal line-break opportunity. - Use the
hiddenboolean attribute to remove an element from both the visual rendering and the accessibility tree. It is preferred over a CSSdisplay: noneorvisibility: hiddenrule for content that is not yet relevant, because it communicates that removal semantically rather than only visually — see Visibility for the cases where CSS is the correct tool instead.
Document head
Legal head elements
The <head> MAY contain only <meta>, <link>, <title>, <style>, <script>, and <base>. A
<noscript> element MAY also appear in <head>, but only to declare a fallback
<meta http-equiv="refresh"> or similar no-script directive — content intended for a no-script body
belongs in <body>, not <head>, since <head> content is never rendered.
The document’s two mandatory <meta> tags — charset and viewport — MUST be the first two children of
<head>, in that order, before <title> or anything else:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title</title>
...
</head>The charset declaration MUST come first because the browser has to know the document’s encoding before it can
correctly parse anything after it, including the rest of <head>.
Base URL
A <base> element is strongly RECOMMENDED. It fixes the document’s base URL for every relative URL on the
page — links, image sources, form actions — which removes an entire class of bugs where a relative URL
resolves correctly on one route but not another (for example, after client-side routing changes the
browser’s current path without a full navigation).
<base href="https://example.com">
The href MUST be written as scheme://domain, with no trailing slash and no path. A trailing slash or a
path segment on <base> changes how every relative URL on the page resolves, in a way that is easy to get
wrong and hard to spot in review; keeping <base> to the origin only avoids the ambiguity entirely. Relative
URLs elsewhere on the page follow the convention set out in Hyperlinks.
Title
Every document MUST have a <title>. It has no visual presence in the page itself, but it is what appears in
the browser tab, the browser history, a search result, and any bookmark — and it is the first thing many
screen reader users hear when a page loads, so it MUST communicate what the page is on its own, without
relying on visible page content for context.
A title SHOULD stay within roughly 55–60 characters, since search engines and browser tabs truncate longer titles, usually mid-word, which reads worse than a shorter, deliberately chosen title.
The title convention differs by product type:
- A content website’s title SHOULD follow "Page Title" with no fixed boilerplate suffix that eats into the character budget on every page.
- A web application’s title SHOULD use "Page Title – App Name" (an en dash, not a hyphen, with a space on each side) so the application’s identity is still visible when several of its tabs are open side by side.
Where a page’s <title> includes an application name, the same name SHOULD be given in an application-name
<meta> tag (see Meta tags), so a browser or platform surfacing the app’s identity outside the tab bar
(a pinned-tab tooltip, an OS taskbar) has it without re-parsing the title string.
A single-page application whose state changes without a full navigation MUST update document.title to
reflect that state change — a title left static after the visible content has moved on misleads a screen
reader user relying on the title as their orientation cue, and misleads browser history and bookmarks
equally.
Title case is not used. A title SHOULD use sentence case, capitalizing only the first word and any proper nouns.
Meta tags
The following <meta> tags are RECOMMENDED, over and above the mandatory charset and viewport:
application-nameThe application’s short name. Web applications only — a content website has no equivalent concept.
descriptionA one-sentence summary of the page, 120–150 characters, written as a full sentence rather than a keyword fragment. REQUIRED on every publicly indexed page. It MUST be unique per page: a description copied across pages, or omitted so a search engine falls back to scraping arbitrary body text, both produce a worse search-result snippet than a deliberately written one.
keywordsCarries no ranking weight with any major search engine today. It MAY be included as an internal, human-readable reference to a page’s intended topic, but MUST NOT be relied on for SEO.
authorThe document’s author or owning organization, where relevant.
referrerControls what
Refererinformation the browser sends when a user follows a link away from the page. This standard does not mandate a value, but whatever value is chosen MUST be a static, deliberate policy decision — not injected dynamically per request, which makes the page’s referrer behavior unauditable from its own source.robots/googlebotControls indexing and link-following for the page —
index/noindexandfollow/nofollow, independently combinable. Most pages need neither, because indexing and following are the default; usenoindexexplicitly on pages that MUST NOT appear in search results (an internal search-results page, a duplicate of canonical content elsewhere) and on error pages such as a 404.Content-Security-PolicyA
<meta http-equiv="Content-Security-Policy">tag MAY set a page’s CSP where the server response cannot set the equivalent HTTP header. See Content Security Policy for what the policy itself should contain.
The full list of registered <meta name> extensions is maintained by the WHATWG; only the values above are
commonly useful for a production document — do not add a <meta> tag speculatively on the strength of it
appearing in that list.
Linked resources
<link> declares a relationship between the document and an external resource. The following rel values
are in regular production use:
stylesheet— the document’s CSS.manifest— the web app manifest, for installable web applications.canonical— the authoritative URL for this content, where the same or substantially similar content is reachable at more than one URL (with and without a tracking query parameter, for example). REQUIRED wherever that ambiguity exists, so a search engine consolidates ranking signal onto one URL instead of splitting it.alternate— an alternate representation of the page: a translated version (paired withhreflang), or a feed (paired withtype, e.g.application/rss+xml).icon— the favicon and any additional sizes/formats for different platforms.
Paginated content SHOULD use rel="prev" and rel="next" to link sequential pages; first and last MAY
also be given where the sequence has a fixed length. These help both a search engine and assistive technology
understand the page’s position within a series independently of any in-page "Next" link the user sees.
<link rel="prefetch"> and <link rel="preload"> MAY be used to hint the browser toward a resource it will
need imminently. Both are advisory — the browser may ignore either under memory or bandwidth pressure — so
neither is a substitute for the resource’s own caching headers.
Text content
Headings
Headings convey document structure, not visual emphasis — a heading MUST be chosen because the content beneath it is a genuine subsection, never because a piece of text needs to look bigger. Presentation is CSS’s job; resizing a heading to get a visual effect, or using a heading to avoid writing a CSS rule for bold, large text, both misuse the element.
- Headings MUST be ordered hierarchically. A page MUST NOT skip a level on the way down (an
<h1>followed directly by an<h3>, with no<h2>in between). - Two headings of the same level MUST NOT appear consecutively with no intervening content — a heading with nothing under it before the next heading is a sign the content is missing, or the heading is superfluous.
- This standard restricts documents to
<h1>–<h3>— see Allowed elements. Three levels are enough for the vast majority of content; a document that seems to need a fourth level is usually a sign the content itself should be split across more pages, or organized with fewer, better-chosen sections. - More than one
<h1>is permitted, provided each is the heading of its own sectioning element (<article>,<section>) — HTML’s outline model treats each sectioning element as starting a new heading context. A page that is not composed of independent sectioned regions SHOULD still use exactly one<h1>, for its own top-level title. - A heading MAY be wrapped in a hyperlink where the heading text itself is the link (a blog index linking
each post’s title). No other inline element is permitted inside a heading — a heading is a plain-text
structural label, not a place for a
<button>, an icon, or a<span>used for partial styling.
Lists
HTML has three list types, each for a different editorial relationship between its items:
<ul>— an unordered list, for items whose relative order carries no meaning (a set of navigation links, a set of features).<ol>— an ordered list, for items whose sequence is meaningful (steps in a procedure, a ranking). The numbering MUST NOT be hidden with CSS — if the order does not need to be visible, the content is not really ordered, and<ul>is the correct element.<dl>— a description list, for term/description pairs (see Allowed elements for why this standard permits it where some earlier drafts of this material did not). Every<dt>MUST be paired with at least one following<dd>.
Both <ul> and <ol> MUST contain at least one <li> — an empty list is not valid content. Nesting SHOULD
NOT go deeper than three levels; content that needs a fourth level of nested list is usually better
restructured with headings.
<ol> accepts a type attribute (type="a", type="i", and similar) for a nested list’s numbering style,
where a numeral-only default would be visually ambiguous against its parent list’s own numbering.
No block-level element other than a nested <ul> or <ol> MAY appear directly inside a <ul>/<ol> beyond
the <li> itself. Keep list item text short — a list item that runs to several sentences of prose is usually
better written as its own paragraph under a heading.
Inline text elements
The permitted inline elements (see Allowed elements) each carry a specific meaning:
emStress emphasis that changes the meaning of the sentence it is in — the word a reader would naturally stress aloud. Not a general-purpose italics tool.
iText in an alternate voice or mood without stress emphasis — a technical term on first use, a foreign-language phrase, a ship’s name. Where the alternate-voice case does not apply,
<em>or plain text is the better choice.strongStrong importance, seriousness, or urgency — content the reader MUST NOT skim past. Not a general-purpose bold tool.
bText that is stylistically offset without conveying extra importance — a keyword in a summary, a product name in a review. Where the extra-emphasis case does apply,
<strong>is the correct choice instead.markA run of text highlighted for reference relevance in the current context — a matched search term, for example — rather than for its own importance.
smallSide comments and small print — a disclaimer, a copyright line. It signals a genuinely different content register, not a bare font-size decrease; apply an actual font-size decrease with CSS.
spanThe inline equivalent of
<div>— a wrapper with no semantic meaning of its own, used only for styling or scripting hooks.codeA fragment of literal code, filename, or computer output. Combined with
<pre>(see Pre-formatted text) for a multi-line block.dataMachine-readable content paired with a human-readable value, via the
valueattribute — for example, a product name in the text content with its SKU invalue.timeA date or time, machine-readable via the
datetimeattribute.a,bdi,bdo,br,sub,supCovered in Hyperlinks, Internationalization, and their own uses respectively — a line break, and subscript/superscript for genuinely subscripted or superscripted characters (a footnote marker, a chemical formula), not for visual positioning.
abbr, dfn, ins, del, kbd, samp, var, wbr are excluded from this standard’s allowed-element set
(see Allowed elements) — each has a narrow, rarely needed use, and the working-standard subset favors the
smaller set above.
Horizontal rules
<hr> represents a paragraph-level thematic break within content — a scene change, a shift in topic within a
single section. It MUST NOT be used purely for visual decoration (a horizontal line for its own sake — that
is a CSS border), and MUST NOT be placed immediately before a heading or between two sectioning elements,
where the sectioning boundary itself already marks the break.
Pre-formatted text
<pre> preserves whitespace and line breaks exactly as written, for content whose structure is inherently
typographic rather than semantic — source code (typically paired with <code>), ASCII art, or any text where
the line breaks and spacing are part of the content’s meaning. It MUST NOT be used as a shortcut to avoid
writing proper markup for structured content that has its own semantic elements available.
Addresses
<address> is a sectioning element for contact information for its nearest <article> or the document as a
whole — not for any physical address that happens to appear in the content. An address printed as part of
unrelated body text (a company’s registered office quoted in a paragraph) does not belong in <address>; the
element is reserved for contact information about the content’s author or owner.
Tables
<table> MUST be used only for genuinely tabular data — content with a real two-dimensional relationship
between rows and columns, such as a comparison matrix or a schedule. It MUST NOT be used for page layout; CSS
grid and flexbox are the tools for visual layout, and a layout table breaks the reading order for a screen
reader, which announces row/column structure that has no relationship to the actual content.
A conforming table MUST use explicit structural elements rather than relying on row position alone:
<table>
<caption>Quarterly revenue by region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">EMEA</th>
<td>$1.2M</td>
<td>$1.4M</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$1.2M</td>
<td>$1.4M</td>
</tr>
</tfoot>
</table><thead>,<tbody>, and<tfoot>MUST be used explicitly rather than left to be inferred from row position — the explicit grouping is what a screen reader uses to announce "column header" versus a data cell, and what lets<tbody>alone scroll independently in a long table.<th>MUST be used for every heading cell, whether it heads a column (scope="col") or a row (scope="row"). A<td>styled to look like a heading carries no heading semantics to assistive technology.<caption>is permitted and RECOMMENDED — see Allowed elements for why this standard includes it where some earlier drafts of this material excluded it. It MUST be used for the table’s own title or a brief note on its source, not as a substitute for a heading in the surrounding document structure; where the table needs a heading the reader can link to or that appears in a page outline, use a normal heading before the table in addition to, or instead of, a<caption>.- The
summaryattribute is obsolete and MUST NOT be used;<caption>and properly scoped<th>elements supersede it. - A
borderattribute MUST NOT be used — table borders are a CSS concern.
Hyperlinks
The anchor element
<a> is a block-or-inline hybrid, valid in either context, and is the only element this standard permits to
wrap another block-level element for the purpose of making a whole card or region clickable.
hrefis mandatory on every<a>used as a link, and MUST resolve to a real resource. An<a>with nohref, or one that points nowhere, is not a link — either give it a real destination or use a<button>instead, per JavaScript-driven links.- Internal links MUST be written relative to the document’s base URL, not as absolute URLs, so the same markup works unchanged across environments (local, staging, production).
titleis advisory only — it is not reliably exposed to assistive technology or to touch input, and MUST NOT carry information the reader needs to use the link. Put anything essential in the visible link text instead.- Other attributes in regular use:
download(suggests a filename for save-as, but unreliable across browsers for cross-origin URLs — prefer aContent-DispositionHTTP header where the download behavior is important),hreflang(the linked resource’s language, primarily forrel="alternate"translations),rel(see below and Microformats),target(see below), andtype(the linked resource’s MIME type, as an advisory hint).
For URL construction and design more broadly — path structure, query parameters, trailing slashes — see TS-63: URL design; this section covers only how a link is marked up, not how the URLs it points to are designed.
URL conventions in link markup
Every URL used in an <a href> MUST conform to
RFC 3986. A literal space in a URL MUST be percent-encoded as %20,
never left literal or replaced with a +, which has a different meaning in a query string.
- An internal link SHOULD be relative rather than absolute, resolved against the page’s base
URL. A link to the site’s homepage SHOULD use
./rather than an empty string or the domain root written out. ../(a parent-relative path) MUST NOT be used — it makes a link’s target depend on where the linking page itself sits in the URL hierarchy, which breaks silently if either page moves.- A link that includes a hash fragment MUST still resolve to the correct canonical page without the fragment — the fragment identifies a location within the page, not a substitute for the page’s own correct path.
- An external link SHOULD carry
rel="external"so it can be styled or handled distinctly (opened in a new tab, flagged visually) without relying on brittle URL-matching logic. - A query parameter that does not change what the user sees SHOULD be avoided — every additional optional
parameter is another URL variant for a search engine to index separately and another case for
rel="canonical"to account for. - A published URL MUST NOT expose a username, password, or non-standard port —
https://user:pass@example.comcredentials embedded in a URL are a security anti-pattern; see TS-52: Security and secrets management.
JavaScript-driven links
Every link rendered on initial page load MUST resolve to a useful resource without requiring JavaScript to run first — a link whose real destination is only known after a script executes fails for any client that does not run that script, including a search engine crawler in some configurations.
Where a click needs to trigger application behavior rather than a plain navigation, and that behavior still
corresponds to a genuine URL (a client-side-routed page, for example), the <a>’s `href MUST still point at
that real URL, so that opening it in a new tab, right-clicking it, or following it with JavaScript disabled
all still work; the click handler is an enhancement on top of a working link, not a replacement for one.
href="javascript:…"MUST NOT be used.- An inline event-handler attribute (
onclick, and similar) MUST NOT be used — see Progressive enhancement for how a handler should be attached instead. - Where a click genuinely does not correspond to any URL — it triggers an in-page action with no navigable
destination, such as opening a modal — the correct element is a
<button>, not an<a>with no realhref. See Buttons.
Telephone and email links
mailto: links SHOULD generally be avoided in favor of a contact form, which does not expose the address to
scraping and gives the sender a consistent, validated submission path.
A tel: link is acceptable, and SHOULD use the international format: a leading +, the country code, then
the national number with no leading trunk zero and no internal spaces, hyphens, or other punctuation —
tel:+442012345678, not tel:+44 (0)20 1234 5678. The visible link text MAY differ from the tel: value and
be formatted however is conventional for the reader’s locale; only the href value has this strict format
requirement.
Forms
The form element
<form> MUST encapsulate every control it submits — a control outside its <form> (associated only via the
HTML5 form attribute pointing at the form’s id) is easy to lose track of during a refactor, so this
standard prefers the simpler, self-contained structure of every submitted control nested inside its own
<form>.
actionandmethodSHOULD be set explicitly, even though both default to the current URL andGETrespectively — an explicit value documents the form’s behavior at the point of definition rather than relying on the reader to know the defaults.- An
actiontargeting the same origin SHOULD be a relative URL, per Hyperlinks; a cross-domainactionMUST be absolute. idis preferred overnamefor identifying the form itself in CSS or JavaScript, consistent with Coding style.nameremains necessary on individual controls, since it is what the browser uses as the submitted field key.enctype="multipart/form-data"MUST be set whenever the form contains a file input, and only works withmethod="post"— a file input inside aGETform, or without the correctenctype, silently fails to submit the file.
Validation and input attributes
Native HTML form validation and autofill are unreliable in appearance and behavior across browsers, and this standard therefore prefers a consistent, custom-styled validation experience over the native one:
novalidateSHOULD be set on the<form>to suppress the browser’s own validation UI, and validation logic implemented in JavaScript instead — see TS-21: HTTP APIs for how that client-side validation relates to the server-side validation the API MUST still perform independently.spellcheck="false"SHOULD be set on any field where the browser’s spellcheck underline is unhelpful noise — a code field, a username, a postal code.autocomplete="off"SHOULD be set at the<form>level for a form where browser-remembered values are actively unhelpful (a search box that should start empty each time), andautocomplete="on"(or a specific autofill token, e.g."email","given-name") set per control where the browser’s autofill genuinely helps the user, such as a standard address or payment form.autocapitalize="none"MAY be set to prevent iOS Safari’s non-standard automatic capitalization on a field where it is unhelpful (a username, an email address) — this attribute is a Safari-specific extension, not part of the HTML standard, but is widely supported as a no-op elsewhere.
Passwords are a specific case worth calling out: users MUST NOT be prevented from pasting into a password field. Blocking paste (historically done to force manual re-entry, on the mistaken belief it improved security) actively harms users of password managers and makes strong, randomly generated passwords harder to use, which the UK National Cyber Security Centre has documented as a net security loss, not a gain.
Input controls
The following <input> types, plus <select> and <textarea>, are the permitted control set:
checkbox,radiovalueis mandatory on both, even where there is only one option, because it is what gets submitted. Both MUST be wrapped in a<label>(see Labels, fieldsets, and legends) so that clicking the label text toggles the control. An unchecked checkbox submits nothing at all, not an empty orfalsevalue — a form that needs to distinguish "unchecked" from "not shown" MUST pair the checkbox with a hidden input carrying the unchecked-state default.email,search,tel,url,number,dateThese specialized types delegate basic format validation and, on mobile, an appropriate on-screen keyboard, to the browser. They degrade gracefully to a plain text input in a browser that does not recognize the type, so there is no compatibility cost to using them wherever the semantic type applies.
rangeanddatetime-localare more inconsistently implemented across browsers and MUST NOT be relied upon as the sole means of input — provide an equivalent text-based fallback or a custom control instead.Where a field expects a specific, limited format, offering the user a constrained choice (a
<select>, a set of radio buttons) is preferable to free text wherever the set of valid values is small and known in advance — it removes an entire class of format errors rather than validating them after the fact.passwordMUST NOT have a
valueset from the server — a pre-filled password field either leaks the value into the page’s HTML source or, worse, is never the correct value the user intended.textThe default.
maxlengthlimits input length;sizesets the control’s visible width in characters but is a layout no-op in any form styled with CSS, sosizeSHOULD be omitted in favor of a CSS width.fileRequires
method="post"andenctype="multipart/form-data"on the parent<form>— see The form element.acceptis an advisory filter on the file picker only, not a validation guarantee, so the server MUST still verify the uploaded file’s actual type.multiplepermits selecting more than one file in a single control.hiddenCarries a value the user does not see or edit, submitted along with the rest of the form.
selectSHOULD be used once a control has around 12 or more options — below that, a set of visible radio buttons is usually more usable, since it does not require an extra interaction to reveal the choices. The
multipleattribute on<select>produces a poorly understood, hard-to-style multi-select widget and SHOULD be avoided in favor of a set of checkboxes.textareacolsandrowsare unitless integers, not CSS lengths.colsSHOULD be overridden with a CSSwidth: 100%(or an explicit width) rather than relied on for layout;rowssets a starting height, which CSSheight(orresize) can then adjust. A reset-type button paired with a<textarea>or any other form is discouraged — see Buttons.
Labels, fieldsets, and legends
Every form control MUST have an associated <label>, connected either by wrapping the control inside the
<label> or by a matching for/id pair. A control with no programmatically associated label is unusable
with a screen reader and gives no larger click/tap target than the control itself.
Related fields MUST be grouped in a <fieldset> wherever the grouping is meaningful — a set of radio
buttons for one question, the address fields of a shipping form. <fieldset> MUST be used even where the
group contains only a single control, for consistency and because groups routinely grow a second field
later. A <fieldset> with more than one control SHOULD have a <legend> naming the group, which a screen
reader announces as the group’s own label, distinct from each individual control’s own <label>.
Buttons
<button> MUST always carry an explicit type — type="submit" for a button that submits its enclosing
<form>, or type="button" for any other push-button behavior handled by JavaScript. A <button> with no
type attribute defaults to type="submit", which is a common source of forms being submitted accidentally
by a button that was only meant to trigger some other in-page behavior.
type="reset"MUST NOT be used, on a<button>or an<input>. A reset button discards user-entered data with one click and no confirmation, which is far more often a source of accidental data loss than a useful feature.<input type="button">and<input type="submit">MUST NOT be used —<button>is the equivalent element, and additionally allows rich content (an icon plus text) inside it, which<input>does not.nameandvalueare meaningful only on a submit button, where they become part of the submitted form data (useful for a form with more than one possible submit action, distinguished by which button was pressed). They serve no purpose on atype="button"button.- A button that triggers an action with in-flight latency (a network request) SHOULD be disabled for the duration of that action, to prevent a double-click from submitting the action twice.
autofocusMAY be used on the button that represents the default, most-likely next action in a view — a dialog’s primary action, for example.autocomplete="off"MAY be set on a button where the browser’s autofill has been observed re-triggering an unwanted saved action; this is rarely needed in practice.
See JavaScript-driven links for the corresponding rule on <a>: an element that triggers behavior with
no real navigable destination is a <button>, never a link with an empty or fake href.
Images
Figures
Any graphic referenced from the surrounding prose — a chart, a photograph, a code sample, a quotation, a
table, or a video — MUST be wrapped in <figure> if it is content the surrounding text refers to, rather
than purely decorative. <figcaption> is RECOMMENDED, and where present MUST sit at the bottom of the
figure, describing what the figure shows. <figure> accepts at most one <figcaption>.
A caption is not a substitute for an image’s alt text — see The img element. The two serve different
readers: alt is what stands in for the image when it cannot be seen or loaded at all, and the caption is
supplementary context shown alongside the image for every reader, sighted or not.
The img element
<img> MUST always carry src, width, height, and alt. ismap and usemap MUST NOT be used — see
Image maps for why that mechanism is obsolete.
widthandheightMUST exactly match the image’s real pixel dimensions. This is what lets the browser reserve the correct amount of layout space before the image has finished downloading, preventing a content reflow ("layout shift") once it loads.altMUST describe the image’s purpose in context, not literally describe its pixels — "Line chart showing revenue up 40% in Q2", not "A blue line going up and to the right." It SHOULD stay under roughly 50 characters where possible; longer context belongs in a<figcaption>or the surrounding prose, not crammed intoalt.- An image that is purely decorative — contributing no information a screen reader user needs — MUST still
carry
alt=""(an empty but present attribute). Omittingaltentirely, rather than setting it empty, causes some screen readers to announce the image’s filename instead, which is worse than announcing nothing. - Three raster/vector formats cover production use: JPEG for photographic content, PNG for content needing
transparency or sharp edges (UI screenshots, diagrams with flat color), and SVG — embedded via
<svg>directly, per Vector graphics, in preference to<img src="*.svg">— for anything that is actually vector artwork (icons, logos, line art).
Raster image formats
BMP, GIF, ICO, JPEG, and PNG are universally supported. JPEG and PNG cover the overwhelming majority of production use — JPEG for photographic content where some lossy compression is acceptable, PNG where transparency or lossless sharp edges are required. Animated PNG (APNG) is supported widely enough to use for short looping animations in place of GIF, which gives visibly worse quality at a comparable file size.
WebP offers meaningfully better compression than JPEG or PNG at equivalent visual quality, but MUST be
served with a <picture>/<source> fallback to JPEG or PNG (see Responsive images) rather than as the
sole <img src>, for the small remaining share of clients without WebP support.
Image optimization
Every image MUST be compressed to the point just short of perceptible quality loss — not merely "reasonably small," but tuned per image. As a general target, an image intended for a typical content page SHOULD be under 25 KB where the source material allows it without visible degradation; a genuinely detailed photograph will legitimately exceed that, and forcing it under the target at the cost of visible artifacts is the wrong tradeoff.
- Export at the size the image is actually rendered at, not larger — a 4000px source photo displayed at 400px wide wastes bandwidth on detail no viewport will show, on top of whatever the browser’s own downscaling costs in decode time.
- DPI/PPI metadata is irrelevant to how a browser renders an image; only the pixel dimensions matter. Do not rely on a DPI setting to control perceived size or quality.
- Export in RGB, never CMYK — CMYK images render incorrectly, or fail to render, in a browser context.
- Where further size reduction is needed and a small amount of blur is an acceptable tradeoff, a slight gaussian blur before JPEG compression can reduce file size noticeably, because JPEG compresses smooth gradients more efficiently than sharp detail.
Serving images
- File extensions MUST be lowercase.
- The server MUST send the correct
Content-Typefor the image format being served. - Images SHOULD be served from a separate, cookie-free domain (or subdomain) where the main site sets cookies, so that every image request does not carry the full cookie header for no benefit.
- Cache headers (
Cache-Control,Expires) SHOULD set a long cache lifetime — six months or more — for images, which rarely change in place. Where an image genuinely does need to change, rename the file (or add a content hash to the filename) rather than relying on cache invalidation of the old URL, which is unreliable across intermediate caches and CDNs. - Many small images (icons, small UI graphics) MAY be inlined as data URIs directly in CSS or HTML, trading one HTTP request for a larger inline payload — worthwhile below a certain size and request-count threshold, and worth measuring rather than assuming for a given page.
Responsive images
A responsive image serves an appropriately sized (and sometimes differently art-directed) file depending on the viewport and the device’s pixel density, rather than one fixed file scaled by CSS.
The simplest and RECOMMENDED mechanism is srcset with x descriptors, for serving a higher-resolution file
to a high-density display without changing which image is shown:
<img
src="photo.jpg"
srcset="photo.jpg 1x, photo@2x.jpg 2x"
alt="…">Where different viewport sizes need genuinely different crops or compositions of the same subject — "art
direction," not just resolution — <picture> with multiple <source> elements, each with a media
condition, is the correct tool:
<picture>
<source media="(min-width: 800px)" srcset="wide-crop.jpg">
<source media="(min-width: 400px)" srcset="medium-crop.jpg">
<img src="narrow-crop.jpg" alt="…">
</picture><source> elements MUST be listed in the order they should be evaluated — the browser uses the first
matching media condition, so a more specific condition MUST come before a more general fallback.
An alternative mechanism — srcset with w descriptors paired with a sizes attribute, where the browser
itself picks the best-fit file based on the rendered layout width — exists and is valid, but its sizes
value requires expressing the image’s rendered width as a function of viewport width (often with calc()),
which is intricate to get right and expensive to keep in sync as a layout changes. This standard generally
advises against it in favor of the simpler x-descriptor or <picture> patterns above, reserving
w-descriptor sizes for cases neither of those covers.
Image maps
<area>/<map> client-side image maps remain valid, standard HTML and are still supported by every current
browser, but are largely obsolete — the interactive-region use case they serve is better met today by SVG
(each shape as its own element, styleable and independently focusable) or by a canvas-based implementation
for anything more dynamic. New work SHOULD NOT use image maps.
Vector graphics
SVG as the default vector format
SVG is the only vector graphics format the web platform supports natively, and SHOULD be used for every graphical UI component that is not a photograph — icons, logos, illustrations, diagrams, and charts alike. It is preferred over an icon font (Font Awesome and similar) or a sprite sheet: an icon font repurposes a text-rendering pipeline for graphics, which brings font-loading failure modes and accessibility problems (an icon font glyph read aloud as its fallback character) that an inline SVG does not have.
Linking versus inlining
An SVG can be referenced like a raster image (<img src="icon.svg">, a CSS background-image) or embedded
directly in the HTML as <svg>…
</svg>. Inlining is RECOMMENDED:
- It costs one fewer HTTP request per graphic.
- An inlined SVG’s internal elements are addressable and styleable from the page’s own CSS and JavaScript — a linked SVG’s internals are opaque to the linking document.
- An inlined SVG can carry
<title>/<desc>that assistive technology reads directly as part of the page’s own accessibility tree; a linked SVG depends on the surrounding<img alt>instead, which cannot expose the SVG’s own internal structure.
A linked SVG (where linking is chosen) MUST be self-contained — no external stylesheet or script reference
inside the SVG file — since a browser’s handling of an external resource reference from within a linked SVG
is inconsistent. <object> is a workaround for accessing a linked SVG’s internals from the outer document,
at the cost of extra markup and its own quirks, and is only worth reaching for where inlining is not
practical.
Where more than one inlined SVG on the same page defines an id (commonly on a <clipPath>, a <mask>, or
a gradient definition), every id MUST be unique across the whole document, not just within its own SVG —
two inlined SVGs both defining id="icon-mask" collide, and the second silently overrides the first.
Namespacing generated IDs per component, or rendering each reusable icon inside a Web Component’s shadow DOM
(which scopes IDs to that shadow tree), both avoid the collision at scale.
A linked SVG (the non-default case above) MUST be served with the file extension .svg and the
image/svg+xml MIME type.
SVG attributes
viewBoxMUST be treated as mandatory. It defines the SVG’s internal coordinate system independently of its rendered size, which is what lets the graphic scale cleanly via CSS.widthandheightare likewise mandatory on the root<svg>element — older browser versions (Internet Explorer 9–11 in particular) fail to render an SVG with no explicit dimensions even whereviewBoxis present.xandyposition an SVG (or a nested<svg>) within its parent coordinate system, where relevant.version="1.1"SHOULD be kept for maximum compatibility, even though SVG 2 is the current specification; SVG 2 does not introduce a version attribute value of its own, and 1.1 remains the safe, broadly supported declaration.xmlns="http://www.w3.org/2000/svg"is mandatory on the root element.xmlns:xlinkis additionally required only where the SVG uses anxlink:hrefreference (a<use>element, for example) rather than the SVG2 barehref.<title>and<desc>are the SVG equivalent of an<img>’s `alttext, and SHOULD be included on any SVG conveying real information —<title>for a short accessible name,<desc>for a longer description where needed.role="presentation"(oraria-hidden="true") SHOULD be set on an inlined SVG that is purely decorative — an icon that duplicates adjacent visible text — so assistive technology does not announce it as a separate, unlabeled graphic.
SVG filters
SVG filter primitives (<feGaussianBlur>, <feColorMatrix>, and others) apply blur, color manipulation, and
compositing effects directly within the SVG. They are supported in every current browser, with the
historical exception of Internet Explorer 9 and earlier and Android 4.4 and earlier — not a live constraint
for a production standard targeting current browsers, but worth knowing if a project has a legacy support
requirement.
SVG animation
Three independent mechanisms can animate SVG content:
- Direct DOM scripting — manipulating attribute or style values via JavaScript, giving full programmatic control at the cost of writing the animation logic by hand.
- CSS transitions and animations — the same CSS mechanisms used elsewhere on the page apply directly to SVG properties, and are the RECOMMENDED default for straightforward animations, consistent with the general preference for CSS-driven animation over JavaScript (see Progressive enhancement).
- SMIL (
<animate>,<animateTransform>, and related elements) — animation declared natively within the SVG markup itself. SMIL has inconsistent and, in some browsers, deprecated support, and SHOULD NOT be relied on for new work; use CSS animation instead.
A JavaScript animation library (Snap.svg, svg.js, Velocity.js, and similar) MAY be reached for where an animation’s complexity genuinely exceeds what CSS can express — coordinated multi-element sequences with runtime-computed values, for example — but is unnecessary overhead for anything CSS transitions or animations already handle.
SVG keyboard navigation
An inlined SVG that contains interactive elements (a clickable icon button rendered as SVG shapes, for
example) is subject to the same keyboard-accessibility requirements as any other interactive control — see
Keyboard navigation in the accessibility section. A purely decorative SVG MUST NOT be focusable at all;
where a browser’s default behavior makes an inlined SVG focusable regardless of its content,
focusable="false" (or tabindex="-1" combined with aria-hidden="true") MUST be set to remove it from the
tab order.
Audio, video, and embedded content
Audio and video
<audio> and <video> MUST be treated as block-level content in layout, even though <video> in particular
is sometimes reached for as an inline decorative element.
autoplayandloopMUST NOT be used. Playback MUST start only on an explicit user action — autoplaying media is disruptive, and major browsers increasingly block autoplaying audio outright regardless of what the markup requests.mutedis not a reliable substitute for the above — some browsers will autoplaymutedcontent, but this MUST NOT be relied upon as a workaround for theautoplayrule; the rule is about not starting playback without user action, not specifically about audible playback.preloadis advisory only ("none", "metadata", or "auto") — the browser may override it under bandwidth or data-saving constraints, so it MUST NOT be relied upon as a guarantee of prefetch behavior.<track kind="subtitles">(WebVTT) SHOULD be provided for any video with spoken content, for accessibility. Older browser versions (Internet Explorer 9 in particular) have gaps in<track>support, but this MUST NOT be used as a reason to omit captions — the standard’s target browsers support it, and captions benefit every viewer in a sound-off context, not only Deaf and hard-of-hearing viewers.
Embedded media and iframes
<embed> requires height, src, type, and width to be specified explicitly; a missing type leaves
the browser guessing the embedded content’s format from the response, which is unreliable.
<iframe>:
- MUST specify explicit
widthandheight(or be sized via CSS), for the same layout-stability reason as<img>— see The img element. - SHOULD set
scrolling="no"where the embedded content is not meant to scroll independently of the page, since some browsers (historically Firefox) render a visible scrollbar by default that others do not. - MUST use
id, notname, for referencing the iframe from the parent document’s own scripts or styles, consistent with Coding style;nameremains relevant only where the iframe is itself the target of a form submission or a link’stargetattribute. - Embedding third-party content (an ad, a widget, a payment form) MUST use the
sandboxattribute, scoped to only the permissions that specific embed genuinely needs (allow-scripts,allow-forms, and similar), rather than left unset — an unsandboxed third-party iframe runs with the same privileges as the embedding page.
On iOS, an <iframe> with scrolling="no" has historically not prevented the embedded document’s own
content from scrolling if that content overflows its frame — the fix is on the embedded document’s own
side, constraining its content’s height or setting overflow: hidden on its own root element, not something
the embedding page’s iframe attribute alone can control.
Scripting and templates
Classes and data attributes
class and data-* MUST be used for strictly separate purposes:
classis exclusively a CSS styling hook, per Coding style. It MUST NOT be queried by JavaScript to select or identify elements — a class renamed during a CSS refactor should never silently break application behavior.data-*is exclusively the mechanism JavaScript uses to read parameters and select elements from the DOM. A script-added class (used, for example, to trigger a CSS transition from JavaScript) SHOULD be prefixedis-orhas-(is-open,has-error) to visually distinguish a script-controlled state class from an ordinary styling class in the stylesheet.
The type attribute on <script> MAY be omitted for a standard JavaScript module or classic script — the
default is correct in either case for a current browser; set it explicitly only for a non-executable use such
as type="text/template" (see Templates) or type="module" where module semantics (deferred execution,
import/export) are specifically needed.
Inline style attributes MUST NOT be used for static presentation — that is what a stylesheet is for. The
one accepted exception is a value that is genuinely computed at runtime by JavaScript, such as the live
position of an element during a drag-and-drop interaction, where the value has no meaningful static CSS
representation.
Progressive enhancement
A meaningful share of real-world traffic — historically estimated around 5% — either has JavaScript disabled, fails to load it, or runs a browser whose JavaScript engine cannot execute a modern bundle at all. A page’s core content MUST render as static HTML that does not depend on JavaScript having run, and interactive enhancements MUST be layered on top of that baseline, not required for it.
- A component that genuinely cannot function without JavaScript (a rich interactive widget with no static equivalent) SHOULD be rendered by JavaScript at runtime, rather than emitted as broken or non-functional markup by the server and then "fixed" once a script runs — a user without JavaScript then sees nothing, rather than seeing something that looks interactive but does not work.
- Prefer a CSS animation or transition over a JavaScript-driven one wherever the effect is achievable in CSS — see SVG animation for the same preference applied to SVG specifically.
- Bundled and minified JavaScript SHOULD be served with a long cache lifetime, the same as static images — see Serving images — since a content-hashed filename invalidates the cache automatically on change.
defer(for scripts that can run after parsing completes, in source order) andasync(for independent scripts that may run as soon as they are fetched, in arbitrary order) SHOULD be used in preference to the legacy pattern of placing an unattributed<script>at the very bottom of<body>to avoid blocking rendering —defer/asyncexpress the actual execution requirement directly, rather than relying on source position as a proxy for it.- Where the difference between "no JavaScript at all" and "JavaScript ran but failed, or the browser’s API
surface is incomplete" matters,
<noscript>alone only covers the first case. A short, feature-tested script placed early and run synchronously — checking for the specific APIs the page depends on (addEventListener,querySelector, and similar; this is the "cut the mustard" pattern) — is what should gate enhancement for the second case, showing an in-place message or falling back to the static baseline where the test fails. - No inline
<script>block containing application logic SHOULD appear in the page body — application JavaScript belongs in an external, cached, minified file. A short, synchronous script used purely to apply an early class or feature-test result before first paint is the accepted exception. - Event handlers MUST be attached with
addEventListener, never with an inline HTML event attribute (onclick,onload, and similar) — see JavaScript-driven links for the same rule applied to links specifically.
Data attributes
data- attributes are the *exclusive channel through which server-rendered HTML passes parameters to
client-side JavaScript.
- JavaScript MUST NOT select elements by
classoridas its primary query mechanism for behavior —data-*attributes (commonly paired with[data-component="…"]-style selectors) decouple styling hooks from behavior hooks, so a designer changing a class name does not silently break a script. - No parameter needed by a script MUST be passed via an inline
<script>block embedding values directly in the page — encode it as adata-*attribute on the relevant element instead, which keeps the parameter next to the markup it describes and inspectable in the DOM. - Server-rendered content is what kicks off client-side behavior — a script reads the already-rendered
markup and its
data-*attributes to initialize, rather than the server rendering an empty placeholder for JavaScript to fill entirely. This is the same principle as Progressive enhancement applied at the level of a single component. - Every enhancement MUST fail silently into the working static baseline if it errors — an uncaught JavaScript error in one component’s enhancement script MUST NOT break the page’s core content or any other component’s behavior.
Templates
<template> declares inert, non-rendered markup that can be cloned and injected into the document at
runtime via JavaScript — the standard mechanism for a client-side reusable markup fragment. Its content is
parsed but not rendered, not executed (scripts inside it do not run until cloned in), and not reachable by
DOM queries against the live document until explicitly cloned.
<template> is now widely supported and SHOULD be used for this purpose in new work. The older interim
pattern of storing a template inside a <script type="text/template"> block (relying on the browser not
executing an unrecognized script type, and reading its textContent) predates broad <template> support and
SHOULD NOT be used in new code — it is mentioned here only because it may still be encountered in legacy
code.
Built-in widgets
The web platform provides a small number of built-in interactive elements beyond form controls. <meter> (a
scalar value within a known range — disk usage, a rating) and <progress> (the completion progress of a
task) are both excluded from this standard’s Allowed elements list, on the grounds of inconsistent visual
rendering across browsers for a working-standard subset that favors elements a team can style predictably; a
custom component built from <div>`s with the equivalent ARIA role (see Other live-region components)
gives more consistent control. A project MAY choose to permit `<meter>/<progress> as an extension to this
standard where their native semantics and lower implementation cost outweigh that inconsistency for its own
use case.
<menu> and its associated context-menu behavior are similarly excluded — see Allowed elements — as
unsupported/inconsistent enough across current browsers to not be relied upon.
Content Security Policy
A Content Security Policy (CSP) restricts which sources a document may load scripts, styles, images, and
other resources from, and is a primary defense against cross-site scripting. A policy SHOULD be set via the
Content-Security-Policy HTTP response header where the server controls it, with the
<meta http-equiv="Content-Security-Policy"> tag (see Meta tags) as the fallback where it does not. See
TS-52: Security and secrets management for the security requirements a CSP forms part of;
this standard covers only where and how its HTML-level declaration is placed.
Semantics and metadata
Semantic markup
HTML’s job is to describe what content is, not how it looks. Semantic markup — choosing the element whose
meaning actually matches the content, <nav> over a <div> styled to look like navigation — matters because
the same markup then delivers that meaning correctly to every consumer, regardless of how they access the
page: a sighted user with CSS, a screen reader, a search engine crawler, a browser extension, or a future
tool that does not exist yet. A <div> styled to look exactly like a button conveys nothing to any of those
beyond the sighted-with-CSS case.
Two disciplines follow from this:
- Use elements only within the scope their specification defines them for — do not repurpose an element
because its default styling happens to be convenient (a
<blockquote>used purely to get an indent, with no actual quotation). - Do not reinvent markup the platform already provides. A custom dropdown built entirely from
<div>`s duplicates work `<select>already does, and starts from zero on every accessibility behavior<select>gets for free.
Presentational tags
The following elements describe presentation directly rather than meaning, and MUST NOT be used: big,
blink, center, font, marquee, strike, tt, nobr. Each is either formally deprecated or was never
standardized, and each has a semantic or CSS-based replacement.
<b>, <i>, <u>, and <small> warrant a specific note, because whether they are presentational depends
on how they are used. <u> is presentational in essentially all modern usage (it is easily confused with a
hyperlink) and is excluded from this standard’s Allowed elements list entirely. <b>, <i>, and
<small> are not excluded — each has a genuine semantic meaning distinct from <strong>/<em> and is
permitted under that meaning; see Inline text elements for exactly what that meaning is in each case.
Using any of the three purely to make text bold, italic, or smaller, with no semantic justification, remains
presentational misuse regardless of the element being technically permitted — reach for CSS instead.
Presentational attributes
border, align, valign, and clear are presentational HTML attributes with direct CSS equivalents, and
MUST NOT be used — a border attribute on <table> is CSS border; align/valign are CSS
text-align/vertical-align; clear is CSS clear. The style attribute itself follows the same rule set
out in Classes and data attributes: reserved for genuinely runtime-computed values, not static
presentation.
Redundancy between HTML, ARIA, and metadata
More than one mechanism sometimes expresses the same semantic — <nav>, role="navigation", and an RDFa
typeof="SiteNavigationElement" (see Schema.org) all mark navigation, and a document that used all three
for the same element would be marking it up three times over for the same one fact.
The rule this standard applies is: prefer native HTML semantics first; add an ARIA role only where HTML has no native element or attribute that already expresses the same meaning; add a metadata vocabulary (Schema.org, Microformats) only for the machine-readable, structured-data facts that neither HTML nor ARIA express at all (a product’s price, a recipe’s cook time). Do not stack a redundant ARIA role onto an element whose tag name already implies that role — see Redundant ARIA for the specific mappings this produces.
Browser support policy
This standard’s Allowed elements list, and the individual exclusions called out throughout (<dialog>,
<meter>/<progress> as noted in Built-in widgets, SMIL animation, image maps as the recommended
default), follow one consistent policy: an element or feature is excluded, or only conditionally permitted,
where its rendering or behavior is not consistent enough across this project’s target browsers to be relied
upon without a fallback. This is a moving target — a feature excluded here today may earn broad enough
support to be added later, and a project extending this standard for its own target-browser matrix SHOULD
revisit each exclusion against that matrix rather than assume the list is permanent.
Schema.org
Public pages MAY be extended with Schema.org structured data, expressed via RDFa Lite, which this standard
prefers over the Microdata serialization — RDFa Lite is more actively maintained as Schema.org’s own
recommended syntax and avoids Microdata’s need to repeat itemscope on every nested object. Do not mix the
two syntaxes on the same page.
RDFa Lite uses five attributes:
vocabDeclares the vocabulary in use, set once on
<body>—vocab="https://schema.org/".typeofDeclares an element’s Schema.org type —
typeof="Product",typeof="Article".propertyDeclares which Schema.org property an element’s content maps to —
property="name",property="price".resourcePoints a property at another resource by URI, where the value is a reference rather than inline content.
prefixDeclares an additional vocabulary prefix beyond the default, where more than one vocabulary is in use.
Markup SHOULD be validated against Google’s or Schema.org’s own structured-data validator before shipping,
since a subtly malformed RDFa attribute fails silently — the page still renders normally, but the structured
data is not picked up. Do not duplicate a Schema.org typeof/property pair for information already
conveyed by a native HTML sectioning element — <main>, <nav>, <header>, <aside> — where an equivalent
Schema.org type exists (see Redundancy between HTML, ARIA, and
metadata).
Microformats
Microformats are a small set of rel values conveying link-relationship semantics, distinct from Schema.org’s
structured-object semantics. The ones in regular production use:
rel="nofollow"Signals a link the page does not want to vouch for or pass ranking signal to — a link in user-submitted comments, a paid or sponsored link (paired with
rel="sponsored"), or any link the page does not editorially endorse.rel="license"Points to the license governing the linked-to content.
rel="tag"Marks a link as a tag/category label for the current page.
Prefer Schema.org for structural, object-shaped information (a product, an article, a recipe) and Microformats only for these link-relationship semantics — the two are not interchangeable, and Schema.org has no equivalent for "this specific link is unendorsed."
Social graphs
The Open Graph protocol (<meta property="og:*">) controls how a page renders when shared on Facebook and
several other platforms that adopted the same tags — title, description, and preview image, independent of
the page’s own <title>/description meta values. Adopt it where social sharing is a meaningful traffic
channel for the product; it is not REQUIRED for every page, only for those a team expects to be shared.
Internationalization
The root <html> element MUST declare both lang and dir, always paired: <html lang="en" dir="ltr">.
lang values SHOULD follow RFC 4646 — an ISO 639-1 language code, optionally combined with an ISO 3166-1
alpha-2 region (en-GB, en-US) where the distinction is meaningful to the content (regional spelling, date
formats, currency). Prefer the culture-specific locale form (en-GB) over the bare language code where the
distinction genuinely matters to the content’s audience.
dir sets the base text direction (ltr or rtl) for the whole document, and is not purely
presentational — several CSS layout properties (margin-inline-start, and similar logical properties) key
off it, so dir functions as a real layout toggle, not just a text-rendering hint.
Two elements handle direction at a finer grain than the document root:
<bdo>Explicitly overrides the bidirectional text algorithm’s default direction for its content — for a short run of text that must render in the opposite direction from its surrounding context regardless of what the algorithm would otherwise infer.
<bdi>Isolates a run of text whose direction is not known in advance (typically user-generated content — a name, a username) so its direction does not leak into and disrupt the surrounding text’s layout.
dir="auto"on the element itself is the fallback where the content’s own direction should be detected automatically rather than isolated.
Accessibility
Keyboard navigation
Keyboard operability is the cornerstone of accessible interaction: every interactive element MUST be fully operable from the keyboard alone, with no functionality that depends on a mouse, touch, or hover.
- Native interactive elements (
<a href>,<button>, form controls) are keyboard-operable by default — this is one of the strongest reasons to prefer them over a custom-built equivalent, per Semantic markup. tabindex="0"adds a non-natively-focusable element to the natural tab order, at its position in the DOM.tabindex="-1"removes an element from the tab order while still allowing it to receive focus programmatically (via.focus()) — used for a container that needs to receive focus on open (a modal, per Modals and focus management) without itself being tab-reachable.- A positive
tabindex(tabindex="1",tabindex="2", and so on) overrides source order with an explicit numeric order, and SHOULD be avoided — it is difficult to maintain consistently as a page evolves, and a page relying on it usually indicates the DOM order itself should be fixed instead. Where a positivetabindexis genuinely unavoidable, use large, widely spaced increments (100, 200, 300) so a later insertion has room to fit in sequence without renumbering everything after it. - Every custom-built interactive component (see Custom input controls, Custom buttons) MUST be fully operable by keyboard, replicating the keyboard behavior a native equivalent would have had.
Modals and focus management
Opening a modal dialog MUST move focus into the modal — typically to its first focusable element or its heading — and background navigation MUST be disabled for the duration: focus MUST be trapped within the modal’s content (Tab and Shift+Tab cycle only among the modal’s own focusable elements) rather than escaping to page content the user cannot currently see or reach. The modal MUST be closable via the Escape key as well as an explicit close control, and closing it MUST return focus to the element that triggered it, so a keyboard user’s position in the page is preserved across the interaction.
Accesskey
accesskey binds a keyboard shortcut directly to an element, but is a suggestion the browser is free to
override, commonly collides with the operating system’s or the assistive technology’s own shortcuts, and has
no good answer for internationalization (a shortcut letter chosen for an English word may be meaningless or
already taken in another locale). accesskey SHOULD NOT be used; where a project wants keyboard shortcuts,
implement them with JavaScript keydown listeners instead, and surface the available shortcuts to the user
through the interface rather than relying on `accesskey’s own (poorly supported) advertisement mechanism.
Visibility
The hidden attribute (see Coding style) removes an element from both rendering and the accessibility
tree — appropriate for content that is "not currently relevant," full stop, for every consumer alike.
Hiding content from some clients but not others — the common case of a tabbed interface, where every panel’s markup exists but only one is visually shown at a time — needs case-by-case judgment rather than one universal technique:
- Content hidden only visually, but still meant to be discoverable by a screen reader (a "skip to content"
link, for example), MUST NOT use
display: noneorvisibility: hidden— both remove the content from the accessibility tree as well as the visual rendering. The standard screen-reader-only technique instead clips the element to a 1px box, positions it absolutely, and hides overflow, while leaving it in normal document flow and unaffected bydisplay/visibility. - An inactive tab panel, conversely, generally SHOULD be hidden from both sighted and assistive-technology
users simultaneously (with
hiddenor an equivalent), since its content genuinely is not part of the current view for anyone — see Tabs and accordions for the ARIA states that accompany this. - Utility classes for "visible only in print" or "announced only by a screen reader" content are a reasonable pattern for expressing these distinct visibility intents consistently across a codebase.
WAI-ARIA
WAI-ARIA supplies roles, states, and properties for conveying semantics that HTML alone does not express — required for custom, JavaScript-driven widgets (tabs, trees, tooltips, custom dialogs) that have no native HTML equivalent. Prefer a native HTML element wherever one exists — see Semantic markup — and reach for ARIA only for the gap that remains. Some ARIA usage that was once necessary has since been superseded by native HTML additions (sectioning elements superseding several landmark roles, for example — see Landmark roles); where a native alternative now exists, prefer it over the ARIA role that used to be the only option.
Redundant ARIA
An ARIA role or state MUST NOT be added where the element’s native semantics already convey the exact same thing — the redundant declaration adds no information and is one more place for markup to drift out of sync with reality as the code evolves:
Redundant | Because |
|---|---|
|
|
|
|
|
|
| The native |
| The native |
role="presentation" (removing an element’s default semantics rather than adding to them) is the inverse
case, and has its own caveat: applying it to an element that has focusable descendants can strip semantics
those descendants still need, so it MUST only be applied where the whole subtree is genuinely
presentation-only.
ARIA roles
The roles below cover the components most commonly built without a native HTML equivalent. Each is
introduced in the section covering the component it applies to, rather than repeated here as a flat list:
alert/alertdialog/dialog (Alerts and dialogs), tooltip (Tooltips), button/toolbar
(Custom buttons), checkbox/radio/radiogroup/textbox/combobox/listbox/spinbutton/slider
(Custom input controls), grid/gridcell/row/columnheader (Grids),
tab/tablist/tabpanel (Tabs and accordions), tree/treeitem/treegrid (Trees),
menu/menubar/menuitem/menuitemcheckbox/menuitemradio (Menus), search (Search),
progressbar/scrollbar/status/timer/marquee/log (Other live-region components), and the
landmark roles (Landmark roles).
ARIA states and properties
Beyond roles, ARIA defines aria-* attributes for states and properties that change dynamically as the user
interacts with a component — whether something is expanded, checked, selected, or invalid. Every ARIA state
that a component’s interaction changes at runtime MUST be updated by the same script that changes the
visible state, so the two never drift apart — a visually expanded panel with aria-expanded="false" still
attached is worse than not having the attribute at all, because it actively reports the wrong state.
Live regions
aria-live marks a region whose content changes are announced to assistive technology as they happen,
without the user having to move focus to notice them — a form validation summary, a "message sent"
confirmation, a live-updating status count.
aria-live="polite"announces the update after the screen reader finishes whatever it is currently reading — the correct default for most updates.aria-live="assertive"interrupts immediately, and SHOULD be reserved for genuinely time-critical updates (an error that blocks progress) — overuse ofassertiveis disruptive in the same way an unexpected interruption is disruptive to a sighted user.
Applications versus documents
role="application" tells assistive technology to hand off most of its own keyboard shortcut handling to
the page’s own JavaScript, and role="document" is its inverse, restoring standard document reading
behavior within a region that would otherwise inherit application. Both are last-resort tools:
role="application" in particular disables assistive-technology behavior users rely on throughout the rest
of the page, and SHOULD only be applied to a component that is genuinely a full interactive application in
miniature (an embedded spreadsheet or code editor), not to an ordinary interactive widget that the specific
roles elsewhere in this section already cover.
Presentational and separator roles
role="presentation" is covered in Redundant ARIA above. role="separator" marks a purely visual
divider that also carries semantic meaning as a boundary (distinct from `<hr>’s thematic-break meaning
within content — see Horizontal rules) — for example, a divider between groups of items in a toolbar or
menu, where the divider itself is not interactive.
Labelling and describing
Three mechanisms attach an accessible name or description to an element, each suited to a different case:
aria-labelledbyReferences the
idof one or more other elements whose text content becomes the accessible name. Preferred where the label text already exists visibly elsewhere on the page — referencing it avoids duplicating the string.aria-describedbyReferences the
idof one or more elements providing a longer supplementary description, distinct from the name — a form field’s associated hint text, for example. Unlikearia-labelledby, a singlearia-describedbytarget is commonly reused by more than one element.aria-labelSets an accessible name directly as a string, with no visible on-page counterpart. Use it only where no visible label exists to reference — an icon-only button with no visible text, for example — since a programmatically supplied label with no visible equivalent cannot be proofread or updated by anyone not editing the markup directly.
Tooltips
There is no native HTML tooltip element. A custom tooltip MUST use role="tooltip" paired with
aria-describedby on the element it describes, and MUST be reachable and dismissible by keyboard: it MUST
appear on focus (not only on hover) and disappear on blur, and MUST be dismissible with Escape without
moving focus away from the element it is attached to.
Alerts and dialogs
role="alert" announces a message immediately and assertively, without requiring focus to move to it —
appropriate for a brief, self-contained notification. role="alertdialog" and role="dialog" are both used
for a modal that requires user interaction to dismiss; alertdialog for one communicating an urgent message
the user must acknowledge, dialog for a general-purpose modal. Either kind MUST move focus into the dialog
on open and return it to the triggering element on close, per Modals and focus management.
Custom buttons
Where a custom-built button-like control cannot use <button> directly (rare, but occurring inside some
composite widgets), it MUST carry role="button", respond to both the Spacebar and Enter keys as activation
triggers, and be in the tab order (tabindex="0"). A toggle button additionally exposes its pressed state
via aria-pressed; a button that controls a separate, identifiable region of content exposes that
relationship via aria-controls. A group of related buttons (a toolbar of formatting controls, for example)
SHOULD be wrapped in a container with role="toolbar".
Forms accessibility
Beyond the native <label>/for association covered in
Labels, fieldsets, and legends:
aria-invalid="true"MUST be set on a field currently failing validation, so assistive technology announces the invalid state at the point of the field, not only in a summary elsewhere on the page.aria-required="true"is needed only where a project has implemented its own required-field validation instead of the nativerequiredattribute (per Validation and input attributes's preference for custom validation) — whererequiredis used directly, it already conveys this andaria-requiredwould be redundant, per Redundant ARIA.aria-describedbyassociates a field with its own hint or error text, the same mechanism as Labelling and describing applied to a form context.aria-labelledbycovers a custom label construction that is not a simple<label for>pairing — a composite label built from more than one text node, for example.
Search
role="search" marks a region of the page as a search feature — a search form plus its associated
controls — for a component more complex than a single <input type="search">, giving assistive technology a
landmark to jump directly to it.
Custom input controls
A custom-built input control (built where no native HTML control fits, or where a project deliberately restyles beyond what native controls allow) MUST replicate both the ARIA role and the keyboard behavior of its native counterpart:
Control | Role | Keyboard |
|---|---|---|
Checkbox |
| Spacebar toggles. |
Radio group |
| Arrow keys move selection within the group; only the selected item is in the tab order. |
Text box |
| Standard text-editing keys; no special handling beyond native input behavior. |
Combobox |
| Down arrow opens the list; arrow keys move through options; Enter selects; Escape closes. |
Listbox |
| Arrow keys move the active option; Enter or Space selects it. |
Spin button |
| Up/Down arrows increment/decrement the value. |
Slider |
| Arrow keys adjust the value; Home/End jump to the min/max. |
aria-readonly marks a control that is visible and focusable but not
editable, distinct from disabled, which removes it from the tab order
entirely.
Drag-and-drop
An accessible drag-and-drop interaction MUST provide a keyboard-operable
equivalent — dragging is inherently a pointer-only gesture, so a
keyboard-only or switch-device user needs an alternative path to the same
outcome (commonly, a "grab" action on Enter/Space, arrow keys to move the
grabbed item, and Enter/Space again to drop). aria-grabbed marks an
item currently picked up, and aria-dropeffect marks a valid drop target,
on the elements participating in the interaction. Where the interaction is
naturally a reordering within a list or grid, the relevant grid roles (see
Grids) combined with keyboard reordering are often a more robust
pattern than raw drag-and-drop roles.
Grids
The grid, gridcell, row, and columnheader roles describe a
table-like interactive structure with cell-level keyboard navigation — a
spreadsheet-style component, for example — and are unrelated to CSS
Grid, which is a purely visual layout mechanism with no bearing on ARIA
semantics. Reach for the grid roles only when building an interactive,
cell-navigable widget; an ordinary data table uses <table> directly, per
Tables.
Tabs and accordions
A tabbed interface uses tablist (the container), tab (each clickable
tab), and tabpanel (each content panel) roles. aria-selected marks
the currently active tab; only the active panel’s content is visible at
once, per Visibility. An accordion is the same pattern rendered
vertically, with aria-expanded on each header controlling its own
panel rather than one panel at a time being exclusive.
Trees
tree, paired with treeitem for each node, describes a hierarchical
navigation structure — a file/directory browser, a nested category list.
treegrid extends the same pattern where each row additionally has
multiple columns of data, combining tree semantics with the grid roles
above.
Menus
menu, menubar, menuitem, menuitemcheckbox, and menuitemradio
describe an application-style menu system (a desktop-application menu
bar, replicated in a web app). For a website’s ordinary navigation menu,
prefer a plain <ul> of links inside <nav> instead — see
Source order and document structure — which is both simpler and better
understood by assistive technology than the menu role family, which was
designed for application menus, not site navigation.
Other live-region components
progressbar, scrollbar, status, timer, marquee, and log cover
further specialized live-updating components — a progress indicator, a
custom scrollbar, a general status message region, a countdown, a
scrolling ticker, and an append-only log respectively. Each is a
narrower, more specific variant of the live-region pattern introduced in
Live regions above, for the specific kind of update it names.
Landmark roles
Most ARIA landmark roles are now superseded by the HTML sectioning
elements in Source order and document structure — <nav>,
<main>, <aside>, and <footer> already imply the navigation,
main, complementary, and contentinfo roles respectively, so adding
the role explicitly is redundant, per Redundant ARIA. role="banner"
and role="contentinfo" remain useful specifically where a project is not
yet in a position to use <header>/<footer> at the page’s outermost
level, or where more than one <header>/<footer> exists and only one
is the page’s banner or contentinfo, which needs distinguishing.
role="heading" SHOULD NOT be used — a heading is always better expressed
with a native <h1>–<h3> element, per Headings.
Best practices
role="navigation"(or<nav>) MUST NOT be nested inside anothernavigationlandmark — nest a<nav>for a sub-menu inside the outer `<nav>’s content without repeating the landmark role.role="navigation"MUST NOT be applied to an individual link — a landmark is a region, not a single interactive element.- A
navigationlandmark SHOULD contain more than one link — a region wrapping a single link gains nothing from being marked as a navigation landmark. - An element MUST NOT be labelled twice by conflicting mechanisms (a
visible
<label>plus a contradictoryaria-label) — where both are present,aria-labelsilently overrides the visible label for assistive technology, which then announces something different from what a sighted user reads. - A document SHOULD have exactly one
mainlandmark (one<main>element).
Keyboard interaction patterns
Beyond the per-control patterns already given in Custom input controls, two composite widgets have their own conventional keyboard patterns:
- Tabs — Left/Right arrow (or Up/Down, for a vertically oriented tablist) moves between tabs; Home/End jump to the first/last tab.
- Trees — Down/Up arrow moves between visible nodes; Right expands a collapsed node or moves into its first child if already expanded; Left collapses an expanded node or moves to its parent if already collapsed; Enter activates the focused node; Home/End jump to the first/last visible node.
Testing
Accessibility MUST be verified by more than automated tooling alone — automated checks catch missing attributes and invalid ARIA but cannot verify that a keyboard interaction actually behaves correctly or that a screen reader announces something sensibly:
- Keyboard-only testing. Unplug the mouse and operate the whole flow being tested using only Tab, Shift+Tab, Enter, Space, and arrow keys.
- One-handed / adverse-conditions mobile testing. Test touch interactions one-handed and, where practical, in a genuinely distracting environment (walking, in bright sunlight) — conditions a meaningful share of real usage happens under.
- An OS screen reader. Test with at least one platform screen reader (VoiceOver, NVDA, or JAWS) actually enabled, not only against automated ARIA-validity checks.
- Real users with disabilities, where feasible, remain the most reliable check of all — automated and expert testing both approximate what actual usage reveals.
Accessibility checklist
The checklist below is organized under WCAG 2.2’s four core principles — Perceivable, Operable, Understandable, and Robust — as a concrete, HTML-level companion to the guidance above, targeting WCAG 2.2 Level AA. It restates specific testable requirements; it does not repeat the rationale already given in the sections above for each.
Perceivable — text alternatives
- Every
<img>has appropriatealttext — see The img element — distinguishing informative images (a description of content) from decorative ones (alt=""). - Every
<button>and icon-only control has an accessible name (visible text, oraria-labelper Labelling and describing). - Every form input has an associated label — see Labels, fieldsets, and legends.
- Non-text media (audio, video) has a text equivalent — see Perceivable — time-based media below.
- Every
<iframe>has atitleattribute describing its content.
Perceivable — time-based media
- Pre-recorded audio and video content has a transcript.
- Video with spoken content has synchronized captions — see
Audio and video's
<track kind="subtitles">requirement. - Video conveying meaning visually that is not in the audio track (an on-screen action with no narration) has an audio description track or equivalent.
Perceivable — adaptable
- Source order is logical when read linearly, independent of CSS — see Source order and document structure.
- Semantic markup is used for headings, landmarks, and lists rather than purely visual styling.
- Sectioning elements or ARIA landmarks divide the page into identifiable regions.
- Tables are used only for genuinely tabular data — see Tables.
<fieldset>/<legend>groups related form fields — see Labels, fieldsets, and legends.- Typed inputs carry an appropriate
autocompletevalue where relevant — see Validation and input attributes. - No instruction relies solely on shape, size, visual position, or sound ("click the round button", "the beep means submit") to be understood.
- Content is not locked to a single orientation unless that orientation is essential to its function.
Perceivable — distinguishable
- Color is never the sole means of conveying information (a form error shown only as a red border, with no icon or text) — pair it with text or an icon.
- Text contrast meets at least 4.5:1 for normal text and 3:1 for large text and UI components, per WCAG 2.2 Level AA.
- Content remains usable at 200% browser zoom.
- Content reflows correctly at a 320px viewport width without requiring horizontal scrolling.
- Text remains readable when a user overrides line height, paragraph spacing, letter spacing, or word spacing via their own stylesheet (WCAG’s text-spacing criterion).
- Content revealed on hover or focus (a tooltip, a submenu) can be dismissed without moving the pointer, remains visible while the pointer is over it, and persists until dismissed or no longer relevant.
Operable — keyboard accessible
- Every interactive element is operable by keyboard alone — see Keyboard navigation.
- No
accesskeyconflicts with a common browser or assistive-technology shortcut — see Accesskey. - No keyboard trap exists anywhere in the page (a widget the user can Tab into but not back out of).
- Any custom keyboard shortcut can be turned off, remapped, or is only active while the relevant component has focus.
Operable — enough time
- Any time limit on completing an action can be turned off, adjusted, or extended by the user, unless the time limit is essential (a live auction, for example).
- Auto-updating or auto-moving content (a carousel, a ticker) can be paused, stopped, or hidden.
Operable — seizures
- Nothing on the page flashes more than three times per second.
Operable — navigable
- A "skip to main content" link is the first focusable element on the page.
- Every page has a descriptive, unique
<title>— see Title. - Focus order follows a logical, predictable sequence matching visual layout.
- Every link’s purpose is clear from its own text or its immediate surrounding context, without requiring the user to guess from destination alone.
- Two links with identical visible text that go to different destinations
are distinguishable by their surrounding context or an
aria-label/aria-describedbyaddition. - More than one way exists to locate a given page (navigation menu, search, sitemap) where the site has more than a handful of pages.
- Headings and labels describe their topic or purpose accurately.
- The current keyboard focus is always visibly indicated.
Operable — input modalities
- Every function that can be triggered by a complex or multi-point gesture also has a single-point activation alternative.
- Click/tap handlers fire on
click(oronmouseup/pointer-up equivalents), not onmousedown/pointer-down alone — activating on the down-event gives the user no chance to move away and cancel the action. - An element’s accessible name includes any visible text label it has, so voice-control software that targets elements by their visible text can find it.
- Any function requiring device motion (shaking, tilting) also has a conventional UI control as an alternative.
Understandable — readable
- The page’s primary language is declared on
<html lang>— see Internationalization. - Any passage in a different language from the page’s primary language
has its own
langattribute.
Understandable — predictable
- Moving keyboard focus to an element does not, by itself, trigger a context change (a navigation, a form submission) — a context change happens only on an explicit action like a click or an Enter press.
- Navigation is presented in the same relative order across pages that share it.
- Components with the same function are identified consistently across the site (the same icon and label for "search" everywhere, for example).
Understandable — input assistance
- Where an input has a required format, that format is stated in its label or adjacent hint text before the user submits, not only after an error.
- Validation errors are announced to assistive technology (via
aria-invalidandaria-describedby, per Forms accessibility) and identified in text, not only by a color change. - Error messages suggest the correction needed, not only that an error occurred.
- A legal, financial, or otherwise consequential submission (a payment, a data-deletion request) can be reviewed, corrected, or reversed before it takes irreversible effect.
Robust
- Markup parses without duplicate
idvalues or improperly nested elements — see Coding style. - ARIA roles, states, and properties are used according to their specification, matching the tables and patterns given earlier in this section.
- Status messages (a "saved" confirmation, a loading state) are programmatically determinable via a live region, per Live regions, without requiring a focus change to be noticed.
References
- National Cyber Security Centre (2019). Let Them Paste Passwords. — The source for the password-field paste requirement in Validation and input attributes.
- W3C Web Accessibility Initiative (2023). ARIA Authoring Practices Guide. — The primary source for the ARIA roles, states, and keyboard interaction patterns throughout Accessibility.
- W3C (2023). Web Content Accessibility Guidelines (WCAG) 2.2. — The source for the four-principle structure and testable criteria in Accessibility checklist.
- WHATWG (2024). HTML Living Standard. — The normative reference for every element and attribute named throughout this standard.
- Schema.org (2024). Getting Started with Schema.org. — The source for the RDFa Lite attributes and usage guidance in Schema.org.