Fold
React + Tailwind
const css = `
/* Fold 1 (0–22%): the bottom half folds up. Fold 2 (22–44%): the right half folds over.
Hold, then the same in reverse. */
@keyframes loader-fold-up {
0%, 4% { transform: rotateX(0deg); }
22%, 74% { transform: rotateX(178deg); }
92%, 100% { transform: rotateX(0deg); }
}
@keyframes loader-fold-over {
0%, 26% { transform: rotateY(0deg); }
44%, 56% { transform: rotateY(-178deg); }
74%, 100% { transform: rotateY(0deg); }
}
@keyframes loader-fold-shade {
0%, 4% { filter: brightness(1); }
13% { filter: brightness(0.55); }
22%, 74% { filter: brightness(0.8); }
83% { filter: brightness(0.55); }
92%, 100% { filter: brightness(1); }
}
.loader-fold { width: 72px; height: 72px; perspective: 260px; }
.loader-fold__paper { position: relative; width: 100%; height: 100%; transform-style: preserve-3d; transform: rotateX(18deg) rotateZ(-8deg); }
.loader-fold__q {
position: absolute;
width: 36px;
height: 36px;
background: light-dark(#50535b, #c6c8cd);
box-shadow: inset 0 0 0 1px light-dark(#373a40, #f1f2f3), 0 0 0 0.5px light-dark(#6d7179, #7d8087);
backface-visibility: visible;
}
.loader-fold__q--tl { left: 0; top: 0; background: light-dark(#42454c, #e5e6e9); }
.loader-fold__right {
position: absolute;
left: 36px;
top: 0;
width: 36px;
height: 72px;
transform-style: preserve-3d;
transform-origin: 0 50%;
animation: loader-fold-over 3.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.loader-fold__right .loader-fold__q--tr { left: 0; top: 0; }
.loader-fold__flap {
position: absolute;
top: 36px;
width: 36px;
height: 36px;
transform-style: preserve-3d;
transform-origin: 50% 0;
animation: loader-fold-up 3.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.loader-fold__flap .loader-fold__q { left: 0; top: 0; animation: loader-fold-shade 3.4s linear infinite; background: light-dark(#5f626a, #a7abb2); }
.loader-fold__flap--bl { left: 0; }
.loader-fold__right .loader-fold__flap { left: 0; }
@media (prefers-reduced-motion: reduce) {
.loader-fold__right, .loader-fold__flap, .loader-fold__flap .loader-fold__q { animation-duration: 10s; }
}
`;
export function FoldLoader() {
return (
<>
<style>
{css}
</style>
<div className="loader-fold" role="status" aria-label="Loading">
<div className="loader-fold__paper" aria-hidden="true">
<span className="loader-fold__q loader-fold__q--tl" />
<span className="loader-fold__flap loader-fold__flap--bl">
<span className="loader-fold__q" />
</span>
{/* The right half carries its own bottom flap, so fold 1 happens on both halves together. */}
<span className="loader-fold__right">
<span className="loader-fold__q loader-fold__q--tr" />
<span className="loader-fold__flap">
<span className="loader-fold__q" />
</span>
</span>
</div>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-fold" role="status" aria-label="Loading">
<div class="loader-fold__paper" aria-hidden="true">
<span class="loader-fold__q loader-fold__q--tl"></span>
<span class="loader-fold__flap loader-fold__flap--bl">
<span class="loader-fold__q"></span>
</span>
<span class="loader-fold__right">
<span class="loader-fold__q loader-fold__q--tr"></span>
<span class="loader-fold__flap">
<span class="loader-fold__q"></span>
</span>
</span>
</div>
</div>CSS
/* Fold 1 (0–22%): the bottom half folds up. Fold 2 (22–44%): the right half folds over.
Hold, then the same in reverse. */
@keyframes loader-fold-up {
0%, 4% { transform: rotateX(0deg); }
22%, 74% { transform: rotateX(178deg); }
92%, 100% { transform: rotateX(0deg); }
}
@keyframes loader-fold-over {
0%, 26% { transform: rotateY(0deg); }
44%, 56% { transform: rotateY(-178deg); }
74%, 100% { transform: rotateY(0deg); }
}
@keyframes loader-fold-shade {
0%, 4% { filter: brightness(1); }
13% { filter: brightness(0.55); }
22%, 74% { filter: brightness(0.8); }
83% { filter: brightness(0.55); }
92%, 100% { filter: brightness(1); }
}
.loader-fold { width: 72px; height: 72px; perspective: 260px; }
.loader-fold__paper { position: relative; width: 100%; height: 100%; transform-style: preserve-3d; transform: rotateX(18deg) rotateZ(-8deg); }
.loader-fold__q {
position: absolute;
width: 36px;
height: 36px;
background: light-dark(#50535b, #c6c8cd);
box-shadow: inset 0 0 0 1px light-dark(#373a40, #f1f2f3), 0 0 0 0.5px light-dark(#6d7179, #7d8087);
backface-visibility: visible;
}
.loader-fold__q--tl { left: 0; top: 0; background: light-dark(#42454c, #e5e6e9); }
.loader-fold__right {
position: absolute;
left: 36px;
top: 0;
width: 36px;
height: 72px;
transform-style: preserve-3d;
transform-origin: 0 50%;
animation: loader-fold-over 3.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.loader-fold__right .loader-fold__q--tr { left: 0; top: 0; }
.loader-fold__flap {
position: absolute;
top: 36px;
width: 36px;
height: 36px;
transform-style: preserve-3d;
transform-origin: 50% 0;
animation: loader-fold-up 3.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.loader-fold__flap .loader-fold__q { left: 0; top: 0; animation: loader-fold-shade 3.4s linear infinite; background: light-dark(#5f626a, #a7abb2); }
.loader-fold__flap--bl { left: 0; }
.loader-fold__right .loader-fold__flap { left: 0; }
@media (prefers-reduced-motion: reduce) {
.loader-fold__right, .loader-fold__flap, .loader-fold__flap .loader-fold__q { animation-duration: 10s; }
}Goal: Create a "Fold" loader.
Visual behavior: A square of paper folds in half from the bottom, then in half again from the right, holds, and opens back out, with each flap shading darker as it turns away from the light.
Technique:
- Four quarters in a preserve-3d paper. The bottom flaps hinge on their top edge (rotateX); the right half, which carries its own bottom flap, hinges on its left edge (rotateY).
- Both folds share one 3.4s timeline: bottom up to 178deg, then right over to -178deg, hold, then unfold in reverse order.
- A brightness keyframe on the flaps darkens them mid-fold, when they face away from the viewer, and settles at a softer shade when folded.
- The sheet is tilted rotateX(18deg) rotateZ(-8deg) so the depth reads.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the folding slows to 10s.
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 "Fold" loader.
Visual behavior: A square of paper folds in half from the bottom, then in half again from the right, holds, and opens back out, with each flap shading darker as it turns away from the light.
Technique:
- Four quarters in a preserve-3d paper. The bottom flaps hinge on their top edge (rotateX); the right half, which carries its own bottom flap, hinges on its left edge (rotateY).
- Both folds share one 3.4s timeline: bottom up to 178deg, then right over to -178deg, hold, then unfold in reverse order.
- A brightness keyframe on the flaps darkens them mid-fold, when they face away from the viewer, and settles at a softer shade when folded.
- The sheet is tilted rotateX(18deg) rotateZ(-8deg) so the depth reads.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the folding slows to 10s.
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 square of paper folds in half from the bottom, then in half again from the right, holds, and opens back out, with each flap shading darker as it turns away from the light.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- continuous, loop, 3d
- Techniques
- preserve 3d, 3d transform, keyframes, filter
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the folding slows to 10s.