---
name: css-modern
description: "Modern CSS: container queries, nesting, @layer, Grid/Flexbox patterns, custom properties, animations, and transitions. Use when writing or reviewing modern CSS: layout, container queries, custom properties, animation."
tags: [css, grid, flexbox, container-queries, nesting, animations, frontend]
version: "2025.1"
---

# Modern CSS (2024-2025)

## CSS Container Queries

Container queries let components respond to their container's size rather than the viewport.
Supported in all modern browsers since 2023.

```css
/* Define a containment context */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* Query the container */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1rem;
  }
}

@container card (min-width: 700px) {
  .card {
    grid-template-columns: 250px 1fr 200px;
  }
  .card__actions {
    flex-direction: column;
  }
}

/* Container query units */
.card__title {
  font-size: clamp(1rem, 3cqi, 1.5rem);  /* cqi = 1% of container inline size */
}

/* Style queries (check custom property values) */
@container style(--theme: dark) {
  .card {
    background: #1f2937;
    color: #f9fafb;
  }
}
```

## CSS Nesting

Native CSS nesting (no preprocessor needed). Supported in all modern browsers.

```css
.card {
  background: white;
  border-radius: 0.75rem;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);

  /* Nested child selectors */
  & .card__title {
    font-size: 1.25rem;
    font-weight: 700;
    color: #111827;
  }

  & .card__body {
    margin-top: 0.75rem;
    color: #6b7280;
    line-height: 1.6;
  }

  /* Pseudo-classes and pseudo-elements */
  &:hover {
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
    transform: translateY(-2px);
  }

  &::before {
    content: '';
    display: block;
    height: 4px;
    background: var(--color-brand);
    border-radius: 4px 4px 0 0;
  }

  /* Media queries inside the rule */
  @media (max-width: 768px) {
    padding: 1rem;
  }

  /* Compound selectors */
  &.card--featured {
    border: 2px solid var(--color-brand);
  }

  /* Parent selector (reverse nesting) */
  .dark-theme & {
    background: #1f2937;
    color: #f9fafb;
  }
}
```

## CSS Layers (@layer)

Cascade layers give explicit control over specificity order.

```css
/* Define layer order  -  first declared = lowest priority */
@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.6;
    color: #111827;
  }

  h1, h2, h3 {
    line-height: 1.2;
    text-wrap: balance;
  }

  a {
    color: var(--color-brand);
    text-decoration-thickness: 2px;
    text-underline-offset: 2px;
  }
}

@layer components {
  .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 1.25rem;
    border-radius: 0.5rem;
    font-weight: 600;
    transition: all 150ms ease;
    cursor: pointer;
  }

  .btn--primary {
    background: var(--color-brand);
    color: white;

    &:hover {
      background: var(--color-brand-dark);
    }
  }
}

@layer utilities {
  .sr-only { /* screen reader only */ }
  .text-center { text-align: center; }
  .mt-4 { margin-top: 1rem; }
}

/* Import third-party CSS into a layer */
@import url('vendor.css') layer(vendor);
```

## CSS Grid Patterns

```css
/* Responsive auto-fill grid */
.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: 1.5rem;
}

/* Holy grail layout */
.page {
  display: grid;
  grid-template-areas:
    "header  header  header"
    "sidebar content aside"
    "footer  footer  footer";
  grid-template-columns: 250px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  min-height: 100dvh;

  @media (max-width: 768px) {
    grid-template-areas:
      "header"
      "content"
      "footer";
    grid-template-columns: 1fr;
  }
}

.page__header  { grid-area: header; }
.page__sidebar { grid-area: sidebar; }
.page__content { grid-area: content; }
.page__aside   { grid-area: aside; }
.page__footer  { grid-area: footer; }

/* Subgrid  -  children align to parent grid */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.card-grid__item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3; /* Header, body, footer */
}

/* Named grid lines */
.layout {
  display: grid;
  grid-template-columns:
    [full-start] 1fr
    [content-start] min(65ch, 100%) [content-end]
    1fr [full-end];
}

.layout > * {
  grid-column: content;
}

.layout > .full-bleed {
  grid-column: full;
}
```

## CSS Flexbox Patterns

