CSS Container Queries: Advanced

Style Queries

Beyond size-based container queries, CSS also supports style queries, allowing components to adapt based on the computed styles of their container.

Basic Style Query Syntax


/* Define a style container */
.theme-container {
  container-name: theme;
  container-type: style;  /* Enable style queries */
}

/* Query based on custom property value */
@container theme style(--theme = dark) {
  .card {
    background-color: #333;
    color: white;
  }
  
  .card-button {
    background-color: #555;
    border-color: #777;
  }
}

/* Query based on multiple style conditions */
@container theme style(--theme = light) and style(--accent = blue) {
  .card-accent {
    border-color: var(--accent-color, blue);
  }
}
                        

Style Query Operators

Operator Description Example
= Equal to style(--theme = dark)
!= Not equal to style(--theme != light)
<, > Less than, Greater than style(--spacing > 16px)
<=, >= Less than or equal, Greater than or equal style(--font-size >= 1.2rem)

Practical Style Query Example


/* HTML */
<div class="theme-container" style="--theme: dark; --accent: blue;">
  <div class="card">
    <h3 class="card-title">Card Title</h3>
    <p class="card-content">Card content goes here...</p>
    <button class="card-button">Action</button>
  </div>
</div>

/* CSS */
.theme-container {
  container-name: theme;
  container-type: style;
  
  /* Default light theme values */
  --theme: light;
  --accent: blue;
  --text-color: #333;
  --bg-color: #fff;
  --accent-color: #0066cc;
}

.card {
  background-color: var(--bg-color);
  color: var(--text-color);
  padding: 1.5rem;
  border-radius: 8px;
  border: 1px solid #ddd;
}

