MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

CSS Container Queries: Component-Level Responsiveness

Use container queries: @container rule, container units, and responsive components.

CSSContainer QueriesResponsiveFrontend

By MinhVo

Introduction

For over a decade, responsive web design has relied on media queries that respond to the viewport size. This approach works well for page-level layouts — switching between mobile, tablet, and desktop column arrangements — but it fundamentally breaks down at the component level. A card component that looks great in a full-width main content area might look terrible when placed in a narrow sidebar, even though the viewport width hasn't changed.

CSS Container Queries solve this problem by allowing components to respond to the size of their parent container rather than the viewport. This means a card component can automatically adjust its layout based on how much space is available in its immediate context, making it truly portable across different areas of a page.

Responsive components with container queries

This guide covers the container query API from fundamentals to advanced patterns, including container units, style queries, and practical patterns for building responsive component libraries.

Understanding Container Queries: Core Concepts

Container queries work through a two-step process. First, you designate an element as a container using the container-type property. This tells the browser to track that element's size for query purposes. Then, you write @container rules that apply styles based on the container's dimensions.

/* Step 1: Make the parent a container */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}
 
/* Step 2: Query the container's size */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}
 
@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

Container Types

The container-type property has three values:

  • inline-size — Tracks the container's inline (typically width) dimension. This is the most common and performant option.
  • size — Tracks both inline and block dimensions. Use this only when you need to respond to height changes.
  • normal — The default; the element is not a containment context for container queries.
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}
 
/* Also track height for split-pane layouts */
.split-pane {
  container-type: size;
  container-name: pane;
}

Container Names

The container-name property assigns a name to the container, allowing @container rules to target specific containers. Without a name, queries match the nearest ancestor container. Named containers are essential when you have multiple nested containers and need to control which one a query targets.

Container Shorthand

The container shorthand combines container-type and container-name:

.card-wrapper {
  container: card / inline-size;
}

Container query architecture

Architecture and Design Patterns

The Responsive Card Pattern

Cards are the canonical use case for container queries. A card component might display as a simple stacked layout in narrow containers but switch to a horizontal layout with a larger image when it has more space. This eliminates the need to create multiple card variants or use JavaScript to detect container width.

The Adaptive Grid Pattern

Grid items that use container queries can adjust their internal layout independently of the grid structure. A grid might show 4 columns on a wide screen, but each card within that grid can further adapt its internal layout based on its actual rendered width.

The Split Pane Pattern

Using container-type: size, split pane layouts can adjust content based on both width and height. A code editor might show a toolbar horizontally when wide and vertically when narrow, while also collapsing the toolbar entirely when the height is too small.

The Widget Dashboard Pattern

Dashboard widgets need to look different in a 2-column layout versus a 4-column layout. Container queries let each widget respond to its actual space allocation, showing full charts in wide widgets and simplified summaries in narrow ones.

Step-by-Step Implementation

Building a Responsive Card Component

<div class="card-container">
  <article class="card">
    <img src="photo.jpg" alt="Article thumbnail" class="card-image">
    <div class="card-content">
      <h3 class="card-title">Article Title</h3>
      <p class="card-excerpt">A brief description of the article content...</p>
      <div class="card-meta">
        <span class="card-date">Jan 15, 2024</span>
        <span class="card-read-time">5 min read</span>
      </div>
    </div>
  </article>
</div>
/* Container setup */
.card-container {
  container: card / inline-size;
}
 
/* Base styles (narrow) */
.card {
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid #e5e7eb;
}
 
.card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}
 
.card-content {
  padding: 16px;
}
 
.card-meta {
  display: none;
}
 
/* Wide layout */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;
  }
  
  .card-image {
    width: 200px;
    height: auto;
  }
  
  .card-meta {
    display: flex;
    gap: 12px;
    margin-top: 12px;
    font-size: 14px;
    color: #6b7280;
  }
}
 
/* Extra wide layout */
@container card (min-width: 600px) {
  .card-content {
    padding: 24px;
  }
  
  .card-title {
    font-size: 1.5rem;
  }
  
  .card-excerpt {
    font-size: 1rem;
    line-height: 1.6;
  }
}

Using Container Query Length Units

Container queries introduce new CSS units that are relative to the container's dimensions:

  • cqw — 1% of the container's width
  • cqh — 1% of the container's height
  • cqi — 1% of the container's inline size
  • cqb — 1% of the container's block size
  • cqmin — Smaller of cqi or cqb
  • cqmax — Larger of cqi or cqb
.card-container {
  container: card / inline-size;
}
 
.card-title {
  font-size: clamp(1rem, 4cqi, 2rem);
}
 
.card-content {
  padding: clamp(12px, 3cqi, 32px);
}

Style Queries (Experimental)

Style queries let you query computed styles of a container, enabling theme-based component variations:

.card-wrapper {
  --theme: light;
  container: card / inline-size;
}
 
@container card style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #e5e7eb;
    border-color: #374151;
  }
}

Container query implementation

Real-World Use Cases

Use Case 1: Sidebar Navigation

A navigation component that collapses from a full sidebar with labels and icons to a narrow icon-only rail based on its container width. In a collapsible sidebar, the navigation items automatically adjust without any JavaScript measurement.

Use Case 2: Form Layout Adaptation

Form fields that arrange labels and inputs differently based on available space. In a wide container, labels appear inline to the left of inputs. In a narrow container, labels stack above inputs. This works regardless of whether the form is in a full-page view, a modal, or a sidebar panel.

Use Case 3: Data Visualization Panels

Dashboard chart widgets that show full detailed charts when wide but switch to simplified sparkline or summary views when narrow. The chart component queries its container to determine how much detail to render.

Use Case 4: E-Commerce Product Grids

Product cards in a grid that show different levels of detail based on their column width. In a 4-column grid, cards show just an image and price. In a 2-column grid, they add a description and rating. In a single-column layout, they show full product details.

Best Practices for Production

  1. Prefer container-type: inline-size over size — Tracking only inline size is more performant because the browser doesn't need to monitor height changes. Only use size when you genuinely need height-based queries.

  2. Always name your containers — Named containers make your CSS more readable and prevent queries from accidentally matching unintended ancestors. Use descriptive names like sidebar, card, or main-content.

  3. Avoid creating containment on elements with overflow: hidden — Container containment can interact with overflow clipping in unexpected ways. Test thoroughly when combining these.

  4. Use container query units for fluid typography — Replace vw-based fluid type with cqi-based sizing so text scales relative to its container, not the viewport. This makes typography work correctly in sidebars and narrow layouts.

  5. Combine container queries with media queries — Use media queries for page-level layout decisions (number of grid columns, sidebar visibility) and container queries for component-level adaptations (internal card layout, text truncation).

  6. Set explicit container boundaries — Don't let containment bubble up unexpectedly. Every element that uses container-type creates a containment boundary that can affect the rendering of its descendants.

  7. Test with dynamic container resizing — Container queries fire as the container resizes, which can happen during window resize, sidebar toggle, or grid reflow. Test these scenarios for smooth transitions.

  8. Use logical properties for writing-mode support — When writing container queries, prefer inline-size and block-size over width and height for better internationalization support.

Common Pitfalls and Solutions

PitfallImpactSolution
Forgetting container-type@container rules never matchAlways set container-type: inline-size on the parent
Using width instead of inline-sizeFails in vertical writing modesUse inline-size and block-size for logical sizing
Too many containers on a pagePerformance degradation from containment overheadOnly create containers where you actually write @container rules
Querying height without container-type: sizeHeight queries silently failUse container-type: size only when height queries are needed
Container has display: flex or gridContainment may affect flex/grid behaviorTest the container with its children after adding containment
Nested containers with same nameQueries may target the wrong containerUse unique, descriptive container names per component

Performance Optimization

Container queries use CSS containment (contain: inline-size) under the hood, which actually improves rendering performance. By telling the browser that an element's size depends only on its inline dimension, the browser can skip expensive layout calculations for the element's children when other parts of the page change.

However, creating too many containment contexts can add overhead. Each contained element requires the browser to maintain a separate layout context. For pages with hundreds of container-queried elements, consider batching related elements under a shared container rather than giving each element its own containment.

/* Less efficient: each card is its own container */
.card { container: card / inline-size; }
 
/* More efficient: one container, shared queries */
.card-grid { container: grid / inline-size; }
@container grid (min-width: 600px) { /* styles for all cards */ }

Use content-visibility: auto on container-queried elements that may be off-screen to skip rendering entirely until they scroll into view:

.card-container {
  container: card / inline-size;
  content-visibility: auto;
  contain-intrinsic-size: 300px 200px;
}

Comparison with Alternatives

FeatureCSS Container QueriesMedia QueriesJavaScript ResizeObservermatchMedia
Responds toContainer sizeViewport sizeAny element sizeViewport size
PerformanceNative CSS (optimal)Native CSS (optimal)JS callback overheadJS callback overhead
ComplexityLowLowMediumMedium
Component-levelYesNoYesNo
Cascade integrationFull CSS cascadeFull CSS cascadeManual class togglingManual class toggling
Browser supportChrome 105+, Safari 16+, Firefox 110+All modernAll modernAll modern

Advanced Patterns

Container Queries with Grid Auto-Fill

Combine container queries with CSS Grid auto-fill for responsive grids that also adapt internal component layouts:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}
 
.grid-item {
  container: card / inline-size;
}

Chaining Container Queries

Write increasingly specific rules for progressive enhancement:

@container card (min-width: 250px) {
  .card-title { font-size: 1.125rem; }
}
 
@container card (min-width: 400px) {
  .card-title { font-size: 1.25rem; }
  .card-meta { display: flex; }
}
 
@container card (min-width: 600px) {
  .card { display: grid; grid-template-columns: 240px 1fr; }
  .card-title { font-size: 1.5rem; }
}

Conditional Container Queries

Use or and and operators for complex conditions:

@container card (min-width: 400px) and (min-height: 300px) {
  .card-layout { display: grid; }
}
 
@container card (max-width: 399px), card (orientation: portrait) {
  .card-layout { flex-direction: column; }
}

Testing Strategies

Container queries require testing at multiple container sizes, not just viewport sizes. Use Playwright to resize containers independently:

test('card adapts layout based on container width', async ({ page }) => {
  await page.goto('/components/card');
  const container = page.locator('.card-container');
  
  // Test narrow layout
  await container.evaluate(el => el.style.width = '300px');
  const narrowCard = page.locator('.card');
  const narrowDisplay = await narrowCard.evaluate(
    el => getComputedStyle(el).flexDirection
  );
  expect(narrowDisplay).toBe('column');
  
  // Test wide layout
  await container.evaluate(el => el.style.width = '500px');
  const wideDisplay = await narrowCard.evaluate(
    el => getComputedStyle(el).flexDirection
  );
  expect(wideDisplay).toBe('row');
});

Future Outlook

Container queries are now supported in all major browsers and are being actively enhanced. Style queries (querying computed property values) are in development and will enable powerful theming patterns. The @function proposal may allow defining reusable responsive patterns as CSS functions. Container queries combined with the @scope rule will enable fully encapsulated, self-responsive components that work anywhere in a page.

Cross-Browser Testing Strategy

Modern CSS features often have varying levels of browser support, making a systematic cross-browser testing strategy essential. Before using any CSS feature in production, verify its support status and implement appropriate fallbacks for browsers that haven't yet implemented it.

Progressive Enhancement with @supports

Use the @supports at-rule to provide fallback styles for browsers that don't support specific CSS features:

/* Base styles for all browsers */
.grid-container {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
 
.grid-container > * {
  flex: 1 1 300px;
}
 
/* Enhanced layout for browsers supporting CSS Grid */
@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.5rem;
  }
 
  .grid-container > * {
    flex: none;
  }
}
 
/* Further enhancement with subgrid support */
@supports (grid-template-columns: subgrid) {
  .grid-container {
    grid-template-columns: subgrid;
  }
}

Visual Regression Testing

Implement visual regression testing to catch unintended layout shifts and styling changes. Tools like Percy, Chromatic, or Playwright's screenshot comparison can detect visual differences across browsers and screen sizes:

const { test, expect } = require('@playwright/test');
 
test('responsive layout matches design', async ({ page }) => {
  await page.goto('/components/dashboard');
 
  // Test at multiple viewport sizes
  for (const viewport of [
    { width: 375, height: 812, name: 'mobile' },
    { width: 768, height: 1024, name: 'tablet' },
    { width: 1440, height: 900, name: 'desktop' },
  ]) {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await expect(page).toHaveScreenshot(
      `dashboard-${viewport.name}.png`,
      { maxDiffPixels: 100 }
    );
  }
});

Browser Compatibility Testing Matrix

Maintain a testing matrix that covers the browsers and versions your users actually use. Use analytics data to determine your browser support baseline, then configure tools like Browserslist to automatically handle polyfilling and prefixing:

{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]
}

This data-driven approach ensures you're spending testing effort where it matters most, rather than trying to support every possible browser configuration.

Production Deployment and Monitoring

Deploying React applications to production requires careful consideration of build optimization, error tracking, and performance monitoring. A well-configured production build can significantly improve user experience through faster load times and more reliable error reporting.

Build Optimization Checklist

Before deploying, verify that your production build is fully optimized:

