Toaster
React + Tailwind
const css = `
/* The slices go down, toast while hidden, then pop clear and fall back to rest. */
@keyframes loader-toaster-slice {
0% { transform: translateY(0); animation-timing-function: ease-in; }
10%, 62% { transform: translateY(15px); animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
70% { transform: translateY(-22px); animation-timing-function: ease-in; }
80% { transform: translateY(0); animation-timing-function: ease-out; }
85% { transform: translateY(-4px); animation-timing-function: ease-in; }
90%, 100% { transform: translateY(0); }
}
/* Browned when they come up; swapped for fresh bread while out of sight. */
@keyframes loader-toaster-brown {
0%, 11% { fill: light-dark(#6d7179, #7d8087); }
12% { fill: light-dark(#ebedf0, #e5e6e9); }
62%, 100% { fill: light-dark(#6d7179, #7d8087); }
}
@keyframes loader-toaster-lever { 0% { transform: translateY(0); } 10%, 62% { transform: translateY(17px); } 66%, 100% { transform: translateY(0); } }
@keyframes loader-toaster-led { 0%, 10% { fill: light-dark(#f3f4f7, #1a1c1f); } 13%, 60% { fill: light-dark(#373a40, #f1f2f3); } 63%, 100% { fill: light-dark(#f3f4f7, #1a1c1f); } }
@keyframes loader-toaster-heat { 0%, 16% { opacity: 0; transform: translateY(4px); } 30%, 56% { opacity: 0.8; } 62%, 100% { opacity: 0; transform: translateY(-6px); } }
.loader-toaster { position: relative; width: 120px; height: 115px; }
.loader-toaster svg { width: 100%; height: 100%; overflow: visible; }
.loader-toaster__slice { animation: loader-toaster-slice 2.8s infinite; }
.loader-toaster__slice + .loader-toaster__slice { animation-delay: 0.05s; }
.loader-toaster__bread { stroke: light-dark(#5f626a, #a7abb2); stroke-width: 1.4; stroke-linejoin: round; animation: loader-toaster-brown 2.8s linear infinite; }
.loader-toaster__heat { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.4; stroke-linecap: round; opacity: 0; animation: loader-toaster-heat 2.8s ease-in-out infinite; }
.loader-toaster__body { fill: light-dark(#dbdde1, #2e3035); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; }
.loader-toaster__sheen { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 2; stroke-linecap: round; }
.loader-toaster__slot { stroke: light-dark(#f3f4f7, #1a1c1f); stroke-width: 2.2; stroke-linecap: round; }
.loader-toaster__lever { fill: light-dark(#50535b, #c6c8cd); animation: loader-toaster-lever 2.8s cubic-bezier(0.3, 0, 0.3, 1) infinite; }
.loader-toaster__led { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#6d7179, #7d8087); stroke-width: 0.8; animation: loader-toaster-led 2.8s linear infinite; }
.loader-toaster__dial { fill: light-dark(#cbced3, #393c41); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1; }
.loader-toaster__foot { fill: light-dark(#cbced3, #393c41); }
@media (prefers-reduced-motion: reduce) {
.loader-toaster__slice, .loader-toaster__bread, .loader-toaster__heat, .loader-toaster__lever, .loader-toaster__led { animation: none; }
.loader-toaster__bread { fill: light-dark(#6d7179, #7d8087); }
}
`;
// A slice of bread: a puffed crown over a straight body; the body sits down inside the toaster.
const BREAD = 'M3 32V14Q-1 12 0 7Q1 1 8 1Q12 -1 16 1Q23 1 24 7Q25 12 21 14V32Z';
export function ToasterLoader() {
return (
<>
<style>
{css}
</style>
<div className="loader-toaster" role="status" aria-label="Loading">
<svg viewBox="0 0 100 96" aria-hidden="true">
<path className="loader-toaster__heat" d="M34 40Q31 34 34 29Q37 24 34 18M50 41Q47 35 50 30Q53 25 50 19M66 40Q63 34 66 29Q69 24 66 18" />
<g transform="translate(25 34)">
<g className="loader-toaster__slice">
<path className="loader-toaster__bread" d={BREAD} />
</g>
</g>
<g transform="translate(51 34)">
<g className="loader-toaster__slice">
<path className="loader-toaster__bread" d={BREAD} />
</g>
</g>
<rect className="loader-toaster__body" x="12" y="46" width="76" height="40" rx="14" />
<path className="loader-toaster__sheen" d="M24 54Q48 51 68 54" />
<path className="loader-toaster__slot" d="M83 54V74" />
<rect className="loader-toaster__lever" x="77" y="50" width="12" height="5" rx="2" />
<circle className="loader-toaster__led" cx="22" cy="64" r="2.4" />
<circle className="loader-toaster__dial" cx="22" cy="76" r="4" />
<rect className="loader-toaster__foot" x="20" y="86" width="10" height="4" rx="1.5" />
<rect className="loader-toaster__foot" x="70" y="86" width="10" height="4" rx="1.5" />
</svg>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-toaster" role="status" aria-label="Loading">
<svg viewBox="0 0 100 96" aria-hidden="true">
<path class="loader-toaster__heat" d="M34 40Q31 34 34 29Q37 24 34 18M50 41Q47 35 50 30Q53 25 50 19M66 40Q63 34 66 29Q69 24 66 18"></path>
<g transform="translate(25 34)">
<g class="loader-toaster__slice">
<path class="loader-toaster__bread" d="M3 32V14Q-1 12 0 7Q1 1 8 1Q12 -1 16 1Q23 1 24 7Q25 12 21 14V32Z"></path>
</g>
</g>
<g transform="translate(51 34)">
<g class="loader-toaster__slice">
<path class="loader-toaster__bread" d="M3 32V14Q-1 12 0 7Q1 1 8 1Q12 -1 16 1Q23 1 24 7Q25 12 21 14V32Z"></path>
</g>
</g>
<rect class="loader-toaster__body" x="12" y="46" width="76" height="40" rx="14"></rect>
<path class="loader-toaster__sheen" d="M24 54Q48 51 68 54"></path>
<path class="loader-toaster__slot" d="M83 54V74"></path>
<rect class="loader-toaster__lever" x="77" y="50" width="12" height="5" rx="2"></rect>
<circle class="loader-toaster__led" cx="22" cy="64" r="2.4"></circle>
<circle class="loader-toaster__dial" cx="22" cy="76" r="4"></circle>
<rect class="loader-toaster__foot" x="20" y="86" width="10" height="4" rx="1.5"></rect>
<rect class="loader-toaster__foot" x="70" y="86" width="10" height="4" rx="1.5"></rect>
</svg>
</div>CSS
/* The slices go down, toast while hidden, then pop clear and fall back to rest. */
@keyframes loader-toaster-slice {
0% { transform: translateY(0); animation-timing-function: ease-in; }
10%, 62% { transform: translateY(15px); animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
70% { transform: translateY(-22px); animation-timing-function: ease-in; }
80% { transform: translateY(0); animation-timing-function: ease-out; }
85% { transform: translateY(-4px); animation-timing-function: ease-in; }
90%, 100% { transform: translateY(0); }
}
/* Browned when they come up; swapped for fresh bread while out of sight. */
@keyframes loader-toaster-brown {
0%, 11% { fill: light-dark(#6d7179, #7d8087); }
12% { fill: light-dark(#ebedf0, #e5e6e9); }
62%, 100% { fill: light-dark(#6d7179, #7d8087); }
}
@keyframes loader-toaster-lever { 0% { transform: translateY(0); } 10%, 62% { transform: translateY(17px); } 66%, 100% { transform: translateY(0); } }
@keyframes loader-toaster-led { 0%, 10% { fill: light-dark(#f3f4f7, #1a1c1f); } 13%, 60% { fill: light-dark(#373a40, #f1f2f3); } 63%, 100% { fill: light-dark(#f3f4f7, #1a1c1f); } }
@keyframes loader-toaster-heat { 0%, 16% { opacity: 0; transform: translateY(4px); } 30%, 56% { opacity: 0.8; } 62%, 100% { opacity: 0; transform: translateY(-6px); } }
.loader-toaster { position: relative; width: 120px; height: 115px; }
.loader-toaster svg { width: 100%; height: 100%; overflow: visible; }
.loader-toaster__slice { animation: loader-toaster-slice 2.8s infinite; }
.loader-toaster__slice + .loader-toaster__slice { animation-delay: 0.05s; }
.loader-toaster__bread { stroke: light-dark(#5f626a, #a7abb2); stroke-width: 1.4; stroke-linejoin: round; animation: loader-toaster-brown 2.8s linear infinite; }
.loader-toaster__heat { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.4; stroke-linecap: round; opacity: 0; animation: loader-toaster-heat 2.8s ease-in-out infinite; }
.loader-toaster__body { fill: light-dark(#dbdde1, #2e3035); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; }
.loader-toaster__sheen { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 2; stroke-linecap: round; }
.loader-toaster__slot { stroke: light-dark(#f3f4f7, #1a1c1f); stroke-width: 2.2; stroke-linecap: round; }
.loader-toaster__lever { fill: light-dark(#50535b, #c6c8cd); animation: loader-toaster-lever 2.8s cubic-bezier(0.3, 0, 0.3, 1) infinite; }
.loader-toaster__led { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#6d7179, #7d8087); stroke-width: 0.8; animation: loader-toaster-led 2.8s linear infinite; }
.loader-toaster__dial { fill: light-dark(#cbced3, #393c41); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1; }
.loader-toaster__foot { fill: light-dark(#cbced3, #393c41); }
@media (prefers-reduced-motion: reduce) {
.loader-toaster__slice, .loader-toaster__bread, .loader-toaster__heat, .loader-toaster__lever, .loader-toaster__led { animation: none; }
.loader-toaster__bread { fill: light-dark(#6d7179, #7d8087); }
}Goal: Create a "Toaster" loader.
Visual behavior: Two slices sink into a toaster as the lever goes down, the light comes on and heat shimmers over the slots, then the toast pops up browned and drops back to rest.
Technique:
- The slices are drawn behind the body, so pushing them down hides them. One keyframe sinks them, holds, pops them clear and lets them fall back with a small bounce.
- Their fill is swapped back to pale bread while they are out of sight and browns while they toast. The lever, the light and the heat shimmer run on the same 2.8s cycle.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the toast sits still.
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 "Toaster" loader.
Visual behavior: Two slices sink into a toaster as the lever goes down, the light comes on and heat shimmers over the slots, then the toast pops up browned and drops back to rest.
Technique:
- The slices are drawn behind the body, so pushing them down hides them. One keyframe sinks them, holds, pops them clear and lets them fall back with a small bounce.
- Their fill is swapped back to pale bread while they are out of sight and browns while they toast. The lever, the light and the heat shimmer run on the same 2.8s cycle.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the toast sits still.
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
Two slices sink into a toaster as the lever goes down, the light comes on and heat shimmers over the slots, then the toast pops up browned and drops back to rest.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- continuous, loop
- Techniques
- keyframes, svg, transform only
- 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 toast sits still.