Modern CSS Features 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 : 1 rem ;
border-radius : 8 px ;
/* Nested element */
& . title {
font-size : 1.5 rem ;
margin-bottom : 0.5 rem ;
}
/* Pseudo-class */
& :hover {
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
}
/* Pseudo-element */
& ::before {
content : '' ;
display : block ;
}
/* Compound selector (BEM-style) */
&--featured {
border : 2 px 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.5 rem 1 rem ;
& . nav-link {
color : #333 ;
text-decoration : none ;
&:hover {
color : #007acc ;
}
& :focus {
outline : 2 px solid currentColor ;
}
}
& .active .nav-link {
font-weight : bold ;
}
}
}
Example: Advanced nesting patterns
/* Media query nesting */
.container {
width : 100 % ;
padding : 1 rem ;
@ media ( min-width : 768 px ) {
& {
width : 750 px ;
margin : 0 auto ;
}
}
@media ( min-width : 1024 px ) {
& {
width : 960 px ;
padding : 2 rem ;
}
}
}
/* Nested at-rules */
.button {
padding : 0.5 rem 1 rem ;
background : #007acc ;
color : white ;
@ supports ( backdrop-filter : blur ( 10 px )) {
& {
background : rgba ( 0 , 122 , 204 , 0.8 );
backdrop-filter : blur ( 10 px );
}
}
@media (prefers-reduced-motion: no-preference) {
& {
transition : transform 0.2 s ;
}
& :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 : 2 rem ;
}
& :has ( img ) {
padding-top : 0 ;
& img {
width : 100 % ;
height : auto ;
}
}
& :has ( > .featured ) {
border-left : 4 px 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.5 rem 1 rem ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
}
.card {
background : white ;
padding : 1 rem ;
border-radius : 8 px ;
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
}
}
@layer utilities {
.text-center {
text-align : center ;
}
.mt-1 {
margin-top : 0.5 rem ;
}
.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.5 rem 1 rem ;
border : none ;
border-radius : 4 px ;
}
.btn-primary {
background : #007acc ;
color : white ;
}
}
@layer cards {
.card {
background : white ;
padding : 1 rem ;
border-radius : 8 px ;
}
}
}
@layer framework.utilities {
.flex {
display : flex ;
}
.gap-1 {
gap : 0.5 rem ;
}
}
/* 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.5 rem ;
--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 : 1200 px ;
margin : 0 auto ;
padding : 0 1 rem ;
}
.flex {
display : flex ;
}
.grid {
display : grid ;
}
}
/* 6. Components */
@layer components {
@layer buttons, cards, forms, navigation;
@layer buttons {
.button {
padding : 0.5 rem 1 rem ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
background : var ( --color-primary );
color : white ;
}
}
@layer cards {
.card {
background : white ;
padding : 1 rem ;
border-radius : 8 px ;
box-shadow : 0 2 px 4 px 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 : 1 px ;
height : 1 px ;
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)
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.5 rem ;
margin-bottom : 0.5 rem ;
}
p {
line-height : 1.6 ;
color : #666 ;
}
button {
padding : 0.5 rem 1 rem ;
background : #007acc ;
color : white ;
}
/* :scope references the .card itself */
:scope {
background : white ;
padding : 1 rem ;
border-radius : 8 px ;
}
/* Equivalent to .card > h2 */
:scope > h2 {
border-bottom : 2 px solid #007acc ;
}
}
/* Without @scope, you'd need: */
.card { background : white ; padding : 1 rem ; border-radius : 8 px ; }
.card h2 { font-size : 1.5 rem ; margin-bottom : 0.5 rem ; }
.card p { line-height : 1.6 ; color : #666 ; }
.card button { padding : 0.5 rem 1 rem ; background : #007acc ; color : white ; }
.card > h2 { border-bottom : 2 px 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 : 1 rem ;
}
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 : 2 rem ;
border-radius : 8 px ;
max-width : 500 px ;
width : 90 % ;
}
.modal-header {
display : flex ;
justify-content : space-between ;
align-items : center ;
margin-bottom : 1 rem ;
}
h2 {
margin : 0 ;
font-size : 1.5 rem ;
}
button {
padding : 0.5 rem 1 rem ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
}
.close-button {
background : transparent ;
font-size : 1.5 rem ;
}
}
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 : 1 rem ;
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.5 rem ;
}
@media ( max-width : 768 px ) {
:scope {
flex-direction : column ;
}
.nav-item {
width : 100 % ;
}
}
}
/* Comparing scope to nesting */
/* With @scope (cleaner for components) */
@scope (.card) {
:scope { padding : 1 rem ; }
h2 { font-size : 1.5 rem ; }
p { color : #666 ; }
}
/* With nesting (more verbose) */
.card {
& {
padding : 1 rem ;
}
& h2 {
font-size : 1.5 rem ;
}
& 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
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 : 2 px solid #007acc ;
box-shadow : 0 4 px 8 px rgba ( 0 , 122 , 204 , 0.2 );
}
}
@container style(--size: large) {
.card {
padding : 2 rem ;
font-size : 1.125 rem ;
}
}
/* Combined size and style queries */
@container style(--theme: dark) and (min-width: 600px) {
.card {
background : #1a1a1a ;
padding : 2 rem ;
}
}
/* 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 : 200 px 1 fr ;
gap : 1 rem ;
}
}
/* 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 2 px 4 px 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 2 px 4 px 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.25 rem 0.5 rem ;
font-size : 0.875 rem ;
}
.button {
padding : 0.25 rem 0.75 rem ;
}
}
@container app style(--density: comfortable) {
.list-item {
padding : 0.5 rem 1 rem ;
font-size : 1 rem ;
}
.button {
padding : 0.5 rem 1 rem ;
}
}
@container app style(--density: spacious) {
.list-item {
padding : 1 rem 1.5 rem ;
font-size : 1.125 rem ;
}
.button {
padding : 0.75 rem 1.5 rem ;
}
}
/* 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 : 4 px 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 ( 200 px , 1 fr ));
gap : 1 rem ;
}
}
@container grid style(--layout: list) {
.items {
display : flex ;
flex-direction : column ;
gap : 0.5 rem ;
}
}
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.6 s ;
}
.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 : 1 rem ;
}
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.3 s ;
}
.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.3 s ;
}
.card:hover {
--spacing : 2 rem ;
}
/* Percentage property */
@property --progress {
syntax: '<percentage > ';
inherits: false;
initial-value : 0%;
}
.progress-bar {
width : var ( --progress );
transition : --progress 1 s 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.5 s ;
}
.gradient-box:hover {
--gradient-angle : 90 deg ;
}
/* Number property (no units) */
@property --scale {
syntax: '<number > ';
inherits: false;
initial-value : 1;
}
.zoomable {
transform : scale ( var ( --scale ));
transition : --scale 0.3 s ;
}
.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 ) 4 px rgba ( 0 , 0 , 0 , 0.1 );
transition : --shadow-offset 0.3 s ;
}
.elevated:hover {
--shadow-offset : 0 px 8 px ;
}
/* Integer property */
@property --columns {
syntax: '<integer > ';
inherits: false;
initial-value : 3;
}
.grid {
display : grid ;
grid-template-columns : repeat ( var ( --columns ), 1 fr );
}
/* Transform list */
@property --transform-x {
syntax: '< length-percentage > ';
inherits: false;
initial-value : 0%;
}
.slider {
transform : translateX ( var ( --transform-x ));
transition : --transform-x 0.5 s 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 (
90 deg ,
transparent var ( --gradient-start ),
#007acc var ( --gradient-start ),
#007acc var ( --gradient-end ),
transparent var ( --gradient-end )
);
transition : --gradient-start 0.3 s , --gradient-end 0.3 s ;
}
.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.3 s , --text-color 0.3 s ;
}
.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 ( 1 rem * var ( --multiplier ));
padding : calc ( 0.5 rem * var ( --multiplier ));
transition : --multiplier 0.3 s ;
}
.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.5 s , --scale-x 0.3 s , --scale-y 0.3 s ;
}
.complex-transform:hover {
--rotate : 15 deg ;
--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 1 s ease-out ;
}
.progress-bar {
width : calc ( var ( --progress-value ) * 1 % );
background : linear-gradient (
90 deg ,
#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 2 s ;
}
.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 (
135 deg ,
var ( --color-stop-1 ),
var ( --color-stop-2 )
);
transition : --color-stop-1 0.5 s , --color-stop-2 0.5 s ;
}
.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 ( 1 rem * var ( --responsive-scale ));
padding : calc ( 1 rem * var ( --responsive-scale ));
transition : --responsive-scale 0.3 s ;
}
@media ( min-width : 768 px ) {
.responsive-element {
--responsive-scale : 1.2 ;
}
}
@media ( min-width : 1024 px ) {
.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 @layer for predictable cascade, define order upfront
Apply @scope for 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 @property for 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