Complete Guide to CSS Container Queries
For years responsive relied on media queries (viewport-based), but components live in arbitrary widths. Container Queries + :has() deliver true component-level responsiveness.
Container Queries
Declare container-type: inline-size on parent; use @container in children to switch layout by parent width. Pair with cqw/cqh/cqi units for contextual adaptation.
.card-wrapper { container-type: inline-size; container-name: productCard; }
.product-card {
display: flex; flex-direction: column; padding: 1rem;
border: 1px solid var(--border); border-radius: 12px; transition: all 0.2s;
}
.product-image { width: 100%; aspect-ratio: 4 / 3; }
.product-info { display: grid; grid-template-columns: 1fr; gap: 0.5rem; }
.product-title { font-size: 1rem; }
.product-price { font-weight: 700; color: var(--primary); }
@container productCard (min-width: 400px) {
.product-card { flex-direction: row; gap: 1rem; }
.product-image { width: 45%; aspect-ratio: 1 / 1; }
.product-title { font-size: 1.125rem; }
}
@container productCard (min-width: 640px) {
.product-card { padding: 1.5rem; }
.product-info { grid-template-columns: 2fr 1fr; }
.product-title { font-size: 1.25rem; grid-row: span 2; }
.product-price { font-size: 1.5rem; align-self: end; }
}
.product-card:has(.out-of-stock) { opacity: 0.7; filter: grayscale(0.3); }
.product-card:has(input[type="checkbox"]:checked) {
border-color: var(--primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 30%, transparent);
}
.product-wrapper:has(> .product-card:hover) .product-card:not(:hover) {
transform: scale(0.98); opacity: 0.85;
}
.hero-title { font-size: clamp(1.5rem, 8cqi, 3rem); line-height: 1.1; }
.hero-cta { padding: 0.05cqw 0.2cqh; border-radius: 0.1cqh; }
The :has() Parent Selector
CSS traditionally styled ancestors -> descendants one-way. :has() breaks the limit: style ancestors by descendant presence/state for hover-sibling-dim, form validation, empty-state styling.
| Feature | @media | @container | :has() |
|---|---|---|---|
| Trigger | Viewport size | Parent size | Descendant state |
| Scope | Page-level | Component-level | Any ancestor |
| Units | vw/vh/vmin | cqw/cqh/cqi | - |
| Support | Universal | Modern evergreen | Chrome/Safari/Firefox |
Best Practices
Avoid deeply nested :has() and universal :has(*:hover). Pair container queries with explicit container-name to prevent multi-instance collisions.