Cat in a box
import { useState } from 'react';
const css = `
.cb-catbox { --fur: light-dark(#50535b, #a7abb2); --mark: light-dark(#373a40, #393c41); --white: light-dark(#fbfcfd, #f1f2f3); --pupil: light-dark(#373a40, #121417); --board: light-dark(#cbced3, #7d8087); --flap: light-dark(#dbdde1, #393c41); --inside: light-dark(#5f626a, #232529); --tape: light-dark(#ebedf0, #c6c8cd); --print: light-dark(#373a40, #121417); flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px; color: light-dark(#42454c, #e5e6e9); font-size: 14px; font-weight: 500; cursor: pointer; -webkit-user-select: none; user-select: none; }
.cb-catbox__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.cb-catbox__art { display: block; width: 256px; height: 190px; overflow: visible; border-radius: 12px; }
.cb-catbox__input:focus-visible + .cb-catbox__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 2px; }
.cb-catbox__floor { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.5; stroke-linecap: round; }
.cb-catbox__cat { transform: translate(64px, 176px); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__cat { transform: translate(184px, 148px); }
.cb-catbox[data-armed] .cb-catbox__input:checked + .cb-catbox__art .cb-catbox__cat { animation: cb-catbox-in 0.8s linear; }
.cb-catbox[data-armed] .cb-catbox__input:not(:checked) + .cb-catbox__art .cb-catbox__cat { animation: cb-catbox-out 0.7s linear; }
@keyframes cb-catbox-in {
0% { transform: translate(64px, 176px); }
16% { transform: translate(60px, 176px) scale(1.16, 0.74); animation-timing-function: ease-out; }
26% { transform: translate(78px, 150px) scale(0.86, 1.16) rotate(8deg); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
54% { transform: translate(140px, 92px) rotate(16deg); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
80% { transform: translate(184px, 110px) scale(0.94, 1.08) rotate(6deg); }
90% { transform: translate(184px, 160px) scale(1.12, 0.84); animation-timing-function: ease-out; }
100% { transform: translate(184px, 148px); }
}
@keyframes cb-catbox-out {
0% { transform: translate(184px, 148px); }
14% { transform: translate(184px, 158px) scale(1.1, 0.86); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
50% { transform: translate(122px, 94px) rotate(-16deg); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
86% { transform: translate(64px, 176px) scale(1.16, 0.76); animation-timing-function: ease-out; }
100% { transform: translate(64px, 176px); }
}
.cb-catbox__head { transform-box: fill-box; transform-origin: 50% 90%; transition: transform 0.3s cubic-bezier(0.3, 1.5, 0.5, 1); transform: rotate(5deg); }
.cb-catbox:hover .cb-catbox__head { transform: rotate(11deg); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__head { transform: rotate(-4deg); }
.cb-catbox__pupil { transform: translate(2.4px, 0.6px); transition: transform 0.25s; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__pupil { transform: translate(-1.4px, 0.8px); }
.cb-catbox__eye { transform-box: fill-box; transform-origin: center; animation: cb-catbox-blink 4.6s linear infinite; }
@keyframes cb-catbox-blink {
0%, 95%, 100% { transform: none; }
97.5% { transform: scaleY(0.1); }
}
.cb-catbox__tail { fill: none; stroke: var(--fur); stroke-width: 8; stroke-linecap: round; transform-box: fill-box; transform-origin: 0 100%; animation: cb-catbox-tail 2.8s ease-in-out infinite; }
@keyframes cb-catbox-tail {
0%, 100% { transform: rotate(-3deg); }
50% { transform: rotate(9deg); }
}
.cb-catbox__body { fill: var(--fur); }
.cb-catbox__chest { fill: var(--white); }
.cb-catbox__stripe { fill: none; stroke: var(--mark); stroke-width: 2.4; stroke-linecap: round; }
.cb-catbox__whisker { fill: none; stroke: var(--white); stroke-width: 0.9; stroke-linecap: round; }
.cb-catbox__whites { fill: var(--white); }
.cb-catbox__dark { fill: var(--pupil); }
.cb-catbox__carton { transform-box: fill-box; transform-origin: 50% 100%; }
.cb-catbox[data-armed] .cb-catbox__input:checked + .cb-catbox__art .cb-catbox__carton { animation: cb-catbox-thump 0.4s cubic-bezier(0.3, 1.5, 0.5, 1) 0.68s; }
@keyframes cb-catbox-thump {
0% { transform: scale(1.03, 0.94); }
100% { transform: none; }
}
.cb-catbox__inside { fill: var(--inside); }
.cb-catbox__flapb { fill: var(--flap); }
.cb-catbox__front { fill: var(--board); }
.cb-catbox__side { fill: var(--flap); transform-box: fill-box; transition: transform 0.3s cubic-bezier(0.3, 2, 0.5, 1) 0.7s; }
.cb-catbox__side--l { transform-origin: 100% 100%; }
.cb-catbox__side--r { transform-origin: 0 100%; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__side--l { transform: rotate(-8deg); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__side--r { transform: rotate(8deg); }
.cb-catbox__tape { fill: var(--tape); }
.cb-catbox__square { fill: none; stroke: var(--print); stroke-width: 2.4; }
.cb-catbox__small { fill: var(--print); font: 700 7.5px system-ui, sans-serif; letter-spacing: 0.08em; text-anchor: middle; }
.cb-catbox__tick { fill: none; stroke: var(--print); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.12s; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__tick { stroke-dashoffset: 0; transition: stroke-dashoffset 0.3s ease-out 0.82s; }
@media (prefers-reduced-motion: reduce) {
.cb-catbox *, .cb-catbox[data-armed] .cb-catbox__input + .cb-catbox__art * { animation: none !important; transition: none !important; }
}
`;
export function CatboxCheckbox() {
// The leaps only play when someone ticks it, not on load.
const [armed, setArmed] = useState(false);
return (
<>
<style>
{css}
</style>
<label className="cb-catbox" data-armed={armed || undefined}>
<input className="cb-catbox__input" type="checkbox" onChange={() => setArmed(true)} />
<svg className="cb-catbox__art" viewBox="0 0 256 190" aria-hidden="true">
<path className="cb-catbox__floor" d="M6 176H250" />
<g className="cb-catbox__carton">
<path className="cb-catbox__inside" d="M134 100L148 84H220L234 100Z" />
<path className="cb-catbox__flapb" d="M148 84L156 66H212L220 84Z" />
</g>
{/* The cat is drawn between the back and the front of the carton, so climbing in hides its body. */}
<g className="cb-catbox__cat">
<path className="cb-catbox__tail" d="M18 -5C40 -3 46 -20 38 -34" />
<path className="cb-catbox__body" d="M-21 0C-27 -18 -21 -46 0 -48C21 -46 27 -18 21 0Z" />
<ellipse className="cb-catbox__chest" cx="0" cy="-20" rx="10" ry="15" />
<ellipse className="cb-catbox__body" cx="-9" cy="-2" rx="7" ry="4" />
<ellipse className="cb-catbox__body" cx="9" cy="-2" rx="7" ry="4" />
<g className="cb-catbox__head">
<path className="cb-catbox__body" d="M-22 -72L-24 -96L-5 -82ZM22 -72L24 -96L5 -82Z" />
<path className="cb-catbox__chest" d="M-19 -76L-20 -89L-10 -81ZM19 -76L20 -89L10 -81Z" />
<path className="cb-catbox__body" d="M-24 -62C-24 -78 -13 -84 0 -84C13 -84 24 -78 24 -62C24 -49 13 -43 0 -43C-13 -43 -24 -49 -24 -62Z" />
<path className="cb-catbox__stripe" d="M0 -83V-74M-7 -82V-76M7 -82V-76" />
<g className="cb-catbox__eye">
<ellipse className="cb-catbox__whites" cx="-10" cy="-64" rx="6" ry="6.8" />
<ellipse className="cb-catbox__dark cb-catbox__pupil" cx="-10" cy="-64" rx="3.1" ry="4.6" />
</g>
<g className="cb-catbox__eye">
<ellipse className="cb-catbox__whites" cx="10" cy="-64" rx="6" ry="6.8" />
<ellipse className="cb-catbox__dark cb-catbox__pupil" cx="10" cy="-64" rx="3.1" ry="4.6" />
</g>
<path className="cb-catbox__dark" d="M-2.6 -55H2.6L0 -52Z" />
<path className="cb-catbox__whisker" d="M-14 -54L-30 -57M-14 -51L-30 -49M14 -54L30 -57M14 -51L30 -49" />
</g>
</g>
<g className="cb-catbox__carton">
<path className="cb-catbox__side cb-catbox__side--l" d="M134 100L114 84L118 112L134 112Z" />
<path className="cb-catbox__side cb-catbox__side--r" d="M234 100L254 84L250 112L234 112Z" />
<path className="cb-catbox__front" d="M134 100H234V176H134Z" />
<rect className="cb-catbox__tape" x="134" y="100" width="100" height="9" />
<rect className="cb-catbox__square" x="166" y="121" width="36" height="36" rx="3" />
<path className="cb-catbox__tick" pathLength={1} d="M173 139L181 148L196 128" />
<text className="cb-catbox__small" x="184" y="170">
THIS WAY UP
</text>
</g>
</svg>
Add a gift box
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initCbCatbox(el) on the mounted element instead.
<label class="cb-catbox">
<input class="cb-catbox__input" type="checkbox"/>
<svg class="cb-catbox__art" viewBox="0 0 256 190" aria-hidden="true">
<path class="cb-catbox__floor" d="M6 176H250"></path>
<g class="cb-catbox__carton">
<path class="cb-catbox__inside" d="M134 100L148 84H220L234 100Z"></path>
<path class="cb-catbox__flapb" d="M148 84L156 66H212L220 84Z"></path>
</g>
<g class="cb-catbox__cat">
<path class="cb-catbox__tail" d="M18 -5C40 -3 46 -20 38 -34"></path>
<path class="cb-catbox__body" d="M-21 0C-27 -18 -21 -46 0 -48C21 -46 27 -18 21 0Z"></path>
<ellipse class="cb-catbox__chest" cx="0" cy="-20" rx="10" ry="15"></ellipse>
<ellipse class="cb-catbox__body" cx="-9" cy="-2" rx="7" ry="4"></ellipse>
<ellipse class="cb-catbox__body" cx="9" cy="-2" rx="7" ry="4"></ellipse>
<g class="cb-catbox__head">
<path class="cb-catbox__body" d="M-22 -72L-24 -96L-5 -82ZM22 -72L24 -96L5 -82Z"></path>
<path class="cb-catbox__chest" d="M-19 -76L-20 -89L-10 -81ZM19 -76L20 -89L10 -81Z"></path>
<path class="cb-catbox__body" d="M-24 -62C-24 -78 -13 -84 0 -84C13 -84 24 -78 24 -62C24 -49 13 -43 0 -43C-13 -43 -24 -49 -24 -62Z"></path>
<path class="cb-catbox__stripe" d="M0 -83V-74M-7 -82V-76M7 -82V-76"></path>
<g class="cb-catbox__eye">
<ellipse class="cb-catbox__whites" cx="-10" cy="-64" rx="6" ry="6.8"></ellipse>
<ellipse class="cb-catbox__dark cb-catbox__pupil" cx="-10" cy="-64" rx="3.1" ry="4.6"></ellipse>
</g>
<g class="cb-catbox__eye">
<ellipse class="cb-catbox__whites" cx="10" cy="-64" rx="6" ry="6.8"></ellipse>
<ellipse class="cb-catbox__dark cb-catbox__pupil" cx="10" cy="-64" rx="3.1" ry="4.6"></ellipse>
</g>
<path class="cb-catbox__dark" d="M-2.6 -55H2.6L0 -52Z"></path>
<path class="cb-catbox__whisker" d="M-14 -54L-30 -57M-14 -51L-30 -49M14 -54L30 -57M14 -51L30 -49"></path>
</g>
</g>
<g class="cb-catbox__carton">
<path class="cb-catbox__side cb-catbox__side--l" d="M134 100L114 84L118 112L134 112Z"></path>
<path class="cb-catbox__side cb-catbox__side--r" d="M234 100L254 84L250 112L234 112Z"></path>
<path class="cb-catbox__front" d="M134 100H234V176H134Z"></path>
<rect class="cb-catbox__tape" x="134" y="100" width="100" height="9"></rect>
<rect class="cb-catbox__square" x="166" y="121" width="36" height="36" rx="3"></rect>
<path class="cb-catbox__tick" pathLength="1" d="M173 139L181 148L196 128"></path>
<text class="cb-catbox__small" x="184" y="170">THIS WAY UP</text>
</g>
</svg>
Add a gift box
</label>.cb-catbox { --fur: light-dark(#50535b, #a7abb2); --mark: light-dark(#373a40, #393c41); --white: light-dark(#fbfcfd, #f1f2f3); --pupil: light-dark(#373a40, #121417); --board: light-dark(#cbced3, #7d8087); --flap: light-dark(#dbdde1, #393c41); --inside: light-dark(#5f626a, #232529); --tape: light-dark(#ebedf0, #c6c8cd); --print: light-dark(#373a40, #121417); flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px; color: light-dark(#42454c, #e5e6e9); font-size: 14px; font-weight: 500; cursor: pointer; -webkit-user-select: none; user-select: none; }
.cb-catbox__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.cb-catbox__art { display: block; width: 256px; height: 190px; overflow: visible; border-radius: 12px; }
.cb-catbox__input:focus-visible + .cb-catbox__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 2px; }
.cb-catbox__floor { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.5; stroke-linecap: round; }
.cb-catbox__cat { transform: translate(64px, 176px); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__cat { transform: translate(184px, 148px); }
.cb-catbox[data-armed] .cb-catbox__input:checked + .cb-catbox__art .cb-catbox__cat { animation: cb-catbox-in 0.8s linear; }
.cb-catbox[data-armed] .cb-catbox__input:not(:checked) + .cb-catbox__art .cb-catbox__cat { animation: cb-catbox-out 0.7s linear; }
@keyframes cb-catbox-in {
0% { transform: translate(64px, 176px); }
16% { transform: translate(60px, 176px) scale(1.16, 0.74); animation-timing-function: ease-out; }
26% { transform: translate(78px, 150px) scale(0.86, 1.16) rotate(8deg); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
54% { transform: translate(140px, 92px) rotate(16deg); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
80% { transform: translate(184px, 110px) scale(0.94, 1.08) rotate(6deg); }
90% { transform: translate(184px, 160px) scale(1.12, 0.84); animation-timing-function: ease-out; }
100% { transform: translate(184px, 148px); }
}
@keyframes cb-catbox-out {
0% { transform: translate(184px, 148px); }
14% { transform: translate(184px, 158px) scale(1.1, 0.86); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
50% { transform: translate(122px, 94px) rotate(-16deg); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
86% { transform: translate(64px, 176px) scale(1.16, 0.76); animation-timing-function: ease-out; }
100% { transform: translate(64px, 176px); }
}
.cb-catbox__head { transform-box: fill-box; transform-origin: 50% 90%; transition: transform 0.3s cubic-bezier(0.3, 1.5, 0.5, 1); transform: rotate(5deg); }
.cb-catbox:hover .cb-catbox__head { transform: rotate(11deg); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__head { transform: rotate(-4deg); }
.cb-catbox__pupil { transform: translate(2.4px, 0.6px); transition: transform 0.25s; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__pupil { transform: translate(-1.4px, 0.8px); }
.cb-catbox__eye { transform-box: fill-box; transform-origin: center; animation: cb-catbox-blink 4.6s linear infinite; }
@keyframes cb-catbox-blink {
0%, 95%, 100% { transform: none; }
97.5% { transform: scaleY(0.1); }
}
.cb-catbox__tail { fill: none; stroke: var(--fur); stroke-width: 8; stroke-linecap: round; transform-box: fill-box; transform-origin: 0 100%; animation: cb-catbox-tail 2.8s ease-in-out infinite; }
@keyframes cb-catbox-tail {
0%, 100% { transform: rotate(-3deg); }
50% { transform: rotate(9deg); }
}
.cb-catbox__body { fill: var(--fur); }
.cb-catbox__chest { fill: var(--white); }
.cb-catbox__stripe { fill: none; stroke: var(--mark); stroke-width: 2.4; stroke-linecap: round; }
.cb-catbox__whisker { fill: none; stroke: var(--white); stroke-width: 0.9; stroke-linecap: round; }
.cb-catbox__whites { fill: var(--white); }
.cb-catbox__dark { fill: var(--pupil); }
.cb-catbox__carton { transform-box: fill-box; transform-origin: 50% 100%; }
.cb-catbox[data-armed] .cb-catbox__input:checked + .cb-catbox__art .cb-catbox__carton { animation: cb-catbox-thump 0.4s cubic-bezier(0.3, 1.5, 0.5, 1) 0.68s; }
@keyframes cb-catbox-thump {
0% { transform: scale(1.03, 0.94); }
100% { transform: none; }
}
.cb-catbox__inside { fill: var(--inside); }
.cb-catbox__flapb { fill: var(--flap); }
.cb-catbox__front { fill: var(--board); }
.cb-catbox__side { fill: var(--flap); transform-box: fill-box; transition: transform 0.3s cubic-bezier(0.3, 2, 0.5, 1) 0.7s; }
.cb-catbox__side--l { transform-origin: 100% 100%; }
.cb-catbox__side--r { transform-origin: 0 100%; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__side--l { transform: rotate(-8deg); }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__side--r { transform: rotate(8deg); }
.cb-catbox__tape { fill: var(--tape); }
.cb-catbox__square { fill: none; stroke: var(--print); stroke-width: 2.4; }
.cb-catbox__small { fill: var(--print); font: 700 7.5px system-ui, sans-serif; letter-spacing: 0.08em; text-anchor: middle; }
.cb-catbox__tick { fill: none; stroke: var(--print); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.12s; }
.cb-catbox__input:checked + .cb-catbox__art .cb-catbox__tick { stroke-dashoffset: 0; transition: stroke-dashoffset 0.3s ease-out 0.82s; }
@media (prefers-reduced-motion: reduce) {
.cb-catbox *, .cb-catbox[data-armed] .cb-catbox__input + .cb-catbox__art * { animation: none !important; transition: none !important; }
}function initCbCatbox(root) {
var input = root.querySelector('.cb-catbox__input');
// The leaps only play when someone ticks it, not on load.
function onChange() {
root.setAttribute('data-armed', 'true');
}
input.addEventListener('change', onChange);
return function () {
input.removeEventListener('change', onChange);
};
}
document.querySelectorAll('.cb-catbox').forEach(initCbCatbox);Goal: Create a "Cat in a box" checkbox.
Visual behavior: A real pun on check box. A cat sits watching an empty carton; tick it and the cat crouches, leaps in with a thump and peeks over the rim while a tick prints itself on the carton.
Technique:
- The cat is drawn between the back and the front of the carton, so climbing in hides its body without a mask. One long keyframe carries the whole leap: crouch, stretch, an arc through the air, a squash on landing and a small rise to peek out. A second keyframe brings it back out.
- A data-armed flag goes on at the first change, so neither leap plays on load. The landing thumps the carton with a small overshoot, then the tick draws in by its dash offset. The tail and the blink loop on their own.
Accessibility: A native checkbox inside a <label>, so "Add a gift box" is its name. The drawing is aria-hidden and keyboard focus outlines it. Under prefers-reduced-motion the cat is simply in or out of the box and the tick appears at once.
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 "Cat in a box" checkbox.
Visual behavior: A real pun on check box. A cat sits watching an empty carton; tick it and the cat crouches, leaps in with a thump and peeks over the rim while a tick prints itself on the carton.
Technique:
- The cat is drawn between the back and the front of the carton, so climbing in hides its body without a mask. One long keyframe carries the whole leap: crouch, stretch, an arc through the air, a squash on landing and a small rise to peek out. A second keyframe brings it back out.
- A data-armed flag goes on at the first change, so neither leap plays on load. The landing thumps the carton with a small overshoot, then the tick draws in by its dash offset. The tail and the blink loop on their own.
Accessibility: A native checkbox inside a <label>, so "Add a gift box" is its name. The drawing is aria-hidden and keyboard focus outlines it. Under prefers-reduced-motion the cat is simply in or out of the box and the tick appears at once.
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 real pun on check box. A cat sits watching an empty carton; tick it and the cat crouches, leaps in with a thump and peeks over the rim while a tick prints itself on the carton.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- click, state change, keyframe, draw, ambient
- Techniques
- keyframes, svg, stroke dashoffset, css transition, css variables
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A native checkbox inside a <label>, so "Add a gift box" is its name. The drawing is aria-hidden and keyboard focus outlines it. Under prefers-reduced-motion the cat is simply in or out of the box and the tick appears at once.