HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
Every HTML semantic element carries an implicit ARIA role that assistive technologies already recognize. The <article> element has a built-in role of article, which signals that the content represents a self-contained composition — such as a blog post, news story, forum comment, or any section that could be independently distributed or reused. When you explicitly add role="article" to an <article> element, you're telling the browser and screen readers something they already know.
While this redundancy won't break anything functionally, it creates unnecessary noise in your markup and goes against the W3C's guidance on using ARIA. The first rule of ARIA use states: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of repurposing an element and adding an ARIA role, state or property to make it accessible, then do so." Redundant roles make code harder to maintain and can signal to other developers that something non-standard is happening when it isn't.
The role="article" attribute is useful when applied to non-semantic elements like <div> or <span> that need to convey article semantics — for instance, in legacy codebases where changing the element isn't feasible. But on the <article> element itself, it should simply be removed.
Examples
❌ Redundant role on <article>
This triggers the validator warning because role="article" duplicates the element's implicit role:
<articlerole="article">
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</article>
✅ Fixed: no explicit role needed
Simply remove the role attribute. The <article> element already communicates the article role to assistive technologies:
<article>
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</article>
✅ Appropriate use of role="article" on a non-semantic element
If you cannot use the <article> element for some reason, applying the role to a generic element like <div> is valid and useful:
<divrole="article">
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</div>
✅ Multiple articles within a feed
A common pattern is nesting several <article> elements inside a feed. No explicit roles are needed on the articles themselves:
<sectionrole="feed"aria-label="Latest posts">
<article>
<h2>First Post</h2>
<p>Content of the first post.</p>
</article>
<article>
<h2>Second Post</h2>
<p>Content of the second post.</p>
</article>
</section>
This same principle applies to other semantic elements with implicit roles — for example, <nav> already has role="navigation", <main> has role="main", and <header> has role="banner". Avoid adding redundant roles to any of these elements to keep your HTML clean and standards-compliant.
The HTML specification defines built-in semantic roles for many elements, and the <header> element is one of them. When a <header> is a direct child of <body> (or at least not nested inside a sectioning element), browsers and assistive technologies already interpret it as a banner landmark — the region of the page that typically contains the site logo, navigation, and other introductory content. Explicitly adding role="banner" duplicates what the browser already knows, which adds unnecessary noise to your markup.
This principle is part of the WAI-ARIA specification's guidance on using ARIA roles: the first rule of ARIA is "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so." Redundant roles don't typically break anything, but they clutter the code, can confuse developers maintaining the project, and signal a misunderstanding of HTML semantics.
It's worth noting an important nuance: the <header> element only maps to the banner role when it is not a descendant of <article>, <aside>, <main>, <nav>, or <section>. When nested inside one of these sectioning elements, <header> has no corresponding landmark role — it simply serves as the header for that particular section. In that context, adding role="banner" would not be redundant; it would actually change the semantics, which is almost certainly not what you want.
To fix the warning, remove the role="banner" attribute from your <header> element. The native semantics are sufficient.
Examples
Incorrect: redundant role="banner" on <header>
This triggers the validator warning because <header> already implies the banner role at the top level:
<headerrole="banner">
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/about">About</a>
</nav>
</header>
Correct: let <header> use its implicit role
Simply remove the role="banner" attribute:
<header>
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/about">About</a>
</nav>
</header>
Correct: using role="banner" on a non-<header> element
If for some reason you cannot use a <header> element (e.g., working within a legacy CMS), applying role="banner" to a <div> is the appropriate way to convey the same landmark semantics:
<divrole="banner">
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/about">About</a>
</nav>
</div>
A <header> inside a sectioning element has no banner role
When <header> is nested inside an <article> or other sectioning element, it does not carry the banner role. This is expected and correct — the <header> here simply introduces the article content:
<article>
<header>
<h2>Article Title</h2>
<p>Published on <timedatetime="2024-01-15">January 15, 2024</time></p>
</header>
<p>Article content goes here.</p>
</article>
The <base> element must appear before any <link> or <script> elements in the <head> section of the document.
The <base> element sets the base URL and/or default target for all relative URLs in a page. Because <link> and <script> elements often reference external resources using relative URLs, the browser needs to know the base URL before it encounters those elements. If <base> appears after a <link> or <script>, the browser may have already resolved their URLs without the intended base, leading to broken references or unexpected behavior.
Only one <base> element is allowed per document, and the HTML specification requires it to appear before any elements that have attributes accepting URLs. In practice, placing it as the first child of <head> (after <meta charset>) is the simplest way to satisfy this rule.
Invalid example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Example</title>
<linkrel="stylesheet"href="styles/main.css">
<scriptsrc="js/app.js"></script>
<basehref="https://example.com/">
</head>
<body>
<p>Hello</p>
</body>
</html>
The <base> element comes after the <link> and <script> elements, which triggers the validation error.
Valid example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<basehref="https://example.com/">
<title>Example</title>
<linkrel="stylesheet"href="styles/main.css">
<scriptsrc="js/app.js"></script>
</head>
<body>
<p>Hello</p>
</body>
</html>
Moving <base> before the <link> and <script> elements fixes the error and ensures all relative URLs resolve correctly.
The <big> element was a purely presentational HTML element that increased the text size by one level (similar to moving from small to medium, or medium to large). HTML5 removed it from the specification because it violates the principle of separating content structure from visual presentation. HTML should describe the meaning of content, while CSS should handle how it looks.
Using obsolete elements causes several problems. First, W3C validation will fail, which can impact code quality standards and SEO audits. Second, while current browsers still support <big> for backward compatibility, there's no guarantee future browsers will continue to do so. Third, the <big> element carries no semantic meaning — it doesn't tell assistive technologies why the text is larger, only that it should be displayed differently. CSS gives you more precise control over font sizing while keeping your HTML clean and standards-compliant.
To fix this issue, replace every <big> element with a <span> (or another semantically appropriate element) and apply CSS to control the font size. You can use inline styles, a <style> block, or an external stylesheet.
If the larger text has a specific meaning — such as emphasizing importance — consider using a semantic element like <strong> or <em> instead, and style it with CSS as needed.
Examples
❌ Obsolete: using the <big> element
<p>This is <big>important text</big> in a paragraph.</p>
✅ Fixed: using a <span> with inline CSS
This is a direct replacement that mimics the original behavior of <big>, which rendered text at font-size: larger:
<p>This is <spanstyle="font-size: larger;">important text</span> in a paragraph.</p>
✅ Fixed: using a CSS class
For better maintainability, use a class instead of inline styles:
<style>
.text-big{
font-size: larger;
}
</style>
<p>This is <spanclass="text-big">important text</span> in a paragraph.</p>
✅ Fixed: using a semantic element with CSS
If the text is larger because it's important or emphasized, use a semantic element and style it:
<style>
.highlight{
font-size:1.25em;
}
</style>
<p>This is <strongclass="highlight">important text</strong> in a paragraph.</p>
Choosing a font-size value
The <big> element historically corresponded to font-size: larger, but with CSS you have full flexibility:
font-size: larger— relative increase, closest to original<big>behaviorfont-size: 1.2em— scales to 120% of the parent's font sizefont-size: 1.25rem— scales relative to the root font sizefont-size: 20px— absolute pixel value (less flexible, generally avoid)
Using relative units like em, rem, or the larger keyword is preferred because they scale well across different screen sizes and respect user font-size preferences.
In earlier versions of HTML, the border attribute on <img> was commonly used to control the border width in pixels. Its most frequent use was border="0" to suppress the default blue border browsers would render around images wrapped in <a> links. While this worked, it mixed presentation with markup — something HTML5 discourages in favor of a clean separation between structure (HTML) and styling (CSS).
The W3C HTML Validator flags this attribute as obsolete because it was removed from the HTML5 specification. Modern browsers still understand it for backward compatibility, but relying on deprecated features is bad practice. It can lead to inconsistencies across browsers, makes your code harder to maintain, and signals to validators and other developers that the markup is outdated.
The recommended approach is to handle image borders entirely in CSS. If you previously used border="0" to remove borders from linked images, most modern CSS resets and normalize stylesheets already handle this. If you're not using a reset, a single CSS rule takes care of it globally — no need to repeat the attribute on every <img> tag.
How to fix it
- Remove the
borderattribute from all<img>elements. - Add a CSS rule to achieve the same effect. For removing borders, use
img { border: 0; }in your stylesheet. For adding a visible border, use properties likeborder: 2px solid #333;.
Examples
❌ Obsolete border attribute
<ahref="/products">
<imgsrc="product.jpg"alt="Our product"border="0">
</a>
This triggers the validator warning because border is no longer a valid attribute on <img>.
✅ Fixed with CSS (external/internal stylesheet)
<style>
img{
border:0;
}
</style>
<ahref="/products">
<imgsrc="product.jpg"alt="Our product">
</a>
A single rule in your stylesheet removes borders from all images, which is cleaner and easier to maintain than repeating the attribute on every element.
✅ Fixed with inline CSS (if needed)
<ahref="/products">
<imgsrc="product.jpg"alt="Our product"style="border:0;">
</a>
Inline styles work but aren't ideal for large-scale fixes. Prefer a stylesheet rule when possible.
✅ Adding a decorative border with CSS
If your intent was to add a visible border (e.g., border="2"), replace it with a more flexible CSS equivalent:
<style>
.bordered{
border:2px solid #333;
}
</style>
<imgsrc="photo.jpg"alt="A scenic landscape"class="bordered">
CSS gives you far more control — you can specify the border style, color, individual sides, and even use border-radius for rounded corners, none of which were possible with the old border attribute.
The border attribute on the <img> element is obsolete in HTML5 and should be replaced with CSS.
Older versions of HTML allowed border as an attribute directly on <img> elements, most commonly set to 0 to remove the blue border that browsers added around images wrapped in links. HTML5 dropped this attribute from the specification. Modern browsers no longer add that default border, so in most cases the attribute can simply be removed.
If you still need to control the border on an image, use CSS instead. You can apply a style directly with the style attribute, use a class, or add a rule in your stylesheet.
HTML examples
Invalid: using the obsolete border attribute
<ahref="/home">
<imgsrc="logo.png"alt="Logo"border="0">
</a>
Valid: using CSS instead
<ahref="/home">
<imgsrc="logo.png"alt="Logo"style="border:0;">
</a>
Or, with a stylesheet rule:
<style>
img{
border:0;
}
</style>
<ahref="/home">
<imgsrc="logo.png"alt="Logo">
</a>
The border attribute on the <table> element is obsolete in HTML5 and should be replaced with CSS styling.
The border attribute was commonly used in older HTML to add borders to tables. In HTML5, the only valid value for the border attribute on a <table> is an empty string ("") or "1", and even then it's considered obsolete. The validator message mentions img { border: 0; } because the border attribute was also frequently used on <img> elements, but the same principle applies to tables — presentational attributes should be replaced with CSS.
To style table borders, use the CSS border property on the <table>, <th>, and <td> elements. The border-collapse property is also useful for controlling whether borders are merged or separated.
HTML Examples
❌ Invalid: using the obsolete border attribute
<tableborder="2">
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
</table>
✅ Valid: using CSS for borders
<style>
table,td{
border:2px solid black;
border-collapse: collapse;
}
</style>
<table>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
</table>
Every HTML element carries an implicit ARIA role that communicates its purpose to assistive technologies like screen readers. The <button> element natively has the button role built in, so explicitly adding role="button" is redundant. The W3C validator flags this as unnecessary because it adds no information — assistive technologies already understand that a <button> is a button.
The role attribute exists primarily to assign interactive semantics to elements that don't have them natively. For example, you might add role="button" to a <div> or <span> that has been styled and scripted to behave like a button (though using a native <button> is always preferable). When you apply it to an element that already carries that role by default, it creates noise in your code and can signal to other developers that something unusual is going on — when in fact nothing is.
This principle applies broadly across HTML. Other examples of redundant roles include role="link" on an <a> element with an href, role="navigation" on a <nav> element, and role="heading" on an <h1> through <h6> element. The WAI-ARIA specification refers to these as "default implicit ARIA semantics," and the general rule is: don't set an ARIA role that matches the element's native semantics.
Removing redundant roles keeps your markup clean, easier to maintain, and avoids potential confusion during code reviews or audits. It also aligns with the first rule of ARIA: "If you can use a native HTML element with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so."
How to fix it
Remove the role="button" attribute from any <button> element. No replacement is needed — the native semantics are already correct.
If you have a non-button element (like a <div>) that uses role="button", consider replacing it with a real <button> element instead. This gives you built-in keyboard support, focus management, and form submission behavior for free.
Examples
❌ Redundant role on a button
<buttonrole="button">Buy now</button>
<buttontype="submit"role="button">Submit</button>
Both of these trigger the validator warning because role="button" duplicates what the <button> element already communicates.
✅ Button without redundant role
<button>Buy now</button>
<buttontype="submit">Submit</button>
Simply removing the role attribute resolves the issue. The element's native semantics handle everything.
❌ Using role="button" on a non-semantic element
<divrole="button"tabindex="0"onclick="handleClick()">Buy now</div>
While this is technically valid and won't trigger the same warning, it requires manual handling of keyboard events, focus styles, and accessibility states.
✅ Using a native button instead
<buttononclick="handleClick()">Buy now</button>
A native <button> provides keyboard interaction (Enter and Space key activation), focusability, and correct role announcement — all without extra attributes or JavaScript.
Other common redundant roles to avoid
<!-- ❌ Redundant -->
<ahref="/about"role="link">About</a>
<navrole="navigation">...</nav>
<h1role="heading">Title</h1>
<inputtype="checkbox"role="checkbox">
<!-- ✅ Clean -->
<ahref="/about">About</a>
<nav>...</nav>
<h1>Title</h1>
<inputtype="checkbox">
The WAI-ARIA specification defines implicit roles (also called "native semantics") for many HTML elements. An <input> element with type="submit" inherently communicates to assistive technologies that it is a button control. Adding role="button" explicitly restates what the browser and screen readers already know, making it redundant.
The role="button" attribute is designed for situations where you need to make a non-interactive element — such as a <div> or <span> — behave like a button for assistive technologies. When applied to elements that already carry this semantic meaning natively, it adds unnecessary noise to your markup without providing any accessibility benefit.
Why this is a problem
- Redundancy: The explicit role duplicates the element's built-in semantics, cluttering the HTML with no added value.
- Maintenance risk: Redundant ARIA attributes can mislead other developers into thinking the role is necessary, or that the element's native semantics differ from what they actually are.
- Standards compliance: The W3C validator flags this as an issue because the ARIA in HTML specification explicitly states that authors should not set ARIA roles or attributes that match an element's implicit native semantics. This principle is sometimes called the "first rule of ARIA" — don't use ARIA when a native HTML element already provides the semantics you need.
- Potential conflicts: While current browsers handle redundant roles gracefully, explicitly overriding native semantics can theoretically interfere with future browser or assistive technology behavior.
How to fix it
Remove the role="button" attribute from any <input type="submit"> element. The same principle applies to other input types with implicit roles, such as <input type="reset"> (which also has an implicit button role) and <button> elements.
Examples
❌ Incorrect: redundant role="button" on a submit input
<formaction="/checkout"method="post">
<inputtype="submit"role="button"value="Buy Now">
</form>
✅ Correct: no explicit role needed
<formaction="/checkout"method="post">
<inputtype="submit"value="Buy Now">
</form>
❌ Incorrect: redundant role on a <button> element
The same issue applies to <button> elements, which also have an implicit button role:
<buttontype="submit"role="button">Submit Order</button>
✅ Correct: let native semantics do the work
<buttontype="submit">Submit Order</button>
✅ Correct: using role="button" where it is appropriate
The role="button" attribute is meaningful when applied to an element that does not natively convey button semantics. Note that you must also handle keyboard interaction and focus management manually in this case:
<divrole="button"tabindex="0">Add to Cart</div>
Even in this scenario, using a native <button> element is strongly preferred over adding ARIA roles to non-interactive elements, since the native element provides built-in keyboard support and focus behavior for free.
The <summary> element serves as the clickable disclosure toggle for a <details> element. Because its built-in behavior is inherently interactive — clicking it expands or collapses the parent <details> content — the HTML specification assigns it an implicit button role. This means assistive technologies like screen readers already announce <summary> as a button without any additional markup.
When you explicitly add role="button" to a <summary> element, the W3C validator flags it as unnecessary. While this doesn't cause functional problems, redundant ARIA roles are discouraged by the first rule of ARIA use: if an HTML element already has the semantics you need, don't re-add them with ARIA attributes. Redundant roles add noise to your code, can confuse other developers into thinking custom behavior is being applied, and in edge cases may interact unexpectedly with certain assistive technologies.
This principle applies broadly — many HTML elements have implicit roles (e.g., <nav> has navigation, <main> has main, <button> has button). Adding the role they already carry is always unnecessary.
How to fix it
Remove the role="button" attribute from the <summary> element. No replacement is needed since the semantics are already built in.
Examples
❌ Incorrect: redundant role="button" on <summary>
<details>
<summaryrole="button">Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The validator will report: The "button" role is unnecessary for element "summary".
✅ Correct: <summary> without an explicit role
<details>
<summary>Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The <summary> element's implicit button role ensures assistive technologies already treat it as an interactive control. No additional attributes are required.
✅ Correct: a more complete <details> example
<details>
<summary>I have keys but no doors. I have space but no room. You can enter but can't leave. What am I?</summary>
<p>A keyboard.</p>
</details>
Clicking the <summary> toggles the parent <details> element between its open and closed states. Screen readers announce it as a button automatically, and keyboard users can activate it with Enter or Space — all without any explicit ARIA role.
The <center> element is a presentational HTML tag that dates back to early web development. The HTML5 specification formally made it obsolete because it violates the principle of separating content (HTML) from presentation (CSS). While most browsers still render <center> correctly for backward compatibility, relying on it is discouraged for several reasons:
- Standards compliance: Using obsolete elements means your HTML does not conform to the current HTML specification, which can cause W3C validation errors.
- Maintainability: Inline presentational elements scatter styling throughout your markup, making it harder to update the design of your site consistently. CSS allows you to control layout from a single stylesheet.
- Accessibility: Semantic HTML helps assistive technologies understand content structure. The
<center>element carries no semantic meaning and adds noise to the document. - Future-proofing: Browser support for obsolete elements is not guaranteed indefinitely. Using CSS ensures your layout will work reliably going forward.
To fix this issue, replace every <center> element with an appropriate CSS technique. The right approach depends on what you're centering.
For inline content (text, images, inline elements), apply text-align: center to the parent container.
For block-level elements (divs, sections, etc.), use margin: 0 auto along with a defined width, or use Flexbox on the parent.
Examples
❌ Obsolete: using <center>
<center>
<p>This text is centered.</p>
</center>
✅ Fixed: using text-align for inline content
<divstyle="text-align:center">
<p>This text is centered.</p>
</div>
Or better yet, use a CSS class to keep styles out of your HTML:
<divclass="centered-text">
<p>This text is centered.</p>
</div>
.centered-text{
text-align: center;
}
❌ Obsolete: centering a block element with <center>
<center>
<div>This box is centered.</div>
</center>
✅ Fixed: centering a block element with margin: auto
<divclass="centered-box">
<p>This box is centered.</p>
</div>
.centered-box{
max-width:600px;
margin:0 auto;
}
✅ Fixed: centering with Flexbox
Flexbox is a powerful and flexible way to center content both horizontally and vertically:
<divclass="flex-center">
<p>This content is centered.</p>
</div>
.flex-center{
display: flex;
justify-content: center;
}
✅ Applying the fix directly to an element
If you only need to center the text within a single element, you can apply the style directly without a wrapper:
<pclass="centered">This paragraph is centered.</p>
.centered{
text-align: center;
}
In every case, the key takeaway is the same: remove the <center> element and use CSS to achieve the desired centering effect. This keeps your HTML clean, semantic, and fully compliant with modern standards.
Character encoding tells the browser how to map the raw bytes of your HTML file into readable characters. When no encoding is declared, browsers must rely on heuristics or defaults to figure out which encoding to use. The validator's fallback to "windows-1252" reflects the behavior described in the HTML specification: if no encoding information is found, parsers may default to this legacy encoding. This can cause serious problems — characters like curly quotes, em dashes, accented letters, emoji, and non-Latin scripts can appear as garbled text (often called "mojibake") if the actual encoding of the file doesn't match what the browser assumes.
Why this matters
- Correctness: If your file is saved as UTF-8 (which is the default in most modern editors) but the browser interprets it as windows-1252, multi-byte characters will render incorrectly.
- Standards compliance: The WHATWG HTML Living Standard requires that a character encoding declaration be present. The encoding must also be declared within the first 1024 bytes of the document.
- Security: Ambiguous encoding can be exploited in certain cross-site scripting (XSS) attacks where an attacker takes advantage of encoding mismatches.
- Interoperability: Different browsers may choose different fallback encodings, leading to inconsistent rendering across platforms.
How to fix it
The simplest and recommended fix is to add <meta charset="utf-8"> as the first child element inside <head>, before <title> or any other elements. It must appear within the first 1024 bytes of the document so the parser encounters it early enough.
UTF-8 is the universal standard for the web. It can represent every character in Unicode, is backward-compatible with ASCII, and is the encoding recommended by the WHATWG HTML specification. Unless you have a very specific reason to use another encoding, always use UTF-8.
You should also ensure your text editor or build tool is actually saving the file in UTF-8 encoding. Declaring <meta charset="utf-8"> while the file is saved in a different encoding will still produce garbled text.
An alternative (less common) approach is to declare the encoding via an HTTP Content-Type header sent by the server, such as Content-Type: text/html; charset=utf-8. However, the <meta> tag is still recommended as a fallback for when files are viewed locally or cached without headers.
Examples
❌ Missing character encoding declaration
This document has no encoding declaration, triggering the validator warning:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Héllo, wörld! — enjoy "quotes" and emöji 🎉</p>
</body>
</html>
✅ Fixed with <meta charset="utf-8">
Adding the <meta charset="utf-8"> tag as the first element in <head> resolves the issue:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Héllo, wörld! — enjoy "quotes" and emöji 🎉</p>
</body>
</html>
❌ Encoding declared too late
The <meta charset> must come before other content in the <head>. Placing it after a <title> that contains non-ASCII characters means the parser may have already committed to a wrong encoding:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Café Menu</title>
<metacharset="utf-8">
</head>
<body>
<p>Welcome to the café.</p>
</body>
</html>
✅ Encoding declared before all other content
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Café Menu</title>
</head>
<body>
<p>Welcome to the café.</p>
</body>
</html>
The key takeaway: always place <meta charset="utf-8"> as the very first element inside <head>. This is a small addition that prevents a whole class of character rendering and security issues.
In earlier versions of HTML, the charset attribute on <link> was used to indicate the character encoding of the linked resource (such as a stylesheet or other external file). The HTML Living Standard has since made this attribute obsolete. Modern browsers determine the character encoding of linked resources through the Content-Type HTTP response header — for example, Content-Type: text/css; charset=UTF-8 — or by using default encoding rules defined in the relevant specification (CSS files, for instance, default to UTF-8).
Using the obsolete charset attribute creates several issues. It gives a false sense of control over encoding, since browsers don't actually use it. It also clutters your markup with unnecessary attributes, and it may signal to other developers that this is the mechanism being relied upon for encoding, when in reality it has no effect. For standards compliance and clean, future-proof markup, it should be removed.
The fix involves two steps: first, remove the charset attribute from your HTML; second, ensure your web server sends the correct Content-Type header for the linked resources.
Examples
Incorrect: using charset on a <link> element
<linkrel="stylesheet"href="styles.css"charset="UTF-8">
<linkrel="stylesheet"href="print.css"charset="iso-8859-1">
Both of these will trigger the validation warning because the charset attribute is obsolete on <link>.
Correct: charset attribute removed
<linkrel="stylesheet"href="styles.css">
<linkrel="stylesheet"href="print.css">
Simply removing the attribute resolves the validation error. The server should handle encoding declaration instead.
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>Example Page</title>
<linkrel="stylesheet"href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Configuring the server to declare character encoding
Once you've removed the charset attribute, make sure your web server sends the proper Content-Type header with the linked resources. Most modern servers already default to UTF-8 for CSS and JavaScript files, but you can configure this explicitly.
Apache
Add the following to your .htaccess file or server configuration:
<FilesMatch "\.css$">
AddCharset UTF-8 .css
</FilesMatch>
This causes Apache to serve CSS files with the header Content-Type: text/css; charset=UTF-8.
Nginx
Add a charset directive to the relevant location block in your server configuration:
location ~* \.css$ {
charset utf-8;
}
Node.js (Express)
Set the Content-Type header explicitly when serving the file:
app.get('/styles.css',function(req,res){
res.setHeader('Content-Type','text/css; charset=UTF-8');
res.sendFile(__dirname+'/styles.css');
});
If you're using Express's built-in static file middleware (express.static), it typically sets correct content types automatically based on file extensions.
Verifying the fix
You can confirm that the server is sending the correct header by opening your browser's developer tools, navigating to the Network tab, clicking on the linked resource, and inspecting the Content-Type response header. It should include charset=UTF-8 (or whichever encoding your file uses). Once the attribute is removed from your HTML and the server is configured correctly, the validation warning will be resolved.
The charset attribute was historically used to declare the character encoding of an external script file when it differed from the document's encoding. In modern HTML, this attribute is obsolete because the HTML specification now requires that the character encoding of an external script must match the encoding of the document itself. Since the document's encoding is already declared via <meta charset="UTF-8"> (or through HTTP headers), specifying it again on individual <script> elements is redundant and no longer valid.
If your external script files use a different encoding than your HTML document, the correct solution is to convert those script files to match the document's encoding (typically UTF-8) rather than using the charset attribute. UTF-8 is the recommended encoding for all web content and is supported universally across browsers.
This matters for standards compliance — the HTML living standard explicitly marks this attribute as obsolete. It also affects maintainability, since having encoding declarations scattered across script tags can create confusion about which encoding is actually in effect. Browsers may also ignore the attribute entirely, leading to unexpected behavior if you're relying on it.
How to fix it
- Remove the
charsetattribute from all<script>elements. - Ensure your document declares its encoding using
<meta charset="UTF-8">inside the<head>. - Convert any script files that aren't already UTF-8 to use UTF-8 encoding. Most modern code editors can do this via "Save with Encoding" or a similar option.
- While you're at it, you can also remove
type="text/javascript"— this is the default type for scripts and is no longer needed.
Examples
Incorrect: using the obsolete charset attribute
<scriptsrc="app.js"type="text/javascript"charset="UTF-8"></script>
This triggers the validation warning because charset is obsolete. The type="text/javascript" is also unnecessary since it's the default.
Incorrect: charset on an inline script
<scriptcharset="UTF-8">
console.log("Hello");
</script>
The charset attribute was only ever meaningful for external scripts, and even in that context it is now obsolete.
Correct: clean script element
<scriptsrc="app.js"></script>
Correct: ensuring encoding is declared at the document level
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>My Page</title>
<scriptsrc="app.js"></script>
</head>
<body>
<p>Content here.</p>
</body>
</html>
The <meta charset="UTF-8"> declaration in the <head> covers the encoding for the entire document, including all linked scripts. No additional charset attributes are needed on individual elements.
Many HTML5 semantic elements come with built-in (implicit) ARIA roles defined in the WAI-ARIA specification. The <aside> element is one of these — it natively maps to the complementary role, which tells assistive technologies that the content is related to the main content but can stand on its own. When you explicitly add role="complementary" to an <aside>, you're stating something the browser already knows, which triggers this W3C validator warning.
While this redundancy won't break anything for end users, it creates unnecessary noise in your code and can signal a misunderstanding of how semantic HTML works. Keeping markup free of redundant ARIA attributes follows the first rule of ARIA use: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so." Clean, semantic HTML is easier to maintain and less prone to errors if ARIA roles are accidentally changed or conflict with the native semantics in the future.
This same principle applies to several other HTML elements, such as <nav> (implicit role navigation), <main> (implicit role main), <header> (implicit role banner when not nested), and <footer> (implicit role contentinfo when not nested).
Examples
❌ Redundant role on <aside>
The role="complementary" attribute is unnecessary because <aside> already implies it:
<asiderole="complementary">
<h2>Related Articles</h2>
<ul>
<li><ahref="/article-1">Understanding ARIA roles</a></li>
<li><ahref="/article-2">Semantic HTML best practices</a></li>
</ul>
</aside>
✅ Using <aside> without the redundant role
Simply remove the role attribute:
<aside>
<h2>Related Articles</h2>
<ul>
<li><ahref="/article-1">Understanding ARIA roles</a></li>
<li><ahref="/article-2">Semantic HTML best practices</a></li>
</ul>
</aside>
✅ When an explicit role is appropriate
If you need to give the <aside> element a different role than its default, an explicit role attribute is valid and useful. For example, you might use <aside> for structural reasons but assign it a different ARIA role:
<asiderole="note">
<p>This feature is only available in version 3.0 and later.</p>
</aside>
✅ Labeling multiple <aside> elements
If your page has multiple <aside> elements, you don't need to add role="complementary" to distinguish them. Instead, use aria-label or aria-labelledby to give each a unique accessible name:
<asidearia-label="Related articles">
<h2>Related Articles</h2>
<ul>
<li><ahref="/article-1">Understanding ARIA roles</a></li>
</ul>
</aside>
<asidearia-labelledby="ad-heading">
<h2id="ad-heading">Sponsored Content</h2>
<p>Check out our latest product.</p>
</aside>
The HTML specification maps certain elements to implicit ARIA roles. The <footer> element, when used as a direct child of <body> (i.e., not nested inside an <article>, <aside>, <main>, <nav>, or <section> element), automatically carries the contentinfo landmark role. This means screen readers and other assistive technologies already announce it as a content information landmark without any extra markup.
Adding role="contentinfo" to a <footer> element is redundant because:
- It duplicates built-in semantics. Browsers already expose the correct role to the accessibility tree. Repeating it adds no benefit and clutters your markup.
- It can cause confusion for developers. Seeing an explicit role might suggest the element doesn't have one by default, leading to misunderstandings about how HTML semantics work.
- It violates the first rule of ARIA use. The W3C's "Using ARIA" guide states: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so."
It's worth noting that when a <footer> is nested inside a sectioning element like <article> or <section>, it does not carry the contentinfo role — it maps to a generic role instead. In that context, adding role="contentinfo" would actually change the element's semantics rather than being redundant, though doing so is generally not appropriate since each page should have only one contentinfo landmark.
If you are working with a <div> that serves as a footer (perhaps in legacy code), the best approach is to replace it with a semantic <footer> element rather than applying role="contentinfo" to the <div>.
Examples
❌ Redundant role on <footer>
This triggers the validator warning because the role is already implicit:
<footerrole="contentinfo">
<p>© 2024 Example Corp. All rights reserved.</p>
</footer>
✅ Fixed: <footer> without redundant role
Simply remove the role="contentinfo" attribute:
<footer>
<p>© 2024 Example Corp. All rights reserved.</p>
</footer>
✅ Using a <div> as a footer (legacy pattern)
If you cannot use a <footer> element for some reason, applying role="contentinfo" to a <div> is valid and meaningful since the <div> has no implicit role:
<divrole="contentinfo">
<p>© 2024 Example Corp. All rights reserved.</p>
</div>
However, replacing the <div> with a <footer> is always the preferred approach.
✅ Nested footer inside a section
When <footer> appears inside a sectioning element, it does not carry the contentinfo role. No explicit role is needed here either — it simply represents footer content for that section:
<article>
<h2>Blog Post Title</h2>
<p>Article content here.</p>
<footer>
<p>Published on January 1, 2024</p>
</footer>
</article>
The <dialog> element triggers an informational warning from the W3C validator, not an error. The markup is valid HTML, and no code change is needed to resolve this message.
The <dialog> element is a native HTML element for modal and non-modal dialog boxes. It was part of the HTML specification for years before all major browsers implemented it. As of 2022, all major browsers (Chrome, Firefox, Safari, and Edge) support <dialog> natively. The W3C validator warning is outdated for most practical purposes, since browser support is now widespread.
The <dialog> element provides built-in features that are difficult to replicate with custom markup: a showModal() method that creates a top-layer modal with a backdrop, automatic focus trapping, and Esc key dismissal. It also exposes the correct ARIA role (dialog) by default, which improves accessibility without extra attributes.
If you still need to support older browsers (such as Internet Explorer or pre-2022 Safari), you can include the dialog-polyfill library. Otherwise, the warning can be safely ignored.
HTML example
<dialogid="confirm">
<formmethod="dialog">
<p>Are you sure?</p>
<buttonvalue="no">Cancel</button>
<buttonvalue="yes">Confirm</button>
</form>
</dialog>
<buttononclick="document.getElementById('confirm').showModal()">
Open dialog
</button>
Using method="dialog" on the form allows the dialog to close automatically when a button inside it is clicked, with the button's value available through the dialog's returnValue property.
The <dialog> element was introduced to provide a native way to create modal and non-modal dialog boxes in HTML. As defined in the WHATWG HTML Living Standard and the ARIA in HTML specification, every <dialog> element automatically carries an implicit dialog role. This means assistive technologies like screen readers already recognize it as a dialog without any additional ARIA markup.
When you explicitly add role="dialog" to a <dialog> element, you're restating what the browser and assistive technologies already know. This violates the first rule of ARIA use: do not use ARIA if you can use a native HTML element or attribute with the semantics already built in. While this redundancy won't break functionality, it clutters your markup and signals to other developers (and validators) that the author may not understand the element's built-in semantics.
This principle applies broadly across HTML. Many elements have implicit ARIA roles — <nav> has navigation, <main> has main, <button> has button, and so on. Adding the matching role explicitly to any of these elements produces a similar validator warning.
How to fix it
Simply remove the role="dialog" attribute from the <dialog> element. The built-in semantics handle everything automatically. If you need to provide additional context for assistive technologies, consider using aria-label or aria-labelledby to give the dialog a descriptive accessible name — that's genuinely useful supplementary information rather than a redundant role.
Examples
Incorrect: redundant role attribute
<dialogrole="dialog">
<h2>Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
This triggers the validator warning because role="dialog" duplicates the implicit role of the <dialog> element.
Correct: relying on implicit semantics
<dialog>
<h2>Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
Correct: adding a descriptive accessible name
<dialogaria-labelledby="dialog-title">
<h2id="dialog-title">Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
Using aria-labelledby to associate the dialog with its heading is a meaningful enhancement — it gives the dialog an accessible name that screen readers announce when the dialog opens. This is the kind of ARIA usage that genuinely improves accessibility, as opposed to redundantly restating the element's role.
The HTML specification restricts what can appear inside a comment. Specifically, a comment must not contain the string -- (two consecutive hyphens) except as part of the opening and closing delimiters. This rule originates from SGML and XML 1.0, where -- is treated as a comment delimiter, and having extra occurrences inside the comment body creates ambiguity about where the comment starts and ends.
While most modern browsers are lenient and will handle comments with double hyphens without issues, the markup is technically invalid. This matters for several reasons:
- XML compatibility: If your HTML is served as XHTML or processed by XML parsers, double hyphens inside comments will cause parsing errors and potentially break the entire document.
- Standards compliance: The HTML living standard explicitly states that comments must not contain
--, so validators flag this as an error. - Predictable parsing: Different parsers may interpret malformed comments differently, leading to inconsistent behavior across tools, crawlers, and assistive technologies.
To fix the issue, look inside your comment text and replace any occurrence of -- with something else, such as a single hyphen, an equals sign, or a different separator.
Examples
Incorrect: double hyphens inside the comment body
<!-- This is a separator ---------- end of section -->
The string of hyphens inside the comment contains multiple consecutive -- pairs, making the comment invalid.
<!-- Do not use -- as a separator -->
Here, -- appears literally in the comment text, which violates the rule.
<!-- TODO -- fix this later -- maybe next sprint -->
Multiple -- sequences appear as casual text separators, each one triggering the validation error.
Correct: avoiding double hyphens
Replace double hyphens with a different character or pattern:
<!-- This is a separator ========== end of section -->
<!-- Do not use dashes as a separator -->
<!-- TODO: fix this later, maybe next sprint -->
Correct: standard comment syntax
Simple comments that don't contain -- in their body are perfectly valid:
<!-- This is a valid comment -->
<!-- Multi-line comments are fine too, as long as they don't contain double hyphens. -->
Multi-line comments are fine too,
as long as they don't contain double hyphens.
-->
Common pitfall: decorative comment borders
Developers sometimes use hyphens to create visual separators in their source code:
<!-- -------------------------------- -->
<!-- Navigation Section -->
<!-- -------------------------------- -->
Replace these with a different character:
<!-- ================================ -->
<!-- Navigation Section -->
<!-- ================================ -->
Or simply remove the decorative lines:
<!-- Navigation Section -->
The WAI-ARIA specification defines strict rules about which elements can be children of interactive roles. An element with role="button" is treated as an interactive control, and the <a> element (when it has an href) is also an interactive control. Nesting one interactive element inside another creates what's known as an interactive content nesting violation. Screen readers and other assistive technologies cannot reliably determine user intent when they encounter this pattern — should they announce a button, a link, or both? The result is unpredictable behavior that can make your content inaccessible.
This issue commonly appears when developers wrap links inside styled containers that have been given role="button", or when using component libraries that apply button semantics to wrapper elements containing anchor tags.
Beyond accessibility, browsers themselves handle nested interactive elements inconsistently. Some may ignore the outer interactive role, while others may prevent the inner link from functioning correctly. This makes the pattern unreliable even for users who don't rely on assistive technologies.
How to fix it
There are several approaches depending on your intent:
- If the element should navigate to a URL, use an
<a>element and style it to look like a button. Remove therole="button"from the parent or eliminate the parent wrapper entirely. - If the element should perform an action (not navigation), use a
<button>element or an element withrole="button", and handle the action with JavaScript. Remove the nested<a>tag. - If you need both a button container and a link, flatten the structure so they are siblings rather than nested.
When using role="button" on a non-button element, remember that you must also handle keyboard interaction (Enter and Space key presses) and include tabindex="0" to make it focusable.
Examples
❌ Incorrect: link nested inside a button role
<divrole="button">
<ahref="/dashboard">Go to Dashboard</a>
</div>
This triggers the validation error because the <a> is a descendant of an element with role="button".
✅ Fix 1: Use a styled link instead
If the intent is navigation, remove the button role and let the <a> element do the job. Style it to look like a button with CSS.
<ahref="/dashboard"class="btn">Go to Dashboard</a>
This is the simplest and most semantically correct fix when the purpose is to navigate the user to another page.
✅ Fix 2: Use a real button for actions
If the intent is to trigger an action (not navigate), replace the entire structure with a <button> element.
<buttontype="button"class="btn"onclick="navigateToDashboard()">
Go to Dashboard
</button>
✅ Fix 3: Use a div with role="button" without nested interactive elements
If you need a custom button using role="button", make sure it contains no interactive descendants.
<divrole="button"tabindex="0">
Go to Dashboard
</div>
When using this approach, you must also add keyboard event handlers for Enter and Space to match native button behavior.
❌ Incorrect: link inside a button element
The same principle applies to native <button> elements, not just elements with role="button":
<button>
<ahref="/settings">Settings</a>
</button>
✅ Fixed: choose one interactive element
<ahref="/settings"class="btn">Settings</a>
❌ Incorrect: deeply nested link
The error applies to any level of nesting, not just direct children:
<divrole="button">
<spanclass="icon-wrapper">
<ahref="/help">Help</a>
</span>
</div>
✅ Fixed: flatten the structure
<ahref="/help"class="btn">
<spanclass="icon-wrapper">Help</span>
</a>
As a rule of thumb, every interactive element in your page should have a single, clear role. If something looks like a button but navigates to a URL, make it an <a> styled as a button. If it performs an in-page action, make it a <button>. Keeping these roles distinct ensures your HTML is valid, accessible, and behaves consistently across browsers and assistive technologies.
The HTML living standard defines the content model of the <a> element as "transparent," meaning it can contain whatever its parent element allows — with one critical exception: it must not contain any interactive content, which includes other <a> elements, <button>, <input>, <select>, <textarea>, and similar elements. This restriction exists at every level of nesting, not just direct children. If an <a> appears anywhere inside another <a>, even deeply nested within <div> or <span> elements, the markup is invalid.
Why This Is a Problem
Unpredictable browser behavior: When browsers encounter nested anchors, they don't agree on how to handle them. Most browsers will attempt to "fix" the invalid markup by automatically closing the outer <a> before opening the inner one, but the resulting DOM structure may not match your intentions at all. This means your page layout, styling, and link targets can all break in unexpected ways.
Accessibility failures: Screen readers rely on the DOM tree to announce links to users. Nested anchors create ambiguous link boundaries — assistive technology may announce the wrong link text, skip links entirely, or confuse users about which link they're activating. Keyboard navigation can also become unreliable, since tab order depends on a well-formed link structure.
Broken click targets: When links overlap, it's unclear which link should activate when a user clicks the shared area. This results in a poor user experience where clicks may navigate to the wrong destination.
Common Causes
This error often arises in a few typical scenarios:
- Card components where the entire card is wrapped in an
<a>, but individual elements inside (like a title or button) also need their own links. - Navigation menus generated by CMS platforms or templating systems that accidentally produce nested link structures.
- Copy-paste mistakes where anchor tags get duplicated during content editing.
How to Fix It
The core fix is straightforward: ensure no <a> element exists as a descendant of another <a> element. Depending on your situation, you can:
- Separate the links so they are siblings rather than nested.
- Remove the outer link if only the inner link is needed.
- Use CSS and JavaScript for card-style patterns where the entire container needs to be clickable but inner links must remain independent.
Examples
Incorrect: Nested Anchors
<ahref="/products">
Browse our <ahref="/products/new">new arrivals</a> today
</a>
This is invalid because the inner <a> is a descendant of the outer <a>.
Fix: Separate the Links
<p>
<ahref="/products">Browse our products</a> or check out our
<ahref="/products/new">new arrivals</a> today.
</p>
Incorrect: Clickable Card with a Nested Link
<ahref="/post/123"class="card">
<h2><ahref="/post/123">Article Title</a></h2>
<p>A brief summary of the article content.</p>
</a>
Fix: Remove the Redundant Inner Link
If both links point to the same destination, simply remove the inner one:
<ahref="/post/123"class="card">
<h2>Article Title</h2>
<p>A brief summary of the article content.</p>
</a>
Fix: Card with Multiple Distinct Links
When a card needs an overall clickable area and independent inner links, avoid wrapping everything in an <a>. Instead, use CSS positioning to stretch the primary link over the card:
<divclass="card">
<h2><ahref="/post/123"class="card-link">Article Title</a></h2>
<p>A brief summary of the article content.</p>
<p>Published by <ahref="/author/jane">Jane Doe</a></p>
</div>
.card{
position: relative;
}
.card-link::after{
content:"";
position: absolute;
inset:0;
}
.carda:not(.card-link){
position: relative;
z-index:1;
}
This approach makes the entire card clickable via the stretched ::after pseudo-element on the primary link, while the author link remains independently clickable above it — all without nesting any <a> elements.
Incorrect: Deeply Nested Anchor
The restriction applies at any depth, not just direct children:
<ahref="/page">
<div>
<span>
<ahref="/other">Nested link</a>
</span>
</div>
</a>
Fix: Restructure to Avoid Nesting
<div>
<ahref="/page">Main page link</a>
<span>
<ahref="/other">Other link</a>
</span>
</div>
Always verify that your final markup contains no <a> element inside another <a>, regardless of how many elements sit between them. Running your HTML through the W3C validator after making changes will confirm the issue is resolved.
The HTML specification defines the <button> element's content model as "phrasing content" but explicitly excludes interactive content. Since the <a> element (when it has an href attribute) is classified as interactive content, nesting it inside a <button> violates this rule. The same restriction applies to any element that has role="button", as it semantically functions as a button.
This is problematic for several reasons:
- Accessibility: Screen readers and assistive technologies cannot reliably convey the purpose of nested interactive elements. A user tabbing through the page may encounter confusing or duplicate focus targets, and the intended action becomes ambiguous.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Clicking the link inside a button might trigger the button's click handler, the link's navigation, both, or neither — depending on the browser.
- Standards compliance: The HTML specification forbids this nesting to ensure a clear, unambiguous interaction model for all users and user agents.
To fix this, decide what the element should do. If it navigates to a URL, use an <a> element (styled as a button if needed). If it performs an action like submitting a form or toggling something, use a <button>. If you need both behaviors, handle navigation programmatically via JavaScript on a <button>, or separate them into two distinct elements.
Examples
❌ Incorrect: link nested inside a button
<button>
<ahref="/dashboard">Go to Dashboard</a>
</button>
❌ Incorrect: link inside an element with role="button"
<divrole="button">
<ahref="/settings">Settings</a>
</div>
✅ Correct: use an anchor styled as a button
If the goal is navigation, use an <a> element and style it to look like a button with CSS:
<ahref="/dashboard"class="button">Go to Dashboard</a>
.button{
display: inline-block;
padding:8px16px;
background-color:#007bff;
color:#fff;
text-decoration: none;
border-radius:4px;
cursor: pointer;
}
✅ Correct: use a button with JavaScript navigation
If you need button semantics but also want to navigate, handle the navigation in JavaScript:
<buttontype="button"onclick="location.href='/dashboard'">
Go to Dashboard
</button>
✅ Correct: separate the two elements
If both a button action and a link are genuinely needed, place them side by side:
<buttontype="button">Save</button>
<ahref="/dashboard">Go to Dashboard</a>
✅ Correct: link without href inside a button (edge case)
An <a> element without an href attribute is not interactive content, so it is technically valid inside a <button>. However, this is rarely useful in practice:
<buttontype="button">
<a>Label text</a>
</button>
As a general rule, never nest one clickable element inside another. This applies not only to <a> inside <button>, but also to other combinations like <button> inside <a>, or <a> inside <a>. Keeping interactive elements separate ensures predictable behavior and a good experience for all users.
An <a> element cannot be placed inside a <button> element because interactive content must not be nested within other interactive content.
The HTML specification forbids nesting clickable elements inside other clickable elements. Both <a> and <button> are interactive content, meaning they each expect to receive user interaction independently. When you nest one inside the other, browsers can't determine which element should handle the click, leading to unpredictable behavior and accessibility problems.
Screen readers and keyboard navigation also struggle with nested interactive elements. A user tabbing through the page may not be able to reach or activate the inner link, or may trigger the wrong action entirely.
To fix this, you have two main options: use a styled <a> element that looks like a button, or use a <button> with JavaScript to handle navigation.
Invalid Example
<button>
<ahref="/dashboard">Go to Dashboard</a>
</button>
Fixed Examples
Option 1: Style the link as a button
This is the preferred approach when the purpose is navigation.
<ahref="/dashboard"class="btn">Go to Dashboard</a>
<style>
.btn{
display: inline-block;
padding:8px16px;
background-color:#007bff;
color:#fff;
text-decoration: none;
border-radius:4px;
border: none;
cursor: pointer;
}
</style>
Option 2: Use a button with JavaScript
This works when you need button semantics but also want navigation.
<buttontype="button"onclick="location.href='/dashboard'">
Go to Dashboard
</button>
Option 1 is generally better for navigation because <a> elements communicate the correct intent to assistive technologies and allow standard browser behaviors like right-click "Open in new tab."
The role="button" attribute tells assistive technologies like screen readers that an element behaves as a button — a widget used to perform actions such as submitting a form, opening a dialog, or triggering a command. When a <button> element appears inside an element with role="button", the result is a nested interactive control. The HTML specification explicitly forbids this because interactive content must not be nested within other interactive content.
This nesting causes real problems. Screen readers may announce the outer element as a button but fail to recognize or reach the inner <button>. Keyboard users may not be able to focus on or activate the inner control. Different browsers handle the situation inconsistently — some may ignore one of the controls entirely, others may fire events on the wrong element. The end result is an interface that is broken for many users.
This issue commonly arises in a few scenarios:
- A
<div>or<span>is givenrole="button"and then a<button>is placed inside it for styling or click-handling purposes. - A component library wraps content in a
role="button"container, and a developer adds a<button>inside without realizing the conflict. - A custom card or list item is made clickable with
role="button", but also contains action buttons within it.
The fix depends on your intent. If the outer element is the intended interactive control, remove the inner <button> and handle interactions on the outer element. If the inner <button> is the intended control, remove role="button" from the ancestor. If both need to be independently clickable, restructure the markup so neither is a descendant of the other.
Examples
❌ Incorrect: <button> inside an element with role="button"
<divrole="button"tabindex="0"onclick="handleClick()">
<buttontype="button">Click me</button>
</div>
This is invalid because the <button> is a descendant of the <div> that has role="button".
✅ Fix option 1: Use only the <button> element
If the inner <button> is the actual control, remove role="button" from the wrapper:
<div>
<buttontype="button"onclick="handleClick()">Click me</button>
</div>
✅ Fix option 2: Use only the outer role="button" element
If the outer element is the intended interactive control, remove the inner <button>:
<divrole="button"tabindex="0"onclick="handleClick()">
Click me
</div>
Note that when using role="button" on a non-<button> element, you must also handle keyboard events (Enter and Space) manually. A native <button> provides this for free, so prefer option 1 when possible.
❌ Incorrect: Clickable card containing action buttons
<divrole="button"tabindex="0"class="card">
<h3>Item title</h3>
<p>Description text</p>
<buttontype="button">Delete</button>
</div>
✅ Fix: Separate the card link from the action buttons
<divclass="card">
<h3><buttontype="button"class="card-link">Item title</button></h3>
<p>Description text</p>
<buttontype="button">Delete</button>
</div>
In this approach, the card's main action is handled by a <button> on the title, while the "Delete" button remains an independent control. Neither is nested inside the other, and both are accessible to keyboard and screen reader users.
The HTML living standard defines both <a> and <button> as interactive content. Interactive content elements cannot be descendants of other interactive content elements. When you place a <button> inside an <a>, you create an ambiguous situation: should a click activate the link navigation or the button action? Browsers handle this inconsistently, which leads to unpredictable behavior for all users.
This is especially problematic for accessibility. Screen readers and other assistive technologies rely on a clear, well-defined element hierarchy to communicate the purpose of controls to users. A button nested inside a link creates a confusing experience — the user may hear both a link and a button announced, with no clear indication of what will actually happen when they activate it. Keyboard navigation can also break, as focus behavior becomes unreliable.
The same rule applies to elements that aren't literally <button> but carry role="button". For example, a <span role="button"> inside an <a> tag triggers the same validation error, because the ARIA role makes it semantically interactive.
How to Fix It
The fix depends on what you're trying to achieve:
- If the element should navigate to a URL, use an
<a>element and style it to look like a button with CSS. Remove the<button>entirely. - If the element should perform a JavaScript action, use a
<button>element and remove the wrapping<a>. Attach the action via an event listener. - If you need both a link and a button, place them side by side as siblings rather than nesting one inside the other.
Examples
❌ Invalid: Button inside a link
<ahref="/dashboard">
<button>Go to Dashboard</button>
</a>
✅ Fixed: Link styled as a button
If the goal is navigation, use a link and style it with CSS:
<ahref="/dashboard"class="btn">Go to Dashboard</a>
.btn{
display: inline-block;
padding:8px16px;
background-color:#007bff;
color:#fff;
text-decoration: none;
border-radius:4px;
}
✅ Fixed: Button with a JavaScript action
If the goal is to trigger an action (like navigating programmatically), use a button on its own:
<buttontype="button"onclick="window.location.href='/dashboard'">
Go to Dashboard
</button>
❌ Invalid: Element with role="button" inside a link
<ahref="/settings">
<spanrole="button">Settings</span>
</a>
✅ Fixed: Remove the redundant role
Since the <a> element already communicates interactivity, the inner role="button" is unnecessary and conflicting. Simply use the link directly:
<ahref="/settings">Settings</a>
❌ Invalid: Link inside a button
Note that the reverse — an <a> inside a <button> — is also invalid for the same reason:
<button>
<ahref="/home">Home</a>
</button>
✅ Fixed: Choose one element
<ahref="/home"class="btn">Home</a>
The key principle is simple: never nest one interactive element inside another. Pick the element that best matches the semantics of your use case — <a> for navigation, <button> for actions — and use CSS to achieve the visual design you need.
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries