HTML Guides for charset
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.
A character encoding declaration tells the browser how to interpret the raw bytes of your document into readable characters. For HTML documents, the standard way to declare this is with <meta charset="utf-8">. The HTML specification requires that this element be serialized completely within the first 1024 bytes of the document. This means that everything from the start of the file—including the doctype, the <html> tag, the <head> tag, and the <meta charset> element itself—must fit within that 1024-byte window.
If the <meta charset> element appears after the first 1024 bytes, the browser must use other heuristics or fallback encodings to guess how to decode the document. This can cause several problems:
- Garbled or broken text: Characters outside the ASCII range (such as accented letters, CJK characters, or emoji) may render incorrectly.
- Security vulnerabilities: Certain encoding-sniffing behaviors have historically been exploited for cross-site scripting (XSS) attacks, which is one reason the spec enforces this strict limit.
- Inconsistent rendering: Different browsers may fall back to different default encodings, meaning your page could look different depending on the user’s browser or system locale.
This issue typically occurs when a large number of <meta> tags, inline <style> blocks, lengthy comments, or <script> elements are placed in the <head> before the <meta charset> declaration. Even excessive whitespace or server-injected content can push it past the 1024-byte boundary.
To fix this, ensure that <meta charset="utf-8"> is the first child element of <head>, appearing before any <title>, <link>, <script>, <style>, or other <meta> tags. Remove or relocate any unnecessary content that precedes it.
Examples
❌ Incorrect: <meta charset> pushed past 1024 bytes
In this example, a large inline style block and several meta tags appear before the charset declaration, easily exceeding the 1024-byte limit:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="A very long description...">
<meta name="keywords" content="many, keywords, here, ...">
<meta name="author" content="Some Author">
<link rel="stylesheet" href="styles.css">
<style>
/* Hundreds of bytes of inline CSS rules...
...pushing the total well past 1024 bytes
before the charset declaration appears */
body { font-family: sans-serif; margin: 0; padding: 0; }
.container { max-width: 1200px; margin: 0 auto; }
/* ...many more rules... */
</style>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
✅ Correct: <meta charset> as the first element in <head>
Move the charset declaration to the very first position inside <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="description" content="A very long description...">
<meta name="keywords" content="many, keywords, here, ...">
<meta name="author" content="Some Author">
<link rel="stylesheet" href="styles.css">
<style>
body { font-family: sans-serif; margin: 0; padding: 0; }
.container { max-width: 1200px; margin: 0 auto; }
</style>
</head>
<body>
<p>Hello world</p>
</body>
</html>
✅ Minimal correct example
For simpler documents, the pattern is straightforward—just keep <meta charset> first:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
As a general rule of thumb, always make <meta charset="utf-8"> the very first thing after the opening <head> tag. This guarantees it falls well within the 1024-byte limit regardless of what follows, and it ensures the browser knows the correct encoding before it encounters any other content.
Both <meta charset="UTF-8"> and <meta http-equiv="content-type" content="text/html; charset=UTF-8"> instruct the browser which character encoding to use when interpreting the document’s bytes into text. Having both declarations in the same document creates a redundant and potentially conflicting situation. The HTML specification explicitly forbids including both, because if they ever specified different encodings, the browser would have to decide which one to trust, leading to unpredictable behavior.
Character encoding is critical for correctly displaying text. If the encoding is wrong or ambiguous, characters like accented letters, emoji, or symbols from non-Latin scripts can appear as garbled text (often called “mojibake”). By requiring a single, unambiguous declaration, the spec ensures browsers can reliably determine the encoding.
The <meta charset="UTF-8"> syntax was introduced with HTML5 as a shorter, cleaner alternative to the older <meta http-equiv="content-type"> approach. Both are valid on their own, but modern best practice strongly favors <meta charset="UTF-8"> for its simplicity. Whichever you choose, it should appear as early as possible within the <head> element — ideally as the first child — and must appear within the first 1024 bytes of the document so the browser can detect the encoding before parsing the rest of the content.
To fix this issue, search your document’s <head> for both forms of the declaration and remove one of them. In most cases, you should keep <meta charset="UTF-8"> and remove the <meta http-equiv="content-type"> element.
Examples
Incorrect: both declarations present
This triggers the validation error because both methods of declaring the character encoding are used simultaneously.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: using <meta charset> (recommended)
This is the modern, preferred approach for HTML5 documents.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: using <meta http-equiv="content-type">
This older syntax is also valid on its own. You might encounter it in legacy codebases or when serving documents as application/xhtml+xml.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Common scenario: declarations split across includes
In templating systems or CMS platforms, the two declarations sometimes end up in different partial files — for example, one in a base layout and another injected by a plugin or theme. If you encounter this error unexpectedly, check all files that contribute to your <head> section, not just the main template.
<!-- base-layout.html -->
<head>
<meta charset="UTF-8">
<!-- ...other tags... -->
</head>
<!-- plugin-head-snippet.html (remove this duplicate) -->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
Audit your includes and partials to ensure only one character encoding declaration ends up in the final rendered <head>.
The <meta charset> element tells the browser which character encoding to use when interpreting the bytes of the HTML document. The HTML specification explicitly states that there must be no more than one <meta> element with a charset attribute per document. This declaration should appear within the first 1024 bytes of the document, so placing it as the first child of <head> (right after the opening <head> tag) is the recommended practice.
Duplicate charset declarations typically happen when code is assembled from multiple templates, partials, or snippets — each contributing its own <meta charset>. It can also occur when a developer manually adds a charset declaration without realizing one is already present, or when migrating from an older <meta http-equiv="Content-Type"> approach and adding a new <meta charset> without removing the old equivalent.
Why this matters
- Standards compliance: The WHATWG HTML living standard mandates at most one <meta charset> per document. Violating this produces a validation error.
- Unpredictable behavior: When a browser encounters conflicting or duplicate charset declarations, the behavior is undefined. While most modern browsers will use the first one encountered, relying on this is fragile and could lead to garbled text or encoding issues in edge cases.
- Maintainability: Multiple charset declarations signal disorganized or duplicated template logic, making the codebase harder to maintain.
How to fix it
- Search your HTML document (including any templates, layouts, or partials that compose the final output) for all instances of <meta charset> or <meta charset="...">.
- Keep exactly one <meta charset="utf-8"> declaration, placed as the first element inside <head>.
- Remove all other <meta charset> elements.
- If you also have a legacy <meta http-equiv="Content-Type" content="text/html; charset=utf-8">, remove it — the shorter <meta charset="utf-8"> form is the modern replacement, and having both counts as duplicate charset declarations.
Examples
❌ Incorrect: multiple charset declarations
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The second <meta charset="utf-8"> triggers the validation error, even though both specify the same encoding.
❌ Incorrect: mixing old and new charset syntax
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Both elements declare a character encoding, so the validator treats this as a duplicate.
✅ Correct: single charset declaration
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
A single <meta charset="utf-8"> appears first in <head>, before any other elements or content. This is the correct and recommended approach. UTF-8 is the strongly recommended encoding for all new HTML documents.
The <meta charset="utf-8"> declaration is not only valid but recommended by the HTML living standard. So when the validator complains that the charset attribute is “not allowed at this point,” the problem isn’t the <meta> tag itself — it’s what surrounds it. The HTML parser follows strict rules about which elements can appear inside <head>. When it encounters an element that doesn’t belong there (like <img>, <div>, <p>, or other flow/phrasing content), it implicitly closes the <head> and opens the <body>. Any <meta> tags that come after that point are now parsed as being inside <body>, where <meta charset> is not permitted.
This is a problem for several reasons. First, the <meta charset> declaration must appear within the first 1024 bytes of the document so browsers can determine the character encoding early. If the parser moves it out of <head>, browsers may not apply the encoding correctly, potentially leading to garbled text — especially for non-ASCII characters. Second, this often signals a structural error in your HTML that could cause other unexpected rendering issues.
Common causes include:
- An element that only belongs in <body> (like <img>, <div>, <span>, or <p>) placed before <meta charset> in the <head>.
- A stray closing tag (like </head>) appearing too early.
- A <script> tag with content that causes the parser to break out of <head>.
To fix the issue, inspect the elements that appear before <meta charset> in your <head>. Move any elements that don’t belong in <head> into <body>, and place <meta charset="utf-8"> as the very first element inside <head>.
Examples
Incorrect — element before <meta> forces parser out of <head>
An <img> tag inside <head> causes the parser to implicitly close <head> and open <body>. The <meta charset> that follows is now parsed as being in <body>, triggering the error.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<img src="photo.jpg" alt="A smiling cat">
<meta charset="utf-8">
</head>
<body>
<p>Some content</p>
</body>
</html>
Correct — <meta charset> first, invalid elements moved to <body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<img src="photo.jpg" alt="A smiling cat">
<p>Some content</p>
</body>
</html>
Incorrect — stray <div> in <head> breaks context
<!DOCTYPE html>
<html lang="en">
<head>
<div>Oops</div>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello</p>
</body>
</html>
Correct — only valid head elements before <meta charset>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<div>Content goes here</div>
<p>Hello</p>
</body>
</html>
Best practice
As a general rule, always make <meta charset="utf-8"> the very first child of <head>. This ensures the browser detects the encoding as early as possible and avoids the risk of other elements accidentally breaking the parser context before the charset is declared.
The HTML5 specification mandates UTF-8 as the only permitted character encoding for web documents declared via <meta> tags. Legacy encodings such as windows-1251 (a Cyrillic character encoding), iso-8859-1, shift_jis, and others are no longer valid values in HTML5 <meta> declarations. This restriction exists because UTF-8 is a universal encoding that can represent virtually every character from every writing system, eliminating the interoperability problems that plagued the web when dozens of competing encodings were in use.
When the validator encounters content="text/html; charset=windows-1251" on a <meta> element, it flags it because the charset= portion must be followed by utf-8 — no other value is accepted. This applies whether you use the longer <meta http-equiv="Content-Type"> syntax or the shorter <meta charset> syntax.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly requires utf-8 as the character encoding when declared in a <meta> tag. Non-conforming encodings will trigger validation errors.
- Internationalization: UTF-8 supports all Unicode characters, making your pages work correctly for users across all languages, including Cyrillic text that windows-1251 was originally designed for.
- Security: Legacy encodings can introduce security vulnerabilities, including certain cross-site scripting (XSS) attack vectors that exploit encoding ambiguity.
- Browser consistency: While browsers may still recognize legacy encodings, relying on them can cause mojibake (garbled text) when there’s a mismatch between the declared and actual encoding.
How to fix it
- Update the <meta> tag to declare utf-8 as the charset.
- Re-save your file in UTF-8 encoding using your text editor or IDE. Most modern editors support this — look for an encoding option in “Save As” or in the status bar.
- Verify your server configuration. If your server sends a Content-Type HTTP header with a different encoding, the server header takes precedence over the <meta> tag. Make sure both agree on UTF-8.
- Convert your content. If your text was originally written in windows-1251, you may need to convert it to UTF-8. Tools like iconv on the command line can help: iconv -f WINDOWS-1251 -t UTF-8 input.html > output.html.
Examples
❌ Incorrect: Using windows-1251 charset
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
✅ Correct: Using utf-8 with http-equiv syntax
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
✅ Correct: Using the shorter <meta charset> syntax (preferred in HTML5)
<meta charset="utf-8">
Full document example
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Пример страницы</title>
</head>
<body>
<p>Текст на русском языке в кодировке UTF-8.</p>
</body>
</html>
The shorter <meta charset="utf-8"> syntax is generally preferred in HTML5 documents because it’s more concise and achieves the same result. Whichever syntax you choose, place the charset declaration within the first 1024 bytes of your document — ideally as the very first element inside <head> — so browsers can detect the encoding as early as possible.
The HTML specification mandates that documents must be encoded in UTF-8. This requirement exists because UTF-8 is the universal character encoding that supports virtually every character from every writing system in the world. Older encodings like windows-1252, iso-8859-1, or shift_jis only support a limited subset of characters and can cause text to display incorrectly — showing garbled characters or question marks — especially for users in different locales or when content includes special symbols, accented letters, or emoji.
When the validator encounters charset=windows-1252 in your <meta> tag, it flags this as an error because the HTML living standard (WHATWG) explicitly states that the character encoding declaration must specify utf-8 as the encoding. This isn’t just a stylistic preference — browsers and other tools rely on this declaration to correctly interpret the bytes in your document. Using a non-UTF-8 encoding can lead to security vulnerabilities (such as encoding-based XSS attacks) and accessibility issues when assistive technologies misinterpret characters.
To fix this issue, take two steps:
- Update the <meta> tag to declare utf-8 as the charset.
- Re-save your file with UTF-8 encoding. Most modern code editors (VS Code, Sublime Text, etc.) let you choose the encoding when saving — look for an option like “Save with Encoding” or check the status bar for the current encoding. If your file was originally in windows-1252, simply changing the <meta> tag without re-encoding the file could cause existing special characters to display incorrectly.
The HTML spec also recommends using the shorter <meta charset="utf-8"> form rather than the longer <meta http-equiv="Content-Type" ...> pragma directive, as it’s simpler and achieves the same result. Either form is valid, but the charset declaration must appear within the first 1024 bytes of the document.
Examples
Incorrect: Using windows-1252 charset
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
This triggers the validator error because the charset is not utf-8.
Correct: Using the short charset declaration (recommended)
<meta charset="utf-8">
Correct: Using the http-equiv pragma directive with utf-8
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Note that the <meta charset="utf-8"> tag should be the first element inside <head>, before any other elements (including <title>), so the browser knows the encoding before it starts parsing the rest of the document.
The <meta> element provides metadata about the HTML document — information that isn’t displayed on the page but is used by browsers, search engines, and other web services. According to the HTML specification, a <meta> tag without any of the recognized attributes is meaningless. The validator flags this because a bare <meta> element (or one with only unrecognized attributes) provides no useful metadata and likely indicates an error or incomplete tag.
This issue commonly occurs when a <meta> tag is left empty by accident, when an attribute name is misspelled (e.g., naem instead of name), or when a required attribute is accidentally deleted during editing.
Most <meta> use cases fall into a few patterns, each requiring specific attribute combinations:
- charset — Used alone to declare the document’s character encoding.
- name + content — Used together to define named metadata like descriptions, viewport settings, or author information.
- http-equiv + content — Used together to simulate an HTTP response header.
- property + content — Used together for Open Graph and similar RDFa-based metadata.
- itemprop + content — Used together for microdata annotations.
Note that content alone is not sufficient — it must be paired with name, http-equiv, property, or itemprop to have meaning.
Examples
Incorrect: bare <meta> tag with no attributes
This triggers the validation error because the <meta> element has no recognized attributes:
<meta>
Incorrect: misspelled attribute
A typo in the attribute name means the validator doesn’t recognize it:
<meta nane="description" content="An example page.">
Incorrect: content without a pairing attribute
The content attribute alone is not enough — it needs name, http-equiv, property, or itemprop:
<meta content="some value">
Correct: character encoding with charset
<meta charset="UTF-8">
Correct: named metadata with name and content
<meta name="description" content="A brief description of the webpage.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Jane Doe">
Correct: HTTP-equivalent with http-equiv and content
<meta http-equiv="X-UA-Compatible" content="IE=edge">
Correct: Open Graph metadata with property and content
<meta property="og:title" content="My Page Title">
<meta property="og:description" content="A summary of the page content.">
Correct: microdata with itemprop and content
<meta itemprop="name" content="Product Name">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A brief description of the webpage.">
<meta property="og:title" content="My Page Title">
<title>Example Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
How to fix
- Find the flagged <meta> tag in your HTML source at the line number the validator reports.
- Check for typos in attribute names — make sure name, charset, http-equiv, property, or itemprop is spelled correctly.
- Add the missing attribute. Determine what the <meta> tag is supposed to do and add the appropriate attribute(s). If you can’t determine its purpose, it may be safe to remove it entirely.
- Ensure proper pairing. If you’re using content, make sure it’s paired with name, http-equiv, property, or itemprop. The charset attribute is the only one that works on its own without content.
The charset attribute on the <script> element tells the browser what character encoding to use when interpreting the referenced external script file. When a script is written directly inside the HTML document (an inline script), the script’s character encoding is inherently the same as the document’s encoding — there is no separate file to decode. Because of this, the HTML specification requires that charset only appear on <script> elements that also have a src attribute pointing to an external file.
Including charset without src violates the HTML specification and signals a misunderstanding of how character encoding works for inline scripts. Validators flag this because browsers ignore the charset attribute on inline scripts, which means it has no effect and could mislead developers into thinking they’ve set the encoding when they haven’t.
It’s also worth noting that the charset attribute on <script> is deprecated in the HTML living standard, even for external scripts. The modern best practice is to serve external script files with the correct Content-Type HTTP header (e.g., Content-Type: application/javascript; charset=utf-8) or to simply ensure all your files use UTF-8 encoding, which is the default. If you’re working with an older codebase that still uses charset, consider removing it entirely and relying on UTF-8 throughout.
Examples
Incorrect: charset on an inline script
This triggers the validation error because charset is specified without a corresponding src attribute.
<script charset="utf-8">
console.log("Hello, world!");
</script>
Correct: Remove charset from inline scripts
Since inline scripts use the document’s encoding, simply remove the charset attribute.
<script>
console.log("Hello, world!");
</script>
Correct: Use charset with an external script (deprecated but valid)
If you need to specify the encoding of an external script, both charset and src must be present. Note that this usage, while valid, is deprecated.
<script src="app.js" charset="utf-8"></script>
Recommended: External script without charset
The preferred modern approach is to omit charset entirely and ensure the server delivers the file with the correct encoding header, or simply use UTF-8 for everything.
<script src="app.js"></script>
The HTML living standard mandates UTF-8 as the only permitted character encoding for HTML documents. Legacy encodings like windows-1252, iso-8859-1, shift_jis, and others were common in older web pages, but they support only a limited subset of characters. UTF-8, on the other hand, can represent every character in the Unicode standard, making it universally compatible across languages and scripts.
This issue typically arises from one or more of these causes:
- Missing or incorrect <meta charset> declaration — Your document either lacks a charset declaration or explicitly declares a legacy encoding like <meta charset="windows-1252">.
- File not saved as UTF-8 — Even with the correct <meta> tag, if your text editor saves the file in a different encoding, characters may become garbled (mojibake).
- Server sends a conflicting Content-Type header — The HTTP Content-Type header can override the in-document charset declaration. If your server sends Content-Type: text/html; charset=windows-1252, the browser will use that encoding regardless of what the <meta> tag says.
Why This Matters
- Standards compliance: The WHATWG HTML living standard explicitly states that documents must be encoded in UTF-8. Using a legacy encoding makes your document non-conforming.
- Internationalization: Legacy encodings like windows-1252 only support a limited set of Western European characters. If your content ever includes characters outside that range—emoji, CJK characters, Cyrillic, Arabic, or even certain punctuation—they won’t render correctly.
- Security: Mixed or ambiguous encodings can lead to security vulnerabilities, including certain types of cross-site scripting (XSS) attacks that exploit encoding mismatches.
- Consistency: When the declared encoding doesn’t match the actual file encoding, browsers may misinterpret characters, leading to garbled text that’s difficult to debug.
How to Fix It
Step 1: Declare UTF-8 in your HTML
Add a <meta charset="utf-8"> tag as the first element inside <head>. It must appear within the first 1024 bytes of the document so browsers can detect it early.
Step 2: Save the file as UTF-8
In most modern text editors and IDEs, you can set the file encoding:
- VS Code: Click the encoding label in the bottom status bar and select “Save with Encoding” → “UTF-8”.
- Sublime Text: Go to File → Save with Encoding → UTF-8.
- Notepad++: Go to Encoding → Convert to UTF-8.
If your file already contains characters encoded in windows-1252, simply changing the declaration without re-encoding the file will cause those characters to display incorrectly. You need to convert the file’s actual encoding.
Step 3: Check your server configuration
If your server sends a charset parameter in the Content-Type HTTP header, make sure it specifies UTF-8. For example, in Apache you can add this to your .htaccess file:
AddDefaultCharset UTF-8
In Nginx, you can set it in your server block:
charset utf-8;
Examples
Incorrect: Legacy encoding declared
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="windows-1252">
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
This triggers the error because windows-1252 is a legacy encoding.
Incorrect: Using the long-form http-equiv with a legacy encoding
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
This older syntax also triggers the error when it specifies a non-UTF-8 encoding.
Correct: UTF-8 declared properly
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
The <meta charset="utf-8"> tag appears as the first child of <head>, and the file itself should be saved with UTF-8 encoding.
Correct: Using http-equiv with UTF-8
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
While the shorter <meta charset="utf-8"> form is preferred, this longer syntax is also valid as long as it specifies UTF-8.
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 charset attribute 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.
Ready to validate your sites?
Start your free trial today.