UI Components Easy
Printable Receipt
Thermal-style restaurant receipt — header, line items with modifiers, subtotal/tax/tip/total, payment line, QR barcode placeholder and @media print styles for actual printing.
Open in Lab
MCP
html css vanilla-js
Targets: JS HTML
Code
:root {
--cream: #f5f0e8;
--cream-2: #ece4d4;
--bone: #faf7f1;
--terracotta: #c1714a;
--terracotta-d: #a05a38;
--forest: #2d4a3e;
--forest-d: #1e3329;
--gold: #c9a84c;
--gold-light: #e6c97a;
--ink: #2c1a0e;
--ink-2: #4a3828;
--warm-gray: #7a6a58;
--success: #4f7a3a;
--danger: #b3432a;
--warning: #d99020;
--font-display: "Playfair Display", Georgia, serif;
--font-body: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
--shadow-1: 0 1px 2px rgba(44, 26, 14, 0.08), 0 2px 6px rgba(44, 26, 14, 0.06);
--shadow-2: 0 8px 24px rgba(44, 26, 14, 0.12);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-body);
background: var(--cream-2);
color: var(--ink);
min-height: 100vh;
padding: 32px 16px 48px;
display: flex;
justify-content: center;
-webkit-font-smoothing: antialiased;
background-image: repeating-linear-gradient(
45deg,
rgba(44, 26, 14, 0.018) 0 2px,
transparent 2px 14px
);
}
.page {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
/* Toolbar */
.toolbar {
display: flex;
gap: 8px;
}
.ghost,
.primary {
border-radius: 999px;
font-family: inherit;
font-size: 0.84rem;
font-weight: 700;
padding: 9px 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
}
.ghost {
background: var(--bone);
border: 1px solid rgba(44, 26, 14, 0.18);
color: var(--ink-2);
}
.ghost:hover {
background: var(--cream);
color: var(--ink);
}
.primary {
background: var(--forest);
border: none;
color: var(--bone);
}
.primary:hover {
background: var(--forest-d);
}
/* Receipt */
.receipt {
width: 320px;
background: var(--bone);
color: var(--ink);
padding: 22px 24px 16px;
font-family: var(--font-mono);
font-size: 11.5px;
line-height: 1.5;
box-shadow: 0 1px 0 rgba(44, 26, 14, 0.04), 0 12px 30px rgba(44, 26, 14, 0.18);
position: relative;
}
/* zig-zag tear edges */
.receipt::before,
.receipt::after {
content: "";
position: absolute;
left: 0;
right: 0;
height: 14px;
background: radial-gradient(circle at 8px 0, var(--bone) 8px, transparent 9px);
background-size: 16px 14px;
background-repeat: repeat-x;
}
.receipt::before {
top: -14px;
}
.receipt::after {
bottom: -14px;
transform: scaleY(-1);
}
/* Head */
.r-head {
text-align: center;
}
.r-brand {
font-family: var(--font-display);
font-size: 1.3rem;
font-weight: 700;
letter-spacing: 0.08em;
}
.r-addr {
font-size: 10px;
color: var(--ink-2);
}
.r-rule {
text-align: center;
color: var(--warm-gray);
letter-spacing: 0.4em;
margin: 10px 0 6px;
}
/* Meta */
.r-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px 14px;
font-size: 11px;
margin-top: 4px;
}
.r-meta div {
display: flex;
justify-content: space-between;
}
.r-meta dt {
color: var(--warm-gray);
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.06em;
font-size: 10px;
}
.r-meta dd {
font-weight: 700;
color: var(--ink);
}
/* Lines */
.r-lines {
list-style: none;
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
}
.r-lines li {
display: grid;
grid-template-columns: 24px 1fr auto;
gap: 8px;
align-items: baseline;
}
.r-qty {
font-weight: 700;
color: var(--ink-2);
text-align: right;
}
.r-name {
font-weight: 600;
letter-spacing: 0.01em;
}
.r-amt {
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.r-mod {
display: block !important;
grid-template-columns: 1fr !important;
padding-left: 32px;
font-size: 10px;
font-style: italic;
color: var(--warm-gray);
margin-bottom: 4px;
}
/* Totals */
.r-totals {
display: flex;
flex-direction: column;
gap: 2px;
}
.r-totals div {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.r-totals dt {
color: var(--ink-2);
}
.r-totals dd {
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.r-grand {
border-top: 1px dashed rgba(44, 26, 14, 0.4);
margin-top: 4px;
padding-top: 6px;
font-size: 14px !important;
font-weight: 800;
}
.r-grand dt,
.r-grand dd {
letter-spacing: 0.1em;
}
/* Pay */
.r-pay {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 11px;
}
.r-pay div {
display: flex;
justify-content: space-between;
}
.r-pay dt {
color: var(--ink-2);
}
.r-pay dd {
font-weight: 700;
}
/* QR */
.r-qr {
display: flex;
justify-content: center;
padding: 4px 0 8px;
}
.qr-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
width: 84px;
background: var(--bone);
padding: 6px;
border: 1px solid var(--ink);
}
.qr-grid span {
width: 10px;
height: 10px;
background: var(--ink);
}
/* Pseudo-random fingerprint pattern */
.qr-grid span:nth-child(odd) {
background: var(--bone);
}
.qr-grid span:nth-child(7n + 1),
.qr-grid span:nth-child(7n + 5),
.qr-grid span:nth-child(3),
.qr-grid span:nth-child(13),
.qr-grid span:nth-child(19),
.qr-grid span:nth-child(27),
.qr-grid span:nth-child(34) {
background: var(--ink);
}
/* Position markers */
.qr-grid span:nth-child(1),
.qr-grid span:nth-child(2),
.qr-grid span:nth-child(3),
.qr-grid span:nth-child(8),
.qr-grid span:nth-child(15),
.qr-grid span:nth-child(5),
.qr-grid span:nth-child(6),
.qr-grid span:nth-child(7),
.qr-grid span:nth-child(12),
.qr-grid span:nth-child(14),
.qr-grid span:nth-child(43),
.qr-grid span:nth-child(36),
.qr-grid span:nth-child(29),
.qr-grid span:nth-child(45) {
background: var(--ink);
}
/* Thanks */
.r-thanks {
text-align: center;
margin-top: 8px;
font-family: var(--font-display);
font-size: 14px;
font-style: italic;
line-height: 1.35;
}
.r-foot {
text-align: center;
font-size: 10px;
color: var(--warm-gray);
margin-top: 8px;
letter-spacing: 0.04em;
}
/* Print */
@media print {
body {
background: white;
padding: 0;
background-image: none;
}
.no-print {
display: none !important;
}
.receipt {
box-shadow: none;
width: 100%;
max-width: 80mm;
padding: 4mm 4mm 6mm;
}
.receipt::before,
.receipt::after {
display: none;
}
}const dateEl = document.getElementById("rDate");
const d = new Date();
const fmt = (n) => String(n).padStart(2, "0");
dateEl.textContent = `${d.getFullYear()}-${fmt(d.getMonth() + 1)}-${fmt(d.getDate())} ${fmt(d.getHours())}:${fmt(d.getMinutes())}`;
document.getElementById("printBtn").addEventListener("click", () => {
window.print();
});
document.getElementById("dup").addEventListener("click", () => {
const wrap = document.querySelector(".page");
const second = document.querySelector(".receipt").cloneNode(true);
second.style.opacity = "0.85";
wrap.appendChild(second);
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Inter:wght@500;600;700&family=JetBrains+Mono:wght@500;600;700&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>Receipt</title>
</head>
<body>
<div class="page">
<div class="toolbar no-print">
<button class="ghost" type="button" id="dup">Duplicate</button>
<button class="primary" type="button" id="printBtn">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M6 9V3h12v6M6 18H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-2M6 14h12v7H6z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linejoin="round"
/>
</svg>
Print
</button>
</div>
<article class="receipt">
<header class="r-head">
<p class="r-brand">CASA OLIVAR</p>
<p class="r-addr">42 Calle del Olivar · Madrid</p>
<p class="r-addr">+34 910 555 042 · casaolivar.es</p>
<p class="r-rule">— · —</p>
</header>
<dl class="r-meta">
<div><dt>Date</dt><dd id="rDate">—</dd></div>
<div><dt>Server</dt><dd>Lina</dd></div>
<div><dt>Table</dt><dd>7</dd></div>
<div><dt>Covers</dt><dd>2</dd></div>
<div><dt>Order</dt><dd>#0184</dd></div>
<div><dt>Terminal</dt><dd>T-02</dd></div>
</dl>
<p class="r-rule">— · —</p>
<ul class="r-lines">
<li>
<span class="r-qty">1</span>
<span class="r-name">Burrata huerta</span>
<span class="r-amt">$16.00</span>
</li>
<li class="r-mod">add focaccia</li>
<li>
<span class="r-qty">1</span>
<span class="r-name">Ribeye 14oz</span>
<span class="r-amt">$48.00</span>
</li>
<li class="r-mod">medium rare · truffle fries (+$4) · bone marrow (+$6)</li>
<li>
<span class="r-qty">1</span>
<span class="r-name">Risotto hongos</span>
<span class="r-amt">$26.00</span>
</li>
<li class="r-mod">no parmesan</li>
<li>
<span class="r-qty">2</span>
<span class="r-name">Tarta de queso</span>
<span class="r-amt">$22.00</span>
</li>
<li>
<span class="r-qty">2</span>
<span class="r-name">Tinto natural (copa)</span>
<span class="r-amt">$24.00</span>
</li>
<li>
<span class="r-qty">1</span>
<span class="r-name">Negroni sbagliato</span>
<span class="r-amt">$14.00</span>
</li>
<li>
<span class="r-qty">2</span>
<span class="r-name">Agua mineral</span>
<span class="r-amt">$10.00</span>
</li>
<li>
<span class="r-qty">2</span>
<span class="r-name">Espresso</span>
<span class="r-amt">$8.00</span>
</li>
</ul>
<p class="r-rule">— · —</p>
<dl class="r-totals">
<div><dt>Subtotal</dt><dd>$168.00</dd></div>
<div><dt>Tax 8.25%</dt><dd>$13.86</dd></div>
<div><dt>Service 10%</dt><dd>$16.80</dd></div>
<div><dt>Tip 18%</dt><dd>$30.24</dd></div>
<div class="r-grand"><dt>TOTAL</dt><dd>$228.90</dd></div>
</dl>
<p class="r-rule">— · —</p>
<dl class="r-pay">
<div><dt>Paid</dt><dd>Visa ··4081</dd></div>
<div><dt>Auth</dt><dd>9F1A02</dd></div>
</dl>
<p class="r-rule">— · —</p>
<div class="r-qr" aria-hidden="true">
<div class="qr-grid">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</div>
<p class="r-thanks">Gracias por venir.<br />¡Vuelve pronto!</p>
<p class="r-foot">casaolivar.es/feedback · Order #0184</p>
</article>
</div>
<script src="script.js"></script>
</body>
</html>Printable Receipt
The thermal-printer-shaped receipt the diner walks away with (and the kitchen ticket the line cook reads). Header with brand + address + phone, table / server / cover count, ordered line items with modifier sublines, subtotal/tax/tip/total, payment line with last-four, a thank-you note, and a QR/barcode placeholder.
@media print strips the surrounding chrome so a single Cmd/Ctrl+P from any of the POS resources prints just the receipt.