HTML Guides for headers
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.
Content Security Policy (CSP) is a security mechanism that lets you control which resources a browser is allowed to load for your page. When defined via a <meta http-equiv="Content-Security-Policy"> tag, the validator checks whether the content attribute contains a well-formed policy. If the policy string contains unrecognized directives, malformed source expressions, or syntax errors, the validator reports “Bad content security policy.”
Common causes of this error include:
- Misspelled directive names — e.g., script-scr instead of script-src.
- Invalid source values — e.g., using self without single quotes (it must be 'self').
- Using directives not allowed in <meta> tags — the frame-ancestors, report-uri, and sandbox directives are not supported when CSP is delivered via a <meta> element.
- Incorrect separators — directives are separated by semicolons (;), not commas or pipes.
- Missing or extra quotes — keywords like 'none', 'self', 'unsafe-inline', and 'unsafe-eval' must be wrapped in single quotes. Conversely, hostnames and URLs must not be quoted.
This matters because a malformed CSP may be silently ignored by browsers, leaving your site without the intended protection against cross-site scripting (XSS) and data injection attacks. Even a small typo can cause an entire directive to be skipped, creating a security gap you might not notice.
How to fix it
- Check directive names against the CSP specification. Valid fetch directives include default-src, script-src, style-src, img-src, font-src, connect-src, media-src, object-src, child-src, worker-src, and others.
- Wrap keyword values in single quotes: 'self', 'none', 'unsafe-inline', 'unsafe-eval', and nonce/hash sources like 'nonce-abc123'.
- Separate directives with semicolons. Multiple source values within a single directive are separated by spaces.
- Avoid directives that are invalid in <meta> tags. If you need frame-ancestors or report-uri, deliver CSP via an HTTP header instead.
- Don’t include the header name inside the content attribute. The content value should contain only the policy itself.
Examples
❌ Misspelled directive and unquoted keyword
<meta http-equiv="Content-Security-Policy"
content="default-src self; script-scr https://example.com">
Here, self is missing its required single quotes, and script-scr is a typo for script-src.
✅ Corrected directive name and properly quoted keyword
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src https://example.com">
❌ Using a directive not allowed in a <meta> tag
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; frame-ancestors 'none'">
The frame-ancestors directive is ignored in <meta> elements and may trigger a validation warning.
✅ Removing the unsupported directive from the <meta> tag
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'">
Deliver frame-ancestors via an HTTP response header on your server instead.
❌ Using commas instead of semicolons between directives
<meta http-equiv="Content-Security-Policy"
content="default-src 'self', script-src 'none', style-src 'self'">
✅ Using semicolons to separate directives
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'none'; style-src 'self'">
❌ Quoting a hostname (hostnames must not be in quotes)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'https://images.example.com'">
✅ Hostname without quotes
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://images.example.com">
When in doubt, use an online CSP evaluator to validate your policy string before adding it to your HTML. This ensures both syntactic correctness and that the policy actually enforces what you intend.
The headers attribute creates explicit associations between data cells (td) and header cells (th) in complex tables. This is especially important for tables with irregular structures—such as those with merged cells or multiple header levels—where the browser cannot automatically determine which headers apply to which data cells.
When the validator reports this error, it means one or more IDs referenced in a td‘s headers attribute cannot be matched to any th element with that id in the same table. Common causes include:
- Typos — A small misspelling in either the headers value or the th element’s id.
- Missing id — The th element exists but doesn’t have an id attribute assigned.
- Removed or renamed headers — The th was deleted or its id was changed during refactoring, but the td still references the old value.
- Cross-table references — The th with the referenced id exists in a different <table>, which is not allowed.
Why this matters
This issue directly impacts accessibility. Screen readers use the headers attribute to announce which header cells are associated with a data cell. When a referenced ID doesn’t resolve to a th in the same table, assistive technology cannot provide this context, making the table confusing or unusable for users who rely on it. Broken headers references also indicate invalid HTML according to the WHATWG HTML specification, which requires that each token in the headers attribute match the id of a th cell in the same table.
How to fix it
- Locate the td element flagged by the validator and note the ID it references.
- Search the same <table> for a th element with a matching id.
- If the th exists but has no id or a different id, add or correct the id attribute so it matches.
- If the th was removed, either restore it or remove the headers attribute from the td.
- Double-check for case sensitivity — HTML id values are case-sensitive, so headers="Name" does not match id="name".
Examples
Incorrect: headers references a non-existent ID
The first td references "product", but no th has id="product". The second th has id="cost", but the second td references "price" — a mismatch.
<table>
<tr>
<th>Product</th>
<th id="cost">Price</th>
</tr>
<tr>
<td headers="product">Widget</td>
<td headers="price">$9.99</td>
</tr>
</table>
Correct: each headers value matches a th with the same id
<table>
<tr>
<th id="product">Product</th>
<th id="cost">Price</th>
</tr>
<tr>
<td headers="product">Widget</td>
<td headers="cost">$9.99</td>
</tr>
</table>
Correct: multiple headers on a single td
In complex tables, a data cell may relate to more than one header. List multiple IDs separated by spaces — each one must correspond to a th in the same table.
<table>
<tr>
<th id="region" rowspan="2">Region</th>
<th id="q1" colspan="2">Q1</th>
</tr>
<tr>
<th id="sales">Sales</th>
<th id="returns">Returns</th>
</tr>
<tr>
<td headers="region">North</td>
<td headers="q1 sales">1200</td>
<td headers="q1 returns">45</td>
</tr>
</table>
Tip: simple tables may not need headers at all
For straightforward tables with a single row of column headers, browsers and screen readers can infer the associations automatically. In those cases, you can omit the headers attribute entirely and avoid this class of error:
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$9.99</td>
</tr>
</table>
Reserve the headers attribute for complex tables where automatic association is insufficient — such as tables with cells that span multiple rows or columns, or tables with headers in both rows and columns.
Ready to validate your sites?
Start your free trial today.