HTML Guides for obsolete
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 doctype declaration tells the browser which version of HTML the document is written in and determines how the browser renders the page. Older HTML versions required long, complex doctype strings that referenced Document Type Definitions (DTDs). With the introduction of HTML5, the doctype was simplified to just <!DOCTYPE html>, which is now the only recommended doctype for modern web pages.
When a browser encounters an obsolete doctype—or no doctype at all—it may switch into "quirks mode," a legacy rendering mode that emulates old browser behavior. In quirks mode, CSS box models, layout calculations, and other rendering behaviors can differ significantly from standards mode, leading to inconsistent appearance across browsers. Using the modern <!DOCTYPE html> ensures the browser operates in "standards mode," giving you predictable and consistent rendering.
Beyond rendering concerns, using an obsolete doctype means your document is referencing outdated specifications. Modern HTML validators, including the W3C Nu HTML Checker, are designed to validate against the current HTML Living Standard. Legacy doctypes may cause the validator to misinterpret your markup or report errors that don't apply, making it harder to maintain clean, valid code.
To fix this, simply replace whatever doctype declaration appears at the top of your HTML file with <!DOCTYPE html>. This single change is backward-compatible—it works in all modern browsers and even in Internet Explorer 6 and later. No DTD reference or version number is needed.
Examples
Obsolete doctypes that trigger this issue
These are common legacy doctypes that the validator will flag:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Correct HTML5 doctype
Replace the obsolete doctype with the simple, modern declaration:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Key points about the modern doctype
- It must appear on the very first line of the document, before the
<html>tag. No comments or whitespace should precede it. - The declaration is case-insensitive—
<!DOCTYPE html>,<!doctype html>, and<!Doctype Html>are all valid, though lowercase or uppercase conventions are most common. - Unlike older doctypes, it does not reference a DTD URL or version number.
- Adding a
langattribute to the<html>element and a<meta charset="utf-8">tag in the<head>are best practices that pair well with the modern doctype for a fully standards-compliant document.
In earlier versions of HTML, there were two separate elements for shortened forms of words and phrases: <abbr> for abbreviations (like "Dr." or "etc.") and <acronym> for acronyms (like "NASA" or "HTML"). The HTML5 specification eliminated this distinction because acronyms are simply a type of abbreviation. The <abbr> element now covers all cases.
Using the obsolete <acronym> element causes W3C validation errors and has several practical drawbacks:
- Standards compliance: The element is not part of the current HTML specification. Validators will flag it as an error, and future browsers are not guaranteed to support it.
- Accessibility: Assistive technologies are designed and tested against current standards. While many screen readers still handle
<acronym>, relying on an obsolete element risks inconsistent behavior. The<abbr>element has well-defined, standardized accessibility semantics. - Consistency: Using
<abbr>for all abbreviations and acronyms simplifies your markup and makes it easier for developers to maintain.
The fix is straightforward: replace every <acronym> tag with <abbr>. The title attribute works the same way on both elements — it provides the expanded form of the abbreviation or acronym that browsers typically display as a tooltip on hover.
Examples
❌ Obsolete: using <acronym>
<p>The <acronym title="World Wide Web">WWW</acronym> was invented by Tim Berners-Lee.</p>
<p>This page is written in <acronym title="HyperText Markup Language">HTML</acronym>.</p>
✅ Fixed: using <abbr>
<p>The <abbr title="World Wide Web">WWW</abbr> was invented by Tim Berners-Lee.</p>
<p>This page is written in <abbr title="HyperText Markup Language">HTML</abbr>.</p>
Using <abbr> for both abbreviations and acronyms
Since <abbr> now handles all shortened forms, you can use it consistently throughout your markup:
<p>
Contact <abbr title="Doctor">Dr.</abbr> Smith at
<abbr title="National Aeronautics and Space Administration">NASA</abbr>
for more information about the <abbr title="International Space Station">ISS</abbr>.
</p>
Styling <abbr> with CSS
Some browsers apply a default dotted underline to <abbr> elements with a title attribute. You can customize this with CSS:
<style>
abbr[title] {
text-decoration: underline dotted;
cursor: help;
}
</style>
<p>Files are transferred using <abbr title="File Transfer Protocol">FTP</abbr>.</p>
If you're migrating a large codebase, a simple find-and-replace of <acronym with <abbr and </acronym> with </abbr> will handle the conversion. No other attributes or content changes are needed — the two elements accept the same attributes and content model.
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 <span style="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 <span class="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 <strong class="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.
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
<a href="/home">
<img src="logo.png" alt="Logo" border="0">
</a>
Valid: using CSS instead
<a href="/home">
<img src="logo.png" alt="Logo" style="border: 0;">
</a>
Or, with a stylesheet rule:
<style>
img {
border: 0;
}
</style>
<a href="/home">
<img src="logo.png" alt="Logo">
</a>
The border attribute is obsolete on the <input> element, and a border on an image button belongs in CSS.
Old markup often puts border="0" on <input type="image">, the image button, a habit copied from linked images. The HTML standard lists border on input as an obsolete attribute, and the validator warns about it on every input type, submit and text included.
Browsers still apply the attribute in one case. On an image button, a value above zero draws a solid border that many pixels wide. A value of 0 changes nothing, and browsers ignore the attribute on every other input type. On most pages you can delete it and nothing on screen changes.
The message suggests img { border: 0; }, but that rule styles <img> elements, not inputs. If the attribute had a value above zero and you want to keep the border, write a CSS rule for the image button itself.
Invalid example
<form action="/search">
<input type="search" name="q" aria-label="Search">
<input type="image" src="search.png" alt="Search" border="0">
</form>
Valid example
<form action="/search">
<input type="search" name="q" aria-label="Search">
<input type="image" src="search.png" alt="Search">
</form>
If the old value was border="2", this rule in your stylesheet draws the same border:
input[type="image"] {
border: 2px solid;
}
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
<link rel="stylesheet" href="styles.css" charset="UTF-8">
<link rel="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
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="print.css">
Simply removing the attribute resolves the validation error. The server should handle encoding declaration instead.
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example Page</title>
<link rel="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
<script src="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
<script charset="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
<script src="app.js"></script>
Correct: ensuring encoding is declared at the document level
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
<script src="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.
The classid attribute told the browser which plugin or ActiveX control should run an <object>, and HTML has dropped it: the resource URL goes in data and its MIME type in type.
Its value was usually clsid: followed by the ID of an ActiveX control, and only Internet Explorer could load those. A typical one is clsid:D27CDB6E-AE6D-11cf-96B8-444553540000, the Flash Player control. Flash embed code usually pairs it with a codebase attribute pointing to the Flash installer, a list of <param> elements naming the .swf file and its settings, and a nested <embed> for other browsers.
Browsers no longer look at classid. An <object> loads the URL in its data attribute, and when data is missing it renders the content between its tags instead. For a Flash embed, that content is the <param> elements, which display nothing, and the <embed>, whose .swf won't play. The validator also reports the missing data as an error next to this warning.
The message still mentions plugins, but current browsers don't run any, and Adobe ended support for Flash Player on December 31, 2020. Moving the .swf URL into data with type="application/x-shockwave-flash" clears the warning, but the box on the page stays empty. Replace the content itself: a video file goes in <video>, a video on a hosting service goes in that service's <iframe> player, and a banner can become an <img> or a short video. Then delete the old <object>, its <param> and <embed> elements included.
When the <object> holds a file that browsers handle without a plugin, such as a PDF or an SVG image, keep the element and remove classid. Point data at the file and set type to its MIME type (application/pdf or image/svg+xml). Anything inside the element, like a download link, shows up when the browser can't display the file.
Examples
Invalid: a Flash embed
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="640" height="360">
<param name="movie" value="intro.swf">
<embed src="intro.swf" type="application/x-shockwave-flash" width="640" height="360">
</object>
Valid: the Flash movie converted to a video
<video src="intro.mp4" controls width="640" height="360"></video>
Invalid: classid left on a PDF embed
<object classid="clsid:CA8A9780-280D-11CF-A24D-444553540000" data="report.pdf" width="600" height="400">
<a href="report.pdf">Download the report (PDF)</a>
</object>
Valid: data and type only
<object data="report.pdf" type="application/pdf" width="600" height="400">
<a href="report.pdf">Download the report (PDF)</a>
</object>
The codebase attribute told the browser where to get the code an <object> needed, and browsers ignore it today: the resource URL goes in data and its MIME type in type.
In Flash embed code, codebase holds the address of the Flash Player installer for Internet Explorer, a swflash.cab file on Macromedia's download server, followed by a fragment such as #version=10,0,0,0. Internet Explorer compared that version with the ActiveX control installed on the computer and, when the control was missing or older, offered to download and install the .cab file. Other ActiveX controls, like the ones for QuickTime and Windows Media Player, came with their own .cab URLs in the same attribute.
HTML 4.01 gave codebase a second job: a base path for relative URLs in the classid, data and archive attributes. With codebase="/media/" and data="intro.swf", the file was meant to load from /media/intro.swf. Browsers now resolve data against the page's URL like any other link, and none of them download installers from codebase.
Deleting the attribute clears this warning without changing what the page shows, since browsers already skip it. For Flash embed code, that isn't enough: the classid warning stays, the validator still reports the missing data attribute and the obsolete <param> elements, and the box stays empty because no current browser plays .swf files. Replace the whole <object>, <param> and <embed> elements included, with the same content in a format browsers support, such as a <video> for a movie or an <img> for a static banner.
If the page relied on codebase as a base path, browsers already request the file from the wrong address. Write the full path into data, then remove codebase.
Examples
Invalid: Flash embed code with a .cab installer
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="https://example.com/cabs/flash/swflash.cab#version=10,0,0,0" width="640" height="360">
<param name="movie" value="intro.swf">
<embed src="intro.swf" type="application/x-shockwave-flash" width="640" height="360">
</object>
Valid: the movie as a video
<video src="intro.mp4" controls width="640" height="360"></video>
Invalid: codebase as a base path for data
<object codebase="/files/reports/" data="annual-report.pdf" type="application/pdf" width="600" height="400">
<a href="/files/reports/annual-report.pdf">Download the annual report (PDF)</a>
</object>
Valid: the full path in data
<object data="/files/reports/annual-report.pdf" type="application/pdf" width="600" height="400">
<a href="/files/reports/annual-report.pdf">Download the annual report (PDF)</a>
</object>
The <font> element was originally introduced to give authors control over text rendering directly in markup. A typical usage looked like <font face="Arial" size="3" color="red">. While browsers still render this element for backward compatibility, it has been obsolete since HTML5 and will trigger a validation error. The W3C validator flags it because it violates the principle of separation of concerns: HTML should define the structure and meaning of content, while CSS should handle its visual presentation.
Using <font> causes several practical problems:
- Maintainability: Styling scattered across
<font>tags throughout your HTML is extremely difficult to update. Changing a color scheme could mean editing hundreds of elements instead of a single CSS rule. - Accessibility: The
<font>element carries no semantic meaning. Screen readers and other assistive technologies gain nothing from it, and its presence can clutter the document structure. - Consistency: CSS enables you to define styles in one place and apply them uniformly across your entire site using classes, selectors, or external stylesheets.
- Standards compliance: Using obsolete elements means your HTML does not conform to the current specification, which can lead to unexpected rendering in future browser versions.
To fix this issue, remove every <font> element and replace its visual effects with equivalent CSS properties. The three attributes of <font> map directly to CSS:
<font> attribute | CSS equivalent |
|---|---|
color | color |
size | font-size |
face | font-family |
You can apply CSS as inline styles for quick fixes, but using a <style> block or an external stylesheet with classes is the preferred approach for any real project.
Examples
Incorrect: using the obsolete <font> element
<p>
<font face="Arial" size="4" color="blue">Welcome to my website</font>
</p>
This triggers the validator error: The "font" element is obsolete. Use CSS instead.
Fix with inline styles
If you need a quick, direct replacement:
<p style="font-family: Arial, sans-serif; font-size: 18px; color: blue;">
Welcome to my website
</p>
Fix with a CSS class (recommended)
Using a class keeps your HTML clean and makes styles reusable:
<style>
.welcome-text {
font-family: Arial, sans-serif;
font-size: 18px;
color: blue;
}
</style>
<p class="welcome-text">Welcome to my website</p>
Nested <font> elements replaced with CSS
Old markup sometimes used multiple nested <font> tags:
<!-- Obsolete -->
<p>
<font color="red" size="5">
Important:
<font face="Courier">code goes here</font>
</font>
</p>
The correct approach uses <span> elements or semantic tags with CSS classes:
<style>
.alert-heading {
color: red;
font-size: 24px;
}
.code-snippet {
font-family: Courier, monospace;
}
</style>
<p>
<span class="alert-heading">
Important:
<span class="code-snippet">code goes here</span>
</span>
</p>
If the text carries a specific meaning — such as marking something as important or representing code — consider using semantic HTML elements like <strong>, <em>, or <code> alongside your CSS:
<style>
.alert-heading {
color: red;
font-size: 24px;
}
</style>
<p class="alert-heading">
<strong>Important:</strong>
<code>code goes here</code>
</p>
This approach gives you full control over appearance through CSS while keeping your HTML meaningful, accessible, and standards-compliant.
The <frame> element is obsolete, so the content each frame loads has to move into the page itself or into an <iframe> inside a normal <body>.
A <frame> only works inside a <frameset>, where it loads a separate HTML file into one pane of the browser window. The validator reports this message once for every <frame> on the page, and the <frameset> around them gets a message of its own. Moving the frames into a <body> doesn't help: the parser drops a <frame> there and reports it as a stray start tag. The frames and the frameset have to be replaced together, and when the frameset only wraps another site in a single frame, an HTTP redirect to that site replaces both.
What replaces each frame depends on what it holds. A header or menu that every page shares belongs in the page itself, written directly or pulled in with a server-side include or a template, so it lives at the same URL as the content around it. A document that has to stay separate can load in an <iframe>. Iframes take src and name just as frames did, so links with a matching target keep loading into them, and the validator stops warning that name is never allowed on <frame>. Give each iframe a title describing what it shows, since screen readers announce it.
The presentation attributes of a frame don't carry over. On an <iframe>, frameborder, scrolling, marginwidth and marginheight are obsolete too, and the validator warns about each one. Remove the border with border: 0 in CSS. Scrollbars and margins belong to the document loaded inside, so set overflow and margin in that document's own stylesheet. noresize needs no replacement, since an iframe has no draggable border between panes.
Invalid example
A header frame above a catalog frame:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Garden supplies</title>
</head>
<frameset rows="80,*">
<frame src="header.html" scrolling="no" noresize frameborder="0">
<frame src="catalog.html" frameborder="0">
</frameset>
</html>
Valid example
The header is part of the page (on a real site it would come from an include), and the catalog keeps loading from its own file in an iframe:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Garden supplies</title>
<style>
iframe {
width: 100%;
height: 80vh;
border: 0;
}
</style>
</head>
<body>
<header>
<a href="/">Garden supplies</a>
</header>
<iframe src="catalog.html" name="catalog" title="Product catalog"></iframe>
</body>
</html>
The <frameset> element is obsolete, so a page split into frames has to become a normal page with a <body>, using CSS for the layout and <iframe> or server-side includes for the shared parts.
A frameset document has a <frameset> where the <body> would go, and each <frame> inside it loads a separate HTML file into one pane of the window. The HTML standard lists <frameset>, <frame> and <noframes> as obsolete. Browsers still render framesets so old sites keep working, but the validator reports the <frameset> once and then each <frame> and <noframes> with a message of its own. The rows and cols attributes go away with the element and aren't reported separately.
Frames cause trouble beyond validation. The address bar keeps showing the frameset's URL whatever is loaded inside it, so a visitor can't bookmark or share a specific page, and search engines can send people straight to a framed page without the navigation that was meant to surround it.
The fix depends on what the frames were for. When they hold shared navigation, the usual answer is one complete page per URL, with the navigation pulled in on the server through an include, a template or a static site generator. When a pane really has to load other documents, an <iframe> inside a regular <body> does that job, sized with CSS and given a title so screen readers can announce it. A frameset with a single frame that fills the window, such as rows="100%,*", is often the "masked forwarding" a domain registrar sets up to show another site under a different domain. In that case, replace it with an HTTP 301 redirect to the real site.
Invalid example
A manual with a table of contents on the left and the chapter on the right:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Product manual</title>
</head>
<frameset cols="25%,75%">
<frame src="contents.html">
<frame src="chapter-1.html">
</frameset>
</html>
Valid example
The table of contents moves into the page, and an <iframe> loads each chapter that its links target:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Product manual</title>
<link rel="stylesheet" href="manual.css">
</head>
<body>
<nav>
<ul>
<li><a href="chapter-1.html" target="chapter">Chapter 1</a></li>
<li><a href="chapter-2.html" target="chapter">Chapter 2</a></li>
</ul>
</nav>
<iframe src="chapter-1.html" name="chapter" title="Chapter text"></iframe>
</body>
</html>
body {
display: flex;
height: 100vh;
margin: 0;
}
nav {
flex: 0 0 25%;
}
iframe {
flex: 1;
border: 0;
}
The iframe keeps the two-pane layout, but every chapter still shares one URL. Publishing each chapter as its own page, with the table of contents included on the server, gives every chapter an address people can bookmark and link to.
The language attribute on the <script> element has been obsolete since HTML4 and should be removed.
Early versions of HTML used language="JavaScript" to specify the scripting language. Modern HTML defaults to JavaScript, so neither language nor type is required in most cases. The type attribute replaced language long ago, but even type="text/javascript" is unnecessary now because all browsers treat scripts as JavaScript by default.
If you do need to specify a non-JavaScript type (such as type="module" or type="application/json"), use the type attribute. Otherwise, omit both attributes entirely.
Examples
Invalid: using the obsolete language attribute
<script language="JavaScript">
console.log("Hello");
</script>
Valid: no attribute needed for plain JavaScript
<script>
console.log("Hello");
</script>
Valid: using type when a specific type is needed
<script type="module">
import { greet } from "./greet.js";
greet();
</script>
The language attribute was used in early HTML to specify the scripting language of a <script> block, typically set to values like "JavaScript" or "VBScript". It was deprecated in HTML 4.01 (in favor of the type attribute) and is now fully obsolete in the HTML Living Standard. While browsers still recognize it for backward compatibility, it serves no functional purpose and triggers a validation warning.
The <script> element accepts several standard attributes, but the two most common are type and src. The type attribute specifies the MIME type or module type of the script (e.g., "module" or "application/json"), and src points to an external script file. When writing standard JavaScript, you can omit type entirely because "text/javascript" is the default. The language attribute, however, should always be removed — it is not a valid substitute for type and has no effect in modern browsers.
Why this matters
- Standards compliance: Using obsolete attributes means your HTML does not conform to the current HTML specification. This can cause validation errors that obscure more important issues in your markup.
- Code clarity: The
languageattribute is misleading to developers who may not realize it's non-functional. Removing it keeps your code clean and easier to maintain. - Future-proofing: While browsers currently tolerate the attribute, there is no guarantee they will continue to do so indefinitely. Relying on obsolete features is a maintenance risk.
How to fix it
Simply remove the language attribute from your <script> elements. If you're using JavaScript (the vast majority of cases), no replacement is needed. If you need to specify a non-default type, use the type attribute instead.
Examples
❌ Obsolete: using the language attribute
<script language="JavaScript">
console.log("Hello, world!");
</script>
<script language="JavaScript" src="app.js"></script>
✅ Fixed: attribute removed
For inline JavaScript, simply omit the attribute:
<script>
console.log("Hello, world!");
</script>
For external scripts, only src is needed:
<script src="app.js"></script>
✅ Using the type attribute when needed
If you need to specify a script type — for example, an ES module or a data block — use the standard type attribute:
<script type="module" src="app.js"></script>
<script type="application/json">
{ "key": "value" }
</script>
Note that type="text/javascript" is valid but redundant, since JavaScript is the default. You can safely omit it for standard scripts.
The longdesc attribute was originally designed to point to a URL containing a detailed description of an element's content, primarily as an accessibility feature for screen reader users. On <iframe> elements, it was intended to describe the framed content for users who couldn't perceive it directly. However, the attribute was poorly implemented across browsers, rarely used by assistive technologies, and was ultimately removed from the HTML specification for <iframe> elements.
Using obsolete attributes causes W3C validation errors and can create a false sense of accessibility. Since browsers and assistive technologies don't reliably process longdesc on iframes, users who need the description may never actually reach it. A visible link, on the other hand, is universally accessible — it works for all users regardless of their browser, device, or assistive technology.
The fix is straightforward: remove the longdesc attribute from the <iframe> and place a regular <a> element near the iframe that links to the description page. This approach is more robust because the link is visible, discoverable, and works everywhere.
Examples
❌ Obsolete: Using longdesc on an <iframe>
<iframe src="report.html" title="Annual report" longdesc="report-description.html"></iframe>
This triggers the validation error because longdesc is no longer a valid attribute on <iframe>.
✅ Fixed: Using a visible link instead
<iframe src="report.html" title="Annual report"></iframe>
<p>
<a href="report-description.html">Read a detailed description of the annual report</a>
</p>
The longdesc attribute is removed, and a descriptive link is placed adjacent to the iframe so all users can access it.
✅ Alternative: Wrapping in a <figure> for better semantics
For a more structured approach, you can use a <figure> element with a <figcaption> to associate the description link with the iframe:
<figure>
<iframe src="report.html" title="Annual report"></iframe>
<figcaption>
Annual report overview.
<a href="report-description.html">Read the full description</a>.
</figcaption>
</figure>
This groups the iframe and its caption together semantically, making the relationship between them clear to both sighted users and assistive technologies.
✅ Alternative: Using aria-describedby for additional context
If you want to provide a short inline description that assistive technologies can announce automatically, you can use aria-describedby:
<p id="report-desc">
This iframe contains the interactive annual report for 2024.
<a href="report-description.html">Read the full description</a>.
</p>
<iframe src="report.html" title="Annual report" aria-describedby="report-desc"></iframe>
This links the iframe to the descriptive paragraph programmatically. Screen readers will announce the content of the referenced element when the iframe receives focus, while the visible link remains available to all users.
Key Takeaways
- Always include a
titleattribute on<iframe>elements to describe their purpose — this is an important baseline accessibility practice. - Replace
longdescwith a visible<a>element that links to the long description. - Place the link near the iframe so users can easily find it.
- Consider using
<figure>and<figcaption>oraria-describedbyfor stronger semantic association between the iframe and its description.
The <marquee> element is obsolete, so text that scrolls across the page has to be animated with CSS instead.
Browsers still render <marquee> so old pages keep working, but the HTML standard lists it among the presentational features authors must not use, and points to CSS transitions and animations as the right tool for moving content. Its attributes, such as behavior, direction, scrollamount and scrolldelay, go away with it. The validator reports the element once and doesn't flag each of those attributes on its own.
Scrolling text is also hard to read, and a <marquee> gives visitors no way to stop it. WCAG success criterion 2.2.2 (Pause, Stop, Hide) asks for a way to pause, stop or hide content that moves by itself for more than five seconds alongside other content. Often the best fix is a plain, static line of text. If the movement has to stay, a CSS @keyframes animation reproduces it, and the prefers-reduced-motion media query turns it off for people who have asked their system to reduce motion. Pausing on hover helps mouse users, but keyboard and touch users still need a pause button to meet 2.2.2.
Invalid example
A news line that scrolls from right to left:
<marquee behavior="scroll" direction="left" scrollamount="4">
Registration for the spring semester closes on Friday.
</marquee>
Valid example
The same line scrolls with a CSS animation that pauses on hover and stops when the user prefers reduced motion:
<p class="ticker">
<span>Registration for the spring semester closes on Friday.</span>
</p>
.ticker {
overflow: hidden;
white-space: nowrap;
}
.ticker span {
display: inline-block;
padding-left: 100%;
animation: ticker 15s linear infinite;
}
.ticker:hover span {
animation-play-state: paused;
}
@keyframes ticker {
to {
transform: translateX(-100%);
}
}
@media (prefers-reduced-motion: reduce) {
.ticker span {
padding-left: 0;
animation: none;
}
}
The validator warns about name on a <frame> because the <frame> element itself is obsolete, and the warning goes away when the frame becomes an <iframe>, which accepts name.
In a frameset, name gives a frame the target name that other documents use to load pages into it. A link with target="content" opens its page in the frame named content, a form with the same target shows its response there, and a script can reach the frame through window.frames["content"]. Menus built with frames depend on it: every link in the menu frame points at the frame that holds the content. Browsers still honor these names, which is why the frames keep working.
The validator checks name on every element against a short list of elements that define it: button, details, fieldset, form, iframe, input, map, meta, object, output, select, slot and textarea. <frame> isn't on that list because it's obsolete. The validator reports this check as an informational message rather than an error, so it shows up as a warning, once for each named frame. The errors come from the elements themselves: each <frame> gets a message saying it's obsolete, and so does the <frameset> around it. Other attributes common on old frames, such as frameborder, scrolling and noresize, get no message on a <frame>.
Deleting name silences the warning but breaks every link and form that targets the frame, and the obsolete errors stay. Replace the frameset instead. A frame whose content moves into the page itself, such as a menu, no longer needs a name, since nothing loads into it. A frame that becomes an <iframe> keeps its name, so links with a matching target keep loading into it.
Invalid example
The links in menu.html use target="content" to open each recipe in the second frame:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Recipe box</title>
</head>
<frameset cols="200,*">
<frame src="menu.html" name="menu">
<frame src="welcome.html" name="content">
</frameset>
</html>
Valid example
The menu moves into the page, and the iframe keeps the name content, so the links still load into it:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Recipe box</title>
</head>
<body>
<nav>
<a href="soups.html" target="content">Soups</a>
<a href="desserts.html" target="content">Desserts</a>
</nav>
<iframe src="welcome.html" name="content" title="Recipe"></iframe>
</body>
</html>
The name attribute is reported on the <param> element because <param> itself is obsolete in the HTML standard.
<param> used to sit inside <object> to pass named values to plugins such as Flash, Java applets, or ActiveX controls. Each one carried a name and a value. Browsers dropped plugin support, the element was removed from the HTML specification, and the Nu validator now rejects its attributes, including name.
There is no replacement attribute to add. The fix is to delete the <param> element. If the surrounding <object> points at a resource that browsers still handle on their own, like an image or a PDF, set the data and type attributes directly on <object> and drop the parameters. If the content depended on a plugin, use a native element such as <video>, <audio>, or <iframe> instead.
Invalid example
<object data="movie.swf" type="application/x-shockwave-flash">
<param name="autoplay" value="true">
</object>
Valid example
<video src="movie.mp4" autoplay controls></video>
The name attribute on <a> elements is obsolete in HTML5 and should be replaced with the id attribute.
In older versions of HTML, the name attribute on anchor elements was used to create fragment identifiers — targets you could link to with #section-name in a URL. In HTML5, this approach has been deprecated in favor of the id attribute, which can be placed on any element, not just <a> tags.
Using id is more flexible because you can turn any element into a link target directly, without wrapping it in an anchor. The id attribute works the same way for fragment navigation: a link pointing to #section-name will scroll to the element with id="section-name".
HTML Examples
❌ Obsolete usage with name
<a name="about"></a>
<h2>About Us</h2>
<p>Welcome to our site.</p>
<a href="#about">Go to About</a>
✅ Fixed using id
<h2 id="about">About Us</h2>
<p>Welcome to our site.</p>
<a href="#about">Go to About</a>
The id attribute is placed directly on the <h2> heading, eliminating the need for an empty <a> tag entirely. The #about link works exactly the same way.
The name attribute was historically used on img elements to reference images through JavaScript's document.images collection or via document.getElementsByName(). In early HTML, name served as an identifier for various elements before the id attribute was widely adopted. The HTML living standard (WHATWG) now marks name as obsolete on img elements, meaning it should no longer be used in new content.
This matters for several reasons:
- Standards compliance: Using obsolete attributes means your HTML does not conform to the current specification, which can cause validation errors and may lead to unexpected behavior in future browser versions.
- Consistency: The
idattribute is the universal mechanism for uniquely identifying any HTML element. Usingidinstead ofnamekeeps your markup consistent and predictable. - JavaScript and CSS targeting: Modern APIs like
document.getElementById()anddocument.querySelector()work withid, notnameon image elements. CSS selectors also target elements byid(e.g.,#myImage), makingidthe more versatile choice. - Fragment linking: The
idattribute allows you to link directly to an element using a URL fragment (e.g.,page.html#myImage), whereas the obsoletenameattribute onimgdoes not serve this purpose.
To fix this issue, simply replace name with id on your img elements. Keep in mind that id values must be unique within the entire document — no two elements can share the same id. If you have JavaScript code that references the image by name (e.g., document.images["myImage"] or document.getElementsByName("myImage")), update those references to use document.getElementById("myImage") or document.querySelector("#myImage") instead.
Examples
Incorrect: using the obsolete name attribute
<img src="photo.jpg" name="heroImage" alt="A sunset over the ocean">
This triggers the validation error because name is no longer a valid attribute on img.
Correct: using the id attribute
<img src="photo.jpg" id="heroImage" alt="A sunset over the ocean">
The name attribute is replaced with id, and the element can now be targeted with document.getElementById("heroImage") or the CSS selector #heroImage.
Updating JavaScript references
If your existing code references the image by name, update it accordingly.
Before (relying on name):
<img src="logo.png" name="siteLogo" alt="Company logo">
<script>
var logo = document.images["siteLogo"];
logo.style.border = "2px solid blue";
</script>
After (using id):
<img src="logo.png" id="siteLogo" alt="Company logo">
<script>
var logo = document.getElementById("siteLogo");
logo.style.border = "2px solid blue";
</script>
Multiple images that previously shared a name
Since id values must be unique, you cannot give the same id to multiple elements. If you previously used the same name on several images and selected them as a group, switch to a shared class instead:
<img src="slide1.jpg" class="gallery-image" alt="Mountain landscape">
<img src="slide2.jpg" class="gallery-image" alt="Forest trail">
<img src="slide3.jpg" class="gallery-image" alt="River valley">
<script>
var images = document.querySelectorAll(".gallery-image");
images.forEach(function(img) {
img.style.borderRadius = "8px";
});
</script>
This approach is standards-compliant and gives you flexible, modern element selection using class for groups and id for unique elements.
The name attribute was historically used on <option> elements in older HTML specifications, but it has been obsolete since HTML5. The WHATWG HTML Living Standard does not list name as a valid attribute for <option>. The valid attributes for <option> are disabled, label, selected, and value, in addition to the global attributes (such as id, class, style, etc.).
It's important to understand that the name attribute on <option> never served the same purpose as name on <input> or <select>. For form submission, the browser sends the name from the parent <select> element paired with the value of the selected <option>. Putting name on individual <option> elements has no effect on form data and can mislead developers into thinking it influences form behavior.
Removing the obsolete name attribute ensures your HTML is standards-compliant, avoids confusion for developers maintaining the code, and prevents potential issues with future browser behavior. If you need to reference a specific <option> in JavaScript or CSS, use the id global attribute instead.
Examples
Incorrect: using the obsolete name attribute
<select id="pet-select" name="pet">
<option value="">--Please choose an option--</option>
<option name="dog-option" value="dog">Dog</option>
<option name="cat-option" value="cat">Cat</option>
<option name="hamster-option" value="hamster">Hamster</option>
</select>
This triggers the validation error because name is not a valid attribute on <option>.
Correct: using id instead of name
If you need to uniquely identify each option (for example, to target them with JavaScript or CSS), use the id attribute:
<select id="pet-select" name="pet">
<option value="">--Please choose an option--</option>
<option id="dog-option" value="dog">Dog</option>
<option id="cat-option" value="cat">Cat</option>
<option id="hamster-option" value="hamster">Hamster</option>
</select>
Correct: simply removing name if no reference is needed
In most cases, you don't need to identify individual options at all. The value attribute is sufficient for form submission, and you can remove name entirely:
<select id="pet-select" name="pet">
<option value="">--Please choose an option--</option>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="hamster">Hamster</option>
</select>
Note that the name attribute on the <select> element itself is perfectly valid and necessary — it defines the key used when the form data is submitted. The obsolete attribute warning applies only to name on <option> elements.
The <noframes> element is obsolete, so remove it and move any content worth keeping into the page's <body> as regular markup.
<noframes> held fallback content for browsers that couldn't display a <frameset>. Every browser in use today displays frames, and the rendering rules in the HTML standard hide <noframes> with display: none wherever it appears, so visitors never see what's inside it. The parser also reads its contents as raw text, the same way it reads a <style> element, so the validator doesn't check the markup inside it either.
The validator reports <noframes> wherever it sits: inside a <frameset>, after the closing </frameset>, in the <head> or in a regular <body>. In the <head> and after </frameset> it also adds an error saying the element isn't allowed there, and that error goes away with this one.
What to do depends on the page around it. In a frameset document, <noframes> goes away together with the <frameset> and its <frame> elements, which get obsolete messages of their own. The fallback often already holds a frameless version of the page, such as links to the documents the frames loaded, and that content can become the start of the new <body>. When <noframes> is left over in a page that already has a <body>, usually copied from an old template, delete it. Nothing inside it was ever displayed, so the page looks the same without it.
Don't move the fallback into an <iframe> instead. An iframe has to stay empty, and the validator reports any content placed inside it.
Invalid example
A two-frame page with a fallback for browsers without frame support:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Club newsletter</title>
</head>
<frameset cols="25%,75%">
<frame src="issues.html">
<frame src="latest.html">
<noframes>
<body>
<p>This site uses frames. Read the <a href="latest.html">latest issue</a> or browse the <a href="issues.html">past issues</a>.</p>
</body>
</noframes>
</frameset>
</html>
Valid example
The frameset becomes a <body>, the links from the fallback become the page's navigation, and an <iframe> loads the issue the second frame used to show:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Club newsletter</title>
</head>
<body>
<nav>
<a href="latest.html" target="issue">Latest issue</a>
<a href="issues.html" target="issue">Past issues</a>
</nav>
<iframe src="latest.html" name="issue" title="Newsletter issue"></iframe>
</body>
</html>
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 profile attribute on <head> comes from HTML 4.01 and is obsolete, so remove it, or move its URL to a <link rel="profile"> element if a tool still reads it.
In HTML 4.01, profile held one or more space-separated URLs of metadata profiles. A profile defined the <meta> names and rel values a page used, so a program reading the page could look up what they meant. Browsers never acted on it, and HTML5 dropped the attribute. The validator reports it as a warning, and the page renders the same with or without it.
The message names a replacement for each of the attribute's two old uses. A page no longer has to declare its <meta> names. The HTML standard defines names like description and generator, and anyone can register other names on the WHATWG MetaExtensions wiki page. If a program read the profile URL to decide how to handle the page, put that URL in a <link rel="profile"> element, which the validator accepts.
The attribute often comes from an older WordPress theme whose header.php prints <head profile="..."> with the URL of the XFN (XHTML Friends Network) profile. Newer WordPress default themes print the same URL in a <link rel="profile"> element instead. Edit the template so the <head> tag has no attributes. If nothing reads the profile, you can leave out the <link> too.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head profile="https://example.com/profiles/xfn">
<title>My blog</title>
</head>
<body>
<p>Welcome to my blog.</p>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>My blog</title>
<link rel="profile" href="https://example.com/profiles/xfn">
</head>
<body>
<p>Welcome to my blog.</p>
</body>
</html>
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.
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.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries