Rime frost creeps across cold blue letters from left to right, a star glint catches the last letter, then it all thaws back. The frost is a noise texture clipped to the text and swept in by a mask.
Also known as: frost text effect, frozen text css, ice crystal text animation, frosty text.
<div class="txt-frost-glint-text"> <span class="txt-frost-glint-text-word">Glacier</span> <span class="txt-frost-glint-text-frost" aria-hidden="true">Glacier</span> <span class="txt-frost-glint-text-glint" aria-hidden="true"></span> </div>
.txt-frost-glint-text {
--c: var(--txt-color, #3b82f6);
font-size: calc(var(--txt-size, 32px) * 1.7);
font-family: system-ui, sans-serif;
font-weight: 900;
line-height: 1.2;
position: relative;
display: inline-grid;
letter-spacing: 0.02em;
filter: drop-shadow(0 0 0.012em color-mix(in srgb, var(--c) 60%, #082f49)) drop-shadow(0 0 0.006em color-mix(in srgb, var(--c) 60%, #082f49)) drop-shadow(0 0.03em 0.04em color-mix(in srgb, var(--c) 30%, rgb(12 74 110 / 0.35)));}
.txt-frost-glint-text > span {
grid-area: 1 / 1;
}
.txt-frost-glint-text-word {
background: linear-gradient(180deg, color-mix(in srgb, var(--c) 35%, #e0f2fe) 0%, color-mix(in srgb, var(--c) 70%, #38bdf8) 55%, color-mix(in srgb, var(--c) 60%, #0c4a6e) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
.txt-frost-glint-text-frost {
background:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 3.2 -1.3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E") 0 0 / 2.4em 2.4em,
linear-gradient(180deg, #f5faff, color-mix(in srgb, var(--c) 30%, #cfe6fb));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
-webkit-mask: linear-gradient(100deg, #000 42%, transparent 58%) 100% 0 / 260% 100% no-repeat;
mask: linear-gradient(100deg, #000 42%, transparent 58%) 100% 0 / 260% 100% no-repeat;
animation: txt-frost-glint-text-creep 6s cubic-bezier(0.5, 0, 0.3, 1) infinite;
}
.txt-frost-glint-text-glint {
justify-self: end;
align-self: start;
width: 0.55em;
height: 0.55em;
margin: -0.12em -0.18em 0 0;
background: radial-gradient(circle, #fff 16%, color-mix(in srgb, var(--c) 75%, #fff) 55%);
clip-path: polygon(50% 0, 57% 43%, 100% 50%, 57% 57%, 50% 100%, 43% 57%, 0 50%, 43% 43%);
transform: scale(0) rotate(-45deg);
animation: txt-frost-glint-text-glint 6s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
@keyframes txt-frost-glint-text-creep {
0%, 6% { -webkit-mask-position: 100% 0; mask-position: 100% 0; opacity: 1; }
46%, 80% { -webkit-mask-position: 0% 0; mask-position: 0% 0; opacity: 1; }
94%, 100% { -webkit-mask-position: 0% 0; mask-position: 0% 0; opacity: 0; }
}
@keyframes txt-frost-glint-text-glint {
0%, 44% { transform: scale(0) rotate(-45deg); }
52% { transform: scale(1) rotate(0deg); }
60%, 100% { transform: scale(0) rotate(30deg); }
}
@media (prefers-reduced-motion: reduce) {
.txt-frost-glint-text-frost, .txt-frost-glint-text-glint { animation: none; }
.txt-frost-glint-text-frost { -webkit-mask-position: 0% 0; mask-position: 0% 0; }
}
@media (prefers-reduced-motion: reduce) {
.txt-frost-glint-text, .txt-frost-glint-text *, .txt-frost-glint-text::before, .txt-frost-glint-text::after, .txt-frost-glint-text *::before, .txt-frost-glint-text *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.