Modern CSS Features (2026+) and Experimental APIs
1. CSS Nesting Syntax and Implementation
| Feature | Syntax | Browser Support | Description |
|---|---|---|---|
| Basic Nesting NEW | .parent { & .child { } } |
Chrome 120+, Safari 17.2+ | Native CSS nesting without preprocessor |
| Direct Nesting | .parent { .child { } } |
Chrome 120+, Safari 17.2+ | Type selector requires & |
| Ampersand (&) | &:hover, &.active |
Modern browsers | Reference parent selector |
| Compound Nesting | &-suffix |
Modern browsers | BEM-style concatenation |
| Media Query Nesting | @media { & { } } |
Modern browsers | Nest media queries inside selectors |
| Multiple Selectors | &:is(.a, .b) |
Modern browsers | Multiple parent references |
Example: Native CSS nesting
/* Basic nesting with & */
.card {
background: white;
padding: 1rem;
border-radius: 8px;
/* Nested element */
& .title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
/* Pseudo-class */
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
/* Pseudo-element */
&::before {
content: '';
display: block;
}
/* Compound selector (BEM-style) */
&--featured {
border: 2px solid #007acc;
}
&__header {
display: flex;
justify-content: space-between;
}
&__title {
font-weight: bold;
}
}
/* Compiles to: */
/*
.card { background: white; padding: 1rem; border-radius: 8px; }
.card .title { font-size: 1.5rem; margin-bottom: 0.5rem; }
.card:hover { box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
.card::before { content: ''; display: block; }
.card--featured { border: 2px solid #007acc; }
.card__header { display: flex; justify-content: space-between; }
.card__title { font-weight: bold; }
*/
/* Direct nesting (type selectors need &) */
.article {
/* ✅ CORRECT: Class/ID can be direct */
.content { }
#main { }
/* ❌ WRONG: Type selector needs & */
p { } /* Won't work! */
/* ✅ CORRECT: Use & with type selector */
& p {
line-height: 1.6;
}
& > p {
/* Direct child paragraphs */
}
}
/* Multiple nesting levels */
.nav {
display: flex;
& .nav-item {
padding: 0.5rem 1rem;
& .nav-link {
color: #333;
text-decoration: none;
&:hover {
color: #007acc;
}
&:focus {
outline: 2px solid currentColor;
}
}
&.active .nav-link {
font-weight: bold;
}
}
}
Example: Advanced nesting patterns
/* Media query nesting */
.container {
width: 100%;
padding: 1rem;
@media (min-width: 768px) {
& {
width: 750px;
margin: 0 auto;
}
}
@media (min-width: 1024px) {
& {
width: 960px;
padding: 2rem;
}
}
}
/* Nested at-rules */
.button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
@supports (backdrop-filter: blur(10px)) {
& {
background: rgba(0, 122, 204, 0.8);
backdrop-filter: blur(10px);
}
}
@media (prefers-reduced-motion: no-preference) {
& {
transition: transform 0.2s;
}
&:hover {
transform: scale(1.05);
}
}
}
/* Multiple parent selectors with :is() */
.nav {
&:is(.horizontal, .vertical) {
display: flex;
}
&:is(.horizontal) {
flex-direction: row;
}
&:is(.vertical) {
flex-direction: column;
}
}
/* Complex nesting scenarios */
.theme-dark {
& .card {
background: #2d2d2d;
color: #f0f0f0;
& .title {
color: white;
}
&:hover {
background: #3d3d3d;
}
}
& .button {
background: #404040;
&:hover {
background: #505050;
}
}
}
/* Nesting with :has() */
.article {
& {
padding: 2rem;
}
&:has(img) {
padding-top: 0;
& img {
width: 100%;
height: auto;
}
}
&:has(> .featured) {
border-left: 4px solid #007acc;
}
}
Note: Native CSS nesting requires & for type selectors and
pseudo-classes. Class/ID selectors can be direct children. Media queries and @supports can be nested inside
selectors. Browser support: Chrome 120+, Safari 17.2+, Firefox 117+.
2. CSS Cascade Layers (@layer)
| Concept | Syntax | Purpose | Specificity |
|---|---|---|---|
| Layer Declaration | @layer name { } |
Create named layer | Order-based, not specificity-based |
| Layer Ordering | @layer reset, base, theme; |
Define layer priority | Later layers have higher priority |
| Anonymous Layer | @layer { } |
Unnamed layer | Lowest priority |
| Nested Layers | @layer outer.inner { } |
Layer hierarchy | Dot notation for sub-layers |
| Layer Import | @import url() layer(name); |
Import into layer | Organize external CSS |
| Unlayered Styles | Normal CSS rules | Highest priority | Always override layered styles |
Example: Cascade layers basics
/* 1. Define layer order (lowest to highest priority) */
@layer reset, base, components, utilities, overrides;
/* 2. Add styles to layers */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
color: #333;
}
a {
color: #007acc;
text-decoration: none;
}
}
@layer components {
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.card {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
}
@layer utilities {
.text-center {
text-align: center;
}
.mt-1 {
margin-top: 0.5rem;
}
.hidden {
display: none;
}
}
@layer overrides {
.force-visible {
display: block !important;
}
}
/* Unlayered styles have HIGHEST priority */
.emergency-fix {
color: red;
/* This beats ALL layered styles, even with !important in layers */
}
/* Layer priority demonstration */
@layer base {
.text {
color: blue; /* Specificity: (0,1,0) */
}
}
@layer components {
p {
color: green; /* Specificity: (0,0,1) but HIGHER layer */
}
}
/* Result: p.text will be GREEN (layer order wins over specificity) */
Example: Nested and sub-layers
/* Define nested layer structure */
@layer framework {
@layer reset, base, components, utilities;
}
/* Add to nested layers using dot notation */
@layer framework.reset {
*, *::before, *::after {
box-sizing: border-box;
}
}
@layer framework.base {
body {
margin: 0;
font-family: system-ui;
}
}
@layer framework.components {
@layer buttons, cards, modals;
@layer buttons {
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
.btn-primary {
background: #007acc;
color: white;
}
}
@layer cards {
.card {
background: white;
padding: 1rem;
border-radius: 8px;
}
}
}
@layer framework.utilities {
.flex {
display: flex;
}
.gap-1 {
gap: 0.5rem;
}
}
/* Import external CSS into layers */
@import url('normalize.css') layer(framework.reset);
@import url('components.css') layer(framework.components);
@import url('utilities.css') layer(framework.utilities);
/* Layer order with imports */
@layer reset, vendor, components;
@import url('reset.css') layer(reset);
@import url('bootstrap.css') layer(vendor);
@import url('custom.css') layer(components);
/* Anonymous layer (lowest priority) */
@layer {
/* Styles here have lowest priority */
.experimental {
color: purple;
}
}
Example: Practical layer architecture
/* Complete layer architecture for an application */
/* 1. Declare all layers upfront */
@layer reset, tokens, base, layout, components, utilities, themes, overrides;
/* 2. Reset layer */
@layer reset {
@import url('modern-normalize.css');
*, *::before, *::after {
box-sizing: border-box;
}
}
/* 3. Design tokens */
@layer tokens {
:root {
--color-primary: #007acc;
--color-text: #1a1a1a;
--spacing-unit: 0.5rem;
--font-sans: system-ui, sans-serif;
}
}
/* 4. Base styles */
@layer base {
body {
font-family: var(--font-sans);
color: var(--color-text);
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
}
/* 5. Layout primitives */
@layer layout {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
}
/* 6. Components */
@layer components {
@layer buttons, cards, forms, navigation;
@layer buttons {
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: var(--color-primary);
color: white;
}
}
@layer cards {
.card {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
}
}
/* 7. Utilities (highest in normal layers) */
@layer utilities {
.text-center { text-align: center; }
.hidden { display: none; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
}
}
/* 8. Theme layer */
@layer themes {
.theme-dark {
--color-bg: #1a1a1a;
--color-text: #f0f0f0;
background: var(--color-bg);
color: var(--color-text);
}
}
/* 9. Overrides (when absolutely necessary) */
@layer overrides {
.js-visible {
display: block !important;
}
}
/* Unlayered: Emergency fixes only */
.critical-fix {
/* This beats everything */
}
/* Benefits of this architecture:
- Predictable specificity
- Easy to understand priority
- No need for !important (mostly)
- Third-party CSS can be contained
- Components can't accidentally override resets
*/
Warning: Unlayered styles always beat layered styles, regardless of specificity.
!important within layers inverts the layer order. Layer order is defined by first declaration. Plan
layer architecture before implementation.
3. CSS Scope (@scope) Rules
| Feature | Syntax | Browser Support | Purpose |
|---|---|---|---|
| Basic Scope | @scope (.root) { } |
Chrome 118+ | Limit styles to subtree |
| Scope with Limit | @scope (.root) to (.limit) { } |
Chrome 118+ | Exclude nested scopes |
| :scope Pseudo-class | :scope { } |
All modern | Reference scope root |
| Multiple Scopes | @scope (.a, .b) { } |
Chrome 118+ | Apply to multiple roots |
| Nested Scopes | Scopes within scopes | Chrome 118+ | Fine-grained control |
Example: CSS @scope basics
/* Basic scope: styles only apply inside .card */
@scope (.card) {
/* All selectors are scoped to .card descendants */
h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
p {
line-height: 1.6;
color: #666;
}
button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
}
/* :scope references the .card itself */
:scope {
background: white;
padding: 1rem;
border-radius: 8px;
}
/* Equivalent to .card > h2 */
:scope > h2 {
border-bottom: 2px solid #007acc;
}
}
/* Without @scope, you'd need: */
.card { background: white; padding: 1rem; border-radius: 8px; }
.card h2 { font-size: 1.5rem; margin-bottom: 0.5rem; }
.card p { line-height: 1.6; color: #666; }
.card button { padding: 0.5rem 1rem; background: #007acc; color: white; }
.card > h2 { border-bottom: 2px solid #007acc; }
/* Scope with exclusion boundary */
@scope (.card) to (.nested-card) {
/* Styles apply inside .card but NOT inside .nested-card */
p {
color: #333;
}
/* This won't affect paragraphs inside .nested-card */
}
/* HTML structure:
<div class="card">
<p>Affected by scope</p>
<div class="nested-card">
<p>NOT affected (excluded)</p>
</div>
</div>
*/
/* Multiple scope roots */
@scope (.header, .footer, .sidebar) {
nav {
display: flex;
gap: 1rem;
}
a {
color: inherit;
text-decoration: none;
}
}
/* Practical example: Modal component */
@scope (.modal) {
:scope {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.5);
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
width: 90%;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
h2 {
margin: 0;
font-size: 1.5rem;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.close-button {
background: transparent;
font-size: 1.5rem;
}
}
Example: Advanced @scope patterns
/* Scope for theming */
@scope (.theme-dark) {
:scope {
background: #1a1a1a;
color: #f0f0f0;
}
.card {
background: #2d2d2d;
border-color: #404040;
}
.button {
background: #404040;
color: white;
}
a {
color: #6db3f2;
}
}
@scope (.theme-light) {
:scope {
background: #ffffff;
color: #1a1a1a;
}
.card {
background: white;
border-color: #e0e0e0;
}
.button {
background: #007acc;
color: white;
}
}
/* Nested scope exclusion */
@scope (.article) to (.codeBlock, .callout) {
/* Styles apply to .article but exclude .codeBlock and .callout */
p {
font-size: 1rem;
line-height: 1.6;
}
/* Code blocks and callouts maintain their own styling */
}
/* Scope with media queries */
@scope (.responsive-nav) {
:scope {
display: flex;
}
.nav-item {
padding: 0.5rem;
}
@media (max-width: 768px) {
:scope {
flex-direction: column;
}
.nav-item {
width: 100%;
}
}
}
/* Comparing scope to nesting */
/* With @scope (cleaner for components) */
@scope (.card) {
:scope { padding: 1rem; }
h2 { font-size: 1.5rem; }
p { color: #666; }
}
/* With nesting (more verbose) */
.card {
& {
padding: 1rem;
}
& h2 {
font-size: 1.5rem;
}
& p {
color: #666;
}
}
/* Use @scope for:
- Component encapsulation
- Theme boundaries
- Section-specific styles
- Preventing style leakage
Use nesting for:
- Parent-child relationships
- State variations
- BEM patterns
- Pseudo-classes/elements
*/
/* Scope specificity */
@scope (.card) {
p {
/* Specificity: (0,1,1) = .card p */
color: blue;
}
}
.card p {
/* Same specificity: (0,1,1) */
/* Last rule wins (cascade) */
color: red;
}
/* But @scope provides better isolation */
@scope (.card) to (.nested) {
/* This won't affect .nested descendants */
p { color: blue; }
}
Note:
@scope provides true style encapsulation without Shadow DOM. Use
to () to exclude nested components. :scope references the root
element. Browser support limited to Chrome 118+, use feature detection or fallbacks.
4. CSS Container Style Queries (@container style)
| Feature | Syntax | Browser Support | Purpose |
|---|---|---|---|
| Container Size Query | @container (width > 400px) |
Chrome 105+, Safari 16+ | Query container dimensions |
| Container Style Query NEW | @container style(--theme: dark) |
Chrome 111+ (flag) | Query container custom properties |
| container-type | inline-size | size |
Modern browsers | Enable container queries |
| container-name | container-name: sidebar |
Modern browsers | Name container for targeting |
| Container Units | cqw, cqh, cqi, cqb |
Modern browsers | Relative to container size |
Example: Container style queries
/* Container with custom property */
.card-container {
container-name: card;
container-type: inline-size;
--theme: light;
}
/* Query the container's style */
@container card style(--theme: light) {
.card {
background: white;
color: #1a1a1a;
}
}
@container card style(--theme: dark) {
.card {
background: #2d2d2d;
color: #f0f0f0;
}
}
/* Dynamic theme switching */
.card-container[data-theme="dark"] {
--theme: dark;
}
.card-container[data-theme="light"] {
--theme: light;
}
/* Multiple style conditions */
.section {
container-type: inline-size;
--variant: default;
--size: medium;
}
@container style(--variant: featured) {
.card {
border: 2px solid #007acc;
box-shadow: 0 4px 8px rgba(0,122,204,0.2);
}
}
@container style(--size: large) {
.card {
padding: 2rem;
font-size: 1.125rem;
}
}
/* Combined size and style queries */
@container style(--theme: dark) and (min-width: 600px) {
.card {
background: #1a1a1a;
padding: 2rem;
}
}
/* Boolean custom properties */
.container {
container-type: inline-size;
--has-image: 0; /* false */
}
.container.with-image {
--has-image: 1; /* true */
}
@container style(--has-image: 1) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1rem;
}
}
/* Numeric comparisons (experimental) */
@container style(--priority >= 5) {
.alert {
background: #dc3545;
color: white;
font-weight: bold;
}
}
@container style(--priority < 5) {
.alert {
background: #ffc107;
color: #1a1a1a;
}
}
Example: Practical style query patterns
/* Theme-aware component system */
.theme-container {
container-name: theme;
container-type: inline-size;
--bg-color: white;
--text-color: #1a1a1a;
--accent-color: #007acc;
}
/* Light theme */
@container theme style(--bg-color: white) {
.button {
background: var(--accent-color);
color: white;
}
.card {
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
}
/* Dark theme */
@container theme style(--bg-color: #1a1a1a) {
.button {
background: #404040;
color: white;
}
.card {
background: #2d2d2d;
box-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
}
/* Density variants */
.app-container {
container-name: app;
container-type: inline-size;
--density: comfortable;
}
@container app style(--density: compact) {
.list-item {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.button {
padding: 0.25rem 0.75rem;
}
}
@container app style(--density: comfortable) {
.list-item {
padding: 0.5rem 1rem;
font-size: 1rem;
}
.button {
padding: 0.5rem 1rem;
}
}
@container app style(--density: spacious) {
.list-item {
padding: 1rem 1.5rem;
font-size: 1.125rem;
}
.button {
padding: 0.75rem 1.5rem;
}
}
/* Component state management */
.widget {
container-name: widget;
container-type: inline-size;
--state: idle;
}
.widget[data-loading] {
--state: loading;
}
.widget[data-error] {
--state: error;
}
@container widget style(--state: loading) {
.content {
opacity: 0.5;
pointer-events: none;
}
.spinner {
display: block;
}
}
@container widget style(--state: error) {
.content {
border-left: 4px solid #dc3545;
}
.error-message {
display: block;
color: #dc3545;
}
}
/* Responsive + style queries */
.grid-container {
container-name: grid;
container-type: inline-size;
--layout: list;
}
@container grid style(--layout: grid) and (min-width: 600px) {
.items {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
}
@container grid style(--layout: list) {
.items {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
Warning: Container style queries are experimental (Chrome 111+
behind flag). Enable at
chrome://flags/#enable-container-queries. Fallback to class-based styling
for production. Not all property types supported yet.
5. CSS Houdini Worklets and Custom Functions
| API | Purpose | Browser Support | Use Case |
|---|---|---|---|
| Paint API | Custom paint() function | Chrome 65+, Edge | Draw custom backgrounds, borders |
| Layout API EXPERIMENTAL | Custom layout algorithms | Chrome (flag) | Masonry, custom grids |
| Animation Worklet | High-performance animations | Chrome 71+ | Parallax, scroll-driven effects |
| Properties & Values API | Typed custom properties | Chrome 78+, Safari 16.4+ | Animatable custom properties |
| Font Metrics API | Access font metrics | Limited | Precise typography control |
Example: CSS Paint API (Houdini)
/* Register paint worklet (JavaScript) */
/*
// checkerboard-paint.js
class CheckerboardPainter {
static get inputProperties() {
return ['--checkerboard-size', '--checkerboard-color'];
}
paint(ctx, geom, properties) {
const size = parseInt(properties.get('--checkerboard-size').toString()) || 20;
const color = properties.get('--checkerboard-color').toString() || '#000';
for (let y = 0; y < geom.height / size; y++) {
for (let x = 0; x < geom.width / size; x++) {
if ((x + y) % 2 === 0) {
ctx.fillStyle = color;
ctx.fillRect(x * size, y * size, size, size);
}
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
*/
/* Load the worklet */
/*
CSS.paintWorklet.addModule('checkerboard-paint.js');
*/
/* Use in CSS */
.checkerboard {
--checkerboard-size: 20;
--checkerboard-color: #e0e0e0;
background-image: paint(checkerboard);
}
/* Animated paint worklet */
/*
// ripple-paint.js
class RipplePainter {
static get inputProperties() {
return ['--ripple-x', '--ripple-y', '--ripple-radius', '--ripple-color'];
}
paint(ctx, geom, properties) {
const x = parseFloat(properties.get('--ripple-x').toString());
const y = parseFloat(properties.get('--ripple-y').toString());
const radius = parseFloat(properties.get('--ripple-radius').toString());
const color = properties.get('--ripple-color').toString();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
}
}
registerPaint('ripple', RipplePainter);
*/
.ripple-button {
--ripple-x: 50;
--ripple-y: 50;
--ripple-radius: 0;
--ripple-color: rgba(255,255,255,0.3);
background-image: paint(ripple);
transition: --ripple-radius 0.6s;
}
.ripple-button:active {
--ripple-radius: 100;
}
/* Complex paint example: gradient border */
/*
// gradient-border-paint.js
class GradientBorderPainter {
static get inputProperties() {
return ['--border-width', '--gradient-angle'];
}
paint(ctx, geom, properties) {
const borderWidth = parseInt(properties.get('--border-width').toString()) || 2;
const angle = parseInt(properties.get('--gradient-angle').toString()) || 45;
const gradient = ctx.createLinearGradient(0, 0, geom.width, geom.height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.strokeStyle = gradient;
ctx.lineWidth = borderWidth;
ctx.strokeRect(
borderWidth / 2,
borderWidth / 2,
geom.width - borderWidth,
geom.height - borderWidth
);
}
}
registerPaint('gradient-border', GradientBorderPainter);
*/
.gradient-box {
--border-width: 4;
--gradient-angle: 45;
background-image: paint(gradient-border);
padding: 1rem;
}
Example: Animation Worklet
/* Animation Worklet for scroll-linked animations */
/*
// parallax-animation.js
registerAnimator('parallax', class {
constructor(options) {
this.rate = options.rate || 0.5;
}
animate(currentTime, effect) {
const scroll = currentTime * this.rate;
effect.localTime = scroll;
}
});
*/
/* CSS setup */
/*
@keyframes parallax {
from {
transform: translateY(0);
}
to {
transform: translateY(-200px);
}
}
*/
.parallax-element {
animation: parallax linear;
animation-timeline: scroll();
}
/* JavaScript registration */
/*
await CSS.animationWorklet.addModule('parallax-animation.js');
const element = document.querySelector('.parallax-element');
new WorkletAnimation(
'parallax',
new KeyframeEffect(element, [
{ transform: 'translateY(0)' },
{ transform: 'translateY(-200px)' }
], {
duration: 1000,
iterations: 1
}),
document.documentElement.scrollTimeline,
{ rate: 0.5 }
).play();
*/
/* Smooth spring animation worklet */
/*
// spring-animation.js
registerAnimator('spring', class {
animate(currentTime, effect) {
const spring = (t, damping = 0.7, stiffness = 100) => {
const w = Math.sqrt(stiffness);
const e = Math.exp(-damping * w * t);
return 1 - e * Math.cos(w * t);
};
effect.localTime = spring(currentTime / 1000) * effect.getTiming().duration;
}
});
*/
Note: CSS Houdini enables low-level access to CSS engine. Paint
API is best supported. Layout and Animation Worklets are experimental. Use feature detection:
if ('paintWorklet' in CSS). Fallback to regular CSS for unsupported browsers.
6. CSS @property Declarations
| Feature | Syntax | Browser Support | Purpose |
|---|---|---|---|
| @property Rule | @property --name { } |
Chrome 85+, Safari 16.4+ | Register typed custom property |
| syntax | syntax: "<color>" |
Modern browsers | Define property type |
| inherits | inherits: true | false |
Modern browsers | Control inheritance |
| initial-value | initial-value: #000 |
Modern browsers | Default fallback value |
| Animatable Properties | Typed custom properties | Modern browsers | Enable smooth transitions |
Example: @property declarations
/* Register a color property */
@property --theme-color {
syntax: '<color>';
inherits: true;
initial-value: #007acc;
}
/* Now it's animatable! */
.button {
background: var(--theme-color);
transition: --theme-color 0.3s;
}
.button:hover {
--theme-color: #005a9e;
}
/* Number property with units */
@property --spacing {
syntax: '<length>';
inherits: false;
initial-value: 1rem;
}
.card {
padding: var(--spacing);
transition: --spacing 0.3s;
}
.card:hover {
--spacing: 2rem;
}
/* Percentage property */
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.progress-bar {
width: var(--progress);
transition: --progress 1s ease-out;
}
.progress-bar.complete {
--progress: 100%;
}
/* Angle property for gradients */
@property --gradient-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.gradient-box {
background: linear-gradient(var(--gradient-angle), #667eea, #764ba2);
transition: --gradient-angle 0.5s;
}
.gradient-box:hover {
--gradient-angle: 90deg;
}
/* Number property (no units) */
@property --scale {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.zoomable {
transform: scale(var(--scale));
transition: --scale 0.3s;
}
.zoomable:hover {
--scale: 1.1;
}
/* Multiple values */
@property --shadow-offset {
syntax: '<length>+'; /* One or more lengths */
inherits: false;
initial-value: 0px 2px;
}
.elevated {
box-shadow: var(--shadow-offset) 4px rgba(0,0,0,0.1);
transition: --shadow-offset 0.3s;
}
.elevated:hover {
--shadow-offset: 0px 8px;
}
/* Integer property */
@property --columns {
syntax: '<integer>';
inherits: false;
initial-value: 3;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
}
/* Transform list */
@property --transform-x {
syntax: '<length-percentage>';
inherits: false;
initial-value: 0%;
}
.slider {
transform: translateX(var(--transform-x));
transition: --transform-x 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.slider.active {
--transform-x: 100%;
}
Example: Advanced @property patterns
/* Animatable gradient positions */
@property --gradient-start {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
@property --gradient-end {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
.animated-gradient {
background: linear-gradient(
90deg,
transparent var(--gradient-start),
#007acc var(--gradient-start),
#007acc var(--gradient-end),
transparent var(--gradient-end)
);
transition: --gradient-start 0.3s, --gradient-end 0.3s;
}
.animated-gradient:hover {
--gradient-start: 20%;
--gradient-end: 80%;
}
/* Color interpolation */
@property --bg-color {
syntax: '<color>';
inherits: false;
initial-value: white;
}
@property --text-color {
syntax: '<color>';
inherits: false;
initial-value: black;
}
.theme-transition {
background: var(--bg-color);
color: var(--text-color);
transition: --bg-color 0.3s, --text-color 0.3s;
}
.theme-transition[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
}
/* Numeric calculations */
@property --multiplier {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.dynamic-sizing {
font-size: calc(1rem * var(--multiplier));
padding: calc(0.5rem * var(--multiplier));
transition: --multiplier 0.3s;
}
.dynamic-sizing.large {
--multiplier: 1.5;
}
/* Complex animation with multiple properties */
@property --rotate {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --scale-x {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
@property --scale-y {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.complex-transform {
transform:
rotate(var(--rotate))
scaleX(var(--scale-x))
scaleY(var(--scale-y));
transition: --rotate 0.5s, --scale-x 0.3s, --scale-y 0.3s;
}
.complex-transform:hover {
--rotate: 15deg;
--scale-x: 1.1;
--scale-y: 0.9;
}
/* JavaScript API for registration */
/*
CSS.registerProperty({
name: '--my-color',
syntax: '<color>',
inherits: false,
initialValue: '#c0ffee'
});
// Now usable in CSS
element.style.setProperty('--my-color', 'rebeccapurple');
*/
/* Syntax types:
<length> - 10px, 2em, 50%
<number> - 1, 0.5, 100
<percentage> - 50%, 100%
<length-percentage> - length or percentage
<color> - #fff, rgb(), hsl()
<image> - url(), gradient()
<url> - url()
<integer> - 1, 2, 3
<angle> - 45deg, 1rad
<time> - 1s, 200ms
<resolution> - 300dpi, 2dppx
<transform-function> - rotate(), scale()
<transform-list> - Multiple transforms
* - Any value (not animatable)
Combinators:
<length>+ - One or more
<length># - Comma-separated list
<length> | <percentage> - Either/or
*/
Example: Practical @property use cases
/* Smooth progress bar animation */
@property --progress-value {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
.progress {
--progress-value: 0;
transition: --progress-value 1s ease-out;
}
.progress-bar {
width: calc(var(--progress-value) * 1%);
background: linear-gradient(
90deg,
#667eea calc(var(--progress-value) * 1%),
#e0e0e0 calc(var(--progress-value) * 1%)
);
}
.progress[data-value="75"] {
--progress-value: 75;
}
/* Animated counter */
@property --counter {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.counter {
--counter: 0;
counter-reset: num var(--counter);
transition: --counter 2s;
}
.counter::after {
content: counter(num);
}
.counter.animate {
--counter: 100;
}
/* Smooth color transitions in gradients */
@property --color-stop-1 {
syntax: '<color>';
inherits: false;
initial-value: #667eea;
}
@property --color-stop-2 {
syntax: '<color>';
inherits: false;
initial-value: #764ba2;
}
.gradient-bg {
background: linear-gradient(
135deg,
var(--color-stop-1),
var(--color-stop-2)
);
transition: --color-stop-1 0.5s, --color-stop-2 0.5s;
}
.gradient-bg:hover {
--color-stop-1: #f093fb;
--color-stop-2: #f5576c;
}
/* Responsive scaling with animation */
@property --responsive-scale {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.responsive-element {
font-size: calc(1rem * var(--responsive-scale));
padding: calc(1rem * var(--responsive-scale));
transition: --responsive-scale 0.3s;
}
@media (min-width: 768px) {
.responsive-element {
--responsive-scale: 1.2;
}
}
@media (min-width: 1024px) {
.responsive-element {
--responsive-scale: 1.5;
}
}
Modern CSS Features Best Practices
- Use native CSS nesting for cleaner code (Chrome 120+, Safari 17.2+)
- Organize with
@layerfor predictable cascade, define order upfront - Apply
@scopefor component encapsulation without Shadow DOM (Chrome 118+) - Container style queries enable theme/state management (experimental, use with caution)
- CSS Houdini Paint API for custom backgrounds, check support with
'paintWorklet' in CSS - Register
@propertyfor animatable custom properties with type safety - Always provide fallbacks for experimental features, use feature detection
- Test across browsers, progressive enhancement is key
- Combine modern features: nesting + @layer + @scope for powerful architecture
- Monitor browser support at caniuse.com, these features are rapidly evolving