Skip to main content

Modern CSS Features

CSS has evolved rapidly in recent years. Features that previously required preprocessors (Sass, Less) or JavaScript are now built into the language. This chapter covers the most impactful additions.

CSS nesting​

You can now nest selectors inside other selectors, reducing repetition and improving readability. For production rules, Sass differences, and specificity guidance, see the deep dive CSS Nesting.

.card {
padding: 24px;
border: 1px solid #ddd;
border-radius: 8px;

& h3 {
margin: 0 0 8px;
font-size: 1.25rem;
}

& p {
margin: 0;
color: #666;
}

&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

& .badge {
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 4px;
background-color: #4a90d9;
color: white;
}
}

Nesting rules​

  • Use & when the nested selector would otherwise start with a type selector (& h3, & p)
  • Class, attribute, and combinator-based relative selectors can nest directly (.badge, > img)
  • Use & to target the parent as a compound selector (&:hover, &.active). Native CSS has no selector concatenation (the Sass shorthand that appends a suffix does not exist here) - write the full class name instead. A bare pseudo-class nests as a descendant (parent *:hover), so use &:hover for the parent's own hover
  • Do not nest deeper than two or three levels - it hurts readability and increases specificity

Before vs after nesting​

Without nesting:

.nav { display: flex; gap: 16px; }
.nav a { color: white; text-decoration: none; }
.nav a:hover { text-decoration: underline; }
.nav .logo { font-weight: bold; font-size: 1.25rem; }

With nesting:

.nav {
display: flex;
gap: 16px;

& a {
color: white;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

.logo {
font-weight: bold;
font-size: 1.25rem;
}
}

Nested CSS groups related rules together, making it easier to see which styles belong to a component.

Nesting media queries​

You can nest @media rules inside selectors:

.sidebar {
display: none;

@media (min-width: 1024px) {
display: block;
width: 250px;
}
}

This keeps the responsive behaviour close to the component it affects.

The :has() selector​

:has() is a relational pseudo-class - it lets you style a parent based on what it contains. This was impossible in CSS until :has() arrived.

.card:has(img) {
padding-top: 0;
}

This targets .card elements that contain an <img>. Cards without images keep their normal padding.

Use cases​

Style a form based on its validity​

form:has(:invalid) .submit-button {
opacity: 0.5;
pointer-events: none;
}

The submit button looks disabled when any form field is invalid.

Highlight a label when its input is focused​

.field:has(input:focus) label {
color: #4a90d9;
}

Change layout based on child count​

.grid:has(> :nth-child(4)) {
grid-template-columns: repeat(2, 1fr);
}

When the grid has four or more direct children, switch to a two-column layout.

Style previous siblings​

:has() combined with the adjacent sibling selector lets you style a previous element based on the next one:

h2:has(+ .subtitle) {
margin-bottom: 4px;
}

Reduce the margin under an <h2> when it is immediately followed by a .subtitle element.

Tip: Think of :has() as "if this element contains..." or "if this element is followed by...". It is one of the most powerful additions to CSS in years.

Container queries​

Media queries respond to the viewport size; container queries respond to the size of a specific container element, so components adapt to wherever they are placed.

Deep dive: For the syntax (container-type, @container, query units), interactive demos, and a full comparison with media queries, see CSS Container Queries.

Logical properties​

Logical properties replace physical directions (left, right, top, bottom) with flow-relative ones (inline-start, inline-end, block-start, block-end). This makes CSS work correctly in right-to-left (RTL) languages and vertical writing modes.

Physical propertyLogical equivalent
margin-leftmargin-inline-start
margin-rightmargin-inline-end
margin-topmargin-block-start
margin-bottommargin-block-end
padding-leftpadding-inline-start
widthinline-size
heightblock-size
border-leftborder-inline-start

Shorthand properties:

.card {
margin-inline: 16px;
padding-block: 24px;
}

margin-inline sets both margin-inline-start and margin-inline-end. padding-block sets both padding-block-start and padding-block-end.

Tip: Start using logical properties in new code. They make your CSS automatically work with different text directions and are the direction CSS is heading.

Deep dive: Logical Properties covers inset, shorthands, and RTL patterns.

accent-color​

Style native form controls (checkboxes, radio buttons, range sliders, progress bars) with a single property:

:root {
accent-color: #4a90d9;
}

This changes the default blue colour of checkboxes, radios, and range inputs to your brand colour, without needing custom checkbox implementations.

input[type="checkbox"] {
accent-color: #28a745;
}

input[type="range"] {
accent-color: #e74c3c;
}

color-mix()​

Blend two colours together in CSS without a preprocessor:

.button:hover {
background-color: color-mix(in srgb, var(--color-primary) 80%, black);
}

This mixes 80% of the primary colour with 20% black - creating a darker hover shade dynamically.

.muted {
color: color-mix(in srgb, var(--color-text) 60%, transparent);
}

Mix with transparent to create semi-transparent versions of any colour.

Deep dive: Modern CSS Color covers oklch, relative colours, light-dark(), and mixing in perceptually uniform spaces.

Subgrid​

When you have nested grids, subgrid lets a child grid inherit the track definitions of its parent:

.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}

.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}

Without subgrid, the titles, descriptions, and buttons inside each card would not align across cards. With subgrid, the child grid inherits the parent's row tracks, and content aligns perfectly.

Other modern features​

FeatureWhat it doesDeep dive
text-wrap: balance / prettyBetter line breaking for headings and paragraphs-
@layer / @scopeExplicit cascade order and subtree scopingCascade Layers & Scope
View TransitionsAnimate between DOM or page statesView Transitions API
@starting-style / discrete transitionsEntry animations for display / popoversTransitions & Animations
Popover + anchor positioningNative overlays positioned against triggersAnchor Positioning & Popover
<dialog>Native modal / non-modal dialogsNative Overlays & Dialogs
Scroll-driven animationsDrive keyframes from scroll / view progressScroll-Driven Animations
@propertyTyped, animatable custom propertiesTyped Custom Properties
Scroll snapCSS carousels and snap scrollingScroll Snap & Carousels
content-visibilitySkip rendering off-screen subtreescontent-visibility & perf

Check caniuse.com or MDN before relying on a feature without a fallback.

Browser support strategy​

Not all browsers support every modern feature. Follow this approach:

  1. Check support: Use caniuse.com or MDN browser compatibility tables - or query them directly in your editor via the MDN MCP server
  2. Use progressive enhancement: Start with styles that work everywhere, layer on modern features
  3. Use @supports: Test for feature support in CSS:
.card {
display: flex;
}

@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
}
  1. Set acceptable fallbacks: If nesting is not supported, the browser ignores it and uses the flat rules

What you learned​

  • CSS nesting groups related rules and reduces repetition
  • :has() styles parents based on their children - one of the most powerful modern selectors
  • Container queries make components respond to their container's size, not the viewport
  • Logical properties replace left/right/top/bottom with flow-relative directions
  • accent-color styles native form controls in one line
  • color-mix() blends colours dynamically without preprocessors
  • Subgrid aligns nested grid children to the parent grid's tracks
  • Use @supports and progressive enhancement for features with limited browser support

Next step​

You now know all the CSS tools available. The next chapter covers architecture and best practices - how to organise your CSS files, name your classes, and structure your code for long-term maintainability.