@property: Typed, Animatable CSS Custom Properties
@property (CSS Properties and Values API / CSS Houdini) lets you register CSS custom properties with a defined type, initial value, and inheritance behavior. The most powerful result: you can animate CSS variables — something normally impossible with standard var().
1. The Problem with Standard Custom Properties
/* Standard custom property — cannot be animated */
:root { --color-primary: #6c63ff; }
.c-btn {
background: var(--color-primary);
transition: --color-primary 0.3s ease; /* ❌ Does nothing */
}
.c-btn:hover {
--color-primary: #a855f7; /* Changes instantly, no transition */
}
/* Why: browser treats custom property values as strings — no interpolation */
2. @property Syntax
@property --color-primary {
syntax: '<color>'; /* Type: this is a color value */
inherits: false; /* Don't inherit from parent */
initial-value: #6c63ff; /* Default value (required when inherits:false) */
}
/* Now --color-primary is a TYPED property — browser knows it's a color */
/* This means: it CAN be interpolated (animated) */
.c-btn {
--color-primary: #6c63ff;
background: var(--color-primary);
transition: --color-primary 0.3s ease; /* ✅ Now works! */
}
.c-btn:hover {
--color-primary: #a855f7; /* Smoothly animates to purple */
}
3. The syntax Types
/* Color */
@property --my-color {
syntax: '<color>';
inherits: false;
initial-value: transparent;
}
/* Number */
@property --my-number {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
/* Integer */
@property --my-int {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
/* Length (px, rem, em, etc.) */
@property --my-size {
syntax: '<length>';
inherits: false;
initial-value: 0px; /* Must include unit for length/percentage */
}
/* Percentage */
@property --my-pct {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
/* Length OR percentage */
@property --my-offset {
syntax: '<length-percentage>';
inherits: false;
initial-value: 0%;
}
/* Angle (for gradients, rotations) */
@property --my-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* Any value (string — not animatable, but gives default) */
@property --my-anything {
syntax: '*';
inherits: true;
initial-value: initial;
}
4. The Key Use Case: Animating Gradients
This is improperly supported with standard custom props:
/* ❌ Cannot animate gradients with standard custom properties */
.c-card {
--start-color: #6c63ff;
--end-color: #a855f7;
background: linear-gradient(135deg, var(--start-color), var(--end-color));
transition: --start-color 0.4s; /* ❌ No effect */
}
/* ✅ Register colors with @property → animatable */
@property --grad-start {
syntax: '<color>';
inherits: false;
initial-value: #6c63ff;
}
@property --grad-end {
syntax: '<color>';
inherits: false;
initial-value: #a855f7;
}
.c-card {
background: linear-gradient(135deg, var(--grad-start), var(--grad-end));
transition: --grad-start 0.4s ease, --grad-end 0.4s ease;
}
.c-card:hover {
--grad-start: #f59e0b; /* Orange — smoothly transitions */
--grad-end: #ef4444; /* Red — smoothly transitions */
}
5. Animating Conic Gradients (Progress Rings)
/* @property for animating a progress ring */
@property --progress {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
.c-progress-ring {
--progress: 0;
background: conic-gradient(
var(--color-primary) calc(var(--progress) * 1%),
var(--color-border) 0%
);
width: 120px;
height: 120px;
border-radius: 50%;
/* Animate the progress entering */
animation: fill-progress 1s ease both;
}
@keyframes fill-progress {
to { --progress: 75; } /* Fill to 75% */
}
/* Or animate on state change */
.c-progress-ring { transition: --progress 0.5s ease; }
.c-progress-ring.is-complete { --progress: 100; }
6. Animating Custom Angles
/* Rotating gradient that animates smoothly */
@property --gradient-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.c-animated-border {
--gradient-angle: 0deg;
background: linear-gradient(var(--gradient-angle), #6c63ff, #a855f7, #06b6d4);
animation: rotate-gradient 3s linear infinite;
}
@keyframes rotate-gradient {
to { --gradient-angle: 360deg; }
}
/* Animated rainbow border effect */
.c-rainbow-card {
--gradient-angle: 0deg;
background: linear-gradient(var(--gradient-angle), #ff6b6b, #ffd93d, #6bcb77, #4d96ff);
background-clip: border-box;
border: 3px solid transparent;
border-radius: 1rem;
animation: rainbow-rotate 4s linear infinite;
}
@keyframes rainbow-rotate {
to { --gradient-angle: 360deg; }
}
7. Number Animations — Counters
/* Animate a number/percentage displayed via CSS */
@property --count {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
/* ⚠ Note: CSS can't display the raw custom property value as text naturally */
/* You need CSS counter workarounds or JavaScript for number display */
/* But you CAN use the number in calculations */
/* Example: animate height based on percentage */
@property --fill-percent {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
.c-bar {
--fill-percent: 0;
height: calc(var(--fill-percent) * 1%);
max-height: 100%;
transition: --fill-percent 1s ease;
background: var(--color-primary);
}
.c-bar.is-loaded { --fill-percent: 75; }
8. inherits — When to Use True vs False
/* inherits: false → each element manages its own value (most common use) */
@property --card-shine {
syntax: '<percentage>';
inherits: false; /* Each card manages its own shine */
initial-value: 0%;
}
/* inherits: true → value passes down the DOM tree like color/font-size */
@property --brand-accent {
syntax: '<color>';
inherits: true; /* Set on parent → all children can use it */
initial-value: #6c63ff;
}
/* Set on parent: */
.c-section { --brand-accent: #a855f7; }
/* Children all inherit the purple accent without setting it themselves */
9. Browser Support
| Browser | Support | Notes |
|---|---|---|
| Chrome | ✅ 85+ | Full |
| Firefox | ✅ 128+ | Full (2024) |
| Safari | ✅ 16.4+ | Full |
| Edge | ✅ 85+ | Full |
| Global | ~92% |
/* @supports check for @property */
@supports (background: paint(something)) {
/* CSS Paint API (advanced Houdini) supported */
}
/* For @property itself — no @supports equivalent
The property just isn't registered if browser doesn't support it
→ falls back to untyped custom property behavior */
/* Progressive enhancement pattern:
Write animation with @property
If not supported: instant change (no tween) — acceptable fallback */
10. AI Prompt for @property
Use @property to create animatable CSS custom properties for:
1. A gradient button that smoothly shifts colors on hover
Register: --grad-start and --grad-end as <color> types
2. A conic-gradient progress ring that animates from 0% to [VALUE]%
Register: --progress as <number>
3. A rotating gradient border animation
Register: --gradient-angle as <angle>
Requirements:
- Include @property declarations before usage
- Add transition on --property for hover-based changes
- Add @keyframes for entrance animations
- Include @supports fallback with instant state change as fallback
- BEM naming: [CLASS NAMES]