CSS Nesting, Cascade Layers, and Container Queries

Master modern CSS syntax. Learn how to write native CSS nesting, organize stylesheets using Cascade Layers (@layer), and implement Container Queries.

Introduction

As web applications grow, stylesheet architecture becomes harder to organize. Historically, developers relied on preprocessors (like SASS/SCSS) to nest rules and avoid repetitive selectors. They also struggled with specificity wars where third-party libraries overrode custom styles, and struggled to build components that resized based on their container size rather than the screen width.

Modern CSS has evolved to solve these issues natively. With Native CSS Nesting, Cascade Layers (@layer), and Container Queries, you can write clean, organized code, control style priorities, and build components that adapt to the size of their parent container.


What You Will Learn

  • How to write Native CSS Nesting rules.
  • Organizing style priorities using Cascade Layers (@layer).
  • How Container Queries differ from Media Queries.
  • Creating container-responsive components using @container.
  • Performance improvements from using modern CSS features.

Prerequisites


Native CSS Nesting

CSS now supports nesting natively in all modern browsers. This allows you to group related styles together inside a single block, reducing repetition:

  • Syntax Rule: Nested selectors target child elements directly. You can use the ampersand (&) to reference the parent selector (especially useful for hover states or modifier classes).
/* Traditional CSS */
.card { background-color: white; }
.card:hover { transform: scale(1.05); }
.card .card-title { font-size: 20px; }

/* Modern Native CSS Nesting */
.card {
  background-color: white;
  
  &:hover {
    transform: scale(1.05); /* & references .card */
  }
  
  .card-title {
    font-size: 20px; /* Targets children of .card */
  }
}

Cascade Layers (@layer)

Specificity conflicts can be difficult to debug in large projects. Cascade Layers allow you to group CSS rules into explicit priority buckets, ensuring your custom styles always override third-party library styles, regardless of selector specificity.

Defining Layer Priorities:

Declare the layer order at the very top of your main CSS file (ordered from lowest priority to highest priority):

/* Declare layer priority order */
@layer framework, base, components, utilities;

/* Apply styles to layers */
@layer framework {
  /* Third-party styles */
  .btn { padding: 20px; background-color: grey; } 
}

@layer components {
  /* Custom styles override framework styles, even with lower selector specificity */
  .btn { background-color: #3b82f6; } 
}

Container Queries (@container)

Media queries inspect the viewport size of the browser window. However, in modular design, you want components (like a newsletter card) to change their layout based on the size of the container they are placed in.

If a card is placed in a narrow sidebar, it should render stacked. If it is placed in a wide main content area, it should render in columns, even though the viewport size remains identical.


1. Declaring a Container Parent

Before you can query a container, you must register it as a container target using container-type:

  • inline-size: (Recommended) Evaluates the container's width.
  • size: Evaluates the container's width and height.
.card-wrapper {
  /* Register container parent */
  container-type: inline-size;
}

2. Querying the Container (@container)

Write styles that apply conditionally based on the container's width:

.card-item {
  display: flex;
  flex-direction: column; /* Stacked layout by default */
}

@container (min-width: 400px) {
  .card-item {
    flex-direction: row; /* Shift to row columns if container is 400px or wider */
  }
}

Visual Learning Diagram (Mermaid)

graph TD
    subgraph Viewport vs Container Queries
    ViewportQuery[Viewport Query <br> Inspects browser window width]
    ContainerQuery[Container Query <br> Inspects parent wrapper element width]
    end

Code Examples

Example 1: Native Nesting with BEM Modifiers

Use nesting syntax to structure components:

.profile-card {
  background-color: #1e293b;
  border-radius: 12px;
  padding: 24px;
  
  /* Nested element class */
  .profile-card__avatar {
    width: 60px;
    height: 60px;
    border-radius: 50%;
  }
  
  /* Nested hover state modifier */
  &:hover {
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
  }
  
  /* Nested state class */
  &.profile-card--featured {
    border: 2px solid #f59e0b;
  }
}

Example 2: Container Responsive Layout

Create a card component that changes layout depending on whether it is placed in a sidebar or main panel:

.grid-container {
  display: grid;
  grid-template-columns: 300px 1fr; /* Sidebar (left) and Main (right) */
}

.panel {
  container-type: inline-size; /* Register both columns as container targets */
  padding: 20px;
}

/* Card Component placed inside the panels */
.promo-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* If the parent panel is wider than 500px, change card to row layout */
@container (min-width: 500px) {
  .promo-card {
    flex-direction: row;
    align-items: center;
  }
}

Best Practices & Common Mistakes

  • Avoid Infinite Nesting: Just because you can nest styles doesn't mean you should. Settle on a limit of 3 nesting levels. Deeply nested rules (e.g. .card { .header { .title { span { a { ... } } } } }) create long, highly specific CSS compiled strings that are hard to override and slow down browser performance.
  • Incorrect Container Queries Elements: Never declare container-type queries directly on the element you want to resize. The @container rule queries parent elements, not the element itself. Always wrap the component in a parent container wrapper and declare container-type on that parent.

FAQs

Q: Do all browsers support container queries? A: Yes. Container queries are supported by all modern browsers (Chrome 105+, Safari 16+, Firefox 110+).

Q: What is the benefit of Cascade Layers? A: Cascade Layers resolve specificity wars. If a style inside a low-priority layer (like @layer base { #logo { color: red; } }) conflicts with a style inside a higher-priority layer (like @layer components { .brand { color: blue; } }), the higher-priority layer wins, even though the base layer has a highly specific ID selector.


Summary

Modern CSS structures scalable stylesheets. While Native Nesting groups related styles together, Cascade Layers (@layer) organize style priorities, and Container Queries (@container) allow components to adapt to their parent container size rather than the viewport.


Related Articles

Next Tutorial

How do we design websites that are accessible to all users, including those using assistive technologies? Let's check: CSS Accessibility & Keyboard Focus.