```css
/* Centered content */
.center-flex {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100dvh;
}

/* Space-between header */
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
  gap: 1rem;
}

/* Wrap with minimum width */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tag {
  flex: 0 0 auto; /* Don't grow, don't shrink */
}

/* Sticky footer */
.page {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

.page__content {
  flex: 1; /* Takes remaining space */
}

/* Equal-width columns */
.equal-cols {
  display: flex;
  gap: 1rem;
}

.equal-cols > * {
  flex: 1;
  min-width: 0; /* Prevent overflow from long content */
}
```

## CSS Custom Properties (Variables)

```css
:root {
  /* Color system */
  --color-brand: oklch(55% 0.25 265);
  --color-brand-light: oklch(70% 0.2 265);
  --color-brand-dark: oklch(40% 0.25 265);

  --color-surface: #ffffff;
  --color-surface-elevated: #f9fafb;
  --color-text: #111827;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;

  /* Spacing scale */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 3rem;

  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;
  --transition-slow: 400ms ease;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px rgb(0 0 0 / 0.1);
}

/* Dark theme override */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface: #111827;
    --color-surface-elevated: #1f2937;
    --color-text: #f9fafb;
    --color-text-muted: #9ca3af;
    --color-border: #374151;
  }
}

/* Component using tokens */
.card {
  background: var(--color-surface-elevated);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  padding: var(--space-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-normal);

  &:hover {
    box-shadow: var(--shadow-md);
  }
}
```

## Animations and Transitions

```css
/* View Transitions API */
@view-transition {
  navigation: auto;
}

.hero-image {
  view-transition-name: hero;
}

::view-transition-old(hero) {
  animation: fade-out 300ms ease;
}

::view-transition-new(hero) {
  animation: fade-in 300ms ease;
}

/* Scroll-driven animations */
@keyframes slide-in {
  from { opacity: 0; transform: translateY(50px); }
  to { opacity: 1; transform: translateY(0); }
}

.scroll-reveal {
  animation: slide-in linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

/* Transition with discrete properties */
.dialog {
  transition: opacity 300ms ease, display 300ms ease allow-discrete;
  opacity: 0;
  display: none;

  &[open] {
    opacity: 1;
    display: block;

    @starting-style {
      opacity: 0;
    }
  }
}

/* Keyframe animation */
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite ease-in-out;
}

/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
  .scroll-reveal {
    animation: none;
    opacity: 1;
  }
}
```

## Do's

- Use container queries for component-level responsive design
- Use CSS custom properties for theming and design tokens
- Use `@layer` to manage specificity in large projects
- Use CSS nesting to reduce repetition and improve readability
- Use `oklch()` or `oklab()` for perceptually uniform color manipulation
- Use `dvh`/`svh`/`lvh` instead of `vh` for mobile-safe viewport heights
- Use `text-wrap: balance` for headings and `text-wrap: pretty` for paragraphs
- Respect `prefers-reduced-motion` and `prefers-color-scheme`

## Don'ts

- Do not use `!important` unless overriding third-party styles in a utility layer
- Do not use `float` for layout; use Grid or Flexbox
- Do not use vendor prefixes manually; use Autoprefixer or Lightning CSS
- Do not nest more than 3 levels deep (even with native nesting)
- Do not use px for font sizes that should scale; use rem/em
- Do not animate `width`, `height`, or `top`/`left`; use `transform` and `opacity`
- Do not use `@import` without `@layer`; it can cause render-blocking
- Do not use `100vh` on mobile; use `100dvh` for dynamic viewport height

## Troubleshooting

| Problem | Cause | Solution |
|---------|-------|----------|
| Container query not working | Missing `container-type` | Add `container-type: inline-size` to parent |
| Nesting not working | Browser too old or `&` missing | Check browser support; always use `&` for nested selectors |
| Layer order unexpected | Layers declared in wrong order | Re-order `@layer` declaration (first = lowest priority) |
| Grid item overflowing | Long word or fixed-width content | Add `min-width: 0` or `overflow: hidden` to grid item |
| Subgrid not aligning | Missing `grid-row: span N` | Set span count to match number of sub-rows |
| Custom property not applying | Scope issue or typo | Check spelling and ensure variable is defined in the right scope |
