Writing CSS: A Modern Developer's Guide

Master the essential CSS features and best practices for building high-performance, maintainable websites in 2025

The State of CSS in 2025

The CSS landscape has transformed dramatically. Features that were once experimental or required preprocessors like Sass are now native to CSS itself, with browser support exceeding 90% across modern browsers. Modern CSS offers unprecedented power for building responsive, maintainable layouts without relying on complex build configurations or JavaScript workarounds.

This guide covers the essential features and best practices every modern web developer should master for clean, performant stylesheets.

Modern CSS Features You Should Be Using

Native CSS Capabilities

Powerful features now available without preprocessors

Native CSS Nesting

Group related styles without Sass. Use the & parent selector for clean, readable selectors.

Subgrid

Align nested elements with parent grid tracks. Perfect for card layouts and form alignment.

Container Queries

Style components based on their container size, not the viewport. Enable truly reusable components.

:has() Selector

The breakthrough 'parent selector' - style elements conditionally based on their children.

Native CSS Nesting

Native CSS nesting eliminates the need for preprocessor nesting while offering the same organizational benefits. The & parent selector makes your intentions explicit and your stylesheets more maintainable.

Key patterns:

  • Use & for pseudo-classes and pseudo-elements
  • Nest child selectors naturally
  • Avoid deep nesting chains for readability
