Modern CSS Techniques for Common Styling Problems
Solve common CSS problems with container queries, cascade layers, nesting, and feature queries—plus practical fallbacks and support guidance.
Modern CSS features can make recurring styling problems easier to manage, but each one has a specific job. Use container queries when a component should respond to its own available space, cascade layers to organize precedence, nesting to keep related rules close, and feature queries to add enhancements safely. Keep a usable baseline, and check your project’s target browsers before relying on newer features.
1. Make a component respond to its container
A media query responds to the viewport or device. A size container query responds to the dimensions of a declared containing element. Choose a container query when the same component needs to fit different slots, such as a wide main column and a narrow sidebar.
Declare a query container with container-type, then query its size to style descendants:
.card-shell {
container-type: inline-size;
}
.card {
display: grid;
gap: 1rem;
}
@container (width > 36rem) {
.card {
grid-template-columns: 8rem 1fr;
}
}
The base card remains a single-column grid. When its container is wide enough, the card becomes two columns. The threshold is an example: choose a value based on the content and layout constraints. The queried styles apply to descendants of the query container, so put the container boundary around the component that supplies the available space.
| Question | Media query | Size container query |
|---|---|---|
| What is measured? | Viewport or device conditions | The dimensions of a declared container |
| When is it useful? | Page-level layout changes tied to the viewport | Reusable components placed in differently sized slots |
| What must you provide? | A media condition | A query container and a size condition |
Keep a sensible base layout for browsers that do not support the query. Check the actual support requirements for your project rather than assuming a feature is available everywhere.
2. Control style precedence with cascade layers
When vendor styles, base rules, components, and utilities keep overriding one another, define precedence explicitly with cascade layers. This can reduce the pressure to increase selector specificity just to win a cascade conflict.
@layer reset, vendor, base, components, utilities;
@layer base {
button {
font: inherit;
}
}
@layer components {
.button {
border-radius: 0.4rem;
}
}
@layer utilities {
.text-center {
text-align: center;
}
}
Layer order matters. For normal declarations from the same origin, later layers outrank earlier layers. Normal unlayered author styles outrank normal layered author styles from the same origin, so an unlayered rule can bypass the ordering you planned. If you can place third-party CSS in a layer, it becomes easier to position it deliberately in that order.
Layers manage precedence between groups of declarations; they do not make every conflict disappear. Check which layer a rule belongs to, whether a competing rule is unlayered, and whether the declarations have the same importance and origin before changing selectors.
Avoid making !important or duplicated selectors your routine fix. MDN recommends avoiding !important and describes cascade layers as a way to prioritize style sections without fighting specificity. If a declaration must be important, investigate why the cascade is producing the wrong result first.
3. Keep related rules together with CSS nesting
Native nesting can keep a component’s base styles and conditional variants near each other. It is useful for locality and organization; it does not remove the need to understand which selectors the nested rules represent.
.card {
padding: 1rem;
&:focus-within {
outline: 2px solid currentColor;
}
@media (width > 48rem) {
padding: 1.5rem;
}
}
Here, &:focus-within refers to the card with the :focus-within pseudo-class. At-rules that contain style rules can also be nested within a style rule. Keep nesting shallow enough that readers can follow the selector relationship without jumping through several levels.
When a nested rule behaves unexpectedly, inspect the resulting selector relationship, especially around & and selectors that contain commas. If the relationship is hard to read, write the rule at the top level instead.
4. Add newer features safely with feature queries
When a design depends on a capability that might be missing from a target browser, start with a usable baseline and put the enhancement inside @supports. A feature query tests whether the browser understands a CSS declaration.
.layout {
display: block;
}
@supports (display: grid) {
.layout {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
}
}
The baseline should preserve the content and primary interaction if the enhancement is unavailable. Feature queries check whether a browser recognizes a declaration; they do not guarantee that every detail of a feature behaves as your design expects. Check compatibility for the feature and the browsers you support.
Container queries can also have a baseline layout followed by an enhancement. Keep the baseline outside the query, and use the query for the layout change that depends on container support.
5. Let a parent theme influence descendants
A custom-property style query can test a custom property on a container and conditionally style descendants. For example, a parent can expose a theme value that child components use to choose a foreground treatment:
.theme-shell {
--theme: dark;
container-type: inline-size;
}
.panel {
color: #222;
background: white;
}
@container style(--theme: dark) {
.panel {
color: white;
background: #222;
}
}
Container style-query support has limits. MDN documents custom-property style queries, but says style queries for regular CSS declarations and properties are not supported in any browser in its current documentation. Do not rely on a query such as @container style(font-weight: bold) to inspect an arbitrary computed property. Also avoid applying queried styles to the queried element itself when that could change the condition and create an infinite loop; target descendants instead.
6. Check support and choose a fallback
Browser support changes, and a support milestone is a dated signal rather than a promise about your project’s browser mix. Google web.dev lists size container queries as Baseline Newly available in February 2023, subgrid in September 2023, CSS nesting in August 2023, and :has() in December 2023. Those dates do not replace checking the live compatibility information for the browsers your project targets.
- Write down the minimum browser versions your project supports.
- Check compatibility for each feature you plan to use.
- Keep the content and primary interaction usable without the enhancement.
- Use
@supportswhere a declaration can be tested and the fallback matters. - Review the actual cascade, including unlayered styles and vendor rules.
For learning the foundations behind these patterns, see the web.dev CSS curriculum. For feature details and compatibility notes, consult the relevant MDN documentation: container size and style queries, cascade layers, CSS nesting, feature queries, and specificity.
7. Troubleshooting common CSS problems
| Symptom | Likely cause | What to do |
|---|---|---|
| A component query never changes the layout | The intended ancestor is not declared as a size-query container, the rule targets the wrong descendant, or the condition is not met. | Check the container boundary, the selector target, and the measured size. Keep a valid base layout outside the query. |
| A vendor style still wins over a layered component rule | A normal unlayered author rule outranks normal layered author styles from the same origin, or another cascade dimension differs. | Inspect whether the competing rule is unlayered and review origin, importance, and layer order. Put vendor CSS in a planned layer where possible. |
| A nested selector matches more than intended | The nesting relationship or use of & does not express the intended selector. |
Trace the parent and nested selectors explicitly. Simplify or flatten the rule if the relationship is unclear. |
| An enhancement disappears in an older browser | The browser does not support the feature or the declaration tested by @supports. |
Confirm the target browser matrix and make sure the baseline still presents content and core interactions. |
| A style query never matches | The custom property is absent, has a different value, or the query relies on an unsupported regular property. | Check the custom property on the container and its exact value. Do not use regular-property style queries as a cross-browser technique. |
8. Performance, reliability, and maintenance
These techniques solve organization and layout questions; the research sources reviewed for this guide provide no performance benchmark for them. Avoid assuming that adopting a newer syntax automatically makes a page faster. Keep selectors understandable, use queries for the conditions they are designed to express, and verify behavior in the browsers your project supports.
For reliability, preserve a working baseline, keep cascade-layer order deliberate, and avoid dependencies on undocumented support. When changing a shared component, check it in each kind of container where it appears. When adding a new feature, review the support data and the fallback together as one change.
9. Capture a page to inspect a CSS change
A screenshot can make it easier to compare how a page looks at different viewport sizes or after a styling change. You can capture it with your own browser automation, or use a screenshot API for the capture step. Keep the page, viewport, browser state, and capture settings consistent when comparing images.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. Its API returns a screenshot or PDF from one GET request. For example, cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
See the ScreenshotNeo API documentation for request options. Cookie and consent banners are accepted and removed before capture, along with 60+ known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. ScreenshotNeo is at screenshotneo.com.
Sign up for 1,000 free screenshots a month with no card.
FAQ
Do container queries replace media queries?
No. Use media queries for viewport or device conditions and container queries when a component’s own containing space should drive its layout.
Do cascade layers eliminate specificity?
No. They give you a way to order groups of declarations. Specificity still matters within the cascade, and unlayered normal author rules outrank normal layered author rules from the same origin.
Can a feature query confirm a feature works exactly as expected?
It confirms support for the tested declaration. It does not replace checking feature behavior and compatibility in your target browsers.
Can container style queries inspect any computed CSS property?
No. The cited MDN guidance documents custom-property style queries and says regular-property style queries are not supported in any browser in its current documentation.


