Freeze and frost
The frozen state, as CSS or as a WebGL frost that spreads over the face.
Freezing is state on Card.Root (frozen, defaultFrozen, onFrozenChange), with Card.FreezeTrigger to toggle it. A frozen card:
- sets
data-frozenon the root; - hides the details, and disables
Card.RevealTrigger; - mounts
Card.FrozenOverlay, and runs<Frost />or<FrostVeil />.
The state changes at once: data-frozen, the disabled reveal and onFrozenChange all happen on the click, and the 750ms frost is decoration on top. Nothing waits for the animation. Shorten it with freezeTiming on Card.Root, or use <FrostVeil /> for no shader at all.
Say what the freeze means next to the card ("Frozen · payments paused"), not on it: the card already shows it.
CSS: Card.FrozenOverlay
An empty <div> that is mounted while the card is frozen. Like Base UI popups, it carries data-starting-style on its first frame and data-ending-style while it leaves, and it waits for your transitions to finish before it unmounts:
[data-slot="card-frozen-overlay"] {
position: absolute;
inset: 0;
background: rgb(214 236 255 / 0.55);
backdrop-filter: blur(3px);
transition: opacity 250ms ease;
}
[data-slot="card-frozen-overlay"][data-starting-style],
[data-slot="card-frozen-overlay"][data-ending-style] {
opacity: 0;
}WebGL: <Frost />
import { Frost } from "@danolekh/cardstock/frost";
<Card.Front className="relative overflow-hidden">
{/* … */}
<Frost
stops={[
["#34322d", 0],
["#171614", 0.55],
["#080807", 1],
]}
version={design}
/>
</Card.Front>;The face turns to frosted glass from the middle out, following the freeze progress (750ms by default). If you unfreeze halfway, it walks back through the same frames. It is a port of "Spreading Frost" (shadertoy XddcRr). A shader can't read the DOM, so Frost redraws the face into a canvas from a clone and refracts that.
Over a shader background, the frost is a layer and the background keeps moving under the ice. Frost then draws in the shaders' shared WebGL context instead of its own, and refracts the background's live frame, with only the card's content (its text, chip and number) taken from the snapshot.
Prop
Type
- Anything marked
data-frost-skipis left out of the snapshot. Use it for things that should stay sharp above the frost. - Without WebGL2,
Frostshows the veil instead. - If the browser drops the WebGL context later (a GPU reset, or too many live contexts), the veil returns at once and the shader is rebuilt on a fresh canvas. If the context is lost again within 10 seconds, the veil stays.
- The veil also covers the face until the shader's first frame, so a frozen card is never shown bare.
- Each
Frostholds a WebGL context while it's mounted, and browsers cap how many can be live at once (often 16). Give only the card in focus aFrost, and the rest aFrostVeil.
No WebGL: <FrostVeil />
A frosted gradient over a slight blur that fades in as the card freezes, with no canvas and no GPU context. Use it for cards out of focus (the neighbours in a carousel), long lists of cards, or anywhere the shader isn't wanted:
import { Frost, FrostVeil } from "@danolekh/cardstock/frost";
<Card.Front className="relative overflow-hidden">
{/* … */}
{active ? <Frost version={design} /> : <FrostVeil />}
</Card.Front>;Prop
Type
Moving from 0.2: <Frost webgl={false} /> is now <FrostVeil />, and fallback is FrostVeil's background.
What Frost captures
A shader can't read the page, so Frost redraws the face into a canvas and refracts that. The snapshot includes:
- background colours, with the top-left border radius
url()background images, withbackground-size,-positionand-repeat<img>elements, withobject-fitandobject-position- inline SVGs
- text
It leaves out CSS gradients (pass stops for the face's own), borders, shadows, filters, transforms, pseudo-elements, video and canvas.
An image from another origin is requested with CORS. If its server doesn't allow that, the image is left out of the snapshot and everything else is still drawn. Serve card artwork from your own origin or with Access-Control-Allow-Origin so the frost can use it.