Steam
import { useId } from 'react';
const W = 256;
const H = 176;
const TICK = 'M58 84L102 126L194 40';
const rnd = (i: number) => {
const v = Math.sin(i * 63.7) * 43758.5453;
return v - Math.floor(v);
};
// Street lamps and lit windows: soft blobs through the fog, crisp points through the wipe.
const LIGHTS = Array.from({ length: 34 }, (_, i) => ({ x: 8 + rnd(i) * 240, y: 44 + rnd(i + 40) * 122, r: 1.4 + rnd(i + 80) * 3.2 }));
// Condensation gathers towards the bottom of the pane.
const BEADS = Array.from({ length: 46 }, (_, i) => ({ x: 6 + rnd(i + 200) * 244, y: 70 + Math.pow(rnd(i + 300), 0.55) * 100, r: 0.6 + rnd(i + 400) * 1.5 }));
const DRIPS = [
{ d: 'M60 94C61 110 58 128 60 170', delay: '0.45s' },
{ d: 'M104 136C103 148 106 158 104 174', delay: '0.62s' },
{ d: 'M150 92C151 106 148 118 150 150', delay: '0.8s' },
];
const css = `
.cb-steam { --night: light-dark(#373a40, #121417); --dusk: light-dark(#50535b, #232529); --bulb: light-dark(#fbfcfd, #f1f2f3); --city: light-dark(#42454c, #1a1c1f); --fog: light-dark(#ebedf0, #c6c8cd); --frame: light-dark(#6d7179, #393c41); --sill: light-dark(#5f626a, #2e3035); --sill-ink: light-dark(#fbfcfd, #f1f2f3); flex: none; position: relative; display: block; width: 256px; height: 216px; cursor: pointer; -webkit-user-select: none; user-select: none; }
.cb-steam__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.cb-steam__art { position: absolute; left: 0; top: 0; display: block; width: ${W}px; height: ${H + 8}px; box-sizing: border-box; padding: 4px 4px 0; border-radius: 8px 8px 0 0; background: var(--frame); }
.cb-steam__input:focus-visible + .cb-steam__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 2px; }
.cb-steam__art svg { display: block; width: 100%; height: 100%; border-radius: 5px 5px 0 0; box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.4); }
.cb-steam__bulb { fill: var(--bulb); }
.cb-steam__blur { filter: blur(5px); }
.cb-steam__bead { fill: var(--bulb); opacity: 0.34; }
.cb-steam__wipe { fill: none; stroke-width: 22; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; transition: opacity 1.4s ease-in, stroke-dashoffset 0s linear 1.4s; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__wipe { stroke-dashoffset: 0; opacity: 1; transition: stroke-dashoffset 0.62s cubic-bezier(0.45, 0, 0.5, 1), opacity 0s; }
.cb-steam__drip { fill: none; stroke-width: 3.2; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; transition: opacity 1.4s ease-in, stroke-dashoffset 0s linear 1.4s; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__drip { stroke-dashoffset: 0; opacity: 1; transition: stroke-dashoffset 1.5s cubic-bezier(0.5, 0, 0.7, 0.8) var(--d), opacity 0s; }
.cb-steam__finger { fill: var(--bulb); opacity: 0; offset-path: path('${TICK}'); offset-distance: 0%; offset-rotate: 0deg; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__finger { animation: cb-steam-finger 0.62s cubic-bezier(0.45, 0, 0.5, 1); }
@keyframes cb-steam-finger {
0% { opacity: 0; offset-distance: 0%; }
12%, 85% { opacity: 0.3; }
100% { opacity: 0; offset-distance: 100%; }
}
.cb-steam__sill { position: absolute; left: -4px; right: -4px; bottom: 4px; display: grid; place-items: center; height: 30px; border-radius: 3px; background: var(--sill); color: var(--sill-ink); font-size: 13.5px; font-weight: 600; box-shadow: 0 7px 10px -5px rgba(0, 0, 0, 0.5); }
@media (prefers-reduced-motion: reduce) {
.cb-steam__wipe, .cb-steam__drip, .cb-steam__input:checked + .cb-steam__art .cb-steam__wipe, .cb-steam__input:checked + .cb-steam__art .cb-steam__drip { transition: none; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__finger { animation: none; }
}
`;
function City() {
return (
<>
<rect width={W} height={H} fill="var(--night)" />
<rect y="66" width={W} height={H - 66} fill="var(--dusk)" opacity="0.55" />
<path d={`M0 ${H}V132H30V112H58V138H84V100H112V128H138V92H160V122H192V108H220V134H${W}V${H}Z`} fill="var(--city)" />
{LIGHTS.map((l) => (
<circle key={`${l.x}${l.y}`} className="cb-steam__bulb" cx={l.x} cy={l.y} r={l.r} />
))}
</>
);
}
export function SteamCheckbox() {
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
return (
<>
<style>
{css}
</style>
<label className="cb-steam">
<input className="cb-steam__input" type="checkbox" />
<span className="cb-steam__art" aria-hidden="true">
<svg viewBox={`0 0 ${W} ${H}`}>
<defs>
{/* The view itself, used as paint: wherever the finger goes, its stroke shows the sharp city. */}
<pattern id={`${uid}view`} patternUnits="userSpaceOnUse" width={W} height={H}>
<City />
</pattern>
<linearGradient id={`${uid}veil`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="var(--fog)" stopOpacity="0.3" />
<stop offset="1" stopColor="var(--fog)" stopOpacity="0.66" />
</linearGradient>
</defs>
<g className="cb-steam__blur">
<City />
</g>
<rect width={W} height={H} fill={`url(#${uid}veil)`} />
{BEADS.map((b) => (
<circle key={`${b.x}${b.y}`} className="cb-steam__bead" cx={b.x} cy={b.y} r={b.r} />
))}
<path className="cb-steam__wipe" stroke={`url(#${uid}view)`} pathLength={1} d={TICK} />
{DRIPS.map((drip) => (
<path key={drip.d} className="cb-steam__drip" stroke={`url(#${uid}view)`} pathLength={1} style={{ ['--d' as string]: drip.delay }} d={drip.d} />
))}
<circle className="cb-steam__finger" r="12" />
</svg>
</span>
<span className="cb-steam__sill">Clear the view</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<label class="cb-steam">
<input class="cb-steam__input" type="checkbox"/>
<span class="cb-steam__art" aria-hidden="true">
<svg viewBox="0 0 256 176">
<defs>
<pattern id="cb-steam-view" patternUnits="userSpaceOnUse" width="256" height="176">
<rect width="256" height="176" fill="var(--night)"></rect>
<rect y="66" width="256" height="110" fill="var(--dusk)" opacity="0.55"></rect>
<path d="M0 176V132H30V112H58V138H84V100H112V128H138V92H160V122H192V108H220V134H256V176Z" fill="var(--city)"></path>
<circle class="cb-steam__bulb" cx="8" cy="123.896" r="2.519"></circle>
<circle class="cb-steam__bulb" cx="146.6" cy="55.981" r="2.353"></circle>
<circle class="cb-steam__bulb" cx="164.235" cy="109.879" r="2.063"></circle>
<circle class="cb-steam__bulb" cx="213.364" cy="98.974" r="1.97"></circle>
<circle class="cb-steam__bulb" cx="47.021" cy="57.878" r="1.848"></circle>
<circle class="cb-steam__bulb" cx="234.874" cy="92.506" r="1.912"></circle>
<circle class="cb-steam__bulb" cx="228.372" cy="89.067" r="1.958"></circle>
<circle class="cb-steam__bulb" cx="162.275" cy="120.833" r="4.146"></circle>
<circle class="cb-steam__bulb" cx="210.703" cy="156.234" r="4.349"></circle>
<circle class="cb-steam__bulb" cx="99.193" cy="136.021" r="3.973"></circle>
<circle class="cb-steam__bulb" cx="155.383" cy="91.534" r="3.723"></circle>
<circle class="cb-steam__bulb" cx="113.083" cy="142.791" r="3.201"></circle>
<circle class="cb-steam__bulb" cx="58.094" cy="93.347" r="1.66"></circle>
<circle class="cb-steam__bulb" cx="211.012" cy="80.218" r="1.518"></circle>
<circle class="cb-steam__bulb" cx="159.811" cy="113.896" r="1.729"></circle>
<circle class="cb-steam__bulb" cx="243.059" cy="129.729" r="2.801"></circle>
<circle class="cb-steam__bulb" cx="116.544" cy="52.23" r="4.077"></circle>
<circle class="cb-steam__bulb" cx="72.532" cy="53.796" r="2.587"></circle>
<circle class="cb-steam__bulb" cx="118.746" cy="154.942" r="2.312"></circle>
<circle class="cb-steam__bulb" cx="203.41" cy="94.036" r="3.292"></circle>
<circle class="cb-steam__bulb" cx="104.446" cy="116.838" r="3.125"></circle>
<circle class="cb-steam__bulb" cx="68.161" cy="152.737" r="2.395"></circle>
<circle class="cb-steam__bulb" cx="85.023" cy="112.939" r="2.088"></circle>
<circle class="cb-steam__bulb" cx="127.212" cy="149.903" r="1.927"></circle>
<circle class="cb-steam__bulb" cx="29.113" cy="109.7" r="2.079"></circle>
<circle class="cb-steam__bulb" cx="236.592" cy="155.286" r="1.968"></circle>
<circle class="cb-steam__bulb" cx="174.683" cy="96.862" r="1.425"></circle>
<circle class="cb-steam__bulb" cx="148.18" cy="103.65" r="2.968"></circle>
<circle class="cb-steam__bulb" cx="86.743" cy="159.767" r="3.903"></circle>
<circle class="cb-steam__bulb" cx="189.732" cy="136.344" r="3.04"></circle>
<circle class="cb-steam__bulb" cx="67.249" cy="80.955" r="4.004"></circle>
<circle class="cb-steam__bulb" cx="45.02" cy="107.788" r="2.955"></circle>
<circle class="cb-steam__bulb" cx="9.227" cy="61.126" r="2.313"></circle>
<circle class="cb-steam__bulb" cx="57.713" cy="113.301" r="4.235"></circle>
</pattern>
<linearGradient id="cb-steam-veil" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="var(--fog)" stop-opacity="0.3"></stop>
<stop offset="1" stop-color="var(--fog)" stop-opacity="0.66"></stop>
</linearGradient>
</defs>
<g class="cb-steam__blur">
<rect width="256" height="176" fill="var(--night)"></rect>
<rect y="66" width="256" height="110" fill="var(--dusk)" opacity="0.55"></rect>
<path d="M0 176V132H30V112H58V138H84V100H112V128H138V92H160V122H192V108H220V134H256V176Z" fill="var(--city)"></path>
<circle class="cb-steam__bulb" cx="8" cy="123.896" r="2.519"></circle>
<circle class="cb-steam__bulb" cx="146.6" cy="55.981" r="2.353"></circle>
<circle class="cb-steam__bulb" cx="164.235" cy="109.879" r="2.063"></circle>
<circle class="cb-steam__bulb" cx="213.364" cy="98.974" r="1.97"></circle>
<circle class="cb-steam__bulb" cx="47.021" cy="57.878" r="1.848"></circle>
<circle class="cb-steam__bulb" cx="234.874" cy="92.506" r="1.912"></circle>
<circle class="cb-steam__bulb" cx="228.372" cy="89.067" r="1.958"></circle>
<circle class="cb-steam__bulb" cx="162.275" cy="120.833" r="4.146"></circle>
<circle class="cb-steam__bulb" cx="210.703" cy="156.234" r="4.349"></circle>
<circle class="cb-steam__bulb" cx="99.193" cy="136.021" r="3.973"></circle>
<circle class="cb-steam__bulb" cx="155.383" cy="91.534" r="3.723"></circle>
<circle class="cb-steam__bulb" cx="113.083" cy="142.791" r="3.201"></circle>
<circle class="cb-steam__bulb" cx="58.094" cy="93.347" r="1.66"></circle>
<circle class="cb-steam__bulb" cx="211.012" cy="80.218" r="1.518"></circle>
<circle class="cb-steam__bulb" cx="159.811" cy="113.896" r="1.729"></circle>
<circle class="cb-steam__bulb" cx="243.059" cy="129.729" r="2.801"></circle>
<circle class="cb-steam__bulb" cx="116.544" cy="52.23" r="4.077"></circle>
<circle class="cb-steam__bulb" cx="72.532" cy="53.796" r="2.587"></circle>
<circle class="cb-steam__bulb" cx="118.746" cy="154.942" r="2.312"></circle>
<circle class="cb-steam__bulb" cx="203.41" cy="94.036" r="3.292"></circle>
<circle class="cb-steam__bulb" cx="104.446" cy="116.838" r="3.125"></circle>
<circle class="cb-steam__bulb" cx="68.161" cy="152.737" r="2.395"></circle>
<circle class="cb-steam__bulb" cx="85.023" cy="112.939" r="2.088"></circle>
<circle class="cb-steam__bulb" cx="127.212" cy="149.903" r="1.927"></circle>
<circle class="cb-steam__bulb" cx="29.113" cy="109.7" r="2.079"></circle>
<circle class="cb-steam__bulb" cx="236.592" cy="155.286" r="1.968"></circle>
<circle class="cb-steam__bulb" cx="174.683" cy="96.862" r="1.425"></circle>
<circle class="cb-steam__bulb" cx="148.18" cy="103.65" r="2.968"></circle>
<circle class="cb-steam__bulb" cx="86.743" cy="159.767" r="3.903"></circle>
<circle class="cb-steam__bulb" cx="189.732" cy="136.344" r="3.04"></circle>
<circle class="cb-steam__bulb" cx="67.249" cy="80.955" r="4.004"></circle>
<circle class="cb-steam__bulb" cx="45.02" cy="107.788" r="2.955"></circle>
<circle class="cb-steam__bulb" cx="9.227" cy="61.126" r="2.313"></circle>
<circle class="cb-steam__bulb" cx="57.713" cy="113.301" r="4.235"></circle>
</g>
<rect width="256" height="176" fill="url(#cb-steam-veil)"></rect>
<circle class="cb-steam__bead" cx="74.461" cy="112.542" r="1.403"></circle>
<circle class="cb-steam__bead" cx="92.608" cy="156.105" r="1.633"></circle>
<circle class="cb-steam__bead" cx="32.663" cy="152.168" r="1.208"></circle>
<circle class="cb-steam__bead" cx="73.056" cy="113.02" r="1.935"></circle>
<circle class="cb-steam__bead" cx="28.707" cy="116.788" r="0.726"></circle>
<circle class="cb-steam__bead" cx="17.851" cy="163.511" r="1.001"></circle>
<circle class="cb-steam__bead" cx="17.493" cy="130.565" r="1.89"></circle>
<circle class="cb-steam__bead" cx="36.343" cy="155.296" r="1.989"></circle>
<circle class="cb-steam__bead" cx="172.508" cy="169.057" r="2.054"></circle>
<circle class="cb-steam__bead" cx="160.101" cy="76.954" r="1.656"></circle>
<circle class="cb-steam__bead" cx="109.346" cy="166.202" r="1.271"></circle>
<circle class="cb-steam__bead" cx="49.988" cy="86.461" r="0.662"></circle>
<circle class="cb-steam__bead" cx="183.767" cy="142.692" r="1.458"></circle>
<circle class="cb-steam__bead" cx="214.189" cy="147.69" r="2.1"></circle>
<circle class="cb-steam__bead" cx="50.355" cy="152.6" r="0.902"></circle>
<circle class="cb-steam__bead" cx="15.321" cy="130.821" r="2.099"></circle>
<circle class="cb-steam__bead" cx="196.072" cy="166.095" r="1.418"></circle>
<circle class="cb-steam__bead" cx="94.638" cy="134.853" r="1.886"></circle>
<circle class="cb-steam__bead" cx="40.211" cy="143.129" r="0.771"></circle>
<circle class="cb-steam__bead" cx="107.393" cy="167.456" r="2.002"></circle>
<circle class="cb-steam__bead" cx="138.387" cy="168.143" r="0.831"></circle>
<circle class="cb-steam__bead" cx="224.783" cy="99.31" r="1.801"></circle>
<circle class="cb-steam__bead" cx="217.15" cy="144.685" r="1.268"></circle>
<circle class="cb-steam__bead" cx="169.221" cy="167.129" r="1.712"></circle>
<circle class="cb-steam__bead" cx="118.522" cy="73.742" r="1.759"></circle>
<circle class="cb-steam__bead" cx="192.968" cy="150.807" r="1.285"></circle>
<circle class="cb-steam__bead" cx="8.757" cy="151.869" r="1.833"></circle>
<circle class="cb-steam__bead" cx="92.184" cy="129.057" r="1.583"></circle>
<circle class="cb-steam__bead" cx="14.01" cy="155.421" r="0.831"></circle>
<circle class="cb-steam__bead" cx="14.306" cy="141.362" r="0.765"></circle>
<circle class="cb-steam__bead" cx="108.966" cy="160.778" r="1.487"></circle>
<circle class="cb-steam__bead" cx="164.589" cy="102.222" r="1.003"></circle>
<circle class="cb-steam__bead" cx="237.15" cy="132.112" r="1.775"></circle>
<circle class="cb-steam__bead" cx="193.761" cy="142.845" r="1.935"></circle>
<circle class="cb-steam__bead" cx="175.918" cy="129.216" r="0.895"></circle>
<circle class="cb-steam__bead" cx="243.34" cy="109.986" r="1.125"></circle>
<circle class="cb-steam__bead" cx="195.33" cy="161.871" r="0.866"></circle>
<circle class="cb-steam__bead" cx="136.225" cy="141.856" r="1.473"></circle>
<circle class="cb-steam__bead" cx="160.744" cy="126.086" r="1.922"></circle>
<circle class="cb-steam__bead" cx="221.39" cy="158.945" r="1.373"></circle>
<circle class="cb-steam__bead" cx="143.894" cy="135.423" r="1.256"></circle>
<circle class="cb-steam__bead" cx="249.292" cy="159.023" r="1.721"></circle>
<circle class="cb-steam__bead" cx="189.433" cy="88.364" r="1.953"></circle>
<circle class="cb-steam__bead" cx="144.634" cy="149.045" r="1.138"></circle>
<circle class="cb-steam__bead" cx="84.613" cy="136.762" r="2.065"></circle>
<circle class="cb-steam__bead" cx="35.869" cy="96.496" r="1.089"></circle>
<path class="cb-steam__wipe" stroke="url(#cb-steam-view)" pathLength="1" d="M58 84L102 126L194 40"></path>
<path class="cb-steam__drip" stroke="url(#cb-steam-view)" pathLength="1" style="--d:0.45s" d="M60 94C61 110 58 128 60 170"></path>
<path class="cb-steam__drip" stroke="url(#cb-steam-view)" pathLength="1" style="--d:0.62s" d="M104 136C103 148 106 158 104 174"></path>
<path class="cb-steam__drip" stroke="url(#cb-steam-view)" pathLength="1" style="--d:0.8s" d="M150 92C151 106 148 118 150 150"></path>
<circle class="cb-steam__finger" r="12"></circle>
</svg>
</span>
<span class="cb-steam__sill">Clear the view</span>
</label>.cb-steam { --night: light-dark(#373a40, #121417); --dusk: light-dark(#50535b, #232529); --bulb: light-dark(#fbfcfd, #f1f2f3); --city: light-dark(#42454c, #1a1c1f); --fog: light-dark(#ebedf0, #c6c8cd); --frame: light-dark(#6d7179, #393c41); --sill: light-dark(#5f626a, #2e3035); --sill-ink: light-dark(#fbfcfd, #f1f2f3); flex: none; position: relative; display: block; width: 256px; height: 216px; cursor: pointer; -webkit-user-select: none; user-select: none; }
.cb-steam__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.cb-steam__art { position: absolute; left: 0; top: 0; display: block; width: 256px; height: 184px; box-sizing: border-box; padding: 4px 4px 0; border-radius: 8px 8px 0 0; background: var(--frame); }
.cb-steam__input:focus-visible + .cb-steam__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 2px; }
.cb-steam__art svg { display: block; width: 100%; height: 100%; border-radius: 5px 5px 0 0; box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.4); }
.cb-steam__bulb { fill: var(--bulb); }
.cb-steam__blur { filter: blur(5px); }
.cb-steam__bead { fill: var(--bulb); opacity: 0.34; }
.cb-steam__wipe { fill: none; stroke-width: 22; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; transition: opacity 1.4s ease-in, stroke-dashoffset 0s linear 1.4s; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__wipe { stroke-dashoffset: 0; opacity: 1; transition: stroke-dashoffset 0.62s cubic-bezier(0.45, 0, 0.5, 1), opacity 0s; }
.cb-steam__drip { fill: none; stroke-width: 3.2; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; transition: opacity 1.4s ease-in, stroke-dashoffset 0s linear 1.4s; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__drip { stroke-dashoffset: 0; opacity: 1; transition: stroke-dashoffset 1.5s cubic-bezier(0.5, 0, 0.7, 0.8) var(--d), opacity 0s; }
.cb-steam__finger { fill: var(--bulb); opacity: 0; offset-path: path('M58 84L102 126L194 40'); offset-distance: 0%; offset-rotate: 0deg; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__finger { animation: cb-steam-finger 0.62s cubic-bezier(0.45, 0, 0.5, 1); }
@keyframes cb-steam-finger {
0% { opacity: 0; offset-distance: 0%; }
12%, 85% { opacity: 0.3; }
100% { opacity: 0; offset-distance: 100%; }
}
.cb-steam__sill { position: absolute; left: -4px; right: -4px; bottom: 4px; display: grid; place-items: center; height: 30px; border-radius: 3px; background: var(--sill); color: var(--sill-ink); font-size: 13.5px; font-weight: 600; box-shadow: 0 7px 10px -5px rgba(0, 0, 0, 0.5); }
@media (prefers-reduced-motion: reduce) {
.cb-steam__wipe, .cb-steam__drip, .cb-steam__input:checked + .cb-steam__art .cb-steam__wipe, .cb-steam__input:checked + .cb-steam__art .cb-steam__drip { transition: none; }
.cb-steam__input:checked + .cb-steam__art .cb-steam__finger { animation: none; }
}Goal: Create a "Steam" checkbox.
Visual behavior: A steamed-up window with the city blurred behind it. Tick it and a fingertip wipes a tick through the fog, sharp lights showing through and drops running down; untick and the fog creeps back.
Technique:
- The city is drawn twice: blurred under a veil of fog, and sharp inside an SVG pattern. The wipe and the drops are ordinary strokes painted with that pattern, so wherever they draw in, the sharp city shows through, with no mask.
- A fingertip rides offset-path along the same tick in the same time, so it stays at the head of the wipe. The drops start later and run slowly, each on its own delay. Unticking fades the strokes out over more than a second, like fog returning. Pure CSS.
Accessibility: A native checkbox inside a <label>, so "Clear the view" is its name. It is the text on the sill, and the window is aria-hidden. Keyboard focus outlines the window. Under prefers-reduced-motion the tick appears at once and the fingertip never shows.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Steam" checkbox.
Visual behavior: A steamed-up window with the city blurred behind it. Tick it and a fingertip wipes a tick through the fog, sharp lights showing through and drops running down; untick and the fog creeps back.
Technique:
- The city is drawn twice: blurred under a veil of fog, and sharp inside an SVG pattern. The wipe and the drops are ordinary strokes painted with that pattern, so wherever they draw in, the sharp city shows through, with no mask.
- A fingertip rides offset-path along the same tick in the same time, so it stays at the head of the wipe. The drops start later and run slowly, each on its own delay. Unticking fades the strokes out over more than a second, like fog returning. Pure CSS.
Accessibility: A native checkbox inside a <label>, so "Clear the view" is its name. It is the text on the sill, and the window is aria-hidden. Keyboard focus outlines the window. Under prefers-reduced-motion the tick appears at once and the fingertip never shows.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A steamed-up window with the city blurred behind it. Tick it and a fingertip wipes a tick through the fog, sharp lights showing through and drops running down; untick and the fog creeps back.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- click, state change, draw, delay stagger
- Techniques
- stroke dashoffset, svg, keyframes, css transition, filter
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A native checkbox inside a <label>, so "Clear the view" is its name. It is the text on the sill, and the window is aria-hidden. Keyboard focus outlines the window. Under prefers-reduced-motion the tick appears at once and the fingertip never shows.