HTML Guides for at-rule
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 @container at-rule enables container queries, allowing you to apply styles to elements based on the size or inline-size of a parent container rather than the viewport. It was introduced as part of the CSS Containment Module Level 3 specification and has been supported in all major browsers (Chrome, Edge, Firefox, Safari) since early 2023.
The W3C CSS validator uses its own CSS parsing engine, which sometimes lags behind the latest CSS specifications. Because @container is relatively new compared to long-established at-rules like @media or @keyframes, the validator may flag it as unrecognized. This does not mean your CSS is invalid or broken — it simply means the validator hasn't caught up with the spec yet.
Why This Warning Appears
The W3C validator checks your CSS against known grammar rules. When it encounters an at-rule it doesn't have in its internal dictionary, it reports it as unrecognized. Other modern CSS features like @layer and @property have historically triggered the same kind of false positive before being added to the validator's parser.
Since this is a validator limitation and not an actual code issue, there's no required "fix." However, you should still make sure your @container usage is syntactically correct and that you've properly set up containment on the parent element.
How Container Queries Work
For @container to function, a parent element must be designated as a containment context using the container-type property (or the container shorthand). Without this, the browser won't know which ancestor to query against.
Examples
Correct usage of @container
The parent element needs container-type set to inline-size or size so its descendants can query against it:
<div class="card-wrapper">
<div class="card">
<h2>Title</h2>
<p>Some content here.</p>
</div>
</div>
.card-wrapper {
container-type: inline-size;
container-name: card-container;
}
@container card-container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
}
In this example, .card-wrapper is established as a containment context. When its inline size is at least 400px, the .card inside switches to a flex layout. The validator may flag the @container block, but this CSS is perfectly valid and works in all modern browsers.
Using @container without a named container
You can omit the container name, and the query will match the nearest ancestor with a container-type set:
.sidebar {
container-type: inline-size;
}
@container (max-width: 300px) {
.sidebar-nav {
flex-direction: column;
}
}
Common mistake: forgetting container-type
If you use @container without declaring a containment context on a parent, the query will never match and your styles won't apply. This won't cause a validator error, but it's a logical bug:
/* Missing container-type — the @container query has nothing to query against */
.wrapper {
max-width: 600px;
}
@container (min-width: 400px) {
.content {
font-size: 1.25rem;
}
}
The fix is to add container-type to the intended parent:
.wrapper {
max-width: 600px;
container-type: inline-size;
}
@container (min-width: 400px) {
.content {
font-size: 1.25rem;
}
}
What You Should Do
- Verify your syntax — make sure you're using
container-typeon a parent element and that your@containerquery follows the correct grammar. - Ignore the validator warning — this is a known limitation of the W3C CSS validator. Your CSS is valid per the specification.
- Check browser support —
@containeris supported in Chrome 105+, Edge 105+, Firefox 110+, and Safari 16+. If you need to support older browsers, consider using@containeras a progressive enhancement alongside a fallback layout.
The validator doesn't know the @property at-rule yet, but it is valid CSS from the CSS Properties and Values API and works in all major browsers, so you don't need to change it.
@property registers a custom property with a type. The syntax descriptor sets the kind of value it accepts, such as "<angle>" or "<length>". inherits says whether child elements inherit the value, and initial-value is the value the property has when nothing sets it. Once the browser knows the type, it can interpolate the property, so a transition or animation on it changes smoothly. To the browser, an unregistered custom property is just a sequence of tokens, and an animation on it jumps from the start value to the end value halfway through. That is why @property often shows up next to a rotating gradient, like the one in the examples below.
Chrome and Edge have supported @property since version 85, Safari since 16.4 and Firefox since 128, which made it Baseline in July 2024.
The validator skips the whole block, so it can't tell you when the rule itself is broken. syntax and inherits are required, and so is initial-value unless syntax is "*". If one of them is missing, browsers ignore the whole rule and the property behaves like an ordinary custom property.
Keep the rule and mute this message. If you want a clean report instead, move the @property rules to an external stylesheet: the validator checks the CSS in <style> elements and style attributes, but it doesn't load linked stylesheets. Wrapping the rule in @supports, @media or @layer doesn't help, because the validator still reports the @property inside it.
Example that triggers the message
<head>
<title>Rotating gradient</title>
<style>
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--angle), #4f46e5, #ec4899);
animation: spin 4s linear infinite;
}
@keyframes spin {
to {
--angle: 360deg;
}
}
</style>
</head>
Example with the rule in an external stylesheet
<head>
<title>Rotating gradient</title>
<link rel="stylesheet" href="gradient.css">
</head>
The same CSS goes into gradient.css, unchanged:
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--angle), #4f46e5, #ec4899);
animation: spin 4s linear infinite;
}
@keyframes spin {
to {
--angle: 360deg;
}
}
The validator doesn't know the @starting-style at-rule yet, but it is valid CSS from CSS Transitions Level 2 and works in all major browsers, so you don't need to change it.
Browsers don't run a CSS transition the first time they style an element, so an element that a script adds to the page, or one that goes from display: none to visible like an opened <dialog> or popover, shows up in its final state at once. @starting-style gives the browser the values to start from in that case, and the element transitions from them to its normal styles. That is how you fade in a toast or slide in a dialog with CSS alone.
You can write @starting-style as a top-level block around whole rules, or nest it inside a rule, where it holds plain declarations for that rule's selector. The validator reports both forms, once for each @starting-style block.
Chrome and Edge have supported @starting-style since version 117, Safari since 17.5 and Firefox since 129, which made it Baseline in August 2024. A browser without support ignores the rule, and the element appears without the entry transition, so nothing breaks.
The validator skips everything inside the block, so it won't report a typo in a property or value there. It still checks the CSS that comes after the block.
Keep the rule and mute this message. If you want a clean report instead, move the CSS that uses @starting-style to an external stylesheet: the validator checks the CSS in <style> elements and style attributes, but it doesn't load linked stylesheets. Wrapping the block in @supports, @media or @layer doesn't help, because the validator still reports the @starting-style inside it.
Example that triggers the message
<head>
<title>Toast notification</title>
<style>
.toast {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s, transform 0.3s;
}
@starting-style {
.toast {
opacity: 0;
transform: translateY(1rem);
}
}
</style>
</head>
Example with the rule in an external stylesheet
<head>
<title>Toast notification</title>
<link rel="stylesheet" href="toast.css">
</head>
The same CSS goes into toast.css, unchanged:
.toast {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s, transform 0.3s;
}
@starting-style {
.toast {
opacity: 0;
transform: translateY(1rem);
}
}
Older builds of the validator reported at-rules like @keyframes and @font-face outside an @scope block in a <style> element in the <body>, but the HTML standard allows them there.
Since October 2026, a <style> element can sit in the <body> as long as its styles can't reach anything earlier in the page. Its style rules have to go inside an @scope block, and it can't use @import. An earlier draft of that change allowed only a few at-rules at the top level of the style sheet, such as @media, @supports and @layer. Builds of the Nu HTML Checker from late September and early October 2026 followed the draft and reported every other at-rule with this message.
The final rule lets at-rules that don't select elements stay outside @scope, at the top level or inside @media. That includes @keyframes, @font-face, @page and @counter-style. Current builds of the validator don't report them, so the code doesn't need to change: validate the page again and the error goes away. @import is still not allowed, and it now has a message of its own.
The names these at-rules define, such as an animation name or a font family, apply to the whole page. The HTML standard suggests using them only in the scoped rules of the same <style> element, and picking names that nothing else on the page uses. That way they can't restyle content the browser has already drawn.
Example
Older builds reported both at-rules in this <style> element. Current builds accept it, because the only style rule is inside @scope:
<div class="card">
<style>
@font-face {
font-family: "Card Sans";
src: url("card-sans.woff2") format("woff2");
}
@keyframes card-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@scope {
p {
font-family: "Card Sans", sans-serif;
animation: card-fade-in 0.3s ease-in;
}
}
</style>
<p>Card content</p>
</div>
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