~/my-app
$ npm create movin@latest? Which framework? ↑↓
❯
◇Scaffolding React in ./my-appScaffolding Vue in ./my-appScaffolding Svelte in ./my-appScaffolding Solid in ./my-appimport { useId } from 'react';
// Box sizes in px for the drawing, and the label's real dimensions in cm.
const SIZES = [
{ value: 's', label: 'S', cm: '20×15×15', w: 58, h: 40, d: 42 },
{ value: 'm', label: 'M', cm: '30×20×20', w: 76, h: 50, d: 52 },
{ value: 'l', label: 'L', cm: '40×25×25', w: 90, h: 56, d: 56 },
{ value: 'xl', label: 'XL', cm: '50×30×30', w: 104, h: 62, d: 60 },
];
const at = (n: number) => `.radio-carton:has(.radio-carton__opt:nth-child(${n + 1}) input:checked)`;
const FOLD = 1.6;
const css = `
@property --radio-carton-w { syntax: '<length>'; inherits: true; initial-value: 76px; }
@property --radio-carton-h { syntax: '<length>'; inherits: true; initial-value: 50px; }
@property --radio-carton-d { syntax: '<length>'; inherits: true; initial-value: 52px; }
.radio-carton { --kraft: light-dark(#6d7179, #7d8087); --kraft-hi: light-dark(#cbced3, #a7abb2); --stencil: light-dark(#373a40, #1a1c1f); display: grid; justify-items: center; gap: 4px; transition: --radio-carton-w 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s, --radio-carton-h 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s, --radio-carton-d 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s; font-family: Stencil, 'Stencil Std', Impact, 'Arial Black', sans-serif; }
${SIZES.map((s, i) => `${at(i)} { --radio-carton-w: ${s.w}px; --radio-carton-h: ${s.h}px; --radio-carton-d: ${s.d}px; }`).join('\n ')}
.radio-carton__scene { position: relative; width: 262px; height: 168px; perspective: 800px; }
.radio-carton__floor { position: absolute; left: 50%; top: 150px; width: calc(var(--radio-carton-w) * 1.7); height: 12px; translate: -50% -50%; border-radius: 50%; box-shadow: inset 0 0 0 40px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.45)); filter: blur(7px); }
.radio-carton__box { --w: var(--radio-carton-w); --h: var(--radio-carton-h); --d: var(--radio-carton-d); position: absolute; left: 50%; top: calc(146px - var(--h) / 2 - var(--d) * 0.2); width: 0; height: 0; transform-style: preserve-3d; transform: rotateX(-26deg) rotateY(-38deg); }
.radio-carton__face { position: absolute; left: 0; top: 0; transform-style: preserve-3d; background: repeating-linear-gradient(90deg, transparent 0 3px, color-mix(in oklab, var(--kraft) 88%, light-dark(#fbfcfd, #121417)) 3px 4px), var(--kraft); }
.radio-carton__face--front { width: var(--w); height: var(--h); transform: translate(-50%, -50%) translateZ(calc(var(--d) / 2)); }
.radio-carton__face--back { width: var(--w); height: var(--h); transform: translate(-50%, -50%) rotateY(180deg) translateZ(calc(var(--d) / 2)); }
.radio-carton__face--right { width: var(--d); height: var(--h); transform: translate(-50%, -50%) rotateY(90deg) translateZ(calc(var(--w) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0.22)); }
.radio-carton__face--left { width: var(--d); height: var(--h); transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--w) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.3)); }
.radio-carton__face--bottom { width: var(--w); height: var(--d); transform: translate(-50%, -50%) rotateX(-90deg) translateZ(calc(var(--h) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.4)); }
/* Flaps hang off each wall's top edge and fold inward; the long ones go on last. */
.radio-carton__flap { position: absolute; left: 0; bottom: 100%; width: 100%; transform-origin: 50% 100%; background: var(--kraft-hi); box-shadow: inset 0 -1px 0 light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.25)); transform: rotateX(90deg); }
.radio-carton__flap--major { height: calc(var(--d) / 2); transform: translateY(-0.6px) rotateX(90deg); }
.radio-carton__flap--minor { height: calc(var(--w) * 0.36); }
.radio-carton__face--right .radio-carton__flap, .radio-carton__face--left .radio-carton__flap { box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.12)); }
.radio-carton__tape { position: absolute; left: -2px; right: -2px; top: -5px; height: 11px; background: color-mix(in oklab, var(--kraft-hi) 50%, light-dark(#373a40, #f1f2f3)); opacity: 0.85; transform-origin: 0 50%; transform: scaleX(1); }
${SIZES.map((_, i) => `${at(i)} .radio-carton__flap--major { animation: radio-carton-major${i} ${FOLD}s cubic-bezier(0.45, 0, 0.3, 1); }\n ${at(i)} .radio-carton__flap--minor { animation: radio-carton-minor${i} ${FOLD}s cubic-bezier(0.45, 0, 0.3, 1); }\n ${at(i)} .radio-carton__tape { animation: radio-carton-tape${i} ${FOLD + 0.35}s cubic-bezier(0.3, 0, 0.2, 1); }\n @keyframes radio-carton-major${i} { 0% { transform: translateY(-0.6px) rotateX(90deg); } 14%, 74% { transform: translateY(-0.6px) rotateX(-32deg); } 93%, 100% { transform: translateY(-0.6px) rotateX(90deg); } }\n @keyframes radio-carton-minor${i} { 0%, 4% { transform: rotateX(90deg); } 20%, 58% { transform: rotateX(-22deg); } 74%, 100% { transform: rotateX(90deg); } }\n @keyframes radio-carton-tape${i} { 0%, 80% { transform: scaleX(0); } 100% { transform: scaleX(1); } }`).join('\n ')}
.radio-carton__print { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; color: var(--stencil); }
.radio-carton__print > b { grid-area: 1 / 1; font-size: calc(var(--h) * 0.5); font-weight: 400; line-height: 1; opacity: 0; transition: opacity 0.2s 0.3s; }
.radio-carton__arrows { position: absolute; left: 5px; top: 4px; width: 12px; height: 10px; fill: none; stroke: var(--stencil); stroke-width: 1.6; }
.radio-carton__fragile { position: absolute; left: 50%; top: 50%; translate: -50% -50%; rotate: -12deg; padding: 1px 4px; border: 1.2px solid var(--stencil); color: var(--stencil); font-size: 8px; letter-spacing: 0.08em; white-space: nowrap; }
${SIZES.map((_, i) => `${at(i)} .radio-carton__print > :nth-child(${i + 1})`).join(', ')} { opacity: 1; }
.radio-carton__opts { display: flex; gap: 6px; }
.radio-carton__opt { position: relative; display: grid; justify-items: center; min-width: 54px; padding: 5px 2px 4px; border: 1.5px dashed transparent; border-radius: 4px; color: light-dark(#6d7179, #7d8087); cursor: pointer; transition: color 0.25s, border-color 0.25s; }
.radio-carton__opt:hover { color: light-dark(#42454c, #e5e6e9); }
.radio-carton__opt:has(input:checked) { color: light-dark(#373a40, #f1f2f3); border-color: light-dark(#6d7179, #7d8087); }
.radio-carton__opt:has(input:focus-visible) { outline: 1.5px solid light-dark(#50535b, #c6c8cd); outline-offset: 2px; }
.radio-carton__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.radio-carton__opt b { font-size: 16px; font-weight: 400; line-height: 1; }
.radio-carton__opt small { margin-top: 2px; font: 600 8.5px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
@media (prefers-reduced-motion: reduce) {
.radio-carton, .radio-carton * { animation: none !important; transition: none !important; }
}
`;
export function Preview() {
const name = useId();
return (
<>
<style href="kit-radios-carton" precedence="kit">
{css}
</style>
<div className="radio-carton">
<div className="radio-carton__scene" aria-hidden="true">
<span className="radio-carton__floor" />
<span className="radio-carton__box">
<span className="radio-carton__face radio-carton__face--bottom" />
<span className="radio-carton__face radio-carton__face--back">
<span className="radio-carton__flap radio-carton__flap--major" />
</span>
<span className="radio-carton__face radio-carton__face--left">
<span className="radio-carton__flap radio-carton__flap--minor" />
</span>
<span className="radio-carton__face radio-carton__face--right">
<span className="radio-carton__flap radio-carton__flap--minor" />
<span className="radio-carton__fragile">FRAGILE</span>
</span>
<span className="radio-carton__face radio-carton__face--front">
<span className="radio-carton__print">
{SIZES.map((s) => (
<b key={s.value}>{s.label}</b>
))}
</span>
<svg className="radio-carton__arrows" viewBox="0 0 12 10">
<path d="M3 9V2M1 4L3 1.5L5 4M9 9V2M7 4L9 1.5L11 4" />
</svg>
<span className="radio-carton__flap radio-carton__flap--major">
<span className="radio-carton__tape" />
</span>
</span>
</span>
</div>
<div className="radio-carton__opts" role="radiogroup" aria-label="Box size">
{SIZES.map((s, i) => (
<label key={s.value} className="radio-carton__opt">
<input className="radio-carton__input" type="radio" name={name} value={s.value} aria-label={`${s.label}, ${s.cm} centimetres`} defaultChecked={i === 1} />
<b aria-hidden="true">{s.label}</b>
<small aria-hidden="true">{s.cm}</small>
</label>
))}
</div>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<div class="radio-carton">
<div class="radio-carton__scene" aria-hidden="true">
<span class="radio-carton__floor"></span>
<span class="radio-carton__box">
<span class="radio-carton__face radio-carton__face--bottom"></span>
<span class="radio-carton__face radio-carton__face--back">
<span class="radio-carton__flap radio-carton__flap--major"></span>
</span>
<span class="radio-carton__face radio-carton__face--left">
<span class="radio-carton__flap radio-carton__flap--minor"></span>
</span>
<span class="radio-carton__face radio-carton__face--right">
<span class="radio-carton__flap radio-carton__flap--minor"></span>
<span class="radio-carton__fragile">FRAGILE</span>
</span>
<span class="radio-carton__face radio-carton__face--front">
<span class="radio-carton__print">
<b>S</b>
<b>M</b>
<b>L</b>
<b>XL</b>
</span>
<svg class="radio-carton__arrows" viewBox="0 0 12 10">
<path d="M3 9V2M1 4L3 1.5L5 4M9 9V2M7 4L9 1.5L11 4"></path>
</svg>
<span class="radio-carton__flap radio-carton__flap--major">
<span class="radio-carton__tape"></span>
</span>
</span>
</span>
</div>
<div class="radio-carton__opts" role="radiogroup" aria-label="Box size">
<label class="radio-carton__opt">
<input class="radio-carton__input" type="radio" aria-label="S, 20×15×15 centimetres" name="radio-carton" value="s"/>
<b aria-hidden="true">S</b>
<small aria-hidden="true">20×15×15</small>
</label>
<label class="radio-carton__opt">
<input class="radio-carton__input" type="radio" aria-label="M, 30×20×20 centimetres" name="radio-carton" checked="" value="m"/>
<b aria-hidden="true">M</b>
<small aria-hidden="true">30×20×20</small>
</label>
<label class="radio-carton__opt">
<input class="radio-carton__input" type="radio" aria-label="L, 40×25×25 centimetres" name="radio-carton" value="l"/>
<b aria-hidden="true">L</b>
<small aria-hidden="true">40×25×25</small>
</label>
<label class="radio-carton__opt">
<input class="radio-carton__input" type="radio" aria-label="XL, 50×30×30 centimetres" name="radio-carton" value="xl"/>
<b aria-hidden="true">XL</b>
<small aria-hidden="true">50×30×30</small>
</label>
</div>
</div>@property --radio-carton-w { syntax: '<length>'; inherits: true; initial-value: 76px; }
@property --radio-carton-h { syntax: '<length>'; inherits: true; initial-value: 50px; }
@property --radio-carton-d { syntax: '<length>'; inherits: true; initial-value: 52px; }
.radio-carton { --kraft: light-dark(#6d7179, #7d8087); --kraft-hi: light-dark(#cbced3, #a7abb2); --stencil: light-dark(#373a40, #1a1c1f); display: grid; justify-items: center; gap: 4px; transition: --radio-carton-w 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s, --radio-carton-h 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s, --radio-carton-d 0.55s cubic-bezier(0.5, 0, 0.2, 1) 0.32s; font-family: Stencil, 'Stencil Std', Impact, 'Arial Black', sans-serif; }
.radio-carton:has(.radio-carton__opt:nth-child(1) input:checked) { --radio-carton-w: 58px; --radio-carton-h: 40px; --radio-carton-d: 42px; }
.radio-carton:has(.radio-carton__opt:nth-child(2) input:checked) { --radio-carton-w: 76px; --radio-carton-h: 50px; --radio-carton-d: 52px; }
.radio-carton:has(.radio-carton__opt:nth-child(3) input:checked) { --radio-carton-w: 90px; --radio-carton-h: 56px; --radio-carton-d: 56px; }
.radio-carton:has(.radio-carton__opt:nth-child(4) input:checked) { --radio-carton-w: 104px; --radio-carton-h: 62px; --radio-carton-d: 60px; }
.radio-carton__scene { position: relative; width: 262px; height: 168px; perspective: 800px; }
.radio-carton__floor { position: absolute; left: 50%; top: 150px; width: calc(var(--radio-carton-w) * 1.7); height: 12px; translate: -50% -50%; border-radius: 50%; box-shadow: inset 0 0 0 40px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.45)); filter: blur(7px); }
.radio-carton__box { --w: var(--radio-carton-w); --h: var(--radio-carton-h); --d: var(--radio-carton-d); position: absolute; left: 50%; top: calc(146px - var(--h) / 2 - var(--d) * 0.2); width: 0; height: 0; transform-style: preserve-3d; transform: rotateX(-26deg) rotateY(-38deg); }
.radio-carton__face { position: absolute; left: 0; top: 0; transform-style: preserve-3d; background: repeating-linear-gradient(90deg, transparent 0 3px, color-mix(in oklab, var(--kraft) 88%, light-dark(#fbfcfd, #121417)) 3px 4px), var(--kraft); }
.radio-carton__face--front { width: var(--w); height: var(--h); transform: translate(-50%, -50%) translateZ(calc(var(--d) / 2)); }
.radio-carton__face--back { width: var(--w); height: var(--h); transform: translate(-50%, -50%) rotateY(180deg) translateZ(calc(var(--d) / 2)); }
.radio-carton__face--right { width: var(--d); height: var(--h); transform: translate(-50%, -50%) rotateY(90deg) translateZ(calc(var(--w) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0.22)); }
.radio-carton__face--left { width: var(--d); height: var(--h); transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--w) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.3)); }
.radio-carton__face--bottom { width: var(--w); height: var(--d); transform: translate(-50%, -50%) rotateX(-90deg) translateZ(calc(var(--h) / 2)); box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.4)); }
/* Flaps hang off each wall's top edge and fold inward; the long ones go on last. */
.radio-carton__flap { position: absolute; left: 0; bottom: 100%; width: 100%; transform-origin: 50% 100%; background: var(--kraft-hi); box-shadow: inset 0 -1px 0 light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.25)); transform: rotateX(90deg); }
.radio-carton__flap--major { height: calc(var(--d) / 2); transform: translateY(-0.6px) rotateX(90deg); }
.radio-carton__flap--minor { height: calc(var(--w) * 0.36); }
.radio-carton__face--right .radio-carton__flap, .radio-carton__face--left .radio-carton__flap { box-shadow: inset 0 0 0 200px light-dark(rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.12)); }
.radio-carton__tape { position: absolute; left: -2px; right: -2px; top: -5px; height: 11px; background: color-mix(in oklab, var(--kraft-hi) 50%, light-dark(#373a40, #f1f2f3)); opacity: 0.85; transform-origin: 0 50%; transform: scaleX(1); }
.radio-carton:has(.radio-carton__opt:nth-child(1) input:checked) .radio-carton__flap--major { animation: radio-carton-major0 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(1) input:checked) .radio-carton__flap--minor { animation: radio-carton-minor0 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(1) input:checked) .radio-carton__tape { animation: radio-carton-tape0 1.9500000000000002s cubic-bezier(0.3, 0, 0.2, 1); }
@keyframes radio-carton-major0 { 0% { transform: translateY(-0.6px) rotateX(90deg); } 14%, 74% { transform: translateY(-0.6px) rotateX(-32deg); } 93%, 100% { transform: translateY(-0.6px) rotateX(90deg); } }
@keyframes radio-carton-minor0 { 0%, 4% { transform: rotateX(90deg); } 20%, 58% { transform: rotateX(-22deg); } 74%, 100% { transform: rotateX(90deg); } }
@keyframes radio-carton-tape0 { 0%, 80% { transform: scaleX(0); } 100% { transform: scaleX(1); } }
.radio-carton:has(.radio-carton__opt:nth-child(2) input:checked) .radio-carton__flap--major { animation: radio-carton-major1 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(2) input:checked) .radio-carton__flap--minor { animation: radio-carton-minor1 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(2) input:checked) .radio-carton__tape { animation: radio-carton-tape1 1.9500000000000002s cubic-bezier(0.3, 0, 0.2, 1); }
@keyframes radio-carton-major1 { 0% { transform: translateY(-0.6px) rotateX(90deg); } 14%, 74% { transform: translateY(-0.6px) rotateX(-32deg); } 93%, 100% { transform: translateY(-0.6px) rotateX(90deg); } }
@keyframes radio-carton-minor1 { 0%, 4% { transform: rotateX(90deg); } 20%, 58% { transform: rotateX(-22deg); } 74%, 100% { transform: rotateX(90deg); } }
@keyframes radio-carton-tape1 { 0%, 80% { transform: scaleX(0); } 100% { transform: scaleX(1); } }
.radio-carton:has(.radio-carton__opt:nth-child(3) input:checked) .radio-carton__flap--major { animation: radio-carton-major2 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(3) input:checked) .radio-carton__flap--minor { animation: radio-carton-minor2 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(3) input:checked) .radio-carton__tape { animation: radio-carton-tape2 1.9500000000000002s cubic-bezier(0.3, 0, 0.2, 1); }
@keyframes radio-carton-major2 { 0% { transform: translateY(-0.6px) rotateX(90deg); } 14%, 74% { transform: translateY(-0.6px) rotateX(-32deg); } 93%, 100% { transform: translateY(-0.6px) rotateX(90deg); } }
@keyframes radio-carton-minor2 { 0%, 4% { transform: rotateX(90deg); } 20%, 58% { transform: rotateX(-22deg); } 74%, 100% { transform: rotateX(90deg); } }
@keyframes radio-carton-tape2 { 0%, 80% { transform: scaleX(0); } 100% { transform: scaleX(1); } }
.radio-carton:has(.radio-carton__opt:nth-child(4) input:checked) .radio-carton__flap--major { animation: radio-carton-major3 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(4) input:checked) .radio-carton__flap--minor { animation: radio-carton-minor3 1.6s cubic-bezier(0.45, 0, 0.3, 1); }
.radio-carton:has(.radio-carton__opt:nth-child(4) input:checked) .radio-carton__tape { animation: radio-carton-tape3 1.9500000000000002s cubic-bezier(0.3, 0, 0.2, 1); }
@keyframes radio-carton-major3 { 0% { transform: translateY(-0.6px) rotateX(90deg); } 14%, 74% { transform: translateY(-0.6px) rotateX(-32deg); } 93%, 100% { transform: translateY(-0.6px) rotateX(90deg); } }
@keyframes radio-carton-minor3 { 0%, 4% { transform: rotateX(90deg); } 20%, 58% { transform: rotateX(-22deg); } 74%, 100% { transform: rotateX(90deg); } }
@keyframes radio-carton-tape3 { 0%, 80% { transform: scaleX(0); } 100% { transform: scaleX(1); } }
.radio-carton__print { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; color: var(--stencil); }
.radio-carton__print > b { grid-area: 1 / 1; font-size: calc(var(--h) * 0.5); font-weight: 400; line-height: 1; opacity: 0; transition: opacity 0.2s 0.3s; }
.radio-carton__arrows { position: absolute; left: 5px; top: 4px; width: 12px; height: 10px; fill: none; stroke: var(--stencil); stroke-width: 1.6; }
.radio-carton__fragile { position: absolute; left: 50%; top: 50%; translate: -50% -50%; rotate: -12deg; padding: 1px 4px; border: 1.2px solid var(--stencil); color: var(--stencil); font-size: 8px; letter-spacing: 0.08em; white-space: nowrap; }
.radio-carton:has(.radio-carton__opt:nth-child(1) input:checked) .radio-carton__print > :nth-child(1), .radio-carton:has(.radio-carton__opt:nth-child(2) input:checked) .radio-carton__print > :nth-child(2), .radio-carton:has(.radio-carton__opt:nth-child(3) input:checked) .radio-carton__print > :nth-child(3), .radio-carton:has(.radio-carton__opt:nth-child(4) input:checked) .radio-carton__print > :nth-child(4) { opacity: 1; }
.radio-carton__opts { display: flex; gap: 6px; }
.radio-carton__opt { position: relative; display: grid; justify-items: center; min-width: 54px; padding: 5px 2px 4px; border: 1.5px dashed transparent; border-radius: 4px; color: light-dark(#6d7179, #7d8087); cursor: pointer; transition: color 0.25s, border-color 0.25s; }
.radio-carton__opt:hover { color: light-dark(#42454c, #e5e6e9); }
.radio-carton__opt:has(input:checked) { color: light-dark(#373a40, #f1f2f3); border-color: light-dark(#6d7179, #7d8087); }
.radio-carton__opt:has(input:focus-visible) { outline: 1.5px solid light-dark(#50535b, #c6c8cd); outline-offset: 2px; }
.radio-carton__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.radio-carton__opt b { font-size: 16px; font-weight: 400; line-height: 1; }
.radio-carton__opt small { margin-top: 2px; font: 600 8.5px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
@media (prefers-reduced-motion: reduce) {
.radio-carton, .radio-carton * { animation: none !important; transition: none !important; }
}Goal: Create a "Carton" radio.
Visual behavior: A box size picker in 3D: the flaps open, the carton grows or shrinks to the new size, the short flaps fold in, the long ones close over them and a strip of tape runs across.
Technique:
- Native radio inputs sharing one name inside a labelled role="radiogroup", visually hidden but focusable; each option is a <label> wrapping its input, so the arrow keys and clicks both work.
- The scene reacts with :has() on the root, one selector per option (such as :has(.opt:nth-child(2) input:checked)), so no script is needed.
- The width, height and depth are registered custom properties (@property, <length>) that transition, and every face, flap and size print is calculated from them, so the box resizes smoothly as one object.
- Each flap hangs off its wall's top edge and folds inward with rotateX(90deg). One keyframe set per size, so they replay, opens them, holds while the box resizes, then closes the short flaps before the long ones; the tape scales in last.
Accessibility: A role="radiogroup" labelled "Box size" around native radio inputs sharing one name, so the arrow keys move the choice and Tab enters the group once. Each input is named with the size and its dimensions (such as "M, 30×20×20 centimetres"); the box is aria-hidden and focus outlines the option. Under prefers-reduced-motion the box changes size 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 "Carton" radio.
Visual behavior: A box size picker in 3D: the flaps open, the carton grows or shrinks to the new size, the short flaps fold in, the long ones close over them and a strip of tape runs across.
Technique:
- Native radio inputs sharing one name inside a labelled role="radiogroup", visually hidden but focusable; each option is a <label> wrapping its input, so the arrow keys and clicks both work.
- The scene reacts with :has() on the root, one selector per option (such as :has(.opt:nth-child(2) input:checked)), so no script is needed.
- The width, height and depth are registered custom properties (@property, <length>) that transition, and every face, flap and size print is calculated from them, so the box resizes smoothly as one object.
- Each flap hangs off its wall's top edge and folds inward with rotateX(90deg). One keyframe set per size, so they replay, opens them, holds while the box resizes, then closes the short flaps before the long ones; the tape scales in last.
Accessibility: A role="radiogroup" labelled "Box size" around native radio inputs sharing one name, so the arrow keys move the choice and Tab enters the group once. Each input is named with the size and its dimensions (such as "M, 30×20×20 centimetres"); the box is aria-hidden and focus outlines the option. Under prefers-reduced-motion the box changes size 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
A box size picker in 3D: the flaps open, the carton grows or shrinks to the new size, the short flaps fold in, the long ones close over them and a strip of tape runs across.
A role="radiogroup" labelled "Box size" around native radio inputs sharing one name, so the arrow keys move the choice and Tab enters the group once. Each input is named with the size and its dimensions (such as "M, 30×20×20 centimetres"); the box is aria-hidden and focus outlines the option. Under prefers-reduced-motion the box changes size at once.