Working example
HTML
<main>
<button class="btn">Button</button>
<button class="btn">Button</button>
<button class="btn">Button</button>
</main>
CSS breakdown
The @property
rule
The @property
rule is part of a set of low-level APIs labeled Houdini ☍.
Houdini exposes parts of the CSS engine, allowing us to develop new CSS features.
The @property
itself lets us explicitly define our custom properties ☍ like --text-color
that can be used in CSS.
With @property
, we can define the syntax (type checking and constraining), set initial values, and define whether a custom property can inherit values or not.
@property
definition:
@property --property-name {
syntax: "<type>";
inherits: true | false;
initial-value: <value>;
}
Syntax type can be any of the following:
<color>
<image>
<number>
<percentage>
<length>
<number>
<angle>
- and more… ☍
@property
usage example:
@property --radial-bg-color {
syntax: "<>";>";
inherits: true;
initial-value: black;
}
So for an example in the CSS below, we have defined a set of custom properties using the @property
rule.
One of those is the --radial-bg-color
custom property, which updates our radial gradient color.
Here --radial-bg-color
is initially set to black
but more crucially it can inherit values further down the chain.
In our animation the --radial-bg-color
property is animated from hsl(290deg 100% 100%)
to var(--accent-color)
to var(--dark-color)
by assigning new values to it.
If --radial-bg-color
hadn’t been defined in the first place as a custom property, the animation wouldn’t work as expected.
The whole CSS
@layer properties {
@property --bg-x {
syntax: "<number>";
inherits: true;
initial-value: 50;
}
@property --bg-y {
syntax: "<number>";
inherits: true;
initial-value: -200;
}
@property --glow-size {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --flash-color {
syntax: "<color>";
inherits: true;
initial-value: hsl(calc(var(--accent-color-hue) * 1deg) 100% 58%);
}
@property --flash-size {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --text-flash-size {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --accent-color-hue {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --accent-color-hue {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --accent-color {
syntax: "<color>";
inherits: true;
initial-value: hsl(calc(var(--accent-color-hue) * 1deg) 100% 58%);
}
@property --radial-bg-color {
syntax: "<color>";
inherits: true;
initial-value: black;
}
}
.btn {
--background-color: hsl(270deg 92% 5%);
--dark-color: hsl(270deg 92% 2%);
--dark-color-hover: hsl(270deg 92% 12%);
--light-color: hsl(211deg 23% 51%);
--accent-color-hue: 260;
--accent-color: hsl(calc(var(--accent-color-hue) * 1deg) 92% 20%);
--radial-bg-color: var(--dark-color);
--bg-y: -50;
--bg-x: 200;
--glow-size: 2;
--flash-color: var(--accent-color);
--flash-size: 0;
--text-flash-size: 0;
--text-flash-color: hsl(calc(var(--accent-color-hue) * 1deg) 92% 80%);
/* Misc */
--outer-radius: 5px;
--inner-padding: 16px;
--outline-color: hsla(
calc(var(--accent-color-hue) * 1deg) 100% 58% / calc(var(--a11y) * 100%)
);
}
.btn {
font-family: "Mona sans", sans-serif;
font-size: 1.1rem;
font-weight: 500;
letter-spacing: 1px;
color: white;
border: none;
background: var(--dark-color) radial-gradient(
ellipse 70% 70% at calc(var(--bg-x) * 1%) calc(var(--bg-y) * 1%),
var(--radial-bg-color) 0%,
var(--dark-color) 100%
);
padding: var(--inner-padding);
border-radius: var(--outer-radius);
position: relative;
width: 200px;
z-index: 1;
box-shadow: 0 0 calc(var(--flash-size) * 1px) var(--flash-color);
text-shadow: 0 0 calc(var(--text-flash-size) * 1px) var(--text-flash-color);
}
.btn:before {
--padding: calc(var(--glow-size) * 1px);
content: "";
display: block;
position: absolute;
width: calc(100% - var(--padding));
height: calc(100% - var(--padding));
top: calc(var(--padding) / 2);
left: calc(var(--padding) / 2);
background: var(--dark-color);
border-radius: var(--outer-radius);
transition: background-color 0.8s ease;
z-index: -1;
}
.btn:hover {
animation: glow 0.8s ease-in-out, flash 0.3s ease-out, text-flash 0.2 ease-out;
cursor: pointer;
}
.btn:hover:before {
background: var(--dark-color-hover);
}
@keyframes glow {
from {
--radial-bg-color: hsl(290deg 100% 100%);
--bg-x: 100;
--bg-y: 0;
}
50% {
--radial-bg-color: var(--accent-color);
--bg-x: 60;
--bg-y: 120;
}
to {
--radial-bg-color: var(--dark-color);
--bg-x: 60;
--bg-y: 120;
}
}
@keyframes flash {
from {
--flash-color: var(--accent-color);
--flash-size: 0;
--text-flash-size: 0;
}
10% {
--flash-color: var(--accent-color);
--flash-size: 5;
--text-flash-size: 2;
}
90% {
--flash-color: var(--accent-color);
--flash-size: 150;
--text-flash-size: 150;
}
to {
--flash-color: var(--accent-color);
--flash-size: 0;
--text-flash-size: 0;
}
}