HTML Guides for link
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 <link> element is used to define relationships between the current document and external resources — most commonly stylesheets, favicons, and preloaded assets. According to the HTML specification, <link> elements belong in the <head> section of a document (with a narrow exception for body-ok link types like certain rel="stylesheet" uses, though validators still expect them in the <head> for clarity and correctness).
This error typically happens in a few common scenarios:
- A
<link>tag is accidentally placed inside the<body>element. - A
<link>tag appears after the closing</head>tag but before the<body>tag. - A
<link>tag is placed after the closing</html>tag, often by a CMS, template engine, or plugin injecting code at the end of the document. - The
</head>tag is missing or misplaced, causing the browser to implicitly close the<head>before reaching the<link>.
When the validator encounters a <link> element outside its expected context, it reports it as a "stray" start tag — meaning the element has been found somewhere it doesn't belong.
Why this matters
Standards compliance: The HTML specification defines <link> as metadata content that belongs in <head>. Placing it elsewhere produces invalid HTML.
Unpredictable behavior: Browsers will attempt to handle misplaced <link> elements, but the behavior can be inconsistent. A stylesheet <link> found in the <body> may trigger a flash of unstyled content (FOUC) as the browser re-renders the page after discovering the new stylesheet.
Performance: Stylesheets linked from the <head> are discovered early during parsing, allowing the browser to fetch and apply them before rendering begins. Misplaced <link> elements can delay resource loading and degrade the user experience.
Maintainability: Keeping all <link> elements in the <head> makes the document structure predictable and easier for other developers to maintain.
How to fix it
- Locate the stray
<link>element — the validator will indicate the line number where the issue occurs. - Move it into the
<head>section — place it after the opening<head>tag and before the closing</head>tag. - Check that your
</head>tag exists and is in the right place — a missing or misplaced</head>can cause elements you thought were in the head to actually end up in the body. - Review template or CMS output — if you use a static site generator, CMS, or framework, check that plugins or includes aren't injecting
<link>tags outside the<head>.
Examples
❌ <link> placed inside the <body>
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<linkrel="stylesheet"href="css/app.css">
<h1>Hello</h1>
</body>
</html>
❌ <link> placed after the closing </html> tag
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
<linkrel="stylesheet"href="css/app.css">
❌ Missing </head> causes <link> to become stray
In this example, the </head> tag is missing. The browser encounters the <body> tag and implicitly closes the <head>, but the <link> after it becomes stray content between the implicit head closure and the body.
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
<body>
<linkrel="stylesheet"href="css/app.css">
<h1>Hello</h1>
</body>
</html>
✅ Correct: all <link> elements inside <head>
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
<linkrel="stylesheet"href="css/app.css">
<linkrel="icon"href="favicon.ico">
</head>
<body>
<h1>Hello</h1>
</body>
</html>
All <link> elements are placed within the <head> section, the </head> closing tag is present, and the document structure is clean and valid.
The <link> element is used to define relationships between the current document and external resources—most commonly stylesheets, favicons, and preloaded assets. It is a void element (it has no closing tag) and it produces no rendered content on the page. Because <link> elements are inherently non-visible and already absent from the accessibility tree, the aria-hidden attribute serves no purpose on them.
The aria-hidden attribute is designed to control the visibility of rendered content for assistive technologies like screen readers. When set to "true" on a visible element, it tells assistive technologies to skip that element and its descendants. Applying it to a <link> element is contradictory—you're trying to hide something from assistive technologies that was never exposed to them in the first place. The HTML specification explicitly disallows this combination, and the W3C validator will flag it as an error.
This issue sometimes arises when developers apply aria-hidden broadly through templating systems, JavaScript frameworks, or build tools that inject attributes across multiple element types without distinguishing between visible content elements and metadata elements. It can also happen when copying attribute patterns from <a> elements (which share the word "link" conceptually but are entirely different elements) onto <link> elements.
How to fix it
The fix is straightforward: remove the aria-hidden attribute from the <link> element. No replacement or alternative attribute is needed because <link> elements are already invisible to assistive technologies.
If your original intent was to hide a visible element from screen readers, make sure you're applying aria-hidden to the correct element—a rendered content element such as <div>, <span>, <img>, or <a>, not a metadata element like <link>.
Examples
Incorrect: aria-hidden on a <link> element
<linkrel="stylesheet"href="styles.css"aria-hidden="true">
<linkrel="icon"href="favicon.ico"aria-hidden="true">
<linkrel="preload"href="font.woff2"as="font"type="font/woff2"aria-hidden="true">
Correct: <link> without aria-hidden
<linkrel="stylesheet"href="styles.css">
<linkrel="icon"href="favicon.ico">
<linkrel="preload"href="font.woff2"as="font"type="font/woff2">
Correct: aria-hidden used on a visible element instead
If you need to hide a decorative element from screen readers, apply aria-hidden to the rendered element itself:
<linkrel="stylesheet"href="styles.css">
<divaria-hidden="true">
<imgsrc="decorative-swirl.png"alt="">
</div>
Incorrect vs. correct in a full document
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
<!-- Incorrect: aria-hidden on link -->
<linkrel="stylesheet"href="styles.css"aria-hidden="true">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
<!-- Correct: no aria-hidden on link -->
<linkrel="stylesheet"href="styles.css">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The <base> element must appear before any <link> or <script> elements in the <head> section of the document.
The <base> element sets the base URL and/or default target for all relative URLs in a page. Because <link> and <script> elements often reference external resources using relative URLs, the browser needs to know the base URL before it encounters those elements. If <base> appears after a <link> or <script>, the browser may have already resolved their URLs without the intended base, leading to broken references or unexpected behavior.
Only one <base> element is allowed per document, and the HTML specification requires it to appear before any elements that have attributes accepting URLs. In practice, placing it as the first child of <head> (after <meta charset>) is the simplest way to satisfy this rule.
Invalid example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Example</title>
<linkrel="stylesheet"href="styles/main.css">
<scriptsrc="js/app.js"></script>
<basehref="https://example.com/">
</head>
<body>
<p>Hello</p>
</body>
</html>
The <base> element comes after the <link> and <script> elements, which triggers the validation error.
Valid example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<basehref="https://example.com/">
<title>Example</title>
<linkrel="stylesheet"href="styles/main.css">
<scriptsrc="js/app.js"></script>
</head>
<body>
<p>Hello</p>
</body>
</html>
Moving <base> before the <link> and <script> elements fixes the error and ensures all relative URLs resolve correctly.
In earlier versions of HTML, the charset attribute on <link> was used to indicate the character encoding of the linked resource (such as a stylesheet or other external file). The HTML Living Standard has since made this attribute obsolete. Modern browsers determine the character encoding of linked resources through the Content-Type HTTP response header — for example, Content-Type: text/css; charset=UTF-8 — or by using default encoding rules defined in the relevant specification (CSS files, for instance, default to UTF-8).
Using the obsolete charset attribute creates several issues. It gives a false sense of control over encoding, since browsers don't actually use it. It also clutters your markup with unnecessary attributes, and it may signal to other developers that this is the mechanism being relied upon for encoding, when in reality it has no effect. For standards compliance and clean, future-proof markup, it should be removed.
The fix involves two steps: first, remove the charset attribute from your HTML; second, ensure your web server sends the correct Content-Type header for the linked resources.
Examples
Incorrect: using charset on a <link> element
<linkrel="stylesheet"href="styles.css"charset="UTF-8">
<linkrel="stylesheet"href="print.css"charset="iso-8859-1">
Both of these will trigger the validation warning because the charset attribute is obsolete on <link>.
Correct: charset attribute removed
<linkrel="stylesheet"href="styles.css">
<linkrel="stylesheet"href="print.css">
Simply removing the attribute resolves the validation error. The server should handle encoding declaration instead.
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>Example Page</title>
<linkrel="stylesheet"href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Configuring the server to declare character encoding
Once you've removed the charset attribute, make sure your web server sends the proper Content-Type header with the linked resources. Most modern servers already default to UTF-8 for CSS and JavaScript files, but you can configure this explicitly.
Apache
Add the following to your .htaccess file or server configuration:
<FilesMatch "\.css$">
AddCharset UTF-8 .css
</FilesMatch>
This causes Apache to serve CSS files with the header Content-Type: text/css; charset=UTF-8.
Nginx
Add a charset directive to the relevant location block in your server configuration:
location ~* \.css$ {
charset utf-8;
}
Node.js (Express)
Set the Content-Type header explicitly when serving the file:
app.get('/styles.css',function(req,res){
res.setHeader('Content-Type','text/css; charset=UTF-8');
res.sendFile(__dirname+'/styles.css');
});
If you're using Express's built-in static file middleware (express.static), it typically sets correct content types automatically based on file extensions.
Verifying the fix
You can confirm that the server is sending the correct header by opening your browser's developer tools, navigating to the Network tab, clicking on the linked resource, and inspecting the Content-Type response header. It should include charset=UTF-8 (or whichever encoding your file uses). Once the attribute is removed from your HTML and the server is configured correctly, the validation warning will be resolved.
The <a> element with an href attribute is one of HTML's most fundamental interactive elements. Browsers and assistive technologies inherently recognize it as a link — it's focusable via the Tab key, activatable with Enter, and announced as "link" by screen readers. This built-in behavior is part of the element's implicit ARIA role, which is link.
When you explicitly add role="link" to an <a href="..."> element, you're telling assistive technologies something they already know. The W3C validator flags this as unnecessary because it violates the principle of not redundantly setting ARIA roles that match an element's native semantics. This principle is codified in 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."
While a redundant role="link" won't typically break anything, it creates noise in your markup. It can also signal to other developers that the role is necessary, leading to confusion or cargo-cult patterns. Clean, semantic HTML that relies on native roles is easier to maintain and less error-prone.
The role="link" attribute is legitimately useful when a non-interactive element like a <span> or <div> needs to behave as a link. In that case, you must also manually implement keyboard interaction (focus via tabindex, activation via Enter key handling) and provide an accessible name. But when you already have a proper <a> element with href, all of that comes for free — no ARIA needed.
Examples
❌ Incorrect: redundant role="link" on an anchor
<ahref="/about"role="link">About Us</a>
The role="link" is redundant here because the <a> element with href already has an implicit role of link.
✅ Correct: anchor without redundant role
<ahref="/about">About Us</a>
Simply remove the role="link" attribute. The browser and assistive technologies already treat this as a link.
✅ Correct: using role="link" on a non-semantic element (when necessary)
<spanrole="link"tabindex="0"onclick="location.href='/about'"onkeydown="if(event.key==='Enter')location.href='/about'">
About Us
</span>
This is the legitimate use case for role="link" — when you cannot use a native <a> element and need to make a non-interactive element behave like a link. Note the additional work required: tabindex="0" for keyboard focusability, a click handler, and a keydown handler for Enter key activation. Using a proper <a> element avoids all of this extra effort.
❌ Incorrect: multiple anchors with redundant roles
<nav>
<ahref="/"role="link">Home</a>
<ahref="/products"role="link">Products</a>
<ahref="/contact"role="link">Contact</a>
</nav>
✅ Correct: clean navigation without redundant roles
<nav>
<ahref="/">Home</a>
<ahref="/products">Products</a>
<ahref="/contact">Contact</a>
</nav>
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