Skip to main content

@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

BrowserSupportNotes
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]