CSS At-Rules: The Complete Overview
At-rules are CSS statements that begin with an @ sign and instruct the browser how to behave. While ordinary rules describe how elements should look, at-rules describe conditions, imports, definitions, and scopes — the meta-layer of your stylesheet. Modern CSS ships more than a dozen of them, and several (like @layer and @container) fundamentally change how you architect styles.
The Two Kinds of At-Rules
At-rules come in two structural flavors: statement at-rules end with a semicolon and contain no block, while block at-rules wrap a set of rules (or descriptors) in curly braces.
/* Statement at-rules — end with a semicolon */
@charset "utf-8";
@import url("reset.css");
@namespace svg url(http://www.w3.org/2000/svg);
/* Block at-rules — contain a { ... } block */
@media (min-width: 768px) {
.sidebar { display: block; }
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
}Quick Reference
At-rule | Purpose | Type |
|---|---|---|
@media | Apply styles based on viewport / device features | Conditional |
@supports | Apply styles only if the browser supports a feature | Conditional |
@container | Apply styles based on a container element size | Conditional |
@import | Pull in another stylesheet | Statement |
@font-face | Define a downloadable font | Definition |
@keyframes | Define animation steps | Definition |
@layer | Declare cascade layers and their order | Cascade control |
@property | Register a typed custom property | Definition |
@counter-style | Define custom list counters | Definition |
@page | Style printed pages | |
@scope | Limit rules to a DOM subtree | Scoping |
@charset | Declare stylesheet encoding | Statement |
@media — Respond to the Environment
The most widely used at-rule. It gates a block of styles behind a media query: viewport size, orientation, color-scheme preference, motion preference, and more.
/* Classic min-width query */
@media (min-width: 64rem) {
.layout { grid-template-columns: 240px 1fr; }
}
/* Modern range syntax */
@media (768px <= width < 1200px) {
.layout { padding-inline: 2rem; }
}
/* User preferences */
@media (prefers-color-scheme: dark) {
:root { --surface: #111; --text: #eee; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important; }
}@supports — Feature Queries
@supports tests whether the browser understands a property/value pair (or a selector), enabling true progressive enhancement in CSS itself — no JavaScript feature detection required.
/* Fallback first, enhancement inside @supports */
.gallery {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}
/* Combine tests with and / or / not */
@supports (display: grid) and (gap: 1rem) {
.gallery { gap: 1rem; }
}
/* Test selector support */
@supports selector(:has(img)) {
.card:has(img) { padding: 0; }
}@import — Load Other Stylesheets
/* Must appear before all other rules (except @charset / @layer) */
@import url("typography.css");
@import url("print.css") print;
@import url("wide.css") (min-width: 1024px);
/* Import directly into a cascade layer */
@import url("vendor.css") layer(vendor);@import creates a sequential network request — the browser cannot fetch the imported file until it has downloaded and parsed the importing file. Prefer multiple link elements or a bundler. See the dedicated @import page for details.@font-face — Custom Fonts
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900; /* variable font range */
font-display: swap; /* show fallback text immediately */
unicode-range: U+0000-00FF; /* only the latin subset */
}@keyframes — Animation Steps
@keyframes slide-in {
from { translate: -100% 0; opacity: 0; }
60% { translate: 8px 0; }
to { translate: 0 0; opacity: 1; }
}
.toast {
animation: slide-in 300ms ease-out;
}@layer — Cascade Layers
@layer lets you declare explicit priority tiers in the cascade. Later layers beat earlier layers regardless of specificity, which finally tames third-party CSS and utility frameworks.
/* Declare order up front — later wins */
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; box-sizing: border-box; }
}
@layer components {
.button { padding: 0.5rem 1rem; background: navy; }
}
@layer utilities {
/* Wins over .button even though specificity is equal */
.bg-red { background: crimson; }
}@container — Container Queries
Where @media asks about the viewport, @container asks about an ancestor element. This makes components genuinely reusable: a card can rearrange itself based on the column it happens to sit in.
.card-list {
container-type: inline-size;
container-name: cards;
}
@container cards (min-width: 480px) {
.card {
display: grid;
grid-template-columns: 160px 1fr;
}
}@property — Typed Custom Properties
@property registers a custom property with a type, initial value, and inheritance behavior — which makes the property animatable.
@property --progress {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.meter {
background: conic-gradient(teal var(--progress), #eee 0);
transition: --progress 400ms ease; /* now animates smoothly */
}
.meter.done { --progress: 100%; }@page and @scope
/* @page — control printed output */
@page {
size: A4;
margin: 2cm;
}
@page :first {
margin-top: 4cm;
}
/* @scope — limit rules to a subtree (donut scoping) */
@scope (.article) to (.comments) {
p { line-height: 1.7; }
/* Applies to p inside .article, but NOT inside .comments */
}Browser Support Snapshot
At-rule | Support status |
|---|---|
@media, @import, @font-face, @keyframes, @page | Universal — safe everywhere |
@supports | Universal in evergreen browsers |
@layer | Baseline widely available (all majors since 2022) |
@container | Baseline widely available (all majors since 2023) |
@property | Baseline newly available (all majors since 2024) |
@scope | Newer — check current support before relying on it |
@counter-style | Broad support; some descriptors vary |
Nesting At-Rules
Conditional at-rules can nest inside each other and, with native CSS nesting, inside selectors too:
.card {
padding: 1rem;
@media (min-width: 768px) {
padding: 2rem;
@supports (backdrop-filter: blur(4px)) {
backdrop-filter: blur(4px);
}
}
}@media, @import, @font-face,@keyframes, and @counter-style. Start with@media — it is the one you will write daily.Key Takeaways
At-rules control conditions, definitions, and cascade behavior — not element appearance directly.
Statement at-rules (@import, @charset) end with a semicolon; block at-rules wrap rules in braces.
@media queries the viewport; @container queries an ancestor element; @supports queries browser capability.
@layer gives you explicit cascade tiers that end specificity wars.
@property makes custom properties typed and animatable.
Conditional at-rules nest — inside each other and inside selectors with native nesting.