Takes about 9 minutes to read
The @property
at-rule recently gained support across all modern browsers, unlocking the ability to explicitly define a syntax, initial value, and inheritance for CSS custom properties. It seems like forever ago that CSS Houdini and its CSS Properties and Values API were initially introduced. I experimented sparingly over time, reading articles that danced around the concepts, but I had barely scratched the surface of what @property
could offer. The ensuing demo explores what's possible in the next generation of CSS.
Calls to action
Ever seen those sleek, attention-seeking, shiny call-to-action webpage elements? Waves of sites across the web, especially the ones marketing services and software urging for you to "Upgrade your account" or "Sign up today," have discovered the look and latched on. I'm not here to knock it and admittedly think it's kind of fresh. I thought I'd give that style a try myself. Check out the result in the CodePen below.
There's a ton to unpack in this demo. Let's start with that shine looping around the button. Toggle open the demo's CSS panel to find a collection of @property
rules related to those custom properties that need to animate. Here's the one defined for the --gradient-angle
:
@property --gradient-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
The @property
rule communicates to the browser that <angle>
is the allowed syntax for this custom property and its initial value is 0deg
. This enables the browser to smoothly transition from 0deg
to 360deg
and output a rotating gradient.
@keyframes rotate-gradient {
to { --gradient-angle: 360deg; }
}
.rotate-gradient {
background: conic-gradient(from var(--gradient-angle), transparent, black);
animation: rotate-gradient 10s linear infinite;
}
I put together a simple gradient spin demo to focus on the handful of lines necessary to render this concept.
We can achieve the shiny animated border effect by evolving this code a bit. We'll introduce a linear-gradient
as the first value of the element's background
property and set a background-origin
to each value.
- The origin of the
linear-gradient
is set topadding-box
. This prevents the gradient from spilling into the border area. - The
conic-gradient
origin is set toborder-box
. This gradient overflows into the space created by the border width. - To reveal the rotating
conic-gradient
, a single-pixel transparent border is added.
.border-gradient {
background:
linear-gradient(black, black) padding-box,
conic-gradient(from var(--gradient-angle), transparent 25%, white, transparent 50%) border-box;
border: 1px solid transparent;
}
In the CSS panel of the simple gradient spin demo, uncomment the .border-gradient
ruleset to reveal the shiny animated border. Looking pretty slick! For more examples, I've included a bunch of animated gradient border articles in the resources section at the end of the post.
Silky smooth hover transitions
A few special ingredients help facilitate a buttery smooth gradient transition when the element is hovered. Let's dig into its background
values:
.shiny-cta {
background:
linear-gradient(var(--shiny-cta-bg), var(--shiny-cta-bg)) padding-box,
conic-gradient(
from calc(var(--gradient-angle) - var(--gradient-angle-offset)),
transparent,
var(--shiny-cta-highlight) var(--gradient-percent),
var(--gradient-shine) calc(var(--gradient-percent) * 2),
var(--shiny-cta-highlight) calc(var(--gradient-percent) * 3),
transparent calc(var(--gradient-percent) * 4)
)
border-box;
}
Each custom property that needs to animate has a syntax
declared in its @property
definition so that the browser can interpolate between corresponding value changes and transition them seamlessly. The size of the shiny area is determined by the --gradient-percent
value. On hover, a higher percentage lengthens the shine. The --gradient-angle-offset
value is used to readjust the gradient angle so that the shine doesn't rubber band back and forth on hover.
I had to fine-tune the percent and offset values until the shine length and transition felt optically aligned. Finally, the --gradient-shine
brightness gets toned down to blend more seamlessly with the adjacent highlight colors.
Slow it on down
This CSS tip to slow down a rotation on hover truly blew my mind. In the tip's example code, the same rotate animation is declared twice. The second one is reversed and paused, its duration divided in half. When the element is hovered, animation-play-state: running
overrides the paused
value and slows the rotation to half speed. The mind-blowing part, at least to me, is that the animation speeds back up at the current position when the element is no longer hovered. No snapping back to a start position, no extra wrapper elements necessary. That is one heck of a tip.
The call-to-action animations rely on this method to slow them down when the button is hovered. This technique keeps all the rotations and movements in sync as they change speed.
Tiny shiny dots
Looking even closer, we'll discover pinhole-sized dots shimmering inside the button as the shiny border passes near them. To render this dot pattern, a radial-gradient
background is created.
.shiny-cta::before {
--position: 2px;
--space: calc(var(--position) * 2);
background: radial-gradient(
circle at var(--position) var(--position),
white calc(var(--position) / 4),
transparent 0
)
padding-box;
background-size: var(--space) var(--space);
background-repeat: space;
}
Remember that --gradient-angle
custom property? It has returned! But this time, it's being used in a conic-gradient
mask that reveals parts of the dot pattern as it rotates. The gradient angle is offset by 45 degrees to align it perfectly with the shiny border rotation.
.shiny-cta::before {
mask-image: conic-gradient(
from calc(var(--gradient-angle) + 45deg),
black,
transparent 10% 90%,
black
);
}
For one last touch of magic, a gradient containing the highlight color is added to the ::after
pseudo element, spinning in unison with the shine area. These highlights flowing through the button add a pleasant, welcoming ambience that was previously missing.
Enhancing the hover colors
The hover styles looked decent. But they didn't seem totally finished. I felt the desire to enhance. Create more depth. Make it pop, as they say.
The button's ::before
and ::after
pseudo elements were already in use so I wrapped the button text in a span
element. A blurred box-shadow
containing the highlight color is applied to one of its pseudo elements which is then expanded to fill the button dimensions. On hover, the pseudo element slowly scales up and down, evoking a vibe similar to relaxed breathing. Paired with the spinning highlight color inside the button, the effect finally resonated with me. This intricately designed call-to-action button felt complete.
In with the new style
Many of the above techniques would have been nearly impossible only a short time ago. Explicitly defining custom properties unlocks a great big world of opportunity. I'm especially eager to see how @property
will be utilized in large-scale applications and design systems. Providing Type Definitions for CSS with @property by Stephanie Eckles as well as Adam Argyle's Type safe CSS design systems with @property are just a couple glimpses into a really promising future for publishing our CSS.