Receipt
React + Tailwind
import { useId } from 'react';
const css = `
/* The slip feeds out a line at a time, waits, gets a tug and is torn away to the left. */
@keyframes loader-printer-feed {
0% { transform: translateY(51px) rotate(0deg); opacity: 1; animation-timing-function: steps(12, end); }
58% { transform: translateY(0) rotate(0deg); opacity: 1; }
70% { transform: translateY(0) rotate(0deg); opacity: 1; animation-timing-function: ease-out; }
74% { transform: translateY(-3px) rotate(2deg); opacity: 1; animation-timing-function: ease-in; }
88% { transform: translate(-16px, -14px) rotate(-22deg); opacity: 0; }
88.1%, 100% { transform: translateY(51px) rotate(0deg); opacity: 0; }
}
@keyframes loader-printer-led { 0%, 56% { opacity: 1; } 28% { opacity: 0.35; } 60%, 100% { opacity: 0.2; } }
.loader-printer { position: relative; width: 104px; height: 113px; }
.loader-printer svg { width: 100%; height: 100%; overflow: visible; }
.loader-printer__slip { transform-origin: 45px 57px; animation: loader-printer-feed 3s infinite; }
/* Paper stays pale in both themes and the ink dark on it. */
.loader-printer__paper { fill: light-dark(#ebedf0, #e5e6e9); stroke: light-dark(#cbced3, #e5e6e9); stroke-width: 0.8; }
.loader-printer__ink { stroke: light-dark(#6d7179, #393c41); stroke-width: 2; stroke-linecap: round; }
.loader-printer__ink--thin { stroke-width: 1; stroke-dasharray: 2 2; stroke-linecap: butt; }
.loader-printer__ink--bold { stroke-width: 3; }
.loader-printer__top { fill: light-dark(#cbced3, #393c41); }
.loader-printer__body { fill: light-dark(#dbdde1, #2e3035); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; }
.loader-printer__slot { fill: light-dark(#fbfcfd, #121417); }
.loader-printer__led { fill: light-dark(#373a40, #f1f2f3); animation: loader-printer-led 3s steps(1, end) infinite; }
.loader-printer__key { fill: light-dark(#cbced3, #393c41); }
.loader-printer__grille { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.5; stroke-linecap: round; }
@media (prefers-reduced-motion: reduce) {
.loader-printer__slip, .loader-printer__led { animation: none; }
}
`;
// A torn top edge: the last slip was ripped off here.
const TEAR = Array.from({ length: 15 }, (_, i) => `${24 + i * 3} ${i % 2 ? 6 : 9}`).join('L');
export function PrinterLoader() {
const clip = `loader-printer-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
return (
<>
<style>
{css}
</style>
<div className="loader-printer" role="status" aria-label="Loading">
<svg viewBox="0 0 92 100" aria-hidden="true">
<defs>
<clipPath id={clip}>
<rect x="-40" y="-40" width="172" height="97.5" />
</clipPath>
</defs>
<g clipPath={`url(#${clip})`}>
<g className="loader-printer__slip">
<path className="loader-printer__paper" d={`M${TEAR}L66 9V57H24Z`} />
<path className="loader-printer__ink loader-printer__ink--bold" d="M38 15H52" />
<path className="loader-printer__ink" d="M29 22H44M54 22H61M29 27H40M54 27H61M29 32H46M54 32H61M29 37H38M54 37H61" />
<path className="loader-printer__ink loader-printer__ink--thin" d="M28 42H62" />
<path className="loader-printer__ink loader-printer__ink--bold" d="M29 48H40M52 48H61" />
</g>
</g>
<rect className="loader-printer__body" x="8" y="58" width="76" height="36" rx="8" />
<rect className="loader-printer__top" x="12" y="54" width="68" height="8" rx="3" />
<rect className="loader-printer__slot" x="20" y="56" width="52" height="3" rx="1.5" />
<path className="loader-printer__grille" d="M18 72H40M18 77H40M18 82H40" />
<circle className="loader-printer__led" cx="70" cy="72" r="2.2" />
<rect className="loader-printer__key" x="58" y="79" width="16" height="6" rx="3" />
</svg>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-printer" role="status" aria-label="Loading">
<svg viewBox="0 0 92 100" aria-hidden="true">
<defs>
<clipPath id="loader-printer-clip">
<rect x="-40" y="-40" width="172" height="97.5"></rect>
</clipPath>
</defs>
<g clip-path="url(#loader-printer-clip)">
<g class="loader-printer__slip">
<path class="loader-printer__paper" d="M24 9L27 6L30 9L33 6L36 9L39 6L42 9L45 6L48 9L51 6L54 9L57 6L60 9L63 6L66 9L66 9V57H24Z"></path>
<path class="loader-printer__ink loader-printer__ink--bold" d="M38 15H52"></path>
<path class="loader-printer__ink" d="M29 22H44M54 22H61M29 27H40M54 27H61M29 32H46M54 32H61M29 37H38M54 37H61"></path>
<path class="loader-printer__ink loader-printer__ink--thin" d="M28 42H62"></path>
<path class="loader-printer__ink loader-printer__ink--bold" d="M29 48H40M52 48H61"></path>
</g>
</g>
<rect class="loader-printer__body" x="8" y="58" width="76" height="36" rx="8"></rect>
<rect class="loader-printer__top" x="12" y="54" width="68" height="8" rx="3"></rect>
<rect class="loader-printer__slot" x="20" y="56" width="52" height="3" rx="1.5"></rect>
<path class="loader-printer__grille" d="M18 72H40M18 77H40M18 82H40"></path>
<circle class="loader-printer__led" cx="70" cy="72" r="2.2"></circle>
<rect class="loader-printer__key" x="58" y="79" width="16" height="6" rx="3"></rect>
</svg>
</div>CSS
/* The slip feeds out a line at a time, waits, gets a tug and is torn away to the left. */
@keyframes loader-printer-feed {
0% { transform: translateY(51px) rotate(0deg); opacity: 1; animation-timing-function: steps(12, end); }
58% { transform: translateY(0) rotate(0deg); opacity: 1; }
70% { transform: translateY(0) rotate(0deg); opacity: 1; animation-timing-function: ease-out; }
74% { transform: translateY(-3px) rotate(2deg); opacity: 1; animation-timing-function: ease-in; }
88% { transform: translate(-16px, -14px) rotate(-22deg); opacity: 0; }
88.1%, 100% { transform: translateY(51px) rotate(0deg); opacity: 0; }
}
@keyframes loader-printer-led { 0%, 56% { opacity: 1; } 28% { opacity: 0.35; } 60%, 100% { opacity: 0.2; } }
.loader-printer { position: relative; width: 104px; height: 113px; }
.loader-printer svg { width: 100%; height: 100%; overflow: visible; }
.loader-printer__slip { transform-origin: 45px 57px; animation: loader-printer-feed 3s infinite; }
/* Paper stays pale in both themes and the ink dark on it. */
.loader-printer__paper { fill: light-dark(#ebedf0, #e5e6e9); stroke: light-dark(#cbced3, #e5e6e9); stroke-width: 0.8; }
.loader-printer__ink { stroke: light-dark(#6d7179, #393c41); stroke-width: 2; stroke-linecap: round; }
.loader-printer__ink--thin { stroke-width: 1; stroke-dasharray: 2 2; stroke-linecap: butt; }
.loader-printer__ink--bold { stroke-width: 3; }
.loader-printer__top { fill: light-dark(#cbced3, #393c41); }
.loader-printer__body { fill: light-dark(#dbdde1, #2e3035); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; }
.loader-printer__slot { fill: light-dark(#fbfcfd, #121417); }
.loader-printer__led { fill: light-dark(#373a40, #f1f2f3); animation: loader-printer-led 3s steps(1, end) infinite; }
.loader-printer__key { fill: light-dark(#cbced3, #393c41); }
.loader-printer__grille { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.5; stroke-linecap: round; }
@media (prefers-reduced-motion: reduce) {
.loader-printer__slip, .loader-printer__led { animation: none; }
}Goal: Create a "Receipt" loader.
Visual behavior: A receipt printer feeding out a slip line by line while its light blinks, then the slip is torn off and flutters away for the next one.
Technique:
- The slip is clipped at the slot and starts hidden below it. A steps() timing function feeds it out a line at a time, then it holds, takes a tug and is torn away with a tilt and a fade.
- The paper stays pale in both themes with dark print, and the light blinks while it prints.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the printer is 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 "Receipt" loader.
Visual behavior: A receipt printer feeding out a slip line by line while its light blinks, then the slip is torn off and flutters away for the next one.
Technique:
- The slip is clipped at the slot and starts hidden below it. A steps() timing function feeds it out a line at a time, then it holds, takes a tug and is torn away with a tilt and a fade.
- The paper stays pale in both themes with dark print, and the light blinks while it prints.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the printer is 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
A receipt printer feeding out a slip line by line while its light blinks, then the slip is torn off and flutters away for the next one.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- continuous, loop
- Techniques
- keyframes, clip path, svg
- 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 printer is still.