.card-button {
  background-color: var(--accent-color);
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

/* Style queries for theme variations */
@container theme style(--theme = dark) {
  .card {
    --bg-color: #222;
    --text-color: #eee;
    border-color: #444;
  }
  
  .card-button {
    background-color: #555;
  }
}

@container theme style(--accent = blue) {
  .card-button {
    --accent-color: #0066cc;
  }
}

@container theme style(--accent = green) {
  .card-button {
    --accent-color: #00cc66;
  }
}
                        
Key Insight: Style queries enable theme switching and dynamic styling based on container properties, creating a powerful system for component adaptations beyond just size changes.

Complex Layout Adaptations

Multi-Level Container Queries


/* HTML */
<div class="page-container">
  <div class="sidebar">
    <div class="widget-container">
      <div class="widget">
        <h3 class="widget-title">Widget Title</h3>
        <div class="widget-content">
          <!-- Widget content -->
        </div>
      </div>
    </div>
  </div>
  <div class="main-content">
    <!-- Main content -->
  </div>
</div>

/* CSS */
.page-container {
  container-type: inline-size;
  container-name: page;
  display: flex;
  flex-direction: column;
}

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.widget-container {
  container-type: inline-size;
  container-name: widget;
}

/* Page-level container query */
@container page (min-width: 768px) {
  .page-container {
    flex-direction: row;
  }
  
  .sidebar {
    width: 300px;
  }
  
  .main-content {
    flex: 1;
  }
}

/* Sidebar-level container query */
@container sidebar (min-width: 250px) {
  .widget-container {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
  }
  
  .widget {
    flex: 1 0 200px;
  }
}

/* Widget-level container query */
@container widget (min-width: 400px) {
  .widget {
    display: flex;
  }
  
  .widget-title {
    width: 30%;
  }
  
  .widget-content {
    width: 70%;
  }
}
                        

Combining Media and Container Queries


/* Base mobile styles */
.dashboard {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

.card-container {
  container-type: inline-size;
}

.card {
  padding: 1rem;
}

/* Media query for overall layout */
@media (min-width: 768px) {
  .dashboard {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1200px) {
  .dashboard {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Container queries for individual cards */
@container (min-width: 300px) {
  .card {
    display: flex;
    align-items: center;
  }
  
  .card-icon {
    margin-right: 1rem;
  }
}

@container (min-width: 400px) {
  .card {
    padding: 1.5rem;
  }
  
  .card-title {
    font-size: 1.25rem;
  }
}
                        

Container Queries with CSS Grid and Flexbox

Adaptive Grid Layouts


/* HTML */
<div class="grid-container">
  <div class="grid">
    <div class="grid-item">Item 1</div>
    <div class="grid-item">Item 2</div>
    <div class="grid-item">Item 3</div>
    <div class="grid-item">Item 4</div>
    <div class="grid-item">Item 5</div>
    <div class="grid-item">Item 6</div>
  </div>
</div>

/* CSS */
.grid-container {
  container-type: inline-size;
  container-name: grid;
}

.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
}

.grid-item {
  background-color: #f0f0f0;
  padding: 1rem;
  border-radius: 4px;
}

/* Container queries for adaptive grid */
@container grid (min-width: 400px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container grid (min-width: 700px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@container grid (min-width: 1000px) {
  .grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
                        

Responsive Flexbox Components


/* HTML */
<div class="feature-container">
  <div class="feature">
    <div class="feature-icon">
      <i class="icon"></i>
    </div>
    <div class="feature-content">
      <h3 class="feature-title">Feature Title</h3>
      <p class="feature-description">Feature description text goes here...</p>
    </div>
  </div>
</div>

/* CSS */
.feature-container {
  container-type: inline-size;
  container-name: feature;
}

.feature {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 1.5rem;
  border: 1px solid #eee;
  border-radius: 8px;
}

.feature-icon {
  width: 60px;
  height: 60px;
  background-color: #f0f0f0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}

/* Container queries for adaptive layout */
@container feature (min-width: 400px) {
  .feature {
    flex-direction: row;
    text-align: left;
    align-items: flex-start;
  }
  
  .feature-icon {
    margin-right: 1.5rem;
    margin-bottom: 0;
  }
  
  .feature-content {
    flex: 1;
  }
}

@container feature (min-width: 600px) {
  .feature-title {
    font-size: 1.5rem;
  }
  
  .feature-description {
    font-size: 1.1rem;
  }
}
                        

Advanced Component Examples

Responsive Data Table


/* HTML */
<div class="table-container">
  <table class="data-table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Position</th>
        <th>Office</th>
        <th>Age</th>
        <th>Start Date</th>
        <th>Salary</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td data-label="Name">John Doe</td>
        <td data-label="Position">Developer</td>
        <td data-label="Office">New York</td>
        <td data-label="Age">35</td>
        <td data-label="Start Date">2020/01/15</td>
        <td data-label="Salary">$85,000</td>
      </tr>
      <!-- More rows... -->
    </tbody>
  </table>
</div>

/* CSS */
.table-container {
  container-type: inline-size;
  container-name: table;
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table th,
.data-table td {
  padding: 0.75rem;
  border-bottom: 1px solid #ddd;
}

.data-table th {
  text-align: left;
  background-color: #f8f9fa;
}

/* Container query for responsive table */
@container table (max-width: 700px) {
  .data-table {
    border: 0;
  }
  
  .data-table thead {
    display: none;
  }
  
  .data-table tr {
    display: block;
    margin-bottom: 1rem;
    border: 1px solid #ddd;
    border-radius: 4px;
  }
  
  .data-table td {
    display: block;
    text-align: right;
    border-bottom: 1px solid #eee;
    position: relative;
    padding-left: 50%;
  }
  
  .data-table td:last-child {
    border-bottom: 0;
  }
  
  .data-table td::before {
    content: attr(data-label);
    position: absolute;
    left: 0.75rem;
    width: 45%;
    text-align: left;
    font-weight: bold;
  }
}
                        

Adaptive Dashboard Widget


/* HTML */
<div class="widget-container">
  <div class="widget">
    <div class="widget-header">
      <h3 class="widget-title">Sales Overview</h3>
      <div class="widget-actions">
        <button class="widget-button">Daily</button>
        <button class="widget-button">Weekly</button>
        <button class="widget-button">Monthly</button>
        <button class="widget-button">Export</button>
      </div>
    </div>
    <div class="widget-body">
      <div class="widget-chart">
        <!-- Chart content -->
      </div>
      <div class="widget-stats">
        <div class="stat-item">
          <span class="stat-label">Total Sales</span>
          <span class="stat-value">$24,500</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">Conversion</span>
          <span class="stat-value">15.3%</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">Growth</span>
          <span class="stat-value">+8.4%</span>
        </div>
      </div>
    </div>
  </div>
</div>

/* CSS */
.widget-container {
  container-type: inline-size;
  container-name: widget;
}

.widget {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.widget-header {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  background-color: #f8f9fa;
  border-bottom: 1px solid #ddd;
}

.widget-title {
  margin: 0 0 1rem 0;
}

.widget-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.widget-button {
  padding: 0.5rem;
  background-color: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.widget-body {
  padding: 1rem;
}

.widget-chart {
  height: 200px;
  background-color: #f0f0f0;
  margin-bottom: 1rem;
}

.widget-stats {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.stat-item {
  display: flex;
  flex-direction: column;
  padding: 0.75rem;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.stat-label {
  font-size: 0.875rem;
  color: #666;
}

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

/* Container queries for adaptive widget */
@container widget (min-width: 500px) {
  .widget-header {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
  }
  
  .widget-title {
    margin: 0;
  }
  
  .widget-stats {
    flex-direction: row;
  }
  
  .stat-item {
    flex: 1;
  }
}

@container widget (min-width: 700px) {
  .widget-body {
    display: flex;
    gap: 1.5rem;
  }
  
  .widget-chart {
    flex: 2;
    margin-bottom: 0;
  }
  
  .widget-stats {
    flex: 1;
    flex-direction: column;
  }
}

@container widget (min-width: 900px) {
  .widget-chart {
    height: 300px;
  }
  
  .stat-value {
    font-size: 1.5rem;
  }
}
                        

Performance Optimization

Using CSS Containment


/* Combining container queries with containment */
.card-container {
  container-type: inline-size;
  container-name: card;
  
  /* Add containment for better performance */
  contain: layout style paint;
}

/* Alternative: use content-visibility */
.offscreen-container {
  container-type: inline-size;
  content-visibility: auto;
  contain-intrinsic-size: 300px;
}
                        

Limiting Container Nesting

Deeply nested containers can impact performance. Consider these strategies:

  • Use named containers to target specific containers without nesting
  • Limit container depth to only what's necessary
  • Apply container queries strategically at key breakpoints

Reducing Layout Shifts


/* Set minimum dimensions to reduce layout shifts */
.card-container {
  container-type: inline-size;
  min-height: 200px;
  min-width: 200px;
}

/* Use aspect-ratio to maintain proportions */
.image-container {
  container-type: inline-size;
  aspect-ratio: 16 / 9;
}

/* Set explicit sizes for container query units */
.element {
  font-size: clamp(1rem, 3cqi, 2rem);
}
                        
Performance Tip: Container queries can trigger layout recalculations when container dimensions change. Use the contain property and limit the number of container queries to optimize performance.

Future Developments

Upcoming Container Query Features

  • Container Query Length Units: More powerful relative units
  • Container Query Value Types: New value types for more complex queries
  • State Queries: Querying based on element states
  • Scoped Styles: Better encapsulation for component styles

Integration with Web Components


/* Example of container queries with web components */
class ResponsiveCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          container-type: inline-size;
          display: block;
        }
        
        .card {
          padding: 1rem;
          border: 1px solid #ddd;
          border-radius: 8px;
        }
        
        @container (min-width: 400px) {
          .card {
            display: flex;
          }
          
          .card-image {
            width: 40%;
          }
          
          .card-content {
            width: 60%;
            padding-left: 1rem;
          }
        }
      </style>
      
      <div class="card">
        <div class="card-image">
          <slot name="image"></slot>
        </div>
        <div class="card-content">
          <slot></slot>
        </div>
      </div>
    `;
  }
}

customElements.define('responsive-card', ResponsiveCard);
                        

Related Topics

To further enhance your understanding of modern CSS layout and responsive design, explore these related topics:

Final Thought: Container queries represent a paradigm shift in responsive design, enabling truly modular, context-aware components. As browser support continues to improve, they will become an essential tool in every front-end developer's toolkit.