HTML and CSS course ยท Module 9: Modern CSS
State of CSS - The Future and Trends of CSS
In this lesson10
Every year CSS brings so many new features that it is hard to tell a passing fashion from things that will last for years, and you need a map, like the pharaoh's cartographer. State of CSS is an annual survey of the web developer community that maps trends, preferences and development directions of CSS. The survey asks whether you know and use a given feature, and for tools it also asks about satisfaction. You will find the results at stateofcss.com.
Current State of CSS
New CSS Features
CSS is constantly evolving, introducing increasingly advanced capabilities. Here is an overview of the features you have met in this module:
/* Container Queries - container-based responsiveness */
@container sidebar (min-width: 250px) {
.card {
display: flex;
flex-direction: row;
}
}
/* CSS Nesting - nesting in native CSS */
.card {
background: white;
border-radius: 8px;
&:hover {
background: #f8f9fa;
}
.title {
font-size: 1.2rem;
font-weight: bold;
&.highlight {
color: #007bff;
}
}
}
/* CSS Cascade Layers - cascade control */
@layer reset, base, theme, components, utilities;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer theme {
:root {
--primary: #007bff;
--secondary: #6c757d;
}
}
/* Subgrid - grid inheritance */
.main-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}Container Queries, nesting, cascade layers and subgrid work today in all major browsers.
CSS Houdini - A New Era of Customization
CSS Houdini allows developers to access the CSS Object Model, the inside of the style engine. CSS.registerProperty() is the JavaScript counterpart of the @property rule, and a Paint Worklet draws a background with code:
// Registering a custom property
CSS.registerProperty({
name: '--gradient-angle',
syntax: '<angle>',
inherits: false,
initialValue: '0deg'
});
// Paint Worklet - custom drawing
class CircleGradient {
static get inputProperties() {
return ['--start-color', '--end-color'];
}
paint(ctx, size, properties) {
const gradient = ctx.createRadialGradient(
size.width / 2, size.height / 2, 0,
size.width / 2, size.height / 2, size.width / 2
);
gradient.addColorStop(0, properties.get('--start-color'));
gradient.addColorStop(1, properties.get('--end-color'));
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size.width, size.height);
}
}
registerPaint('circle-gradient', CircleGradient);The class goes into a separate file loaded with CSS.paintWorklet.addModule(), and inputProperties lists the variables the worklet will read. In the stylesheet you use it with the paint() function:
/* Using Paint Worklet */
.custom-background {
--start-color: #667eea;
--end-color: #764ba2;
background: paint(circle-gradient);
}The Paint API works today only in Chromium-based browsers, so treat it as an enhancement with a fallback.
Trends and Statistics from State of CSS
Most Popular CSS Features
This is what a summary of usage, satisfaction and interest in features could look like when written as data:
const cssFeatureUsage = {
flexbox: {
usage: "94%",
satisfaction: "92%",
interest: "65%"
},
grid: {
usage: "89%",
satisfaction: "94%",
interest: "71%"
},
customProperties: {
usage: "87%",
satisfaction: "89%",
interest: "68%"
},
containerQueries: {
usage: "23%",
satisfaction: "91%",
interest: "89%"
},
cssNesting: {
usage: "31%",
satisfaction: "87%",
interest: "84%"
},
cascadeLayers: {
usage: "8%",
satisfaction: "73%",
interest: "76%"
}
};
// Growth trends
const emergingFeatures = [
'Container Queries',
'CSS Nesting',
'Cascade Layers',
'CSS Subgrid',
'CSS Scroll-Driven Animations',
'View Transitions API'
];The numbers are examples that show the data format, not the results of a specific edition of the survey.
CSS Frameworks - Developer Preferences
The survey also compares utility-first, component and CSS-in-JS frameworks, here in the same format:
const cssFrameworksRanking = {
// Utility-first frameworks
tailwind: {
usage: "78%",
satisfaction: "84%",
retention: "87%",
trend: "rising"
},
// Component frameworks
bootstrap: {
usage: "45%",
satisfaction: "43%",
retention: "38%",
trend: "declining"
},
materialUI: {
usage: "32%",
satisfaction: "67%",
retention: "62%",
trend: "stable"
},
// CSS-in-JS solutions
styledComponents: {
usage: "41%",
satisfaction: "71%",
retention: "68%",
trend: "stable"
},
emotion: {
usage: "23%",
satisfaction: "73%",
retention: "69%",
trend: "stable"
}
};These numbers are illustrative too. Tailwind CSS is the flagship example of the utility-first approach and has been among the most popular frameworks in the survey for years.
The Future of CSS - Upcoming Features
1. CSS Anchor Positioning
Anchor Positioning lets you pin an element, for example a tooltip, to another element without JavaScript:
/* Positioning relative to an anchor */
.anchor {
anchor-name: --my-anchor;
}
.positioned {
position: absolute;
position-anchor: --my-anchor;
top: anchor(bottom);
left: anchor(center);
margin-top: 10px;
}
/* Anchor queries - anchor-based responsiveness */
@position-try --fallback {
top: anchor(top);
bottom: auto;
margin-top: -10px;
}
.positioned {
position-try-fallbacks: --fallback;
}The anchor gets its name through anchor-name, and the anchor() function points to its edges. @position-try describes a fallback position in case there is no room, and position-try-fallbacks (formerly position-try-options) attaches it. Chromium has supported this since 2024, and other browsers are gradually joining.
2. View Transitions API
The View Transitions API is used for smooth, animated transitions between states or views of a page. In CSS you animate the pseudo-elements of the old and the new view:
/* Animations between views */
::view-transition-old(root) {
animation: slide-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease-in;
}
@keyframes slide-out {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
@keyframes slide-in {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
/* Named view transitions */
.card {
view-transition-name: card-transition;
}view-transition-name gives the chosen element, for example a card, its own transition animation. On the JavaScript side, you start a transition with the document.startViewTransition() method:
// JavaScript API for View Transitions
function navigateToPage(url) {
if (document.startViewTransition) {
document.startViewTransition(() => {
// Navigation logic
window.location.href = url;
});
} else {
// Fallback for browsers without support
window.location.href = url;
}
}
// Programmatic view transitions
function updateContent(newContent) {
if (document.startViewTransition) {
document.startViewTransition(() => {
document.querySelector('.content').innerHTML = newContent;
});
} else {
document.querySelector('.content').innerHTML = newContent;
}
}The browser takes a snapshot of the old view, performs the DOM change and animates the transition. For navigation between separate pages, however, it is better to add the @view-transition { navigation: auto; } rule to the CSS of both pages, without JavaScript.
3. Scroll-Driven Animations
Scroll-driven animations connect @keyframes to a scroll timeline instead of a clock:
/* Scroll-based animations */
@keyframes reveal {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.scroll-reveal {
animation: reveal linear;
animation-timeline: view();
animation-range: entry 25% cover 50%;
}
/* Progress bar based on scroll */
.progress-bar {
animation: progress-bar linear;
animation-timeline: scroll(root);
}
@keyframes progress-bar {
from { width: 0%; }
to { width: 100%; }
}
/* Parallax effect with scroll timeline */
.parallax-bg {
animation: parallax linear;
animation-timeline: scroll(nearest);
animation-range: exit -100% entry 100%;
}
@keyframes parallax {
from { transform: translateY(-50%); }
to { transform: translateY(50%); }
}view() tracks an element appearing in the view, and scroll() tracks scroll progress. animation-timeline must come after the animation shorthand, because the shorthand resets this property.
4. CSS Color Functions Level 5
New color functions let the browser calculate shades. Notice light-dark() at the end of the example:
/* New color functions */
.advanced-colors {
/* Relative color syntax */
--primary: #007bff;
--primary-light: color(from var(--primary) srgb r g b / 0.5);
--primary-dark: color(from var(--primary) srgb calc(r * 0.8) calc(g * 0.8) calc(b * 0.8));
/* Color mixing */
--mixed-color: color-mix(in srgb, var(--primary) 70%, white);
/* Contrast color function */
--text-color: contrast-color(var(--primary));
/* Wide gamut colors */
--vibrant-red: color(display-p3 1 0.2 0.3);
--rec2020-green: color(rec2020 0.4 0.9 0.1);
}
/* Light-dark function */
:root {
color-scheme: light dark; /* required by light-dark() */
}
.theme-aware {
background: light-dark(white, #1a1a1a);
color: light-dark(#333, white);
}light-dark(white, #1a1a1a) picks the first color in a light theme and the second in a dark theme, but only when color-scheme allows both themes. contrast-color() is the youngest and has limited support.
Methodologies and Best Practices According to the Community
Utility-First vs Component-First
Compare the utility-first and the component approach on a single button:
/* Utility-first approach (Tailwind style) */
.btn-primary {
@apply inline-flex items-center px-4 py-2
bg-blue-600 border border-transparent
rounded-md font-semibold text-xs text-white
uppercase tracking-widest hover:bg-blue-700
focus:bg-blue-700 active:bg-blue-900
focus:outline-none focus:ring-2 focus:ring-indigo-500
focus:ring-offset-2 transition ease-in-out duration-150;
}
/* Component-first approach */
.button {
--button-bg: var(--color-primary);
--button-text: var(--color-white);
--button-border: var(--color-primary);
--button-hover-bg: var(--color-primary-dark);
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
background: var(--button-bg);
color: var(--button-text);
border: 1px solid var(--button-border);
border-radius: 0.375rem;
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: all 0.15s ease-in-out;
&:hover {
background: var(--button-hover-bg);
}
&:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
&.secondary {
--button-bg: var(--color-secondary);
--button-border: var(--color-secondary);
--button-hover-bg: var(--color-secondary-dark);
}
}@apply works only with Tailwind. ring is a Tailwind concept, and in plain CSS a similar effect comes from outline with outline-offset.
CSS Architecture Patterns
CUBE CSS divides styles into composition, utilities, blocks and exceptions (Composition, Utility, Block, Exception):
/* CUBE CSS methodology */
/* Composition */
.stack {
display: flex;
flex-direction: column;
gap: var(--space, 1rem);
}
.cluster {
display: flex;
flex-wrap: wrap;
gap: var(--space, 1rem);
align-items: var(--align, flex-start);
justify-content: var(--justify, flex-start);
}
/* Utilities */
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.flow > * + * {
margin-top: var(--flow-space, 1em);
}
/* Block */
.card {
background: var(--surface);
border-radius: var(--radius);
padding: var(--space-m);
box-shadow: var(--shadow);
}
/* Exception */
.card[data-variant="ghost"] {
background: transparent;
box-shadow: none;
border: 1px solid var(--border);
}The [data-variant="ghost"] exception changes the card with an attribute instead of yet another class.
CSS Tools Ecosystem - Tooling Trends
Build Tools Preferences
The survey also asks about tools: preprocessors, PostCSS plugins and bundlers. The data could look like this:
const buildToolsUsage = {
// CSS Processors
sass: {
usage: "71%",
satisfaction: "79%",
retention: "74%"
},
less: {
usage: "23%",
satisfaction: "61%",
retention: "45%"
},
stylus: {
usage: "8%",
satisfaction: "68%",
retention: "52%"
},
// PostCSS plugins
autoprefixer: {
usage: "84%",
satisfaction: "87%",
retention: "89%"
},
purgecss: {
usage: "45%",
satisfaction: "82%",
retention: "78%"
},
cssnano: {
usage: "52%",
satisfaction: "79%",
retention: "76%"
},
// Bundlers
webpack: {
usage: "68%",
satisfaction: "65%",
retention: "58%"
},
vite: {
usage: "67%",
satisfaction: "91%",
retention: "89%"
},
parcel: {
usage: "23%",
satisfaction: "78%",
retention: "71%"
}
};These numbers are examples as well, so treat them as a template, not a result.
CSS-in-JS Evolution
CSS-in-JS has moved from generating styles in the browser to generating them during the build. Here are three approaches:
// Styled components approach
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
&:hover {
opacity: 0.9;
}
@media (max-width: 768px) {
font-size: 0.875rem;
}
`
// Zero-runtime CSS-in-JS (Compiled, Vanilla Extract)
import { style } from '@vanilla-extract/css';
export const button = style({
background: '#007bff',
color: 'white',
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '0.25rem',
cursor: 'pointer',
':hover': {
opacity: 0.9
},
'@media': {
'(max-width: 768px)': {
fontSize: '0.875rem'
}
}
});
// Atomic CSS approach (Stitches)
const Button = styled('button', {
background: '$blue600',
color: '$white',
padding: '$2 $4',
border: 'none',
borderRadius: '$md',
cursor: 'pointer',
'&:hover': {
opacity: 0.9
},
variants: {
size: {
small: {
padding: '$1 $2',
fontSize: '$sm'
},
large: {
padding: '$3 $6',
fontSize: '$lg'
}
}
}
});Zero-runtime solutions, such as Vanilla Extract, turn styles into a plain CSS file already during the build.
Performance Trends
CSS Optimization Strategies
Performance is another trend. The first rule, despite its comment, reduces motion for people who need it:
/* Critical CSS loading */
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-delay: -1ms !important;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
background-attachment: initial !important;
scroll-behavior: auto !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
}
/* Container-based responsive design */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
@container sidebar (min-width: 250px) {
.navigation {
display: block;
}
}
/* CSS containment for performance */
.card {
contain: layout style paint;
}
.infinite-list-item {
contain: strict;
content-visibility: auto;
contain-intrinsic-size: 200px;
}content-visibility: auto skips rendering content outside the screen, and contain-intrinsic-size reserves space for it so the page does not jump.
Modern CSS Loading Patterns
We put the key styles into the HTML and load the rest so that it does not block the first render:
<!-- Critical CSS inline -->
<style>
/* Critical above-the-fold styles */
.hero { display: flex; min-height: 100vh; }
.header { position: fixed; top: 0; width: 100%; }
</style>
<!-- Non-critical CSS with media queries -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
<!-- CSS for specific components -->
<link rel="stylesheet" href="components.css" media="print" onload="this.media='all'">The preload and media="print" tricks load CSS asynchronously, and noscript provides a fallback without JavaScript.
Browser Support Strategies
Progressive Enhancement Patterns
We introduce new features so that older browsers get a working base and newer ones get something more:
/* Feature detection with @supports */
.grid-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
@supports (display: grid) {
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
@supports (container-type: inline-size) {
.responsive-component {
container-type: inline-size;
}
@container (min-width: 300px) {
.component-content {
display: flex;
}
}
}
/* Graceful degradation for new color spaces */
.vibrant-bg {
background: #ff6b6b; /* Fallback */
background: color(display-p3 1 0.4 0.4); /* Modern browsers */
}
/* Progressive enhancement for scroll animations */
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: scroll()) {
.scroll-animation {
animation: fadeIn linear;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
}
}First a plain value, then @supports with the new feature. For animations we also check that the user is not asking for less motion.
Accessibility Trends
Inclusive Design Patterns
Preference media queries adapt the page to the user's system settings:
/* Respect user preferences */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--text-primary: #ffffff;
--border-color: #404040;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@media (prefers-contrast: more) {
:root {
--border-width: 2px;
--focus-ring-width: 3px;
}
}
/* Focus management */
.button {
&:focus-visible {
outline: 2px solid var(--focus-color);
outline-offset: 2px;
}
&:focus:not(:focus-visible) {
outline: none;
}
}
/* Screen reader optimizations */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Color contrast helpers */
.ensure-contrast {
background: var(--bg-color);
color: contrast-color(var(--bg-color)); /* Future CSS function */
}For prefers-contrast the valid values are more, less, custom and no-preference, and :focus-visible shows the focus outline only when it is needed.
Future Directions for CSS Development
Predictions Based on State of CSS
Finally, the predictions. Written as data, they look like this:
const futureCSSTrends = {
shortTerm: [
'Widespread Container Queries adoption',
'CSS Nesting in all browsers',
'View Transitions API expansion',
'Better CSS Houdini support'
],
mediumTerm: [
'CSS Anchor Positioning',
'Advanced color functions',
'Scroll-driven animations',
'CSS Modules native support'
],
longTerm: [
'CSS Variables 2.0 with functions',
'CSS Layout API (Houdini)',
'CSS Parser API',
'Advanced typography controls'
],
emerging: [
'CSS Container Style Queries',
'CSS Conditional Rules Level 4',
'CSS Spatial Navigation',
'CSS Environment Variables'
]
};
// Developer priorities
const developerWants = {
features: [
'Better debugging tools',
'Native CSS mixins',
'Improved CSS math functions',
'Cross-browser consistency'
],
tooling: [
'Faster build tools',
'Better IntelliSense',
'Visual CSS editors',
'Performance analyzers'
],
ecosystem: [
'Standardized design tokens',
'Better framework interop',
'Component sharing standards',
'CSS testing tools'
]
};Some of them have already come true, for example nesting works in all major browsers.
Design System Evolution
The example below is a fantasy about the future of design systems. The @design-tokens and @component rules do not exist in any CSS specification:
/* Future design token approach */
@design-tokens {
--color-primary: #007bff;
--color-secondary: #6c757d;
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--typography-heading-1: {
font-family: Inter, sans-serif;
font-size: 2.5rem;
font-weight: 700;
line-height: 1.2;
};
}
/* Advanced component definition */
@component button {
base: {
display: inline-flex;
align-items: center;
padding: var(--space-sm) var(--space-md);
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.15s ease;
};
variants: {
variant: {
primary: {
background: var(--color-primary);
color: white;
},
secondary: {
background: var(--color-secondary);
color: white;
}
},
size: {
small: {
padding: var(--space-xs) var(--space-sm);
font-size: 0.875rem;
},
large: {
padding: var(--space-md) var(--space-lg);
font-size: 1.125rem;
}
}
}
}Today you write tokens as CSS variables, and the W3C Design Tokens Community Group is working on a shared JSON format for tokens.
State of CSS Summary
Key Takeaways from Research
- Container Queries are revolutionizing responsive design
- Utility-first frameworks (Tailwind) are dominating over traditional ones
- CSS Nesting is becoming a standard in modern CSS
- Performance is a key factor in tool selection
- Accessibility is gaining importance in the community
- Zero-runtime CSS-in-JS is the future of styling in React
Recommendations for Developers
- Keep up with Container Queries - this is a fundamental change in CSS
- Experiment with CSS Nesting - already available in most browsers
- Consider Tailwind CSS - if you're not using utility-first yet
- Prepare for View Transitions - a new era of web animations
- Invest in accessibility - it's not just a trend, it's a necessity
My advice: review the State of CSS results once a year, but adopt new features only after checking their support on caniuse.com. In the sandbox below, experiment with color-mix(), one of the new color functions.
Remember: State of CSS is a cartographer's map that shows the direction, but you still check the path through the desert with your own steps.
Code for this lesson: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>color-mix()</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>color-mix() - mixing colors</h1>
<div class="palette">
<div class="swatch s-lightest">90% white</div>
<div class="swatch s-lighter">70% white</div>
<div class="swatch s-light">50% white</div>
<div class="swatch s-base">Base</div>
<div class="swatch s-dark">70% black</div>
<div class="swatch s-darker">50% black</div>
</div>
<div class="demo-card">
<h2>Card with a palette</h2>
<p>Colors generated from a single base color!</p>
</div>
</body>
</html>Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is State of CSS?
2. Which framework represents the rising utility-first trend according to State of CSS?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
Arrange the valid light-dark() function call for a background color on one line: