Guias HTML
Aprenda como identificar e corrigir erros comuns de validação HTML sinalizados pelo W3C Validator — para que as suas páginas cumpram os padrões e sejam renderizadas corretamente em todos os navegadores. Consulte também o nosso Guias de acessibilidade.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The menuitem role represents an option in a set of choices and is only meaningful when it exists within the context of a menu. When a menuitem appears outside of a menu or menubar, screen readers and other assistive technologies have no way to determine that it belongs to a menu widget. They cannot announce the total number of items, provide keyboard navigation between items, or convey the menu’s hierarchical structure to the user.
This requirement follows the concept of required owned elements and required context roles in ARIA. Just as a <li> element belongs inside a <ul> or <ol>, a menuitem belongs inside a menu or menubar. The relationship can be established in two ways:
- DOM nesting — the menuitem element is a DOM descendant of the menu or menubar element.
- The aria-owns attribute — the menu or menubar element uses aria-owns to reference the menuitem by its id, establishing ownership even when the elements aren’t nested in the DOM.
It’s important to note that ARIA menu roles are intended for application-style menus — the kind you’d find in a desktop application (e.g., File, Edit, View menus). They are not meant for standard website navigation. For typical site navigation, use semantic HTML elements like <nav> with <ul>, <li>, and <a> elements instead.
How to fix it
- Identify every element with role="menuitem" in your markup.
- Ensure each one is contained within an element that has role="menu" or role="menubar", either through DOM nesting or via aria-owns.
-
Choose the correct parent role:
- Use role="menubar" for a persistent, typically horizontal menu bar (like a desktop application’s top-level menu).
- Use role="menu" for a popup or dropdown menu that contains a group of menu items.
- If you’re using menus for site navigation, consider removing the ARIA menu roles entirely and using semantic HTML (<nav>, <ul>, <li>, <a>) instead.
Examples
Incorrect — menuitem without a menu context
This triggers the validator error because the menuitem elements have no parent menu or menubar:
<div>
<div role="menuitem">Cut</div>
<div role="menuitem">Copy</div>
<div role="menuitem">Paste</div>
</div>
Correct — menuitem inside a menu
Wrapping the items in an element with role="menu" resolves the issue:
<div role="menu">
<div role="menuitem" tabindex="0">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
Correct — menuitem inside a menubar
For a persistent horizontal menu bar with application-style actions:
<div role="menubar">
<div role="menuitem" tabindex="0">File</div>
<div role="menuitem" tabindex="-1">Edit</div>
<div role="menuitem" tabindex="-1">View</div>
</div>
Correct — nested menus with dropdown submenus
A menubar with a dropdown menu containing additional menuitem elements:
<div role="menubar">
<div role="menuitem" tabindex="0" aria-haspopup="true" aria-expanded="false">
File
<div role="menu">
<div role="menuitem" tabindex="-1">New</div>
<div role="menuitem" tabindex="-1">Open</div>
<div role="menuitem" tabindex="-1">Save</div>
</div>
</div>
<div role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false">
Edit
<div role="menu">
<div role="menuitem" tabindex="-1">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
</div>
</div>
Correct — using aria-owns for ownership without DOM nesting
When the menuitem elements cannot be nested inside the menu in the DOM (e.g., due to layout constraints), use aria-owns to establish the relationship:
<div role="menu" aria-owns="item-cut item-copy item-paste"></div>
<div role="menuitem" id="item-cut" tabindex="0">Cut</div>
<div role="menuitem" id="item-copy" tabindex="-1">Copy</div>
<div role="menuitem" id="item-paste" tabindex="-1">Paste</div>
Better alternative — use semantic HTML for site navigation
If you’re building standard website navigation (not an application-style menu), avoid ARIA menu roles altogether:
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
This approach is simpler, more accessible by default, and doesn’t trigger the validator warning. Reserve role="menu", role="menubar", and role="menuitem" for true application-style menus that implement full keyboard interaction patterns as described in the ARIA Authoring Practices Guide.
The WAI-ARIA specification defines a strict ownership model for tab-related roles. An element with role="tab" controls the visibility of an associated role="tabpanel" element, and tabs are expected to be grouped within a tablist. This relationship is how assistive technologies like screen readers understand and communicate the tab interface pattern to users — for example, announcing “tab 2 of 4” when focus moves between tabs.
When a tab is not contained in or owned by a tablist, screen readers cannot determine how many tabs exist in the group, which tab is currently selected, or how to navigate between them. This fundamentally breaks the accessibility of the tab interface, making it confusing or unusable for people who rely on assistive technologies.
There are two ways to establish the required relationship:
- Direct containment: Place the role="tab" elements as direct children of the role="tablist" element. This is the most common and straightforward approach.
- Using aria-owns: If the DOM structure prevents direct nesting, add aria-owns to the tablist element with a space-separated list of id values referencing each tab. This tells assistive technologies that the tablist owns those tabs even though they aren’t direct children in the DOM.
Examples
Incorrect: tab outside of a tablist
In this example, the role="tab" buttons are siblings of the tablist rather than children of it, which triggers the validation error.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs"></div>
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Second Tab
</button>
</div>
Correct: tabs as direct children of tablist
The simplest fix is to place the role="tab" elements directly inside the role="tablist" element.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Correct: using aria-owns when DOM nesting isn’t possible
If your layout or framework makes it difficult to nest the tabs directly inside the tablist, you can use aria-owns to establish the relationship programmatically.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs" aria-owns="tab-1 tab-2"></div>
<div class="tab-wrapper">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Additional notes
- Each role="tab" element should use aria-selected to indicate which tab is active ("true") and which are not ("false").
- Use aria-controls on each tab to reference the id of its associated tabpanel.
- Use aria-labelledby on each tabpanel to point back to its controlling tab.
- Set tabindex="0" on the active tab and tabindex="-1" on inactive tabs to support keyboard navigation with arrow keys within the tablist.
- Always include an aria-label or aria-labelledby on the tablist to give it an accessible name.
The HTML specification defines strict rules about which elements can be nested inside others. The <a> element is classified as interactive content, and its content model explicitly forbids other interactive content as descendants. Elements with role="button" — whether a native <button> or any element with the ARIA role — are also interactive. Nesting one inside the other creates an ambiguous situation: should a click activate the link or the button?
This matters for several important reasons:
- Accessibility: Screen readers and assistive technologies rely on a clear, unambiguous element hierarchy. When a button is inside a link, the announced role becomes confusing — users may hear both a link and a button, or the assistive technology may only expose one of them, hiding the other’s functionality.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Some may split the elements apart in the DOM, while others may swallow click events. This leads to broken or unreliable behavior across different browsers.
- Standards compliance: The WHATWG HTML Living Standard and W3C HTML specification both explicitly prohibit this nesting pattern.
To fix this issue, decide what the element should actually do. If it navigates to a URL, use an <a> element. If it performs an action (like submitting a form or triggering JavaScript), use a <button>. If you need both visual styles, use CSS to style one element to look like the other.
Examples
❌ Incorrect: Element with role="button" inside an <a>
<a href="/dashboard">
<span role="button">Go to Dashboard</span>
</a>
The <span> with role="button" is interactive content nested inside the interactive <a> element.
❌ Incorrect: <button> inside an <a>
<a href="/settings">
<button>Open Settings</button>
</a>
A <button> element is inherently interactive and must not appear inside an <a>.
✅ Correct: Use a link styled as a button
If the purpose is navigation, use the <a> element and style it with CSS:
<a href="/dashboard" class="btn">Go to Dashboard</a>
✅ Correct: Use a button that navigates via JavaScript
If you need a button that also navigates, handle navigation in JavaScript:
<button type="button" onclick="location.href='/dashboard'">Go to Dashboard</button>
✅ Correct: Place elements side by side
If you truly need both a link and a button, keep them as siblings rather than nesting one inside the other:
<div class="actions">
<a href="/dashboard">Go to Dashboard</a>
<button type="button">Save Preference</button>
</div>
✅ Correct: Link styled as a button using ARIA (when appropriate)
If the element navigates but you want assistive technologies to announce it as a button, you can apply role="button" directly to the element — just don’t nest it inside an <a>:
<span role="button" tabindex="0" onclick="location.href='/dashboard'">
Go to Dashboard
</span>
Note that using role="button" on a non-interactive element like <span> requires you to also add tabindex="0" for keyboard focusability and handle keydown events for the Enter and Space keys. In most cases, a native <a> or <button> element is the better choice.
When an element has role="button", assistive technologies treat it as a single interactive control — just like a native <button>. Users expect to tab to it once, and then activate it with Enter or Space. If a focusable descendant (an element with tabindex) exists inside that button, it creates a second tab stop within what should be a single control. This breaks the expected interaction model and confuses both keyboard users and screen readers.
The WAI-ARIA specification explicitly states that certain roles, including button, must not contain interactive or focusable descendants. This is because a button is an atomic widget — it represents one action and should receive focus as a single unit. When a screen reader encounters a role="button" element, it announces it as a button and expects the user to interact with it directly. A nested focusable element disrupts this by creating an ambiguous focus target: should the user interact with the outer button or the inner focusable element?
This issue commonly arises when developers wrap inline elements like <span> or <a> with tabindex inside a <div role="button">, often to style parts of the button differently or to add click handlers. The correct approach is to ensure only the outermost button-like element is focusable.
How to fix it
-
Use a native <button> element. This is always the best solution. Native buttons handle focus, keyboard interaction (Enter and Space key activation), and accessibility announcements automatically — no role or tabindex needed.
-
Move tabindex to the role="button" container. If you must use role="button" (for example, when a <div> needs to behave as a button due to design constraints), place tabindex="0" on the container itself and remove tabindex from all descendants.
-
Remove tabindex from descendants. If the inner element doesn’t actually need to be independently focusable, simply remove the tabindex attribute from it.
When using role="button" on a non-interactive element, remember you also need to implement keyboard event handlers for Enter and Space to fully replicate native button behavior.
Examples
Incorrect: focusable descendant inside role="button"
<div role="button">
<span tabindex="0">Click me</span>
</div>
The <span> with tabindex="0" creates a focusable element inside the role="button" container, which violates the ARIA authoring rules.
Incorrect: anchor element inside role="button"
<div role="button" tabindex="0">
<a href="/action" tabindex="0">Perform action</a>
</div>
Even though the container itself is focusable, the nested <a> with tabindex is also focusable, creating two tab stops for what should be a single control.
Correct: use a native <button> element
<button>Click me</button>
A native <button> handles focus, keyboard events, and accessibility semantics out of the box with no additional attributes.
Correct: move tabindex to the role="button" container
<div role="button" tabindex="0">
<span>Click me</span>
</div>
The tabindex="0" is on the role="button" element itself, and the inner <span> is not independently focusable.
Correct: native button with styled inner content
<button>
<span class="icon">★</span>
<span class="label">Favorite</span>
</button>
You can still use inner elements for styling purposes inside a <button> — just don’t add tabindex to them. The button manages focus as a single unit, and screen readers announce the combined text content.
The <a> element is an interactive content element — it’s already focusable and keyboard-navigable by default. When you place an element with a tabindex attribute inside a link, you create a nested focus target. This means that keyboard users and screen readers encounter two (or more) focusable items where only one is expected. The browser may not handle this consistently, and the user experience becomes unpredictable: should pressing Enter activate the link, or the inner focusable element?
The HTML specification defines that certain interactive elements must not be nested within other interactive elements. An <a> element’s content model explicitly forbids interactive content as descendants. Adding tabindex to any element makes it interactive (focusable), which violates this rule.
This matters for several reasons:
- Accessibility: Screen readers may announce nested focusable elements in confusing ways, or skip them entirely. Users relying on keyboard navigation may get trapped or confused by unexpected tab stops inside a link.
- Standards compliance: The W3C validator flags this as an error because it violates the HTML content model for anchor elements.
- Browser inconsistency: Different browsers may handle nested focusable elements differently, leading to unpredictable behavior across platforms.
To fix this issue, you have a few options:
- Remove the tabindex attribute from the descendant element if it doesn’t need to be independently focusable.
- Restructure your markup so the focusable element is a sibling of the <a> element rather than a descendant.
- Rethink the design — if you need multiple interactive targets in the same area, consider using separate elements styled to appear as a single unit.
Examples
❌ Invalid: Element with tabindex inside an <a> element
<a href="/products">
<div tabindex="0">
<h2>Our Products</h2>
<p>Browse our full catalog</p>
</div>
</a>
The <div> has tabindex="0", making it focusable inside an already-focusable <a> element. This creates conflicting focus targets.
✅ Fixed: Remove tabindex from the descendant
<a href="/products">
<div>
<h2>Our Products</h2>
<p>Browse our full catalog</p>
</div>
</a>
Since the <a> element is already focusable and clickable, the inner <div> doesn’t need its own tabindex. Removing it resolves the conflict.
❌ Invalid: span with tabindex inside a link
<a href="/profile">
<span tabindex="-1">User Name</span>
</a>
Even tabindex="-1" (which removes the element from the natural tab order but still makes it programmatically focusable) triggers this validation error when used inside an <a> element.
✅ Fixed: Remove tabindex from the span
<a href="/profile">
<span>User Name</span>
</a>
❌ Invalid: Button-like element nested inside a link
<a href="/dashboard">
<div tabindex="0" role="button">Settings</div>
</a>
✅ Fixed: Separate the interactive elements
<div class="card-actions">
<a href="/dashboard">Dashboard</a>
<button type="button">Settings</button>
</div>
If you truly need two distinct interactive elements, place them as siblings rather than nesting one inside the other. Use CSS to style them as a cohesive visual unit if needed.
The HTML specification defines button as an interactive content element that accepts phrasing content as its children, but explicitly forbids interactive content as descendants. When you add a tabindex attribute to an element, you make it focusable and potentially interactive, which violates this content model restriction.
This rule exists for important reasons. A button element is a single interactive control — when a user presses Tab, the entire button receives focus as one unit. If elements inside the button also have tabindex, screen readers and keyboard users encounter nested focusable items within what should be a single action target. This creates confusing, unpredictable behavior: users may tab into the button’s internals without understanding the context, and assistive technologies may announce the inner elements separately, breaking the expected interaction pattern.
Browsers may also handle nested focusable elements inconsistently. Some may ignore the inner tabindex, while others may allow focus on the nested element but not properly trigger the button’s click handler, leading to broken functionality.
How to fix it
The most straightforward fix is to remove the tabindex attribute from any elements inside the button. If the inner element was given tabindex="0" to make it focusable, it doesn’t need it — the button itself is already focusable. If it was given tabindex="-1" to programmatically manage focus, reconsider whether that focus management is necessary within a button context.
If you genuinely need multiple interactive elements in the same area, restructure your markup so that the interactive elements are siblings rather than nested inside a button.
Examples
❌ Incorrect: span with tabindex inside a button
<button type="button">
<span tabindex="0">Click me</span>
</button>
The span has tabindex="0", making it a focusable descendant of the button. This violates the content model.
✅ Correct: Remove tabindex from the descendant
<button type="button">
<span>Click me</span>
</button>
The span no longer has tabindex, so the button behaves as a single focusable control.
❌ Incorrect: Multiple focusable elements inside a button
<button type="button">
<span tabindex="0" class="icon">★</span>
<span tabindex="-1" class="label">Favorite</span>
</button>
Both inner span elements have tabindex attributes, which is invalid regardless of the tabindex value.
✅ Correct: Style inner elements without making them focusable
<button type="button">
<span class="icon">★</span>
<span class="label">Favorite</span>
</button>
✅ Correct: Restructure if separate interactions are needed
If you need an icon and a separate action side by side, use sibling elements instead of nesting:
<span class="icon" aria-hidden="true">★</span>
<button type="button">Favorite</button>
This keeps the button as a clean, single interactive element while placing the decorative icon outside of it.
The id attribute uniquely identifies an element within a document, making it essential for fragment links, JavaScript targeting, CSS styling, and label associations. According to the HTML specification, an id value must contain at least one character and must not contain any ASCII whitespace. This means spaces, tabs, line feeds, carriage returns, and form feeds are all prohibited.
A common mistake is accidentally including a space in an id value, which effectively makes it look like multiple values — similar to how the class attribute works. However, unlike class, the id attribute does not support space-separated values. When a browser encounters an id with a space, behavior becomes unpredictable: some browsers may treat only the first word as the ID, while CSS and JavaScript selectors may fail entirely.
Why this matters
- Broken fragment links: A link like <a href="#my section"> won’t correctly scroll to an element with id="my section" in all browsers.
- JavaScript failures: document.getElementById("my section") may not return the expected element, and document.querySelector("#my section") will throw a syntax error because spaces are not valid in CSS ID selectors without escaping.
- CSS issues: A selector like #my section targets an element with id="my" that has a descendant <section> element — not what you intended.
- Accessibility: Screen readers and assistive technologies rely on id attributes for label associations (e.g., <label for="...">). A broken id can break form accessibility.
Best practices for id values
While the HTML specification technically allows any non-whitespace character in an id, it’s best practice to stick to ASCII letters, digits, hyphens (-), and underscores (_). Starting the value with a letter also ensures maximum compatibility with CSS selectors without needing escaping.
Examples
❌ Invalid: id contains a space
<div id="main content">
<p>Welcome to the page.</p>
</div>
The space between main and content makes this an invalid id.
✅ Fixed: Replace the space with a hyphen
<div id="main-content">
<p>Welcome to the page.</p>
</div>
✅ Fixed: Replace the space with an underscore
<div id="main_content">
<p>Welcome to the page.</p>
</div>
✅ Fixed: Use camelCase
<div id="mainContent">
<p>Welcome to the page.</p>
</div>
❌ Invalid: id with a space breaks label association
<label for="first name">First Name</label>
<input type="text" id="first name">
The for attribute won’t properly associate with the input, breaking accessibility.
✅ Fixed: Remove whitespace from both for and id
<label for="first-name">First Name</label>
<input type="text" id="first-name">
❌ Invalid: Trailing or leading whitespace
Whitespace isn’t always obvious. A trailing space or a copy-paste error can introduce invisible whitespace:
<section id="about ">
<h2>About Us</h2>
</section>
✅ Fixed: Trim all whitespace
<section id="about">
<h2>About Us</h2>
</section>
If you’re generating id values dynamically (e.g., from a CMS or database), make sure to trim and sanitize the values by stripping whitespace and replacing spaces with hyphens or underscores before rendering them into HTML.
The alt attribute is one of the most important accessibility features in HTML. When a screen reader encounters an <img> element, it reads the alt text aloud so that visually impaired users understand the image’s content and purpose. Without it, screen readers may fall back to reading the file name (e.g., “DSC underscore 0042 dot jpeg”), which is meaningless and confusing. Search engines also use alt text to understand and index image content, so including it benefits SEO as well.
The HTML specification requires the alt attribute on all <img> elements, with only narrow exceptions (such as when the image’s role is explicitly overridden via certain ARIA attributes, or when the image is inside a <figure> with a <figcaption> that fully describes it—though even then, including alt is strongly recommended).
How to choose the right alt text
The value of the alt attribute depends on the image’s purpose:
- Informative images — Describe the content concisely. For example, a photo of a product should describe the product.
- Functional images — Describe the action or destination, not the image itself. For example, a search icon used as a button should have alt="Search", not alt="Magnifying glass".
- Decorative images — Use an empty alt attribute (alt=""). This tells screen readers to skip the image entirely. Do not omit the attribute—use an empty string.
- Complex images (charts, diagrams) — Provide a brief summary in alt and a longer description elsewhere on the page or via a link.
Examples
❌ Missing alt attribute
This triggers the W3C validation error:
<img src="photo.jpg">
A screen reader has no useful information to convey, and the validator flags this as an error.
✅ Informative image with descriptive alt
<img src="photo.jpg" alt="Person holding an orange tabby cat in a sunlit garden">
The alt text describes what the image shows, giving screen reader users meaningful context.
✅ Decorative image with empty alt
<img src="decorative-border.png" alt="">
When an image is purely decorative and adds no information, an empty alt attribute tells assistive technology to ignore it. This is valid and preferred over omitting the attribute.
✅ Functional image inside a link
<a href="/home">
<img src="logo.svg" alt="Acme Corp — Go to homepage">
</a>
Because the image is the only content inside the link, the alt text must describe the link’s purpose.
✅ Image inside a <figure> with <figcaption>
<figure>
<img src="chart.png" alt="Bar chart showing quarterly revenue growth from Q1 to Q4 2024">
<figcaption>Quarterly revenue growth for fiscal year 2024.</figcaption>
</figure>
Even when a <figcaption> is present, including a descriptive alt attribute is best practice. The alt should describe the image itself, while the <figcaption> provides additional context visible to all users.
Common mistakes to avoid
- Don’t start with “Image of” or “Picture of” — Screen readers already announce the element as an image. Write alt="Golden retriever playing fetch", not alt="Image of a golden retriever playing fetch".
- Don’t use the file name — alt="IMG_4392.jpg" is not helpful.
- Don’t duplicate surrounding text — If the text next to the image already describes it, use alt="" to avoid redundancy.
- Don’t omit alt thinking it’s optional — A missing alt attribute and alt="" are fundamentally different. Missing means the screen reader may announce the file path; empty means the screen reader intentionally skips the image.
An empty alt attribute on an img element is a deliberate signal that the image is purely decorative and carries no meaningful content. According to the WHATWG HTML specification, this maps the element to the “presentation” role in the accessibility tree, effectively hiding it from screen readers and other assistive technologies.
When you add a role attribute to an img with alt="", you’re sending contradictory instructions: the empty alt says “this image is decorative, ignore it,” while the role attribute says “this image has a specific semantic purpose.” Browsers and assistive technologies cannot reliably resolve this conflict, which can lead to confusing or inconsistent behavior for users who rely on screen readers.
This rule exists to enforce clarity in how images are exposed to the accessibility tree. If an image is truly decorative, it should have alt="" and no role. If an image serves a functional or semantic purpose — such as acting as a button, link, or illustration — it should have both a descriptive alt value and, if needed, an appropriate role.
How to fix it
You have two options depending on the image’s purpose:
-
The image is decorative: Remove the role attribute entirely. The empty alt attribute already communicates that the image should be ignored by assistive technologies.
-
The image is meaningful: Provide a descriptive alt value that explains the image’s purpose. If a specific role is genuinely needed, keep it alongside the non-empty alt.
Examples
❌ Incorrect: empty alt with a role attribute
<img src="icon.png" alt="" role="img">
<img src="banner.jpg" alt="" role="presentation">
Even role="presentation" is redundant and invalid here — the empty alt already implies presentational semantics.
✅ Correct: decorative image with no role
<img src="icon.png" alt="">
If the image is decorative, simply remove the role attribute. The empty alt is sufficient.
✅ Correct: meaningful image with a descriptive alt and a role
<img src="warning-icon.png" alt="Warning" role="img">
If the image conveys information, give it a descriptive alt value. The role="img" is typically unnecessary here since img elements already have an implicit role of img when alt is non-empty, but it is at least valid.
✅ Correct: meaningful image used in a specific context
<button>
<img src="search-icon.png" alt="Search">
</button>
Here the image has a descriptive alt and doesn’t need an explicit role because its purpose is conveyed through the alt text and its context within the button.
The autocomplete attribute tells the browser whether it can assist the user in filling out a form field, and if so, what type of data is expected. The generic values "on" and "off" control whether the browser should offer autofill suggestions to the user. Since type="hidden" inputs are never displayed and never receive direct user input, these values don’t apply — there’s no user interaction to assist with.
According to the HTML specification, hidden inputs can have an autocomplete attribute, but only with specific named autofill detail tokens (like "transaction-id" or "cc-number"). These tokens serve a programmatic purpose by providing hints about the semantic meaning of the hidden value, which can be useful for form processing. The generic "on" and "off" values, however, are explicitly disallowed because they only relate to the user-facing autofill behavior.
This validation error matters for standards compliance and can indicate a logical mistake in your markup. If you added autocomplete="off" to a hidden input hoping to prevent the browser from caching or modifying the value, it won’t have that effect. Hidden input values are controlled entirely by the server or by JavaScript, not by browser autofill.
How to fix it
- Remove the autocomplete attribute if it’s not needed — this is the most common fix.
- Use a specific autofill token if you need to convey semantic meaning about the hidden value (e.g., autocomplete="transaction-id").
- Reconsider the input type — if the field genuinely needs autofill behavior controlled, it probably shouldn’t be type="hidden".
Examples
Incorrect: using autocomplete="off" on a hidden input
<form action="/submit" method="post">
<input type="hidden" name="token" value="abc123" autocomplete="off">
<button type="submit">Submit</button>
</form>
Incorrect: using autocomplete="on" on a hidden input
<form action="/submit" method="post">
<input type="hidden" name="session-id" value="xyz789" autocomplete="on">
<button type="submit">Submit</button>
</form>
Correct: removing the autocomplete attribute
<form action="/submit" method="post">
<input type="hidden" name="token" value="abc123">
<button type="submit">Submit</button>
</form>
Correct: using a specific autofill token
If the hidden input carries a value with a well-defined autofill semantic, you can use a named token:
<form action="/checkout" method="post">
<input type="hidden" name="txn" value="TXN-001" autocomplete="transaction-id">
<button type="submit">Complete Purchase</button>
</form>
This is valid because "transaction-id" is a specific autofill detail token recognized by the specification, unlike the generic "on" or "off" values.
Hidden inputs are designed to carry data between the client and server without any user interaction or visual presence. The browser does not render them, screen readers do not announce them, and they are entirely excluded from the accessibility tree. Because aria-* attributes exist solely to convey information to assistive technologies, adding them to an element that assistive technologies cannot perceive is contradictory and meaningless.
The HTML specification explicitly prohibits aria-* attributes on input elements with type="hidden". This restriction exists because WAI-ARIA attributes — such as aria-label, aria-invalid, aria-describedby, aria-required, and all others in the aria-* family — are meant to enhance the accessible representation of interactive or visible elements. A hidden input has no such representation, so these attributes have nowhere to apply.
This issue commonly arises when:
- JavaScript frameworks or templating engines apply aria-* attributes indiscriminately to all form inputs, regardless of type.
- A developer changes an input’s type from "text" to "hidden" but forgets to remove the accessibility attributes that were relevant for the visible version.
- Form libraries or validation plugins automatically inject attributes like aria-invalid onto every input in a form.
To fix the issue, simply remove all aria-* attributes from any input element that has type="hidden". If the aria-* attribute was meaningful on a previously visible input, no replacement is needed — the hidden input doesn’t participate in the user experience at all.
Examples
Incorrect: hidden input with aria-invalid
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com" aria-invalid="false">
<button type="submit">Submit</button>
</form>
Correct: hidden input without aria-* attributes
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com">
<button type="submit">Submit</button>
</form>
Incorrect: hidden input with multiple aria-* attributes
<form action="/save" method="post">
<input
type="hidden"
name="session_token"
value="abc123"
aria-label="Session token"
aria-required="true"
aria-describedby="token-help">
<button type="submit">Save</button>
</form>
Correct: all aria-* attributes removed
<form action="/save" method="post">
<input type="hidden" name="session_token" value="abc123">
<button type="submit">Save</button>
</form>
Correct: aria-* attributes on a visible input (where they belong)
If the input is meant to be visible and accessible, use an appropriate type value instead of "hidden":
<form action="/login" method="post">
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
aria-required="true"
aria-invalid="false"
aria-describedby="username-help">
<p id="username-help">Enter your registered email or username.</p>
<button type="submit">Log in</button>
</form>
The label element represents a caption for a form control. There are two ways to associate a label with its control:
- Implicit association — Place the form control directly inside the label element. No for attribute is needed because the browser automatically pairs the label with the nested control.
- Explicit association — Use the for attribute on the label, setting its value to the id of the target form control. The control doesn’t need to be nested inside the label in this case.
Both methods are valid on their own. The problem occurs when you combine them incorrectly: you nest an input inside a label that has a for attribute, but the input either has no id or has an id that doesn’t match the for value. This creates a contradiction — the for attribute points to a specific id, yet the nested input doesn’t fulfill that reference. Browsers may handle this inconsistently, and assistive technologies like screen readers could fail to announce the label correctly, harming accessibility.
Why this matters
- Accessibility: Screen readers rely on the for/id pairing to announce labels for form controls. A mismatched or missing id can leave the control unlabeled for users who depend on assistive technology.
- Standards compliance: The HTML specification requires that when a for attribute is present, it must reference the id of a labelable element. A mismatch violates this rule.
- Browser behavior: Some browsers will fall back to the implicit association when for doesn’t resolve, but others may prioritize the broken explicit association, leaving the control effectively unlabeled.
How to fix it
You have two options:
- Remove the for attribute if the input is already nested inside the label. The implicit association is sufficient on its own.
- Add or correct the id on the nested input so it matches the for attribute value exactly.
Examples
❌ Nested input with no matching id
The for attribute says "email", but the input has no id at all:
<label for="email">
Email
<input type="email" name="email">
</label>
❌ Nested input with a mismatched id
The for attribute says "email", but the input‘s id is "user-email":
<label for="email">
Email
<input type="email" name="email" id="user-email">
</label>
✅ Fix by removing the for attribute (implicit association)
Since the input is nested inside the label, the association is automatic:
<label>
Email
<input type="email" name="email">
</label>
✅ Fix by adding a matching id (explicit association)
The for value and the id value are identical:
<label for="email">
Email
<input type="email" name="email" id="email">
</label>
✅ Fix by using explicit association without nesting
If you prefer to keep the for attribute, the input doesn’t need to be nested at all:
<label for="email">Email</label>
<input type="email" name="email" id="email">
In most cases, choosing either implicit or explicit association — rather than mixing both — is the simplest way to avoid this error. If you do combine them, just make sure the for value and the id value match exactly.
The label element associates a caption with a form control. There are two ways to create this association:
- Implicit association — Place the form control directly inside the label element. No for or id attributes are needed.
- Explicit association — Use the for attribute on the label, setting its value to the id of the associated form control.
Both methods work independently. The problem arises when you mix them incorrectly — nesting a select inside a label that has a for attribute, but the select either has no id or has an id that doesn’t match the for value. In this situation, the explicit association (via for) points to nothing or to the wrong element, while the implicit association (via nesting) still exists. This contradictory state violates the HTML specification and can cause assistive technologies like screen readers to misidentify or skip the label, reducing accessibility for users who rely on them.
The WHATWG HTML specification requires that when a for attribute is present on a label, it must reference a valid labelable element by id. If the form control is already nested inside the label, the for attribute is redundant — but if present, it still must correctly reference that control.
How to Fix
You have two options:
- Remove the for attribute — If the select is already inside the label, implicit association handles everything. This is the simplest fix.
- Add or correct the id — Keep the for attribute but ensure the select has a matching id.
Examples
❌ Incorrect: for attribute with no matching id
The for attribute references "age", but the select has no id at all:
<label for="age">
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: for attribute with a mismatched id
The for attribute references "age", but the select has a different id:
<label for="age">
Age
<select id="age-select">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 1: Remove the for attribute (implicit association)
Since the select is nested inside the label, implicit association is sufficient:
<label>
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 2: Match the for attribute with the id (explicit association)
Keep the for attribute and give the select a matching id:
<label for="age">
Age
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 3: Separate the label and select (explicit association only)
If you prefer to keep the select outside the label, explicit association with matching for and id is required:
<label for="age">Age</label>
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
In most cases, option 1 (removing the for attribute) is the cleanest solution when the control is already nested. Use explicit association when the label and control are in separate parts of the DOM, such as in complex table or grid layouts.
The <article> element is meant to wrap independent, self-contained content such as blog posts, news stories, forum posts, product cards, or user comments. The W3C validator raises this warning because an <article> without a heading has no programmatic label, making it harder for users—especially those relying on screen readers—to understand what the article is about and to navigate between multiple articles on a page.
Screen readers often present a list of landmarks and headings to help users jump through a page’s structure. When an <article> has no heading, it appears as an unlabeled region, which is confusing and defeats the purpose of using semantic HTML. A heading inside the <article> acts as its identifying title, giving both sighted users and assistive technology a clear summary of the content that follows.
This is a warning rather than a hard validation error, but it signals a real accessibility and usability concern. In nearly all cases, every <article> should contain a heading. The heading level you choose should fit logically within the document’s heading hierarchy—typically <h2> if the <article> sits directly under the page’s main <h1>, or <h3> if it’s nested inside a section that already uses <h2>.
How to fix it
- Add a heading element (<h2>–<h6>) as one of the first children inside each <article>.
- Choose the correct heading level based on the document outline. Don’t skip levels (e.g., jumping from <h1> to <h4>).
- Make the heading descriptive. It should clearly summarize the article’s content.
If your design doesn’t visually display a heading, you can use CSS to visually hide it while keeping it accessible to screen readers (see the examples below).
Examples
❌ Article without a heading (triggers the warning)
<h1>Latest news</h1>
<article>
<p>Our new product launched today with great success.</p>
</article>
<article>
<p>We are hiring frontend developers. Apply now!</p>
</article>
Each <article> here has no heading, so assistive technologies cannot identify them, and the validator raises a warning.
✅ Articles with proper headings
<h1>Latest news</h1>
<article>
<h2>Product launch a success</h2>
<p>Our new product launched today with great success.</p>
</article>
<article>
<h2>We're hiring frontend developers</h2>
<p>We are hiring frontend developers. Apply now!</p>
</article>
✅ Nested articles with correct heading hierarchy
<h1>Our blog</h1>
<article>
<h2>How to validate accessibility</h2>
<p>Use automated tools for an in-depth scan of your site.</p>
<section>
<h3>Comments</h3>
<article>
<h4>Comment by Alex</h4>
<p>Great article, very helpful!</p>
</article>
</section>
</article>
When articles are nested (e.g., comments inside a blog post), each level gets the next heading level to maintain a logical outline.
✅ Visually hidden heading for design flexibility
If your design doesn’t call for a visible heading but you still need one for accessibility, use a CSS utility class to hide it visually while keeping it in the accessibility tree:
<style>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<article>
<h2 class="visually-hidden">Featured promotion</h2>
<p>Save 20% on all items this weekend only!</p>
</article>
This approach satisfies both the validator and assistive technologies without affecting your visual layout. Avoid using display: none or visibility: hidden, as those also hide the heading from screen readers.
The HTML required attribute was introduced in HTML5 and tells both the browser and assistive technologies that a form field must be filled in before the form can be submitted. The aria-required attribute serves the same purpose but comes from the WAI-ARIA specification, which was designed to fill accessibility gaps in HTML. Now that required is widely supported, using both attributes on the same element is unnecessary duplication.
The W3C validator raises this warning because redundant ARIA attributes add noise to your markup without providing any benefit. One of the core principles of ARIA usage is the first rule of ARIA: if you can use a native HTML element or attribute that already has the semantics you need, use that instead of adding ARIA. The native required attribute provides built-in browser validation, visual cues, and accessibility information all at once — something aria-required alone cannot do.
Modern screen readers such as NVDA, JAWS, and VoiceOver all correctly interpret the native required attribute and announce the field as required to users. Keeping both attributes doesn’t cause a functional problem, but it creates maintenance overhead, clutters your code, and signals to other developers that something special might be going on when it isn’t.
How to fix it
- If your element already has the required attribute, remove aria-required="true".
- If your element only has aria-required="true" and you want browser-native validation, replace it with required.
- In rare cases where you need to support assistive technologies that don’t recognize the native required attribute (some very old screen reader versions), keeping both is a conscious trade-off — but for the vast majority of projects, the native attribute alone is sufficient.
Examples
❌ Redundant aria-required alongside required
<form action="/order">
<label for="city">City</label>
<input id="city" name="city" type="text" aria-required="true" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" aria-required="true" required>
<label for="comments">Comments</label>
<textarea id="comments" name="comments" aria-required="true" required></textarea>
<button type="submit">Submit</button>
</form>
This triggers the validator warning on every element where both attributes appear.
✅ Using the native required attribute only
<form action="/order">
<label for="city">City</label>
<input id="city" name="city" type="text" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="comments">Comments</label>
<textarea id="comments" name="comments" required></textarea>
<button type="submit">Submit</button>
</form>
✅ Using aria-required on a non-native element
There are valid use cases for aria-required — specifically when you build custom form controls that don’t use native form elements. In that scenario, aria-required is the correct choice because the native required attribute has no effect on non-form elements.
<div role="combobox" aria-required="true" aria-expanded="false" aria-labelledby="color-label">
<span id="color-label">Favorite color</span>
</div>
Here, aria-required="true" is necessary because a <div> doesn’t support the native required attribute.
The autocomplete attribute tells the browser whether and how to automatically fill in a form field based on previously entered data. It makes sense for fields where users type or select values from a predictable set — like names, emails, addresses, dates, and numbers. However, certain input types don’t produce text or numeric data that browsers could meaningfully store and recall. These include checkbox, radio, file, button, submit, reset, and image.
The HTML specification explicitly limits autocomplete to the following input types: color, date, datetime-local, email, hidden, month, number, password, range, search, tel, text, time, url, and week. Using it on any other type violates the spec and produces a validation error.
Why this matters
- Standards compliance: Browsers are not required to honor autocomplete on unsupported input types, so including it has no practical effect and produces invalid markup.
- Code clarity: Invalid attributes can confuse other developers reading your code, suggesting a behavior that doesn’t actually exist.
- Accessibility: Screen readers and assistive technologies rely on valid markup to accurately convey form behavior to users. Unexpected attributes can introduce ambiguity.
How to fix it
- Identify the input type that has the autocomplete attribute.
- If the type is checkbox, radio, file, button, submit, reset, or image, remove the autocomplete attribute.
- If you genuinely need autocomplete behavior, reconsider whether the correct input type is being used. For example, a field that should accept text input might have been incorrectly set to type="checkbox".
Examples
❌ Invalid: autocomplete on a checkbox
<form>
<label>
<input type="checkbox" name="terms" autocomplete="off"> I agree to the terms
</label>
</form>
The browser cannot meaningfully autocomplete a checkbox, so this attribute is not allowed here.
✅ Fixed: remove autocomplete from the checkbox
<form>
<label>
<input type="checkbox" name="terms"> I agree to the terms
</label>
</form>
❌ Invalid: autocomplete on radio buttons
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email" autocomplete="off"> Email
</label>
<label>
<input type="radio" name="contact" value="phone" autocomplete="off"> Phone
</label>
</fieldset>
</form>
✅ Fixed: remove autocomplete from radio buttons
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email"> Email
</label>
<label>
<input type="radio" name="contact" value="phone"> Phone
</label>
</fieldset>
</form>
❌ Invalid: autocomplete on a file input
<form>
<label for="resume">Upload resume:</label>
<input type="file" id="resume" name="resume" autocomplete="off">
</form>
✅ Fixed: remove autocomplete from the file input
<form>
<label for="resume">Upload resume:</label>
<input type="file" id="resume" name="resume">
</form>
✅ Valid: autocomplete on supported types
<form>
<label for="email">Email:</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="bday">Birthday:</label>
<input type="date" id="bday" name="bday" autocomplete="bday">
<label for="query">Search:</label>
<input type="search" id="query" name="query" autocomplete="off">
</form>
If you want to disable autocomplete for an entire form — including checkboxes and other non-text fields — set autocomplete="off" on the <form> element itself rather than on individual inputs that don’t support the attribute:
<form autocomplete="off">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</form>
The <meta charset="utf-8"> declaration is not only valid but recommended by the HTML living standard. So when the validator complains that the charset attribute is “not allowed at this point,” the problem isn’t the <meta> tag itself — it’s what surrounds it. The HTML parser follows strict rules about which elements can appear inside <head>. When it encounters an element that doesn’t belong there (like <img>, <div>, <p>, or other flow/phrasing content), it implicitly closes the <head> and opens the <body>. Any <meta> tags that come after that point are now parsed as being inside <body>, where <meta charset> is not permitted.
This is a problem for several reasons. First, the <meta charset> declaration must appear within the first 1024 bytes of the document so browsers can determine the character encoding early. If the parser moves it out of <head>, browsers may not apply the encoding correctly, potentially leading to garbled text — especially for non-ASCII characters. Second, this often signals a structural error in your HTML that could cause other unexpected rendering issues.
Common causes include:
- An element that only belongs in <body> (like <img>, <div>, <span>, or <p>) placed before <meta charset> in the <head>.
- A stray closing tag (like </head>) appearing too early.
- A <script> tag with content that causes the parser to break out of <head>.
To fix the issue, inspect the elements that appear before <meta charset> in your <head>. Move any elements that don’t belong in <head> into <body>, and place <meta charset="utf-8"> as the very first element inside <head>.
Examples
Incorrect — element before <meta> forces parser out of <head>
An <img> tag inside <head> causes the parser to implicitly close <head> and open <body>. The <meta charset> that follows is now parsed as being in <body>, triggering the error.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<img src="photo.jpg" alt="A smiling cat">
<meta charset="utf-8">
</head>
<body>
<p>Some content</p>
</body>
</html>
Correct — <meta charset> first, invalid elements moved to <body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<img src="photo.jpg" alt="A smiling cat">
<p>Some content</p>
</body>
</html>
Incorrect — stray <div> in <head> breaks context
<!DOCTYPE html>
<html lang="en">
<head>
<div>Oops</div>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello</p>
</body>
</html>
Correct — only valid head elements before <meta charset>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<div>Content goes here</div>
<p>Hello</p>
</body>
</html>
Best practice
As a general rule, always make <meta charset="utf-8"> the very first child of <head>. This ensures the browser detects the encoding as early as possible and avoids the risk of other elements accidentally breaking the parser context before the charset is declared.
The controlslist attribute was proposed to give developers a way to hint to the browser which default media controls to show or hide. It accepts values like nodownload, nofullscreen, and noremoteplayback, allowing you to selectively disable specific buttons in the browser’s built-in media player UI. For example, controlslist="nodownload" hides the download button on the video player.
However, this attribute was never adopted into the WHATWG HTML Living Standard or any W3C specification. It remains a Chromium-specific feature, meaning it only works in browsers like Chrome and Edge. Firefox, Safari, and other non-Chromium browsers simply ignore it. Because it’s not part of any standard, the W3C HTML Validator rightfully reports it as an invalid attribute.
While using controlslist won’t break your page — browsers that don’t recognize it will silently ignore it — relying on non-standard attributes has downsides:
- Standards compliance: Your HTML won’t validate, which can mask other real issues in validation reports.
- Browser compatibility: The behavior only works in Chromium-based browsers, giving an inconsistent experience across browsers.
- Future uncertainty: Non-standard attributes can be removed or changed without notice.
To fix this, you have a few options. The simplest is to remove the attribute entirely if the customization isn’t critical. If you need fine-grained control over media player buttons, the most robust approach is to build custom media controls using JavaScript and the HTMLMediaElement API. For the specific case of disabling remote playback, there is a standardized attribute — disableremoteplayback — that you can use instead.
Examples
❌ Invalid: Using the non-standard controlslist attribute
<video src="video.mp4" controls controlslist="nodownload nofullscreen"></video>
The validator will report: Attribute “controlslist” not allowed on element “video” at this point.
✅ Valid: Removing the attribute
<video src="video.mp4" controls></video>
The simplest fix is to remove controlslist and accept the browser’s default controls.
✅ Valid: Using custom controls with JavaScript
<video id="my-video" src="video.mp4"></video>
<div class="custom-controls">
<button id="play-btn">Play</button>
<input id="seek-bar" type="range" min="0" max="100" value="0">
<button id="fullscreen-btn">Fullscreen</button>
</div>
<script>
const video = document.getElementById("my-video");
document.getElementById("play-btn").addEventListener("click", () => {
video.paused ? video.play() : video.pause();
});
</script>
By omitting the controls attribute and building your own UI, you have full control over which buttons appear — across all browsers.
✅ Valid: Using disableremoteplayback for that specific need
<video src="video.mp4" controls disableremoteplayback></video>
If your goal was specifically controlslist="noremoteplayback", the standardized disableremoteplayback attribute achieves the same result and is valid HTML.
Audio element
The same issue and solutions apply to the <audio> element:
<!-- ❌ Invalid -->
<audio src="song.mp3" controls controlslist="nodownload"></audio>
<!-- ✅ Valid -->
<audio src="song.mp3" controls></audio>
The HTML specification defines a specific set of allowed attributes for each element. The <span> element supports global attributes (such as id, class, style, title, etc.) but does not recognize currency as a valid attribute. When you add a non-standard attribute like currency to an element, the W3C validator flags it because it doesn’t conform to the HTML standard.
This pattern often appears in e-commerce sites, financial applications, or internationalization contexts where developers want to associate a currency code (like USD, EUR, or GBP) with a price displayed in a <span>. While browsers will typically ignore unrecognized attributes without breaking the page, using them creates several problems:
- Standards compliance: Invalid HTML can lead to unpredictable behavior across different browsers and future browser versions.
- Maintainability: Other developers (or tools) won’t recognize non-standard attributes, making the codebase harder to understand and maintain.
- Accessibility: Assistive technologies rely on valid, well-structured HTML. Non-standard attributes may be ignored or misinterpreted.
- JavaScript interoperability: The HTMLElement.dataset API is specifically designed to work with data-* attributes, providing a clean and standard way to read custom data from elements.
The fix is straightforward: HTML provides the data-* attribute mechanism specifically for embedding custom data on elements. Any attribute prefixed with data- is valid on any HTML element, and its value is accessible in JavaScript via the element.dataset property.
Examples
❌ Invalid: Non-standard currency attribute
<span currency="USD">49.99</span>
This triggers the validation error because currency is not a recognized attribute for <span>.
✅ Fixed: Using a data-currency attribute
<span data-currency="USD">49.99</span>
The data-currency attribute is valid HTML. In JavaScript, you can access its value like this:
const span = document.querySelector('span');
console.log(span.dataset.currency); // "USD"
✅ Alternative: Using data-* with richer markup
If you need to convey more structured information, you can combine multiple data-* attributes:
<span class="price" data-currency="EUR" data-amount="29.99">€29.99</span>
✅ Alternative: Using microdata or structured markup
For SEO and machine-readable data, consider using established vocabularies like Schema.org:
<span itemscope itemtype="https://schema.org/MonetaryAmount">
<meta itemprop="currency" content="USD">
<span itemprop="value">49.99</span>
</span>
This approach provides semantic meaning that search engines and other consumers can understand, while remaining fully valid HTML.
The HTML specification defines a specific set of global attributes (like class, id, title, style, etc.) that are allowed on all elements, plus element-specific attributes for certain tags. Any attribute that isn’t part of these standard sets and doesn’t follow the data-* custom attribute convention is considered invalid and will trigger a validation error.
This issue is especially common with older integrations of third-party tools like ShareThis, AddThis, or similar social sharing widgets, particularly when embedded through a CMS like Drupal or WordPress. These older implementations relied on proprietary attributes such as displayText, st_url, and st_title directly on <span> or other elements. Modern versions of these tools have since migrated to valid data-* attributes.
Why This Matters
- Standards compliance: Non-standard attributes violate the HTML specification, meaning your markup is technically invalid and may behave unpredictably across browsers.
- Forward compatibility: Browsers could introduce a native displaytext attribute in the future with entirely different behavior, causing conflicts with your code.
- Accessibility: Assistive technologies rely on well-formed HTML. Non-standard attributes can confuse screen readers or other tools that parse the DOM.
- Maintainability: Using the standardized data-* convention makes it immediately clear to other developers that an attribute holds custom data, improving code readability.
How to Fix It
- Identify all non-standard attributes on your elements (e.g., displayText, st_url, st_title).
- Prefix each one with data- to convert it into a valid custom data attribute (e.g., data-displaytext, data-st-url, data-st-title).
- Update any JavaScript that references these attributes. If JavaScript accesses them via element.getAttribute('displayText'), change it to element.getAttribute('data-displaytext') or use the dataset API (element.dataset.displaytext).
- If using a CMS or third-party plugin, update the plugin or module to its latest version, which likely uses valid data-* attributes already.
Note that data-* attribute names should be all lowercase. Even if the original attribute used camelCase like displayText, the corrected version should be data-displaytext.
Examples
Invalid: Non-standard attribute on a <span>
<span class="st_sharethis" displaytext="ShareThis" st_url="https://example.com" st_title="My Page">
Share
</span>
This triggers validation errors for displaytext, st_url, and st_title because none of these are valid HTML attributes.
Valid: Using data-* attributes
<span class="st_sharethis" data-displaytext="ShareThis" data-st-url="https://example.com" data-st-title="My Page">
Share
</span>
Accessing data-* attributes in JavaScript
If your JavaScript relied on the old attribute names, update the references:
<span id="share-btn" data-displaytext="ShareThis">Share</span>
<script>
const btn = document.getElementById('share-btn');
// Using getAttribute
const text = btn.getAttribute('data-displaytext');
// Using the dataset API
const textAlt = btn.dataset.displaytext;
</script>
Valid: Using a standard attribute instead
In some cases, the intent of displaytext is simply to provide a label or tooltip. If so, a standard attribute like title may be more appropriate:
<span class="st_sharethis" title="ShareThis">Share</span>
Choose the approach that best matches your use case — data-* for custom data consumed by JavaScript, or a semantic HTML attribute if one already serves the purpose.
The <table> element in HTML supports a limited set of attributes — primarily global attributes like class, id, and style. The height attribute was never part of the HTML standard for tables (unlike width, which was valid in HTML 4 but has since been deprecated). Despite this, many browsers historically accepted height on <table> as a non-standard extension, which led to its widespread but incorrect use.
Because height is not a recognized attribute on <table>, using it means your markup is invalid and may behave inconsistently across browsers. Some browsers might honor it, others might ignore it entirely, and future browser versions could change their behavior at any time. Relying on non-standard attributes makes your code fragile and harder to maintain.
The fix is straightforward: remove the height attribute from the <table> element and use CSS to set the desired height. You can apply the CSS inline via the style attribute, or better yet, use an external or internal stylesheet with a class or ID selector.
Examples
❌ Invalid: height attribute on <table>
<table height="300">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
This triggers the validator error: Attribute “height” not allowed on element “table” at this point.
✅ Fixed: Using inline CSS
<table style="height: 300px;">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
✅ Fixed: Using a CSS class (preferred)
<style>
.tall-table {
height: 300px;
}
</style>
<table class="tall-table">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
Using a class keeps your HTML clean and makes it easy to adjust the styling later without touching the markup. Note that min-height is often a better choice than height for tables, since table content can naturally grow beyond a fixed height, and min-height ensures the table is at least a certain size without clipping content.
Why This Happens
In HTML, certain attributes are boolean and can appear without a value (e.g., disabled, required). The href attribute is not one of them — it expects a valid URL, path, or fragment identifier as its value. When a validator encounters href with no value or an empty value, it raises a warning because the attribute is being used in a way that doesn’t conform to the specification.
Writing <a href> produces a valueless attribute. Writing <a href=""> produces an attribute whose value is an empty string, which resolves to the current page’s URL per the rules of relative URL resolution. Both forms are problematic:
- href without a value: The HTML spec requires href to contain a valid URL. A valueless attribute is ambiguous and was actively dropped by IE7, meaning the element would lose its link behavior entirely in that browser.
- href="": While technically parsed as a relative URL pointing to the current document, this is almost never the developer’s intention. It causes the page to reload or scroll to the top when clicked, which is confusing for users and harmful for accessibility.
The Impact
- Accessibility: Screen readers rely on the href attribute to identify an <a> element as an interactive link. A missing or empty value creates confusion — the screen reader may announce it as a link but provide no destination, or may not treat it as a link at all.
- Browser compatibility: As the validator message notes, IE7 would strip the attribute entirely, meaning the element lost its link semantics and styling. While IE7 is no longer in common use, valueless attributes can still cause edge-case issues in parsers, crawlers, and automated tools.
- Standards compliance: Valid HTML ensures consistent rendering and behavior across browsers, assistive technologies, and search engine crawlers.
How to Fix It
The fix depends on your intent:
- If the element should link somewhere, provide a real URL: href="https://example.com" or href="/about".
- If the element is a placeholder link (e.g., during development or for JavaScript-driven actions), use href="#" and prevent the default navigation with JavaScript.
- If the element doesn’t need to be a link at all, use a <button> for interactive actions or a <span> for non-interactive text. This is often the most semantically correct choice.
Examples
❌ Incorrect: href without a value
<a href>Click here</a>
This triggers the validation warning. The attribute has no value, which is invalid for href.
❌ Incorrect: href with an empty string
<a href="">Click here</a>
This resolves to the current page URL, likely causing an unintended page reload.
✅ Correct: Provide a real URL
<a href="https://example.com">Visit Example</a>
✅ Correct: Use a fragment as a placeholder
<a href="#" onclick="doSomething(); return false;">Perform action</a>
The return false prevents the page from scrolling to the top. A better modern approach uses event.preventDefault():
<a href="#" id="action-link">Perform action</a>
<script>
document.getElementById("action-link").addEventListener("click", function (e) {
e.preventDefault();
doSomething();
});
</script>
✅ Correct: Use a <button> for actions
If the element triggers a JavaScript action rather than navigating somewhere, a <button> is the most semantically appropriate choice:
<button type="button" onclick="doSomething();">Perform action</button>
Buttons are natively focusable, keyboard-accessible, and clearly communicate interactive intent to assistive technologies — no href needed.
✅ Correct: Use a <span> for non-interactive text
If the element is purely visual and shouldn’t be interactive at all:
<span class="label">Not a link</span>
This removes any expectation of interactivity for both the browser and the user.
The http-equiv attribute on a <meta> element simulates an HTTP response header, allowing you to define document-level metadata that would otherwise require server configuration. Because this metadata applies to the entire document and must be processed before the page content is rendered, the HTML specification requires that <meta http-equiv> elements appear within the <head> element. Placing them in the <body> is invalid and may cause browsers to ignore them entirely, leading to unexpected behavior like incorrect character encoding, broken content security policies, or missing refresh directives.
This error commonly occurs when:
- A <meta http-equiv> tag is accidentally placed inside <body>.
- Content is copy-pasted from one document into the body of another, bringing along <meta> tags.
- A templating system or CMS injects <meta> tags in the wrong location.
Common http-equiv values
The http-equiv attribute supports several standard values:
- content-type — Declares the document’s MIME type and character encoding. In HTML5, the shorthand <meta charset="UTF-8"> is preferred instead.
- refresh — Instructs the browser to reload the page or redirect after a specified number of seconds. Note that automatic refreshing is discouraged for accessibility reasons: it can disorient users, move focus back to the top of the page, and disrupt assistive technology. Avoid it unless absolutely necessary.
- content-security-policy — Defines a Content Security Policy for the document, helping prevent cross-site scripting (XSS) and other code injection attacks.
- default-style — Specifies the preferred stylesheet from a set of alternative stylesheets.
How to fix it
Find every <meta http-equiv> element in your document and ensure it is placed inside the <head> element, before any content in <body>. If the tag is duplicated or unnecessary, remove it.
Examples
❌ Incorrect: http-equiv inside <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<p>Hello, world!</p>
</body>
</html>
The <meta http-equiv> tag is inside <body>, which triggers the validation error.
✅ Correct: http-equiv inside <head>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: using the HTML5 charset shorthand
In HTML5, you can replace <meta http-equiv="content-type" content="text/html; charset=UTF-8"> with the simpler charset attribute:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: Content Security Policy in <head>
<head>
<meta charset="UTF-8">
<meta http-equiv="content-security-policy" content="default-src 'self'">
<title>Secure Page</title>
</head>
The content-security-policy value is particularly placement-sensitive — browsers will ignore it if it appears outside <head>, leaving your page without the intended security protections.
The W3C HTML Validator reports this error when it encounters isolang on the <html> element because isolang is not a recognized attribute in any version of HTML. This typically happens when developers attempt to specify the document’s language but use an incorrect or made-up attribute name, possibly confusing it with ISO language code terminology.
The correct attribute for declaring a document’s language is lang. This attribute accepts a valid BCP 47 language tag, which in most cases is a simple two-letter ISO 639-1 code (like en for English, fr for French, or pt for Portuguese). You can also use extended subtags for regional variants, such as en-US for American English or pt-BR for Brazilian Portuguese.
Setting the lang attribute properly is important for several reasons:
- Accessibility: Screen readers use the lang attribute to select the correct pronunciation rules, ensuring content is read aloud accurately.
- SEO: Search engines use the language declaration to serve the right content to users based on their language preferences.
- Browser behavior: Browsers rely on lang for features like spell-checking, hyphenation, and selecting appropriate default fonts for the given language.
- Standards compliance: Only recognized attributes pass W3C validation, and valid markup ensures consistent, predictable behavior across browsers.
To fix this issue, simply replace isolang with lang on your <html> element. Keep the same language code value—it’s the attribute name that’s wrong, not the value.
Examples
❌ Incorrect: Using the invalid isolang attribute
<!DOCTYPE html>
<html isolang="pt">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
This triggers the error: Attribute “isolang” not allowed on element “html” at this point.
✅ Correct: Using the lang attribute
<!DOCTYPE html>
<html lang="pt">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
✅ Correct: Using a regional language subtag
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
Common language codes
Here are some frequently used ISO 639-1 language codes for the lang attribute:
- en — English
- es — Spanish
- fr — French
- de — German
- pt — Portuguese
- zh — Chinese
- ja — Japanese
- ar — Arabic
- ko — Korean
- ru — Russian
The aria-labelledby attribute is part of the WAI-ARIA specification and provides an accessible name for an element by referencing the id values of other elements that contain the labeling text. Without the aria- prefix, labelledby is simply an unrecognized attribute that browsers and assistive technologies will ignore. This means your SVG graphic won’t have the accessible label you intended, leaving screen reader users without a meaningful description of the content.
This issue is especially important for <svg> elements because SVG graphics are often used for icons, charts, and illustrations that need descriptive labels for accessibility. Using the incorrect attribute name means the graphic is effectively unlabeled for users who rely on assistive technology.
How to Fix It
Replace labelledby with aria-labelledby on your <svg> element. The attribute’s value should be a space-separated list of one or more id values that reference elements containing the label text.
If you want to label an SVG using text that’s already visible on the page, aria-labelledby is the ideal approach. You can also reference a <title> element inside the SVG itself.
Examples
❌ Incorrect: Using labelledby (invalid attribute)
<h2 id="chart-title">Monthly Sales</h2>
<svg labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference an external heading
<h2 id="chart-title">Monthly Sales</h2>
<svg aria-labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference the SVG’s own <title>
<svg aria-labelledby="icon-title" role="img" viewBox="0 0 24 24">
<title id="icon-title">Search</title>
<path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5z"/>
</svg>
✅ Correct: Referencing multiple label sources
You can combine multiple id values to build a composite accessible name, separated by spaces:
<h2 id="section-title">Revenue</h2>
<p id="section-desc">Q1 2024 revenue by region</p>
<svg aria-labelledby="section-title section-desc" role="img" viewBox="0 0 400 200">
<!-- chart content -->
</svg>
In this case, a screen reader would announce something like “Revenue Q1 2024 revenue by region” as the accessible name for the SVG.
Tips
- When using aria-labelledby on <svg>, also add role="img" to ensure consistent behavior across screen readers.
- If the SVG is purely decorative, use aria-hidden="true" instead of labeling it.
- The aria-labelledby attribute overrides other labeling mechanisms like aria-label or the <title> element, so use it when you want a specific label to take precedence.
Pronto para validar os seus sites?
Comece o seu teste gratuito hoje.