CSS Nesting Example
1.card {2 padding: 1rem;3 4 &:hover {5 background: #f0f0f0;6 }7 8 &:focus-within {9 outline: 2px solid blue;10 }11 12 .title {13 font-weight: bold;14 font-size: 1.25rem;15 }16 17 .content {18 color: #333;19 }20}
Container Queries Example
1.card {2 container-type: inline-size;3}4 5@container (min-width: 400px) {6 .card-content {7 display: flex;8 gap: 1rem;9 }10 11 .card-image {12 flex: 0 0 120px;13 }14}15 16@container (min-width: 600px) {17 .card-content {18 flex-direction: row;19 }20}

Container Queries

Container queries revolutionize component-based design by allowing components to respond to their parent container's size rather than the viewport. This enables truly reusable, responsive components that work in any context.

Implementation:

  1. Set container-type on the parent
  2. Use @container for responsive rules
  3. Combine with CSS Grid and Flexbox

Use cases:

  • Card components in different sized grid cells
  • Navigation menus in sidebars vs headers
  • Product cards in featured vs listing views
:has() Examples
1/* Style card if it contains an image */2.card:has(img) {3 border: 1px solid purple;4}5 6/* Highlight form group when focused */7.form-group:focus-within {8 outline: 2px solid #7f5af0;9 box-shadow: 0 0 8px rgba(127, 90, 240, 0.3);10}11 12/* Style section with visible warnings */13.section:has(.warning) {14 background: #fff3cd;15}16 17/* Hide parent when child is hidden */18.item:has(.hidden) {19 display: none;20}

The :has() Parent Selector

The :has() pseudo-class is a game-changer, enabling conditional styling based on child elements--something that was previously impossible without JavaScript. This breakthrough feature, now supported in all modern browsers, opens new possibilities for responsive and conditional styling patterns.

Practical applications:

  • Highlight forms with invalid inputs
  • Style cards containing images differently
  • Show/hide elements based on content
  • Create responsive layouts that adapt to their children

CSS Layers with @layer

CSS Layers give you explicit control over specificity without fighting with selector wars. Declare layer order upfront and let CSS handle the cascade intelligently. This is particularly valuable when integrating third-party CSS or managing large design systems.

Layer benefits:

  • Predictable cascade behavior
  • Easy third-party CSS integration
  • Clean override patterns

Layer strategy:

  1. Reset/normalize (base layer)
  2. Design system tokens (tokens layer)
  3. Component styles (components layer)
  4. Utilities (utilities layer)
@layer base, components, utilities;

@layer base {
 body { margin: 0; font-family: system-ui; }
}

@layer components {
 .btn { padding: 0.5rem 1rem; border-radius: 4px; }
}

@layer utilities {
 .text-center { text-align: center; }
}

CSS Performance Optimization

Optimization Techniques

Minification & Compression Remove all formatting whitespace and comments. Combined with gzip/Brotli compression, this can reduce CSS file size by 60% or more. Modern build tools like Tailwind CSS and Next.js handle this automatically in production builds.

Critical CSS Extract styles needed for above-the-fold content and inline them in the HTML head. Defer the remaining styles asynchronously. This technique can improve First Contentful Paint (FCP) significantly.

Remove Unused CSS Over time, stylesheets accumulate dead code. Use tools like PurgeCSS or the built-in optimization in Tailwind to analyze HTML/JS and remove unreferenced selectors automatically.

Preload Critical CSS Add preload links in your HTML head to prioritize critical stylesheets:

<link rel="preload" href="/styles/main.css" as="style">

Efficient Selectors

Browsers evaluate selectors from right to left. Complex, deeply nested selectors force the browser to do extra work before finding a match. According to MDN's CSS Performance guide, keeping selectors simple and avoiding over-qualification is crucial for rendering performance.

Inefficient:

body #sidebar ul li .nav-link { color: blue; }

Efficient:

.sidebar-nav-link { color: blue; }

Best practices:

  • Prefer classes over element selectors
  • Avoid the universal selector (*) in production
  • Keep specificity low and flat
  • Use semantic HTML to reduce selector depth
  • Consider using [data-*] attributes sparingly
CSS Containment Example
1.card {2 contain: layout;3 contain-intrinsic-size: 300px 200px;4}5 6.widget {7 contain: layout style;8 contain-intrinsic-size: 400px auto;9}10 11.feed-item {12 contain: layout paint;13 contain-intrinsic-size: auto 100px;14}

CSS Containment

The contain property and contain-intrinsic-size tell the browser that an element's layout, style, or size won't affect its ancestors. As documented in the MDN CSS Performance guide, this prevents unnecessary recalculations and stabilizes layout for dynamic content.

Contain values:

  • layout: Element doesn't affect ancestors' layout
  • style: Element doesn't affect ancestors' style calculations
  • paint: Element is invisible outside its bounds

contain-intrinsic-size reserves space for elements with dynamic content, preventing Cumulative Layout Shift (CLS) and improving Core Web Vitals scores. This is especially valuable for components that load data asynchronously.

Animation Performance

GPU-Friendly Animations

Animating properties like width, height, margin, or top forces the browser to recalculate the layout of the entire page (reflow), which is expensive. Instead, animate properties that can be handled by the GPU for smoother 60fps performance. This is a key technique for building performant web applications that feel responsive.

For smooth, natural-feeling animations, understanding easing functions is essential--they control the timing curve of your transitions.

GPU-accelerated properties:

  • transform (translate, scale, rotate)
  • opacity

Layout-triggering properties (avoid):

  • width, height, margin, padding
  • top, left, right, bottom
  • font-size, line-height
  • border-width
Efficient vs Costly Animations
1/* ❌ Costly: Triggers reflow */2.box {3 transition: width 0.3s ease;4}5 6/* ✅ Efficient: GPU compositing */7.box {8 transition: transform 0.3s ease;9}10 11.box:hover {12 transform: scale(1.05);13}14 15/* ✅ Use transform for all animations */16.btn {17 transition: transform 0.2s,18 opacity 0.2s;19}20 21.btn:hover {22 transform: translateY(-2px);23 opacity: 0.9;24}25 26/* ✅ Respect user preferences */27@media (prefers-reduced-motion: reduce) {28 * {29 animation-duration: 0.01ms !important;30 transition-duration: 0.01ms !important;31 }32}

Best Practices Checklist

Use this checklist when building modern CSS for your web projects:

Architecture

  • Use native CSS nesting instead of preprocessor nesting
  • Organize with @layer for large codebases
  • Keep selectors simple and specific
  • Use CSS Modules or similar for component isolation

Performance

  • Minify and compress production CSS
  • Extract critical CSS for above-the-fold content
  • Remove unused styles with automated tools
  • Preload critical CSS assets

Animations

  • Animate only transform and opacity
  • Use will-change sparingly and strategically
  • Respect prefers-reduced-motion for accessibility
  • Test on lower-end devices

Responsive Design

  • Use container queries for components
  • Implement mobile-first approach
  • Use clamp() for fluid typography
  • Test at real device breakpoints

Maintenance

  • Document design tokens and global styles
  • Use consistent naming conventions (BEM or similar)
  • Keep stylesheets modular and organized
  • Review and refactor periodically

For fluid typography and spacing, explore how min(), max(), and clamp() can simplify your responsive design workflow.

Frequently Asked Questions

Do I still need Sass or other CSS preprocessors?

Native CSS nesting, CSS custom properties (variables), and modern layout capabilities have replaced many preprocessor features. You may still want Sass for advanced mixins or module systems, but many projects now work fine with vanilla CSS. The key is evaluating your team's specific needs.

How do container queries differ from media queries?

Media queries respond to the viewport size, while container queries respond to the parent container's size. Container queries enable truly reusable components that adapt to their container context rather than the page layout. This is essential for component-based architectures.

What's the best way to handle CSS in Next.js?

Next.js supports CSS Modules for component-scoped styles and global CSS for application-wide styles. Use CSS Modules by default for component isolation, and be mindful of the order global styles are loaded to avoid specificity issues. Tailwind CSS is also well-integrated with Next.js for utility-first styling.

How do I measure CSS performance impact?

Use browser DevTools: the Coverage panel shows unused CSS, the Performance Monitor tracks layout recalculations, and the Network waterfall shows render-blocking CSS. Focus on Critical CSS extraction and unused code removal for the biggest performance improvements.

Ready to Build High-Performance Websites?

Our team specializes in modern web development using Next.js and clean, performant CSS architecture. From responsive layouts to optimized animations, we build websites that perform.