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.
The <param> element is obsolete in HTML5 and should no longer be used inside <object> elements.
The <param> element was originally used to pass named parameters to plugins embedded via <object>, such as Flash, Java applets, or Windows Media Player. Since modern browsers have dropped support for these plugins, the <param> element lost its purpose and was removed from the HTML living standard.
If the <object> element references an external resource like a video, PDF, or image, the resource URL belongs in the data attribute of the <object> element itself. The type attribute should specify the MIME type of the resource.
For media playback, the <video> and <audio> elements are the standard replacements. For other embedded content, <iframe> or <embed> may be more appropriate depending on the use case.
HTML examples
Before: obsolete param element
<object>
<param name="movie" value="video.mp4">
<param name="autoplay" value="true">
</object>
After: using the data attribute on object
<object data="video.mp4" type="video/mp4">
<p>Your browser does not support this content.</p>
</object>
After: using video for media playback
<video src="video.mp4" controls>
<p>Your browser does not support this video.</p>
</video>
The role="presentation" on this element is ignored because the element also carries a global ARIA attribute, such as aria-label or aria-describedby.
role="presentation" (and its synonym role="none") removes an element's implicit semantics so assistive technologies treat it as if it were plain content. Global ARIA states and properties, such as aria-label, aria-describedby, aria-live, and aria-hidden, are allowed on any element regardless of its role. When one of them is present, the browser cannot silence the element, because that attribute needs a role to attach to. The ARIA specification resolves this conflict by ignoring the presentation role and exposing the element's implicit role instead.
The result is that role="presentation" does nothing here, which is almost always a mistake. Decide which of the two you actually want.
If the element should stay presentational, remove the global ARIA attribute. If you need the ARIA attribute, remove role="presentation" and let the element keep its semantics.
Invalid example
The aria-label cancels the presentation role, so the <div> is not silenced:
<div role="presentation" aria-label="Main navigation">
...
</div>
Valid example
Keep the label and drop the ineffective role:
<div aria-label="Main navigation">
...
</div>
Or, if the element was only meant to be presentational, remove the ARIA attribute:
<div role="presentation">
...
</div>
The role="radio" attribute is redundant on an <input type="radio"> element because the browser already exposes this element with the radio role to assistive technologies.
Screen readers and other assistive tools determine an element's purpose through its implicit ARIA role. The <input type="radio"> element has an implicit role of radio as defined in the ARIA in HTML specification. Adding role="radio" explicitly just repeats what the browser already communicates, and the W3C validator flags this as unnecessary.
Redundant roles add clutter to your markup without any accessibility benefit. In some edge cases, explicitly setting a role that matches the implicit one can even cause unexpected behavior in certain browser and screen reader combinations. The general rule: don't set a role on an element that already has that same role by default.
This applies to many other elements too. For example, <button role="button">, <a href="..." role="link">, and <nav role="navigation"> are all similarly redundant.
HTML examples
Before: redundant role
<label>
<input type="radio" name="color" value="red" role="radio">
Red
</label>
<label>
<input type="radio" name="color" value="blue" role="radio">
Blue
</label>
After: role removed
<label>
<input type="radio" name="color" value="red">
Red
</label>
<label>
<input type="radio" name="color" value="blue">
Blue
</label>
Remove the role="radio" attribute. The element already communicates its role to assistive technologies without it.
The HTML specification and WAI-ARIA guidelines establish that certain HTML elements carry implicit landmark roles. The <section> element implicitly maps to role="region", meaning assistive technologies like screen readers already recognize it as a region landmark without any additional ARIA markup. This principle is captured by 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."
Adding role="region" to a <section> doesn't change the element's behavior or how assistive technologies interpret it — it simply duplicates what the browser already communicates. The W3C Validator warns about this redundancy to encourage cleaner, more maintainable markup and to help developers understand native HTML semantics.
This same principle applies to other HTML elements with implicit roles: <nav> has an implicit role="navigation", <main> has role="main", <aside> has role="complementary", <header> has role="banner" (when not nested in a sectioning element), and <footer> has role="contentinfo" (when not nested in a sectioning element). Adding these explicit roles to their corresponding elements will trigger similar validator warnings.
It's worth noting that a <section> element is only exposed as a region landmark by assistive technologies when it has an accessible name. If your <section> doesn't have an accessible name (via aria-label, aria-labelledby, or similar mechanisms), screen readers may not treat it as a navigable landmark — but this still doesn't mean you should add role="region", since the implicit role mapping remains the same regardless.
How to fix it
- Remove the
role="region"attribute from any<section>element. - If you want the section to be a meaningful landmark for screen reader users, give it an accessible name using
aria-labelledby(pointing to a heading) oraria-label. - Never add explicit ARIA roles that duplicate the implicit role of a native HTML element.
Examples
Incorrect: redundant role on section
<section role="region">
<h2>Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section without redundant role
<section>
<h2>Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section with an accessible name for landmark navigation
Using aria-labelledby to associate the section with its heading ensures assistive technologies expose it as a named landmark region:
<section aria-labelledby="contact-heading">
<h2 id="contact-heading">Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section with aria-label when no visible heading exists
<section aria-label="Contact information">
<p>Email us at info@example.com</p>
</section>
Incorrect: redundant roles on other landmark elements
The same principle applies to other native landmark elements. Avoid these patterns:
<nav role="navigation">
<a href="/">Home</a>
</nav>
<main role="main">
<p>Page content</p>
</main>
<aside role="complementary">
<p>Related links</p>
</aside>
Correct: landmark elements without redundant roles
<nav>
<a href="/">Home</a>
</nav>
<main>
<p>Page content</p>
</main>
<aside>
<p>Related links</p>
</aside>
A <tr> element already has an implicit ARIA role of row, so adding role="row" is redundant when the parent <table> uses its default semantics or has a role of table, grid, or treegrid.
HTML tables come with built-in accessibility semantics. The <table> element implicitly has role="table", and <tr> implicitly has role="row". Browsers and assistive technologies already understand this structure, so explicitly adding these roles is unnecessary and flagged by the W3C validator.
The only time you'd need to add a role to a <tr> is when the table's native semantics have been overridden — for example, if the <table> has been repurposed with a non-table role like role="presentation" or role="none". In that case, you'd need explicit ARIA roles to restore row semantics.
Incorrect Example
<table>
<tr role="row">
<th>Name</th>
<th>Email</th>
</tr>
<tr role="row">
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Fixed Example
Simply remove the redundant role="row" from the <tr> elements:
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
The same fix applies if your <table> explicitly has role="table", role="grid", or role="treegrid" — the <tr> elements still don't need an explicit role="row" because the browser infers it automatically.
The role attribute is not allowed on a label element when that label is associated with a form control (a labelable element) through the for attribute or by nesting.
When a label is associated with a form control, the browser already understands its purpose — it's a label. Adding a role attribute overrides this native semantics, which is redundant at best and confusing for assistive technologies at worst.
A label becomes "associated" with a labelable element in two ways: explicitly via the for attribute pointing to the control's id, or implicitly by wrapping the control inside the label. Labelable elements include input (except type="hidden"), select, textarea, button, meter, output, and progress.
If the label is associated, simply remove the role attribute. The native semantics are already correct and sufficient.
If you truly need a custom role for some reason and the label is not functionally labeling a control, you can disassociate it by removing the for attribute or unnesting the control — but this is rarely the right approach.
Invalid Example
<label for="email" role="presentation">Email</label>
<input type="email" id="email">
Valid Example
<label for="email">Email</label>
<input type="email" id="email">
In older HTML specifications (HTML 4.01), the scheme attribute was used to provide additional context for interpreting the content value of a <meta> element. It told browsers or metadata processors which encoding scheme, format, or vocabulary applied to the metadata. For example, you could specify that a date followed the W3CDTF format or that a subject classification used a particular taxonomy.
HTML5 dropped the scheme attribute because it was rarely used by browsers and its purpose was better served by making the scheme part of the metadata value itself. The WHATWG HTML living standard does not recognize scheme as a valid attribute on <meta>, so including it will produce a validation error. Keeping obsolete attributes in your markup can cause confusion for developers maintaining the code and signals outdated practices that may accompany other compatibility issues.
This issue most commonly appears in documents that use Dublin Core Metadata Initiative (DCMI) metadata, which historically relied on scheme to indicate the encoding format for dates, identifiers, and subject classifications.
How to fix it
There are several approaches depending on your use case:
- Simply remove the
schemeattribute if the format is already clear from context (e.g., ISO 8601 dates are universally understood). - Incorporate the scheme into the
nameattribute by using a more specific property name that implies the scheme. - Include the scheme declaration in the
contentvalue so the format information is preserved within the value itself.
For Dublin Core metadata specifically, the modern recommended approach is to use the DCTERMS namespace with RDFa or to simply drop the scheme attribute, since most date formats like YYYY-MM-DD are unambiguous.
Examples
Obsolete: using the scheme attribute
This triggers the validation error because scheme is not a valid attribute in HTML5:
<meta name="DC.Date.Created" scheme="W3CDTF" content="2009-11-30">
Another common example with subject classification:
<meta name="DC.Subject" scheme="LCSH" content="Web development">
Fixed: removing the scheme attribute
If the value format is self-evident (as with ISO 8601 dates), simply remove scheme:
<meta name="DC.Date.Created" content="2009-11-30">
Fixed: incorporating the scheme into the value
When the scheme information is important for processors to understand the value, embed it in the content attribute:
<meta name="DC.Subject" content="LCSH: Web development">
Fixed: using a more specific property name
You can make the scheme implicit by using a more descriptive name value:
<meta name="DCTERMS.created" content="2009-11-30">
Fixed: using RDFa for richer metadata
For documents that require precise, machine-readable metadata with explicit schemes, consider using RDFa attributes instead of the obsolete scheme:
<meta property="dcterms:created" content="2009-11-30">
This approach is compatible with HTML5 and provides the same semantic richness that the scheme attribute was originally designed to offer.
The scope attribute tells browsers and assistive technologies how a header cell relates to the data cells around it. Its valid values are col, row, colgroup, and rowgroup. In older versions of HTML, scope was permitted on <td> elements, but the current HTML Living Standard restricts it to <th> elements only. When the W3C validator encounters scope on a <td>, it flags it as obsolete.
This matters for several reasons. First, if a cell acts as a header for other cells, it should be marked up as a <th>, not a <td>. Using <td scope="row"> sends conflicting signals — the element says "I'm a data cell" while the attribute says "I'm a header for this row." Second, screen readers rely on proper <th> elements with scope to announce table relationships. A <td> with scope may not be interpreted correctly, making the table harder to navigate for users of assistive technology. Third, using obsolete attributes means your markup doesn't conform to current standards, which could lead to unpredictable behavior in future browsers.
The fix is straightforward: if a cell has a scope attribute, it's acting as a header and should be a <th> element. Change the <td> to <th> and keep the scope attribute. If the cell is genuinely a data cell and not a header, remove the scope attribute entirely and leave it as a <td>.
Examples
Incorrect: scope on a <td> element
<table>
<tr>
<td scope="col">Name</td>
<td scope="col">Role</td>
</tr>
<tr>
<td scope="row">Alice</td>
<td>Engineer</td>
</tr>
</table>
This triggers the validation error because scope is used on <td> elements. The first row contains column headers and the first column contains row headers, yet they are all marked as data cells.
Correct: scope on <th> elements
<table>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
</tr>
<tr>
<th scope="row">Alice</th>
<td>Engineer</td>
</tr>
</table>
Now the header cells are correctly marked with <th>, and the scope attribute is valid on each one. Screen readers can properly associate "Alice" with "Engineer" and announce the column header "Role" when navigating to that cell.
A more complete table example
<table>
<thead>
<tr>
<th scope="col">Day</th>
<th scope="col">Morning</th>
<th scope="col">Afternoon</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Monday</th>
<td>Meeting</td>
<td>Code review</td>
</tr>
<tr>
<th scope="row">Tuesday</th>
<td>Workshop</td>
<td>Planning</td>
</tr>
</tbody>
</table>
Here, scope="col" on the column headers in <thead> tells assistive technology that "Day," "Morning," and "Afternoon" each apply to the cells below them. scope="row" on "Monday" and "Tuesday" indicates they apply to the cells in their respective rows. Every scope attribute sits on a <th>, so the markup is valid and accessible.
When to remove scope instead
If the cell truly contains data and isn't a header, simply remove the scope attribute:
<!-- Before (invalid) -->
<td scope="row">Some data</td>
<!-- After (valid) -->
<td>Some data</td>
Only add scope when a cell genuinely serves as a header. If it does, make it a <th>. If it doesn't, leave it as a plain <td> without scope.
Every HTML element carries an implicit ARIA role based on its type and attributes. For <input type="search"> elements that do not have a list attribute, the browser automatically exposes the element with the searchbox role to assistive technologies. This mapping is defined in the ARIA in HTML specification, which establishes the correspondence between native HTML semantics and ARIA roles.
When you explicitly add role="searchbox" to an element that already carries that role implicitly, the validator raises a warning because the attribute is doing nothing useful. While it won't break functionality, redundant roles clutter your markup and can signal to other developers (or future you) that something special is intended when it isn't. Following the general principle of ARIA — "don't use ARIA if you can use native HTML" — also means not restating what the browser already communicates.
Note the distinction the validator makes: this applies specifically to <input type="search"> elements without a list attribute. When a list attribute is present (linking the input to a <datalist>), the implicit role changes to combobox, so in that specific scenario the implicit role is different. However, for a plain search input without list, the searchbox role is already baked in.
Why it matters
- Standards compliance: The W3C validator flags redundant roles to encourage clean, semantic markup that relies on native HTML behavior.
- Maintainability: Redundant attributes add noise. Other developers may wonder why the role was explicitly set and whether removing it would break something.
- ARIA best practices: The first rule of ARIA is to use native HTML semantics whenever possible. Restating implicit roles goes against this principle and can mask situations where an explicit role would actually be meaningful.
How to fix it
Simply remove the role="searchbox" attribute from any <input type="search"> element that does not have a list attribute. The browser and assistive technologies will continue to treat it as a search box.
Examples
Incorrect — redundant role
The role="searchbox" is unnecessary here because <input type="search"> already implies it:
<label for="site-search">Search the site:</label>
<input type="search" id="site-search" role="searchbox" placeholder="Search...">
Correct — relying on implicit role
Remove the redundant role attribute and let native HTML semantics do the work:
<label for="site-search">Search the site:</label>
<input type="search" id="site-search" placeholder="Search...">
Correct — explicit role when implicit role differs
When a list attribute is present, the implicit role changes to combobox. If you want assistive technologies to treat it as a searchbox instead, an explicit role is justified:
<label for="city-search">Search cities:</label>
<input type="search" id="city-search" list="cities" role="searchbox">
<datalist id="cities">
<option value="Amsterdam">
<option value="Berlin">
<option value="Cairo">
</datalist>
In this case, the validator will not flag the role as redundant because the implicit role (combobox) differs from the explicitly set role (searchbox).
The <select> element provides a menu of options for the user. By default, it operates as a single-selection control — the user can pick exactly one option from the list. The selected attribute on an <option> element indicates which option should be pre-selected when the page loads. When two or more <option> elements have the selected attribute inside a single-choice <select>, this creates an invalid and contradictory state: the browser is told to pre-select multiple items in a control that only supports one selection.
When browsers encounter this contradiction, their behavior is inconsistent. Most will silently pick the last <option> marked as selected and ignore the others, but this is not guaranteed by the specification. Relying on undefined behavior leads to unpredictable results across browsers and can confuse both users and developers about which value will actually be submitted with a form.
From an accessibility standpoint, assistive technologies may announce the selected state of options to users. Multiple selected attributes on a single-choice <select> can cause screen readers to provide misleading or confusing information about which option is currently active.
The HTML specification (WHATWG) explicitly states that if the multiple attribute is absent, no more than one <option> descendant of the <select> may have the selected attribute.
How to fix it
You have two options depending on your intent:
- If only one option should be pre-selected: Remove the
selectedattribute from all but one<option>. This keeps the<select>as a standard single-choice dropdown. - If multiple options should be pre-selected: Add the
multipleattribute to the<select>element. This changes the control from a dropdown to a list box where users can select multiple items (typically by holding Ctrl or Cmd while clicking). Keep in mind that this changes the visual appearance and interaction model of the control, so make sure it fits your design and use case.
Examples
Incorrect: multiple selected options without multiple
This triggers the validation error because two options are marked as selected in a single-choice <select>:
<select name="color">
<option value="red" selected>Red</option>
<option value="green" selected>Green</option>
<option value="blue">Blue</option>
</select>
Correct: only one selected option
If the intent is a single-choice dropdown, keep selected on only one <option>:
<select name="color">
<option value="red" selected>Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
Correct: multiple selected options with the multiple attribute
If the intent is to allow multi-selection and pre-select more than one option, add the multiple attribute:
<select name="color" multiple>
<option value="red" selected>Red</option>
<option value="green" selected>Green</option>
<option value="blue">Blue</option>
</select>
Correct: no selected attribute at all
If you don't need any option pre-selected, you can omit selected entirely. The browser will typically display the first <option> by default:
<select name="color">
<option value="">Choose a color</option>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
In older versions of HTML, the <a> element supported a shape attribute (with values like rect, circle, poly, and default) to define clickable hotspot regions within an image map. This feature was removed from the HTML specification, and the shape attribute is now considered obsolete on <a> elements.
The modern and correct way to create image maps is to use the <map> element containing one or more <area> elements. Each <area> element accepts a shape attribute along with coords to define clickable regions, and an href to specify the link destination. The <img> element is then associated with the map via its usemap attribute.
Why this matters
- Standards compliance: The
shapeattribute on<a>is not part of the current HTML living standard. Using it produces a validation error and relies on deprecated behavior that browsers are not required to support. - Browser compatibility: Modern browsers implement image maps through
<map>and<area>. Using the obsolete<a shape="...">syntax may not work reliably across browsers. - Accessibility: The
<area>element is designed to work with assistive technologies in the context of image maps. It supports thealtattribute, which provides text alternatives for each clickable region — something essential for screen reader users.
How to fix it
- Remove the
shapeattribute from any<a>elements. - Create a
<map>element with a uniquenameattribute. - Inside the
<map>, add<area>elements with the appropriateshape,coords,href, andaltattributes. - Associate the map with an
<img>element using theusemapattribute, referencing the map'snamewith a#prefix.
Examples
Incorrect: using shape on an <a> element
<img src="workspace.png" usemap="#workspace" alt="Workspace diagram" width="400" height="300">
<map name="workspace">
<a shape="rect" coords="0,0,200,150" href="/monitor.html">Monitor</a>
<a shape="circle" coords="300,200,50" href="/lamp.html">Desk lamp</a>
</map>
This triggers the validation error because the shape attribute is obsolete on <a> elements.
Correct: using <area> elements instead
<img src="workspace.png" usemap="#workspace" alt="Workspace diagram" width="400" height="300">
<map name="workspace">
<area shape="rect" coords="0,0,200,150" href="/monitor.html" alt="Monitor">
<area shape="circle" coords="300,200,50" href="/lamp.html" alt="Desk lamp">
</map>
Each <area> element defines a clickable region with shape and coords, links to a destination with href, and provides an accessible label with alt. The <area> element is a void element (no closing tag needed), and the alt attribute is required when href is present.
Supported shape values on <area>
| Value | Description | coords format |
|---|---|---|
rect | A rectangle | x1,y1,x2,y2 |
circle | A circle | centerX,centerY,radius |
poly | A polygon defined by multiple points | x1,y1,x2,y2,...,xn,yn |
default | The entire image area not covered by other shapes | No coords needed |
Example with multiple shape types
<img src="floorplan.png" usemap="#floorplan" alt="Office floor plan" width="600" height="400">
<map name="floorplan">
<area shape="rect" coords="10,10,200,150" href="/conference-room.html" alt="Conference room">
<area shape="circle" coords="400,300,60" href="/break-room.html" alt="Break room">
<area shape="poly" coords="300,10,350,80,250,80" href="/lobby.html" alt="Lobby">
<area shape="default" href="/office-overview.html" alt="General office area">
</map>
This example demonstrates all four shape types working together in a single image map, with proper alt text for each clickable region.
The sizes attribute and the srcset attribute work together as a system for responsive images. The srcset attribute provides the browser with a list of image candidates (typically at different widths or pixel densities), while the sizes attribute tells the browser how much space the image will occupy in the layout. The browser uses both pieces of information together to pick the most appropriate image file to download.
When you specify sizes without srcset, the attribute has no purpose. There's only one image source (the src attribute), so the browser has nothing to choose from, and the layout hints provided by sizes are meaningless. The HTML specification explicitly states that the sizes attribute must not be present unless srcset is also specified with width descriptors (w). This isn't just a stylistic concern — it signals to validators and other tools that the markup is incomplete or incorrect, which could indicate a copy-paste error or a missing attribute.
This issue commonly occurs when:
- The
srcsetattribute is accidentally removed during refactoring, leavingsizesorphaned. - A developer adds
sizesin preparation for responsive images but forgets to addsrcset. - Code is copied from a template and partially modified.
Examples
❌ Invalid: sizes without srcset
<img
src="photo.jpg"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
The sizes attribute is present, but there is no srcset to provide multiple image candidates. The browser has no use for the sizing information.
✅ Fix: Add a matching srcset attribute
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
Now sizes tells the browser: "Below 600px viewports, the image fills 100% of the viewport width; otherwise it fills 50%." The browser combines this with the width descriptors in srcset to select the best image.
✅ Fix: Remove sizes if you don't need responsive images
<img
src="photo.jpg"
alt="A landscape photo">
If you only have a single image source and don't need responsive behavior, simply remove the sizes attribute.
✅ Using sizes with <source> inside <picture>
The same rule applies to <source> elements inside a <picture> block:
<picture>
<source
srcset="photo-dark-400.jpg 400w, photo-dark-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
media="(prefers-color-scheme: dark)">
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
</picture>
Each element that uses sizes also includes a corresponding srcset with width descriptors.
A note on srcset with pixel density descriptors
The sizes attribute is specifically designed for use with width descriptors (w) in srcset. If you're using pixel density descriptors (x) instead, sizes is not needed:
<img
src="photo.jpg"
srcset="photo-2x.jpg 2x, photo-3x.jpg 3x"
alt="A landscape photo">
In this case, the browser selects based on device pixel ratio rather than viewport size, so sizes would be unnecessary.
The sizes attribute and the srcset attribute are designed to work as a pair for responsive image delivery. The srcset attribute provides the browser with a list of image files and their intrinsic widths (e.g., 480w, 800w), while the sizes attribute tells the browser how much space the image will occupy in the layout at different viewport sizes. The browser combines this information to select the most appropriate image file to download.
When sizes appears without srcset, it serves no purpose. The browser has only the single image specified in the src attribute, so there's no decision to make about which image to load. The HTML specification explicitly requires that sizes must not be present unless srcset is also specified with width descriptors.
This error commonly occurs when a CMS or templating system outputs the sizes attribute by default, when srcset is accidentally removed during refactoring, or when developers copy markup snippets without including all the necessary attributes.
Beyond standards compliance, leaving orphaned sizes attributes creates confusing, harder-to-maintain code. Other developers (or your future self) may assume responsive images are configured when they aren't, leading to wasted debugging time.
How to fix it
You have two options:
- Add a
srcsetattribute if you want the browser to choose from multiple image sizes based on viewport width. Thesrcsetmust use width descriptors (w) forsizesto be meaningful. - Remove the
sizesattribute if you don't need responsive images and a singlesrcis sufficient.
Note that sizes is also valid on <source> elements inside a <picture> element — the same rule applies there. Every <source> with a sizes attribute must also have a srcset attribute.
Examples
❌ Incorrect: sizes without srcset
<img
src="image.jpg"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
The sizes attribute is present but there's no srcset, so the browser has no alternative images to pick from.
✅ Correct: sizes paired with srcset
<img
src="image-800w.jpg"
srcset="image-480w.jpg 480w, image-800w.jpg 800w"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
Here, srcset provides two images with their intrinsic widths. The sizes attribute tells the browser: "If the viewport is 600px or narrower, the image will display at 480px wide; otherwise, it will display at 800px wide." The browser uses this information to download the most efficient file.
✅ Correct: removing sizes when responsive images aren't needed
<img src="image.jpg" alt="A mountain landscape">
If a single image is sufficient, simply drop the sizes attribute.
❌ Incorrect: sizes on a <source> without srcset
<picture>
<source
media="(min-width: 800px)"
sizes="50vw">
<img src="fallback.jpg" alt="A sunset over the ocean">
</picture>
✅ Correct: sizes on a <source> with srcset
<picture>
<source
media="(min-width: 800px)"
srcset="wide-480w.jpg 480w, wide-960w.jpg 960w"
sizes="50vw">
<img src="fallback.jpg" alt="A sunset over the ocean">
</picture>
The <source> element now includes a srcset with width descriptors, giving the browser the candidate images it needs to make use of sizes.
The sizes attribute has a value starting with auto, but the image is not lazy-loaded. A sizes value of auto only works together with loading="lazy".
sizes="auto" tells the browser to pick the srcset candidate that matches the image's actual rendered width, instead of you writing out media conditions by hand. The browser can only know that width after it has laid the image out on the page, which is exactly the point at which a lazy-loaded image is fetched. For an image that loads eagerly, the width is not available in time, so the specification restricts auto to elements that also carry loading="lazy".
To fix the error, add the loading attribute to the same element:
<img
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="auto"
loading="lazy"
alt="A field of sunflowers">
If the image needs to load eagerly, for example a hero image above the fold, drop auto and give sizes an explicit value instead:
<img
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A field of sunflowers">
A sizes value that starts with auto lets the browser work out an image's display size on its own, but the specification only permits it when the same <img> is lazy-loaded with loading="lazy".
The auto keyword removes the need to hand-write media-condition source sizes for a responsive image. For the browser to measure the rendered width and pick a candidate from srcset, the layout around the image has to already exist when the image is fetched. That holds for a lazy-loaded image, which is requested only as it approaches the viewport. An eagerly loaded image can be fetched before its box is laid out, so auto would have nothing to measure, and the validator rejects it.
To fix the error, add loading="lazy" to the same <img>. If the image has to load eagerly, such as a hero image at the top of the page, drop the auto keyword and give sizes explicit lengths instead.
Invalid example
<img
srcset="small.jpg 480w, large.jpg 1024w"
sizes="auto"
src="large.jpg"
alt="Product photo">
Valid example
<img
srcset="small.jpg 480w, large.jpg 1024w"
sizes="auto"
loading="lazy"
src="large.jpg"
alt="Product photo">
When the characters right after an unescaped & spell a known entity name, the parser reads them as a character reference instead of literal text.
HTML treats & as the start of a character reference such as &, ©, or ®. Many of these references are also recognised without their trailing semicolon, so a bare & followed by a name like reg, copy, or para gets converted to ®, ©, or ¶. The validator reports this because the result is almost never what the author intended.
This usually bites in URL query strings, where parameter names happen to match entity names. In ?id=1®=eu, the ® turns into ®, so the link points somewhere different from what the source shows. Escape every literal & as & to keep the text intact.
HTML examples
Invalid
<!-- ® becomes ® and ¶ becomes ¶ -->
<a href="/news?id=10®=europe¶=3">Read the report</a>
Valid
<!-- The & characters are kept literal -->
<a href="/news?id=10&reg=europe&para=3">Read the report</a>
Browsers convert & back to a single & before requesting the URL, so the link works exactly as written while the markup stays valid.
The summary attribute was used in HTML 4 to provide a text description of a table's structure and purpose, primarily for screen reader users. In HTML5, this attribute was deprecated because it was invisible to sighted users, creating an unequal experience. It was also frequently misused — authors often duplicated the table's caption or provided unhelpful descriptions, diminishing its accessibility value.
The HTML Living Standard offers several better alternatives, each suited to different situations:
- Use a
<caption>element — Best for a concise title or description that benefits all users, not just screen reader users. The<caption>must be the first child of the<table>element. - Use a
<figure>with<figcaption>— Ideal when you want to provide a longer description or contextual information alongside the table. This approach also semantically groups the table with its description. - Simplify the table — If your table is straightforward with clear headers, it may not need any additional description at all. Well-structured
<th>elements with appropriatescopeattributes often provide enough context.
From an accessibility standpoint, the <caption> and <figcaption> approaches are superior because they are visible to all users and part of the document flow. Screen readers announce <caption> content when a user navigates to a table, providing the same benefit the summary attribute once offered — but now everyone can see it.
Examples
❌ Obsolete: Using the summary attribute
This triggers the validation warning because summary is no longer a valid attribute on <table>.
<table summary="This table shows monthly sales figures for 2024.">
<tr>
<th>Month</th>
<th>Sales</th>
</tr>
<tr>
<td>January</td>
<td>$1,000</td>
</tr>
<tr>
<td>February</td>
<td>$1,200</td>
</tr>
</table>
✅ Fix 1: Using a <caption> element
Replace the summary attribute with a <caption> as the first child of the <table>. This is the most common and straightforward fix.
<table>
<caption>Monthly sales figures for 2024</caption>
<tr>
<th>Month</th>
<th>Sales</th>
</tr>
<tr>
<td>January</td>
<td>$1,000</td>
</tr>
<tr>
<td>February</td>
<td>$1,200</td>
</tr>
</table>
✅ Fix 2: Using <figure> and <figcaption>
This approach is useful when you want to provide a longer description or when the table is referenced as a figure within surrounding content.
<figure>
<figcaption>
Monthly sales figures for 2024, showing a steady increase in revenue
during the first quarter.
</figcaption>
<table>
<tr>
<th>Month</th>
<th>Sales</th>
</tr>
<tr>
<td>January</td>
<td>$1,000</td>
</tr>
<tr>
<td>February</td>
<td>$1,200</td>
</tr>
</table>
</figure>
✅ Fix 3: Simplify and rely on clear headers
For simple tables where the data is self-explanatory, well-labeled headers with scope attributes may be sufficient. No extra description is needed.
<table>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Sales</th>
</tr>
</thead>
<tbody>
<tr>
<td>January</td>
<td>$1,000</td>
</tr>
<tr>
<td>February</td>
<td>$1,200</td>
</tr>
</tbody>
</table>
You can also combine approaches — use a <caption> for a brief title and wrap the table in a <figure> with a <figcaption> for additional context. The key takeaway is to remove the summary attribute and use visible, semantic HTML elements to describe your table instead.
The role="table" attribute on a <table> element is redundant because the <table> element already has an implicit ARIA role of table.
Every HTML element carries a default ARIA role defined by the HTML specification. The <table> element's built-in role is table, so adding role="table" explicitly tells assistive technologies something they already know. The W3C validator flags this as unnecessary markup.
This applies to many other elements too. A <nav> element has an implicit role of navigation, a <button> has a role of button, and so on. Adding these explicit roles creates noise in the code without any accessibility benefit.
Incorrect example
<table role="table">
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Alice</td>
<td>30</td>
</tr>
</table>
Fixed example
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Alice</td>
<td>30</td>
</tr>
</table>
The <tabs> element does not exist in the HTML specification and is not a valid HTML element.
Browsers parse unknown elements like <tabs> as generic inline elements with no semantics. Screen readers and other assistive technologies cannot interpret them correctly, so users who rely on those tools get no meaningful information about the content's purpose or structure.
If the goal is to create a tabbed interface, use standard HTML elements with appropriate ARIA roles. The WAI-ARIA Authoring Practices describe a well established tabs pattern built from <div>, <button>, and role attributes. Each tab is a <button> with role="tab", grouped inside a container with role="tablist". Each panel is a <div> with role="tabpanel".
HTML examples
Invalid: unknown <tabs> element
<tabs>
<tab>Tab 1</tab>
<tab>Tab 2</tab>
<tab-panel>Content for tab 1</tab-panel>
<tab-panel>Content for tab 2</tab-panel>
</tabs>
Valid: ARIA tabs pattern with standard elements
<div role="tablist" aria-label="Sample tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Tab 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Tab 2
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<p>Content for tab 1</p>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<p>Content for tab 2</p>
</div>
In this pattern, aria-selected indicates the active tab, aria-controls links each tab to its panel, and aria-labelledby links each panel back to its tab. The hidden attribute hides inactive panels. JavaScript is needed to toggle aria-selected, tabindex, and hidden when the user switches tabs.
The <time> element represents a specific moment or duration in time. Browsers, search engines, and assistive technologies rely on parsing its value to understand temporal data programmatically. The element can get its machine-readable value from two places: the datetime attribute, or, if that attribute is absent, from the element's text content directly.
When there is no datetime attribute, the text content itself must be in one of the valid formats specified by the HTML standard. This is where the error typically occurs—authors write a human-readable date like "March 20, 2025" or "last Tuesday" as the text content without providing a datetime attribute, and the validator rejects it because that string isn't machine-parsable.
Why This Matters
- Machine readability: Search engines (via structured data) and browser features (like calendar integration) depend on parsing the
<time>element's value. An invalid format means these tools can't understand the date or time. - Accessibility: Screen readers and other assistive technologies may use the machine-readable datetime to present temporal information more helpfully to users.
- Standards compliance: The HTML specification explicitly defines which formats are valid. Using anything else makes your document non-conforming.
How to Fix It
You have two options:
- Add a
datetimeattribute with the machine-readable value, and keep the human-readable text as the visible content. This is the most common and practical approach. - Use a valid format directly as the text content if you don't mind displaying a machine-readable format to users.
Valid Formats
Here is a reference of accepted formats for the <time> element:
| Type | Example(s) |
|---|---|
| Valid year | 2011 |
| Valid month | 2011-11 |
| Valid date | 2011-11-18 |
| Valid yearless date | 11-18 |
| Valid week | 2011-W47 |
| Valid time | 14:54, 14:54:39, 14:54:39.929 |
| Valid local date and time | 2011-11-18T14:54:39.929 or 2011-11-18 14:54:39.929 |
| Valid global date and time | 2011-11-18T14:54:39.929Z, 2011-11-18T14:54:39.929-04:00 |
| Valid duration | PT4H18M3S, P2D (2 days), P3DT4H (3 days, 4 hours) |
Examples
Incorrect: Human-readable text without datetime attribute
The validator will report the error because "March 20, 2025" is not a valid machine-readable format:
<p>The concert is on <time>March 20, 2025</time>.</p>
Incorrect: Informal text as content
Similarly, casual date strings are not valid:
<p>Updated <time>last Friday</time>.</p>
Correct: Using the datetime attribute
Add a datetime attribute with the machine-readable value and keep the human-friendly text visible:
<p>The concert is on <time datetime="2025-03-20">March 20, 2025</time>.</p>
Correct: Including time and timezone
<p>
The event starts at
<time datetime="2025-03-20T13:00-05:00">1:00 PM EST on March 20, 2025</time>.
</p>
Correct: Machine-readable format as text content
If no datetime attribute is provided, the text content itself must be a valid format:
<p>Date: <time>2025-03-20</time></p>
Correct: Representing a duration
<p>Cooking time: <time datetime="PT1H30M">1 hour and 30 minutes</time>.</p>
Correct: Using just a time value
<p>The shop opens at <time>09:00</time> every day.</p>
As a general rule, whenever you want to display a date or time in a natural, human-friendly way, always pair it with a datetime attribute that contains the machine-readable equivalent. This keeps your HTML valid, your content accessible, and your temporal data useful to machines.
When you use semantic HTML elements, browsers automatically assign appropriate ARIA roles behind the scenes. An <input type="text"> element without a list attribute is inherently recognized by browsers and assistive technologies as a textbox — a control that accepts free-form text input. Explicitly declaring role="textbox" on such an element repeats information that is already conveyed natively, which is what the validator flags.
The distinction about the list attribute matters because when an <input type="text"> does have a list attribute (linking it to a <datalist>), its implicit role changes to combobox rather than textbox. In that scenario, a role="textbox" would not only be redundant — it would actually be incorrect. The validator's message specifically targets the case where there is no list attribute, meaning the implicit role is already textbox.
Why this is a problem
- Redundancy clutters your code. Adding roles that elements already possess makes HTML harder to read and maintain without providing any benefit.
- Potential for confusion. Other developers (or your future self) may wonder if the explicit role was added intentionally to override some other behavior, leading to unnecessary investigation.
- Standards compliance. The W3C and WAI-ARIA authoring practices recommend against setting ARIA roles that duplicate the native semantics of an element. The first rule of ARIA use 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."
- No accessibility benefit. Assistive technologies already understand that
<input type="text">is a textbox. The explicit role adds no additional information for screen readers or other tools.
How to fix it
Simply remove the role="textbox" attribute from your <input type="text"> element. The native semantics of the element are sufficient.
If you've added the role because the input is styled or behaves differently, consider whether you actually need a different element or a different ARIA pattern instead.
Examples
❌ Incorrect: redundant role="textbox"
<label for="username">Username</label>
<input type="text" id="username" role="textbox">
The role="textbox" is unnecessary here because <input type="text"> without a list attribute already has an implicit role of textbox.
✅ Correct: no explicit role needed
<label for="username">Username</label>
<input type="text" id="username">
✅ Also correct: input with list attribute (different implicit role)
<label for="color">Favorite color</label>
<input type="text" id="color" list="colors">
<datalist id="colors">
<option value="Red">
<option value="Green">
<option value="Blue">
</datalist>
In this case, the list attribute changes the implicit role to combobox, so the validator warning about a redundant textbox role would not apply. Note that adding role="textbox" here would be incorrect rather than merely redundant, since it would override the proper combobox semantics.
❌ Incorrect: redundant role on implicit text input
<label for="search-field">Search</label>
<input id="search-field" role="textbox">
When the type attribute is omitted, <input> defaults to type="text", so the implicit role is still textbox and the explicit role remains redundant.
✅ Correct: let the default type handle semantics
<label for="search-field">Search</label>
<input id="search-field">
The <tt> element was a purely presentational element — it described how text should look (monospaced) rather than what the text meant. HTML5 removed it as part of a broader effort to separate content from presentation. Browsers still render <tt> for backward compatibility, but validators will flag it as obsolete, and its use is discouraged in all new code.
The key issue is that <tt> was used for many different purposes — displaying code snippets, keyboard input, sample output, variables, filenames, and more — yet it conveyed none of that meaning to assistive technologies or search engines. HTML5 provides dedicated semantic elements for most of these use cases, making your content more meaningful and accessible.
Choosing the Right Replacement
Before reaching for a generic <span>, consider what the monospaced text actually represents:
- Code: Use
<code>for inline code fragments (e.g., variable names, short statements). - Keyboard input: Use
<kbd>for text the user should type. - Sample output: Use
<samp>for output from a program or system. - Variables: Use
<var>for mathematical or programming variables. - No semantic meaning: Use a
<span>with CSS when the monospaced styling is purely visual and none of the above elements apply.
All of these elements render in a monospaced font by default in most browsers (except <var>, which is typically italic). You can further style them with CSS as needed.
Examples
❌ Obsolete usage with <tt>
<p>Run the command <tt>npm install</tt> to install dependencies.</p>
<p>The variable <tt>x</tt> holds the result.</p>
<p>The output was <tt>Hello, world!</tt></p>
These all trigger the validator error: The "tt" element is obsolete. Use CSS instead.
✅ Fixed with semantic elements
<p>Run the command <kbd>npm install</kbd> to install dependencies.</p>
<p>The variable <var>x</var> holds the result.</p>
<p>The output was <samp>Hello, world!</samp></p>
Each replacement conveys the meaning of the text. <kbd> tells assistive technologies this is something the user types, <var> marks a variable, and <samp> indicates program output.
✅ Fixed with <code> for inline code
<p>Use the <code>Array.prototype.map()</code> method to transform each element.</p>
✅ Fixed with a <span> and CSS when no semantic element fits
If the monospaced text doesn't represent code, input, output, or a variable — for example, a stylistic choice for a filename or an arbitrary design decision — use a <span> with CSS:
<p>Edit the file <span class="mono">config.yaml</span> to change the settings.</p>
.mono {
font-family: monospace;
}
This keeps your HTML valid and your styling in the CSS layer where it belongs.
✅ Block-level code with <pre> and <code>
If you previously used <tt> inside a <pre> block for multi-line code, the standard approach is to combine <pre> with <code>:
<pre><code>function greet(name) {
return "Hello, " + name;
}</code></pre>
Summary
Replace every <tt> element with the semantic HTML element that best describes its content — <code>, <kbd>, <samp>, or <var>. If none of these fit, use a <span> styled with font-family: monospace in CSS. This keeps your markup valid, meaningful, and accessible.
In earlier versions of HTML (HTML 4 and XHTML), the type attribute was required on the <style> element to declare the MIME type of the styling language being used. The value was almost always text/css, as CSS has been the dominant stylesheet language for the web since its inception.
With HTML5, the specification changed. The type attribute on <style> now defaults to text/css, and since no browser supports any other styling language, the attribute serves no practical purpose. The WHATWG HTML Living Standard explicitly notes that the attribute is unnecessary and can be omitted. The W3C validator flags its presence as a warning to encourage cleaner, more modern markup.
Why This Matters
- Cleaner code: Removing unnecessary attributes reduces file size (even if marginally) and improves readability. Every attribute should earn its place in your markup.
- Standards compliance: Modern HTML encourages omitting default values when they add no information. Including
type="text/css"signals outdated coding practices. - Consistency: The same principle applies to
<script>elements, wheretype="text/javascript"is also unnecessary. Keeping your markup consistent by omitting both makes your codebase easier to maintain.
How to Fix It
The fix is straightforward: find every <style> element in your HTML that includes a type attribute and remove it. No other changes are needed — the browser behavior will be identical.
If you're working on a large codebase, a simple search for <style type= across your files will help you find all instances.
Examples
❌ Incorrect: Redundant type attribute
<style type="text/css">
p {
color: red;
}
</style>
<p>This text will be red.</p>
The type="text/css" attribute is unnecessary and triggers the W3C validator warning.
✅ Correct: type attribute omitted
<style>
p {
color: red;
}
</style>
<p>This text will be red.</p>
Without the type attribute, the browser still interprets the contents as CSS — the behavior is exactly the same.
❌ Incorrect: Other variations that also trigger the warning
The warning is triggered regardless of how the type value is formatted:
<style type="text/css" media="screen">
body {
font-family: sans-serif;
}
</style>
✅ Correct: Other attributes are fine, just remove type
<style media="screen">
body {
font-family: sans-serif;
}
</style>
Note that other valid attributes like media or nonce should be kept — only the type attribute needs to be removed.
The <script> element's type attribute specifies the MIME type of the script. In earlier HTML versions (HTML 4 and XHTML), the type attribute was required and authors had to explicitly declare type="text/javascript". However, the HTML5 specification changed this — JavaScript is now the default scripting language, so when the type attribute is omitted, browsers automatically treat the script as JavaScript.
Because of this default behavior, including type="text/javascript" (or variations like type="application/javascript") is unnecessary. The W3C HTML Validator raises a warning to encourage cleaner, more concise markup. While this isn't an error that will break your page, removing the redundant attribute keeps your HTML lean and aligned with modern standards.
There are legitimate uses for the type attribute on <script> elements, such as type="module" for ES modules or type="application/ld+json" for structured data. These values change the behavior of the <script> element and should absolutely be kept. The validator only flags the attribute when its value is a JavaScript MIME type, since that's already the default.
Examples
Incorrect — unnecessary type attribute
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
console.log("Hello, world!");
</script>
Correct — type attribute removed
<script src="app.js"></script>
<script>
console.log("Hello, world!");
</script>
Correct — type attribute used for non-default purposes
The type attribute is still necessary and valid when you're using it for something other than plain JavaScript:
<!-- ES module -->
<script type="module" src="app.mjs"></script>
<!-- JSON-LD structured data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example Inc."
}
</script>
<!-- Import map -->
<script type="importmap">
{
"imports": {
"utils": "./utils.js"
}
}
</script>
Quick fix checklist
- Search your HTML files for
type="text/javascript"andtype="application/javascript". - Remove the
typeattribute from those<script>tags entirely. - Leave the
typeattribute on any<script>tags that usetype="module",type="importmap",type="application/ld+json", or other non-JavaScript MIME types.
The type attribute on the <menu> element is obsolete and should be removed.
The <menu> element was originally designed to support different types of menus, including type="context" for context menus and type="toolbar" for toolbars. These features were never widely implemented by browsers and have been removed from the HTML specification.
In the current HTML living standard, the <menu> element is simply a semantic alternative to <ul> for representing a list of interactive items or commands, such as a toolbar of buttons. It no longer accepts a type attribute.
If you need a custom context menu (right-click menu), the recommended approach is to use JavaScript to listen for the contextmenu event and display your own custom menu using standard HTML and CSS.
Invalid Example
<menu type="context" id="my-menu">
<menuitem label="Copy"></menuitem>
<menuitem label="Paste"></menuitem>
</menu>
Valid Example
Using <menu> as a simple list of commands:
<menu>
<li><button>Copy</button></li>
<li><button>Paste</button></li>
</menu>
If you need a custom context menu, handle it with JavaScript:
<div id="target">Right-click here</div>
<menu id="context-menu" style="display: none; position: absolute;">
<li><button>Copy</button></li>
<li><button>Paste</button></li>
</menu>
<script>
const target = document.getElementById("target");
const menu = document.getElementById("context-menu");
target.addEventListener("contextmenu", (e) => {
e.preventDefault();
menu.style.display = "block";
menu.style.left = e.pageX + "px";
menu.style.top = e.pageY + "px";
});
document.addEventListener("click", () => {
menu.style.display = "none";
});
</script>
Note that the <menuitem> element is also obsolete and no longer part of the HTML specification. Use standard elements like <button> or <a> inside <li> elements instead.
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