// next.config.js
module.exports = {
  reactStrictMode: true,
  poweredByHeader: false,
  compress: true,
 
  // Optimize images
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
  },
 
  // Security headers
  async headers() {
    return [{
      source: '/(.*)',
      headers: [
        { key: 'X-Frame-Options', value: 'DENY' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
      ],
    }];
  },
 
  // Webpack optimization
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.optimization.splitChunks = {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendor',
            chunks: 'all',
          },
        },
      };
    }
    return config;
  },
};

Error Tracking Integration

Configure Sentry or a similar error tracking service to capture and categorize production errors:

import * as Sentry from '@sentry/nextjs';
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
  beforeSend(event) {
    // Filter out known non-critical errors
    if (event.exception?.values?.[0]?.type === 'ChunkLoadError') {
      return null;
    }
    return event;
  },
});

Health Check Endpoints

Implement health check endpoints that your load balancer and monitoring systems can use to verify application availability:

// pages/api/health.ts
export default async function handler(req, res) {
  try {
    // Check database connectivity
    await db.raw('SELECT 1');
 
    // Check external service dependencies
    const redisPing = await redis.ping();
 
    res.status(200).json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      services: {
        database: 'connected',
        redis: redisPing === 'PONG' ? 'connected' : 'degraded',
      },
      uptime: process.uptime(),
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message,
    });
  }
}

This comprehensive monitoring approach ensures you detect and respond to production issues quickly, maintaining high availability for your users.

Community Resources and Further Learning

The technology landscape evolves rapidly, making continuous learning essential for maintaining expertise. Building a systematic approach to staying current with developments in your technology stack ensures you can leverage new features and avoid deprecated patterns.

Curated Learning Pathways

Rather than consuming content randomly, create structured learning pathways aligned with your current projects and career goals. Start with official documentation and specification documents, which provide the most accurate and comprehensive information. Follow this with hands-on tutorials and workshops that reinforce concepts through practical application.

Technical blogs from framework maintainers and core team members often provide deeper insights into design decisions and upcoming features. Subscribe to the official blogs of your primary frameworks and libraries to stay ahead of breaking changes and deprecation timelines.

Contributing to Open Source

Contributing to open-source projects in your technology stack provides unparalleled learning opportunities. Start with documentation improvements and bug reports, then progress to fixing small issues tagged as "good first issue" in your favorite projects. This direct engagement with maintainers and the codebase accelerates your understanding far beyond what passive learning can achieve.

# Setting up for contribution
git clone https://github.com/project/repository.git
cd repository
git checkout -b fix/issue-description
 
# Run the project's contribution setup
npm run setup:dev
npm run test  # Ensure tests pass before making changes
 
# Make your changes, then run the full test suite
npm run test:full
npm run lint
npm run build
 
# Submit your contribution
git add -A
git commit -m "fix: description of the fix
 
Closes #1234"
git push origin fix/issue-description

Building a Technical Knowledge Base

Maintain a personal knowledge base that captures insights, solutions, and patterns you discover during your work. Tools like Obsidian, Notion, or even a simple Markdown repository can serve as an external memory that grows more valuable over time.

Organize your notes by topic rather than chronologically, and include code examples, links to relevant documentation, and explanations of why certain approaches work better than others. When you encounter a particularly insightful article or conference talk, write a summary that captures the key takeaways and how they apply to your current projects.

Follow key conferences and their published talks to stay informed about emerging patterns and best practices. Many conferences publish recorded talks on YouTube within weeks of the event, making world-class technical content freely accessible.

Join relevant Discord servers, Slack communities, and forums where practitioners discuss real-world challenges and solutions. These communities provide early warning about emerging issues and access to collective wisdom that isn't available through formal documentation.

Mentorship and Knowledge Sharing

Teaching others is one of the most effective ways to deepen your own understanding. Consider writing technical blog posts, giving talks at local meetups, or mentoring junior developers. The process of explaining concepts to others forces you to organize your knowledge and identify gaps in your understanding.

Pair programming sessions with colleagues of different experience levels create mutual learning opportunities. Senior developers gain fresh perspectives on problems they've solved the same way for years, while junior developers benefit from exposure to production-grade thinking and decision-making processes.

Conclusion

CSS Container Queries are the missing piece of responsive design. The key takeaways are:

  1. Set container-type: inline-size on parent elements to enable container queries for their children.
  2. Use @container rules to apply styles based on the container's dimensions, not the viewport.
  3. Use container query units (cqi, cqw, cqh) for fluid sizing relative to the container.
  4. Combine with media queries — media queries for page layout, container queries for component layout.
  5. Name your containers for readability and to prevent accidental query matching.

Container queries make components truly portable. A card, form, or navigation component built with container queries will look correct anywhere it's placed, without requiring context-specific CSS overrides.