The CSS Revolution Has Arrived
CSS in 2025 has fundamentally transformed from a styling language into a powerful system for handling state, logic, and complex interactions--capabilities that previously required JavaScript. The browser platform now provides native tools that enable developers to build responsive, stateful, and interactive interfaces using pure CSS, reducing bundle sizes and improving performance.
This evolution represents a paradigm shift: CSS can now respond to container dimensions, scroll states, element relationships, and user interactions without custom JavaScript. By leveraging these native capabilities, development teams can create faster, more accessible, and more maintainable interfaces that scale across projects.
For organizations investing in professional web development services, understanding these capabilities provides a competitive advantage in building modern digital experiences.
Key features that define CSS development in 2025
Container Queries
Component-level responsiveness based on container dimensions, not viewport
Scroll-State Queries
Style elements based on scroll position, sticky state, and snap points
Style Queries
Respond to computed styles on parent containers conditionally
Customizable Select
Full styling control over native select elements with CSS
CSS Carousel Primitives
Native scroll-marker and scroll-button for carousel navigation
Anchor Positioning
Declarative positioning relative to anchor elements with fallback detection
Container Queries: The Foundation of Component-Based Design
Container queries enable you to apply styles to an element based on certain attributes of its container: the container's size, styles applied to the container, and the container's scroll-state. This represents a fundamental shift from media queries, which apply styles based on viewport size, to container queries that respond to the actual space available to a component.
For teams practicing modern web development with component-based architectures, container queries enable truly reusable components that adapt to their context rather than relying on viewport assumptions. This approach aligns with component-driven development patterns where each component manages its own responsive behavior.
Container Types
The container-type property establishes different types of containment contexts:
| Type | Description |
|---|---|
size | Query based on inline and block dimensions with full layout containment |
inline-size | Query based on inline dimension only, lighter containment |
normal | Not a query container for size queries, but supports style queries |
Container Query Length Units
Container query length units specify a length relative to the dimensions of a query container, making components flexible across different containers:
| Unit | Description |
|---|---|
cqw | 1% of a query container's width |
cqh | 1% of a query container's height |
cqi | 1% of a query container's inline size |
cqb | 1% of a query container's block size |
cqmin | The smaller value of either cqi or cqb |
cqmax | The larger value of either cqi or cqb |
Learn more about container queries from MDN's comprehensive guide
1/* Create a containment context */2.card-container {3 container-type: inline-size;4 container-name: card;5}6 7/* Apply styles based on container size */8@container card (width > 700px) {9 .card {10 display: grid;11 grid-template-columns: 2fr 1fr;12 }13 14 .card-title {15 font-size: clamp(1.25rem, 3cqi, 2rem);16 }17}18 19/* Fallback for containers narrower than 700px */20@container card (width <= 700px) {21 .card {22 display: flex;23 flex-direction: column;24 }25}The :has() Selector: CSS Can Now Think in State
The :has() pseudo-class represents a transformative leap in CSS capabilities, enabling parent selection and sibling selection with state awareness. Previously impossible in CSS, :has() allows you to style elements based on the presence or state of their children or subsequent siblings.
This capability opens entirely new patterns for responsive and conditional styling, reducing the need for JavaScript-based class manipulation. When combined with modern CSS techniques, developers can create sophisticated interactive patterns that would previously require substantial client-side logic.
Practical Applications
- Style a parent card differently when it contains a featured image
- Create responsive layouts that adapt based on which child elements are present
- Build form validation feedback that responds to input states
- Create conditional layouts based on content structure
These patterns integrate seamlessly with component libraries and align with best practices for building maintainable front-end architectures.
1/* Style cards based on their contents */2.card:has(.featured-image) {3 grid-template-columns: 1fr 2fr;4}5 6/* Add badges to cards that contain promo content */7.card:has(.promo-tag) {8 position: relative;9}10 11.card:has(.promo-tag)::before {12 content: 'PROMO';13 position: absolute;14 top: -10px;15 right: -10px;16 background: #e63946;17 color: white;18 padding: 0.25rem 0.5rem;19 border-radius: 4px;20 font-size: 0.75rem;21 font-weight: bold;22}23 24/* Form validation feedback */25.form-group:has(input:invalid:not(:placeholder-shown)) {26 --error-color: #dc2626;27}28 29.form-group:has(input:focus) label {30 color: #0066cc;31}Scroll-State Queries: Responding to Scroll Behavior
Chrome 133 introduced scroll-state container queries, enabling CSS to detect and respond to scroll-related states. This revolutionary feature allows styling elements based on whether they're stuck, snapped, or overflowing--without JavaScript IntersectionObserver implementations.
For performance-critical applications, eliminating JavaScript scroll detection can significantly improve Time to Interactive metrics. The browser handles scroll-state detection on the compositor thread, avoiding main-thread blocking that can cause janky scroll behavior and impact Core Web Vitals scores.
Available States
stuck: top/bottom/left/right- Detects sticky positioning state and edgesnapped: self/none- Detects if element is snapped in a scroll containeroverflowing- Detects if content overflows the container
Use Cases
- Sticky headers with dynamic shadows that appear when stuck
- Active navigation highlighting as users scroll through sections
- Carousel indicators that show which slide is currently in view
- Scroll-spy table of contents with active section highlighting
Explore the official Chrome documentation on scroll-state queries
1/* Sticky header with dynamic shadow */2.header-container {3 container-type: scroll-state;4 position: sticky;5 top: 0;6 z-index: 100;7}8 9header {10 transition: box-shadow 0.3s ease, background 0.3s ease;11 background: white;12}13 14/* Only show shadow when header is actually stuck */15@container scroll-state(stuck: top) {16 header {17 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);18 }19}20 21/* Carousel active state */22.carousel-item {23 scroll-snap-align: center;24 flex: 0 0 80%;25 transition: transform 0.3s ease, opacity 0.3s ease;26}27 28@container scroll-state(snapped: self) {29 .carousel-item {30 transform: scale(1.1);31 opacity: 1;32 z-index: 10;33 }34}Customizable Select: The End of JavaScript Dropdowns
One of the most requested features in CSS history has arrived: the ability to fully customize native <select> elements. Using appearance: base-select, developers can now style every part of a select element, including the button, dropdown list, and individual options.
This advancement eliminates the need for custom dropdown libraries that add substantial JavaScript payload. Teams can now build accessible, performant select components using pure CSS while maintaining semantic HTML structure.
Key Features
- Full CSS control over the button, dropdown list, and options
- Top-layer rendering ensures dropdowns appear above all content
- Automatic positioning that flips based on available viewport space
- HTML content support in options including images and icons
- Progressive enhancement with graceful fallback
Check the Chrome Dev documentation for the latest customizable select API details
1/* Enable customizable select styling */2select,3select::picker(select) {4 appearance: base-select;5}6 7/* Style the select button */8select {9 background: linear-gradient(to bottom, #fff, #f8f9fa);10 border: 2px solid #dee2e6;11 border-radius: 12px;12 padding: 0.875rem 3rem 0.875rem 1.25rem;13 font-size: 1rem;14 min-width: 200px;15 cursor: pointer;16 transition: all 0.2s ease;17}18 19select:hover {20 border-color: #0066cc;21}22 23select:focus {24 outline: none;25 border-color: #0066cc;26 box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);27}28 29/* Style the dropdown list */30select::picker(select) {31 background: white;32 border-radius: 12px;33 box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);34 padding: 0.5rem;35 margin-top: 0.5rem;36}37 38/* Style individual options */39select::picker(select) option {40 padding: 0.75rem 1rem;41 border-radius: 8px;42 cursor: pointer;43}44 45select::picker(select) option:hover {46 background: #f0f7ff;47}CSS Carousel Primitives: Scroll-Marker and Scroll-Button
The CSS Carousel API introduces native pseudo-elements for creating accessible, performant carousels without JavaScript libraries. The browser handles marker grouping, state management, and button behavior automatically.
New Pseudo-Elements
::scroll-marker- Navigation dots or markers for carousel items::scroll-button- Scroll buttons (previous/next arrows)::scroll-marker-group- Container for grouping markers:target-current- Pseudo-class for the currently active marker
By leveraging these native primitives, developers can create carousel components that are significantly smaller in bundle size and benefit from browser-level performance optimizations. This approach reduces the maintenance burden of keeping third-party carousel libraries updated.
See Smashing Magazine's comprehensive coverage for detailed carousel code examples and demos
1/* Basic carousel structure */2.carousel {3 overflow-x: auto;4 scroll-snap-type: x mandatory;5 scroll-marker-group: after;6 display: flex;7 gap: 1rem;8 padding: 1rem;9}10 11.carousel-item {12 scroll-snap-align: center;13 flex: 0 0 80%;14 height: 300px;15 border-radius: 12px;16 background: linear-gradient(135deg, #667eea, #764ba2);17}18 19/* Style scroll markers */20.carousel::scroll-marker {21 width: 12px;22 height: 12px;23 border-radius: 50%;24 background: #dee2e6;25 cursor: pointer;26 transition: all 0.2s ease;27}28 29.carousel::scroll-marker:target-current {30 background: #0066cc;31 transform: scale(1.25);32}33 34/* Style scroll buttons */35.carousel::scroll-button(inline-start),36.carousel::scroll-button(inline-end) {37 content: '';38 width: 40px;39 height: 40px;40 border-radius: 50%;41 background: white;42 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);43 cursor: pointer;44 align-self: center;45}46 47.carousel::scroll-button(inline-start)::before {48 content: '◀';49 display: flex;50 align-items: center;51 justify-content: center;52 height: 100%;53}54 55.carousel::scroll-button(inline-end)::before {56 content: '▶';57 display: flex;58 align-items: center;59 justify-content: center;60 height: 100%;61}Anchor Positioning and Anchored Container Queries
CSS anchor positioning enables declarative positioning of elements relative to anchor elements, with automatic fallback handling. Combined with anchored container queries, you can detect which fallback position was used and adapt styles accordingly.
Key Concepts
anchor-name- Defines an anchor reference on an elementposition-anchor- Links a positioned element to an anchorposition-area- Specifies the anchor area for positioningposition-try-fallbacks- Automatic repositioning when space is limited- Anchored queries - Style based on which fallback was used
These capabilities simplify the implementation of tooltips, popovers, and dropdown menus while ensuring consistent behavior across viewport sizes.
1/* Define anchor element */2.trigger-button {3 anchor-name: --tooltip-trigger;4}5 6/* Position tooltip relative to anchor */7.tooltip {8 position-anchor: --tooltip-trigger;9 position-area: top;10 position-try-fallbacks: flip-block, flip-inline, closest-side;11 container-type: anchored;12}13 14/* Default arrow pointing down */15.tooltip::before {16 content: '';17 position: absolute;18 bottom: -8px;19 left: 50%;20 transform: translateX(-50%);21 border-left: 8px solid transparent;22 border-right: 8px solid transparent;23 border-top: 8px solid #333;24}25 26/* Flip arrow when using flip-block fallback */27@container anchored(fallback: flip-block) {28 .tooltip::before {29 content: '';30 bottom: auto;31 top: -8px;32 border-top: none;33 border-bottom: 8px solid #333;34 }35}36 37/* Different positioning for inline flip */38@container anchored(fallback: flip-inline) {39 .tooltip::before {40 left: auto;41 right: -8px;42 top: 50%;43 transform: translateY(-50%);44 border: none;45 border-left: 8px solid #333;46 }47}Invoker Commands: Declarative Interactions
The Invoker Commands API enables buttons to perform actions on other elements without JavaScript event handlers. Using commandfor and command attributes, developers can create interactive UI patterns purely in HTML.
For teams building interactive web applications, invoker commands reduce the amount of glue code needed to connect buttons to their target elements. This declarative approach improves code organization and reduces potential sources of bugs.
Built-in Commands
show-popover/hide-popover/toggle-popover- Popover API controlshow-modal- Open dialogs modallyclose- Close dialogs- Custom commands with
--prefix (handled viacommandevent)
1<!-- Open a dialog without JavaScript -->2<button commandfor="settings-dialog" command="show-modal">3 Open Settings4</button>5 6<!-- Toggle a popover -->7<button commandfor="menu-popover" command="toggle-popover">8 Menu9</button>10 11<!-- Close a dialog -->12<dialog id="settings-dialog">13 <h2>Settings</h2>14 <p>Configure your preferences here.</p>15 16 <!-- Custom command handler via JavaScript -->17 <button commandfor="settings-dialog" command="--save-settings">18 Save19 </button>20 21 <button commandfor="settings-dialog" command="close">22 Close23 </button>24</dialog>25 26<!-- Popover example -->27<div id="menu-popover" popover>28 <nav>29 <a href="/">Home</a>30 <a href="/about">About</a>31 <a href="/contact">Contact</a>32 </nav>33</div>Performance Benefits of Native CSS
Using native CSS features instead of JavaScript solutions provides significant performance advantages:
Zero JavaScript Bundle Impact
Native CSS features add no JavaScript to your bundle, reducing download size, parse time, and execution time. Every kilobyte of JavaScript eliminated improves Time to Interactive (TTI) and Core Web Vitals scores.
Browser-Optimized Rendering
Browser rendering engines are highly optimized for CSS. Native features leverage the same code paths used for browser UI, benefiting from ongoing performance improvements across all browsers.
Main Thread Offloading
CSS-based interactions run on the browser's compositor thread, separate from the main JavaScript thread. This prevents interaction blocking during heavy JavaScript operations and provides smoother 60fps animations.
Automatic Accessibility
Native CSS features include proper ARIA attribute management, keyboard navigation, and focus handling. Building with platform features ensures accessible interactions without custom implementation.
Progressive Enhancement
CSS gracefully degrades when features aren't supported. Users with modern browsers get enhanced experiences, while users with older browsers receive functional baselines without broken functionality.
These performance characteristics make modern CSS an essential consideration for SEO-focused web development, where page speed directly impacts search rankings and user experience.
Progressive Enhancement Strategy
Implementing modern CSS while supporting older browsers requires thoughtful progressive enhancement:
Feature Detection
Use @supports to detect browser capabilities and provide appropriate fallbacks:
/* Base styles for all browsers */
.card { ... }
/* Enhanced styles for supporting browsers */
@supports (container-type: inline-size) {
.card {
/* Container query-based responsive behavior */
}
}
/* Legacy fallback */
@supports not (container-type: inline-size) {
@media (min-width: 700px) {
.card {
/* Media query-based responsive behavior */
}
}
}
Testing Matrix
Test across multiple browser versions:
- Chrome 100+ (full feature support)
- Firefox 110+ (container queries, scroll-state in progress)
- Safari 16+ (container queries, :has())
- Edge 100+ (equivalent to Chrome)
This testing approach ensures your implementations work reliably across the browser landscape while taking advantage of newer features where supported.
Conclusion
CSS in 2025 represents a fundamental transformation in frontend development. The language now supports state management, conditional logic, and complex interactions natively, enabling developers to build faster, more accessible, and more maintainable interfaces.
Key Takeaways
- Container queries enable truly component-based responsive design
- Scroll-state queries replace complex JavaScript scroll detection
- The :has() selector enables parent and sibling selection
- Customizable select eliminates JavaScript dropdown libraries
- CSS carousel primitives provide native carousel functionality
- Anchor positioning simplifies tooltip and popover implementations
- Invoker commands enable declarative interactions without JavaScript
By embracing these features while maintaining progressive enhancement strategies, development teams can deliver excellent experiences to modern browser users while ensuring broad compatibility. The investment in learning and implementing modern CSS pays dividends in reduced bundle sizes, improved performance, and more maintainable codebases.
Sources: