Button Library · Signature
Wax Seal
A lump of poured wax with a pressed ring; a click cracks it open.
States
Default
Hover
Active
Focus
Code
sig-wax.html
<button class="btn-sig-wax" type="button"> <span class="btn-sig-wax__crack" aria-hidden="true"></span> <span class="btn-sig-wax__word">Open me</span> </button>
sig-wax.css
.btn-sig-wax {
--btn-bg: #8e1b22;
--btn-fg: #f3c9a8;
--btn-font: Georgia, serif;
--btn-size: 15px;
--btn-weight: 600;
--btn-px: 12px;
--btn-py: 11px;
--btn-w: 116px;
--btn-h: 116px;
--btn-bw: 0px;
--btn-line: #8b6cff;
--btn-speed: 180ms;
}
.btn-sig-wax {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
box-sizing: border-box;
width: var(--btn-w);
height: var(--btn-h);
padding: var(--btn-py) var(--btn-px);
font-family: var(--btn-font);
font-size: var(--btn-size);
font-weight: var(--btn-weight);
line-height: 1.2;
color: var(--btn-fg);
background: var(--btn-bg);
border: var(--btn-bw) solid var(--btn-line);
border-radius: 47% 53% 50% 50% / 52% 46% 54% 48%;
box-shadow: inset 3px 4px 6px color-mix(in srgb, var(--btn-bg) 70%, #fff), inset -4px -5px 8px color-mix(in srgb, var(--btn-bg) 55%, #000), 0 5px 10px rgba(0,0,0,.35);
cursor: pointer;
text-decoration: none;
white-space: normal;
transition: all var(--btn-speed) ease;
text-align: center;
}
.btn-sig-wax:focus-visible {
outline: 2px solid var(--btn-bg);
outline-offset: 2px;
}
.btn-sig-wax:disabled {
opacity: 0.5;
pointer-events: none;
}
.btn-sig-wax__icon {
width: 1.15em;
height: 1.15em;
flex-shrink: 0;
}
.btn-sig-wax::before {
content: "";
position: absolute;
inset: 12px;
border: 2px solid color-mix(in srgb, var(--btn-bg) 55%, #000);
border-radius: 50%;
box-shadow: 0 1px 0 color-mix(in srgb, var(--btn-bg) 72%, #fff);
}
.btn-sig-wax[data-state=stamped] {
--crack: btn-crack 1100ms ease both;
animation: btn-seal 1100ms ease;
}
.btn-sig-wax__crack {
position: absolute;
inset: 0;
opacity: 0;
background: color-mix(in srgb, var(--btn-bg) 35%, #000);
clip-path: inset(0);
mask: linear-gradient(115deg, transparent 47%, #000 48%, #000 50%, transparent 51%) 0 0 / 100% 34%, linear-gradient(65deg, transparent 47%, #000 48%, #000 50%, transparent 51%) 0 50% / 100% 34%, linear-gradient(115deg, transparent 47%, #000 48%, #000 50%, transparent 51%) 0 100% / 100% 34%;
mask-repeat: no-repeat;
animation: var(--crack, none);
pointer-events: none;
}
@keyframes btn-seal {
0% { transform: scale(1); }
30% { transform: scale(0.94); }
100% { transform: scale(1); }
}
@keyframes btn-crack {
0% { clip-path: inset(0 0 100% 0); opacity: 1; }
35% { clip-path: inset(0); opacity: 1; }
80% { opacity: 1; }
100% { clip-path: inset(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.btn-sig-wax,
.btn-sig-wax__crack {
animation: none !important;
transition: none !important;
}
}
sig-wax.js
document.querySelectorAll(".btn-sig-wax").forEach((el) => {
const land = () => {
el.removeAttribute("data-state");
void el.offsetWidth;
el.dataset.state = "stamped";
};
const clear = () => el.removeAttribute("data-state");
el.addEventListener("click", land);
el.addEventListener("animationend", clear);
return () => {
el.removeEventListener("click", land);
el.removeEventListener("animationend", clear);
};
});
sig-wax.tsx
import { useEffect, useRef } from "react";
import type React from "react";
import "./sig-wax.css"; // the CSS tab
function attach(el: HTMLElement) {
const land = () => {
el.removeAttribute("data-state");
void el.offsetWidth;
el.dataset.state = "stamped";
};
const clear = () => el.removeAttribute("data-state");
el.addEventListener("click", land);
el.addEventListener("animationend", clear);
return () => {
el.removeEventListener("click", land);
el.removeEventListener("animationend", clear);
};
}
export function BtnSigWax({ children = "Open me", ...props }: React.ComponentProps<"button">) {
const ref = useRef<HTMLButtonElement>(null);
useEffect(() => attach(ref.current!), []);
return (
<button className="btn-sig-wax" type="button" ref={ref} {...props}>
<span className="btn-sig-wax__crack" aria-hidden="true" />
<span className="btn-sig-wax__word">{children}</span>
</button>
);
}
sig-wax.html
<button class="btn-sig-wax relative inline-flex items-center justify-center gap-[0.5em] [box-sizing:border-box] w-[116px] h-[116px] [padding:11px_12px] [font-family:Georgia,_serif] text-[15px] font-[600] [line-height:1.2] text-[#f3c9a8] bg-[#8e1b22] [border:0px_solid_#8b6cff] rounded-[47%_53%_50%_50%_/_52%_46%_54%_48%] [box-shadow:inset_3px_4px_6px_color-mix(in_srgb,_#8e1b22_70%,_#fff),_inset_-4px_-5px_8px_color-mix(in_srgb,_#8e1b22_55%,_#000),_0_5px_10px_rgba(0,0,0,.35)] cursor-pointer no-underline [white-space:normal] [transition:all_180ms_ease] [text-align:center] focus-visible:[outline:2px_solid_#8e1b22] focus-visible:[outline-offset:2px] disabled:[opacity:0.5] disabled:[pointer-events:none] before:content-[''] before:absolute before:[inset:12px] before:[border:2px_solid_color-mix(in_srgb,_#8e1b22_55%,_#000)] before:rounded-[50%] before:[box-shadow:0_1px_0_color-mix(in_srgb,_#8e1b22_72%,_#fff)] [&[data-state=stamped]]:[--crack:btn-crack_1100ms_ease_both] [&[data-state=stamped]]:[animation:btn-seal_1100ms_ease] motion-reduce:[animation:none!important] motion-reduce:[transition:none!important]" type="button">
<span class="absolute [inset:0] [opacity:0] [background:color-mix(in_srgb,_#8e1b22_35%,_#000)] [clip-path:inset(0)] [mask:linear-gradient(115deg,_transparent_47%,_#000_48%,_#000_50%,_transparent_51%)_0_0_/_100%_34%,_linear-gradient(65deg,_transparent_47%,_#000_48%,_#000_50%,_transparent_51%)_0_50%_/_100%_34%,_linear-gradient(115deg,_transparent_47%,_#000_48%,_#000_50%,_transparent_51%)_0_100%_/_100%_34%] [mask-repeat:no-repeat] [animation:var(--crack,_none)] [pointer-events:none] motion-reduce:[animation:none!important]" aria-hidden="true"></span>
<span>Open me</span>
</button>
<!-- A class cannot hold keyframes: keep this in your CSS. -->
<style>
@keyframes btn-seal {
0% { transform: scale(1); }
30% { transform: scale(0.94); }
100% { transform: scale(1); }
}
@keyframes btn-crack {
0% { clip-path: inset(0 0 100% 0); opacity: 1; }
35% { clip-path: inset(0); opacity: 1; }
80% { opacity: 1; }
100% { clip-path: inset(0); opacity: 0; }
}
</style>
<!-- Needs the script from the JS tab. -->