Wireframe
React + Tailwind
const css = `
.btn-wireframe {
--w: 170px;
--h: 50px;
--d: 26px;
position: relative;
width: var(--w);
height: var(--h);
padding: 0;
border: none;
background: none;
font-family: inherit;
cursor: pointer;
perspective: 600px;
}
.btn-wireframe:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 14px; border-radius: 8px; }
.btn-wireframe__box {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: rotateX(-24deg) rotateY(32deg);
transition: transform 0.8s cubic-bezier(0.34, 1.3, 0.64, 1);
}
.btn-wireframe__face {
position: absolute;
display: grid;
place-items: center;
border: 1px solid light-dark(#5f626a, #a7abb2);
background: transparent;
color: transparent;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
backface-visibility: visible;
transition: background-color 0.35s ease var(--t, 0s), border-color 0.35s ease var(--t, 0s), color 0.35s ease var(--t, 0s);
}
.btn-wireframe__face--front, .btn-wireframe__face--back { width: var(--w); height: var(--h); }
.btn-wireframe__face--left, .btn-wireframe__face--right { width: var(--d); height: var(--h); left: calc((var(--w) - var(--d)) / 2); }
.btn-wireframe__face--top, .btn-wireframe__face--bottom { width: var(--w); height: var(--d); top: calc((var(--h) - var(--d)) / 2); }
.btn-wireframe__face--front { transform: translateZ(calc(var(--d) / 2)); color: light-dark(#50535b, #c6c8cd); --t: 0.3s; }
.btn-wireframe__face--back { transform: rotateY(180deg) translateZ(calc(var(--d) / 2)); border-style: dashed; --t: 0s; }
.btn-wireframe__face--right { transform: rotateY(90deg) translateZ(calc(var(--w) / 2)); --t: 0.1s; }
.btn-wireframe__face--left { transform: rotateY(-90deg) translateZ(calc(var(--w) / 2)); border-style: dashed; --t: 0.05s; }
.btn-wireframe__face--top { transform: rotateX(90deg) translateZ(calc(var(--h) / 2)); --t: 0.15s; }
.btn-wireframe__face--bottom { transform: rotateX(-90deg) translateZ(calc(var(--h) / 2)); border-style: dashed; --t: 0.2s; }
/* Materialise: the slab squares up to the viewer and its faces fill in one after another. */
.btn-wireframe:hover .btn-wireframe__box,
.btn-wireframe:focus-visible .btn-wireframe__box { transform: rotateX(-8deg) rotateY(0deg); }
.btn-wireframe:hover .btn-wireframe__face,
.btn-wireframe:focus-visible .btn-wireframe__face { border-color: light-dark(#cbced3, #393c41); border-style: solid; }
.btn-wireframe:hover .btn-wireframe__face--front,
.btn-wireframe:focus-visible .btn-wireframe__face--front { background: light-dark(#ebedf0, #232529); color: light-dark(#373a40, #f1f2f3); border-color: light-dark(#6d7179, #7d8087); }
.btn-wireframe:hover .btn-wireframe__face--top,
.btn-wireframe:focus-visible .btn-wireframe__face--top { background: light-dark(#dbdde1, #2e3035); }
.btn-wireframe:hover .btn-wireframe__face--bottom,
.btn-wireframe:focus-visible .btn-wireframe__face--bottom { background: light-dark(#fbfcfd, #121417); }
.btn-wireframe:hover .btn-wireframe__face--left,
.btn-wireframe:hover .btn-wireframe__face--right,
.btn-wireframe:focus-visible .btn-wireframe__face--left,
.btn-wireframe:focus-visible .btn-wireframe__face--right { background: light-dark(#f3f4f7, #1a1c1f); }
.btn-wireframe:hover .btn-wireframe__face--back,
.btn-wireframe:focus-visible .btn-wireframe__face--back { background: light-dark(#f3f4f7, #1a1c1f); }
@media (prefers-reduced-motion: reduce) {
.btn-wireframe__box { transition: none; }
}
`;
export function WireframeButton() {
return (
<>
<style>
{css}
</style>
<button type="button" className="btn-wireframe" aria-label="Render">
<span className="btn-wireframe__box" aria-hidden="true">
<span className="btn-wireframe__face btn-wireframe__face--back" />
<span className="btn-wireframe__face btn-wireframe__face--left" />
<span className="btn-wireframe__face btn-wireframe__face--right" />
<span className="btn-wireframe__face btn-wireframe__face--top" />
<span className="btn-wireframe__face btn-wireframe__face--bottom" />
<span className="btn-wireframe__face btn-wireframe__face--front">Render</span>
</span>
</button>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<button type="button" class="btn-wireframe" aria-label="Render">
<span class="btn-wireframe__box" aria-hidden="true">
<span class="btn-wireframe__face btn-wireframe__face--back"></span>
<span class="btn-wireframe__face btn-wireframe__face--left"></span>
<span class="btn-wireframe__face btn-wireframe__face--right"></span>
<span class="btn-wireframe__face btn-wireframe__face--top"></span>
<span class="btn-wireframe__face btn-wireframe__face--bottom"></span>
<span class="btn-wireframe__face btn-wireframe__face--front">Render</span>
</span>
</button>CSS
.btn-wireframe {
--w: 170px;
--h: 50px;
--d: 26px;
position: relative;
width: var(--w);
height: var(--h);
padding: 0;
border: none;
background: none;
font-family: inherit;
cursor: pointer;
perspective: 600px;
}
.btn-wireframe:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 14px; border-radius: 8px; }
.btn-wireframe__box {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: rotateX(-24deg) rotateY(32deg);
transition: transform 0.8s cubic-bezier(0.34, 1.3, 0.64, 1);
}
.btn-wireframe__face {
position: absolute;
display: grid;
place-items: center;
border: 1px solid light-dark(#5f626a, #a7abb2);
background: transparent;
color: transparent;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
backface-visibility: visible;
transition: background-color 0.35s ease var(--t, 0s), border-color 0.35s ease var(--t, 0s), color 0.35s ease var(--t, 0s);
}
.btn-wireframe__face--front, .btn-wireframe__face--back { width: var(--w); height: var(--h); }
.btn-wireframe__face--left, .btn-wireframe__face--right { width: var(--d); height: var(--h); left: calc((var(--w) - var(--d)) / 2); }
.btn-wireframe__face--top, .btn-wireframe__face--bottom { width: var(--w); height: var(--d); top: calc((var(--h) - var(--d)) / 2); }
.btn-wireframe__face--front { transform: translateZ(calc(var(--d) / 2)); color: light-dark(#50535b, #c6c8cd); --t: 0.3s; }
.btn-wireframe__face--back { transform: rotateY(180deg) translateZ(calc(var(--d) / 2)); border-style: dashed; --t: 0s; }
.btn-wireframe__face--right { transform: rotateY(90deg) translateZ(calc(var(--w) / 2)); --t: 0.1s; }
.btn-wireframe__face--left { transform: rotateY(-90deg) translateZ(calc(var(--w) / 2)); border-style: dashed; --t: 0.05s; }
.btn-wireframe__face--top { transform: rotateX(90deg) translateZ(calc(var(--h) / 2)); --t: 0.15s; }
.btn-wireframe__face--bottom { transform: rotateX(-90deg) translateZ(calc(var(--h) / 2)); border-style: dashed; --t: 0.2s; }
/* Materialise: the slab squares up to the viewer and its faces fill in one after another. */
.btn-wireframe:hover .btn-wireframe__box,
.btn-wireframe:focus-visible .btn-wireframe__box { transform: rotateX(-8deg) rotateY(0deg); }
.btn-wireframe:hover .btn-wireframe__face,
.btn-wireframe:focus-visible .btn-wireframe__face { border-color: light-dark(#cbced3, #393c41); border-style: solid; }
.btn-wireframe:hover .btn-wireframe__face--front,
.btn-wireframe:focus-visible .btn-wireframe__face--front { background: light-dark(#ebedf0, #232529); color: light-dark(#373a40, #f1f2f3); border-color: light-dark(#6d7179, #7d8087); }
.btn-wireframe:hover .btn-wireframe__face--top,
.btn-wireframe:focus-visible .btn-wireframe__face--top { background: light-dark(#dbdde1, #2e3035); }
.btn-wireframe:hover .btn-wireframe__face--bottom,
.btn-wireframe:focus-visible .btn-wireframe__face--bottom { background: light-dark(#fbfcfd, #121417); }
.btn-wireframe:hover .btn-wireframe__face--left,
.btn-wireframe:hover .btn-wireframe__face--right,
.btn-wireframe:focus-visible .btn-wireframe__face--left,
.btn-wireframe:focus-visible .btn-wireframe__face--right { background: light-dark(#f3f4f7, #1a1c1f); }
.btn-wireframe:hover .btn-wireframe__face--back,
.btn-wireframe:focus-visible .btn-wireframe__face--back { background: light-dark(#f3f4f7, #1a1c1f); }
@media (prefers-reduced-motion: reduce) {
.btn-wireframe__box { transition: none; }
}Goal: Create a "Wireframe" button.
Visual behavior: A 3D slab drawn in wire, turned at an angle with dashed hidden edges. On hover it squares up to face you and materialises face by face, each side filling in a beat after the last.
Technique:
- Six faces form a box with transform-style: preserve-3d; sizes come from --w, --h and --d, and each face is rotated and pushed out by half the relevant dimension.
- At rest every face is a bare 1px outline, with the back, left and bottom dashed as hidden lines, and the box is turned rotateX(-24deg) rotateY(32deg).
- On hover the box springs to rotateX(-8deg) rotateY(0) and the faces fill with different surface tones; each face has its own --t transition-delay, so they materialise in sequence and the label lands last.
Accessibility: A real <button> named by aria-label; the 3D faces are aria-hidden. Focus materialises it like hover. Under prefers-reduced-motion it does not rotate.
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 "Wireframe" button.
Visual behavior: A 3D slab drawn in wire, turned at an angle with dashed hidden edges. On hover it squares up to face you and materialises face by face, each side filling in a beat after the last.
Technique:
- Six faces form a box with transform-style: preserve-3d; sizes come from --w, --h and --d, and each face is rotated and pushed out by half the relevant dimension.
- At rest every face is a bare 1px outline, with the back, left and bottom dashed as hidden lines, and the box is turned rotateX(-24deg) rotateY(32deg).
- On hover the box springs to rotateX(-8deg) rotateY(0) and the faces fill with different surface tones; each face has its own --t transition-delay, so they materialise in sequence and the label lands last.
Accessibility: A real <button> named by aria-label; the 3D faces are aria-hidden. Focus materialises it like hover. Under prefers-reduced-motion it does not rotate.
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 3D slab drawn in wire, turned at an angle with dashed hidden edges. On hover it squares up to face you and materialises face by face, each side filling in a beat after the last.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- hover, 3d, fill, delay stagger
- Techniques
- preserve 3d, 3d transform, css variables, transition delay stagger
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A real <button> named by aria-label; the 3D faces are aria-hidden. Focus materialises it like hover. Under prefers-reduced-motion it does not rotate.