POS開発 UIテンプレート集
コピペで使える HTML / CSS / JavaScript テンプレート
各テンプレートは HTML・CSS・JavaScript の3ブロックで構成されています。
使いたいテンプレートの各ブロックをコピーし、プロジェクトの該当ファイルに貼り付けてください。
クラス名やIDは必要に応じて変更してください。
1. タブ切り替え
使い方: タブボタンとパネルを
data-tab 属性で紐づけます。
タブの数は自由に増減可能。初期表示タブには active クラスを付与してください。
POS画面では「商品カテゴリの切り替え」や「注文/履歴の切り替え」に最適です。
HTML
<!-- タブ切り替え --> <div class="tabs"> <div class="tab-nav"> <button class="tab-btn active" data-tab="tab1">タブ1</button> <button class="tab-btn" data-tab="tab2">タブ2</button> <button class="tab-btn" data-tab="tab3">タブ3</button> </div> <div class="tab-panel active" id="tab1">タブ1の内容</div> <div class="tab-panel" id="tab2">タブ2の内容</div> <div class="tab-panel" id="tab3">タブ3の内容</div> </div>
CSS
/* タブ切り替え */ .tab-nav { display: flex; gap: 0; border-bottom: 2px solid #ddd; } .tab-btn { padding: 10px 20px; border: none; background: transparent; cursor: pointer; font-size: 14px; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: color 0.2s, border-color 0.2s; } .tab-btn:hover { color: #333; } .tab-btn.active { color: #e94560; border-bottom-color: #e94560; font-weight: bold; } .tab-panel { display: none; padding: 16px 0; } .tab-panel.active { display: block; }
JavaScript
// タブ切り替え document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { // 全タブのactiveを解除 document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); // クリックしたタブをactive btn.classList.add('active'); document.getElementById(btn.dataset.tab).classList.add('active'); }); });
2. モーダルウィンドウ
使い方: トリガーボタンをクリックでモーダルを開きます。
背景オーバーレイのクリック・ESCキー・閉じるボタンで閉じます。
POS画面では「商品詳細の表示」「確認ダイアログ」「設定画面」に使えます。
複数モーダルを使う場合は
data-modal 属性でIDを紐づけてください。
HTML
<!-- モーダルを開くボタン --> <button type="button" class="modal-open" data-modal="myModal">モーダルを開く</button> <!-- モーダル本体 --> <div class="modal-overlay" id="myModal"> <div class="modal"> <div class="modal-header"> <h3>タイトル</h3> <button class="modal-close">×</button> </div> <div class="modal-body"> <p>モーダルの内容をここに記述します。</p> </div> <div class="modal-footer"> <button class="modal-close">キャンセル</button> <button class="btn-primary">OK</button> </div> </div> </div>
CSS
/* モーダルウィンドウ */ .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; } .modal-overlay.open { display: flex; } .modal { background: #fff; border-radius: 8px; width: 90%; max-width: 500px; max-height: 80vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #eee; } .modal-header h3 { margin: 0; } .modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; } .modal-body { padding: 20px; } .modal-footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid #eee; } .btn-primary { background: #e94560; color: #fff; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; }
JavaScript
// モーダル — 開く document.querySelectorAll('.modal-open').forEach(btn => { btn.addEventListener('click', () => { document.getElementById(btn.dataset.modal).classList.add('open'); }); }); // モーダル — 閉じる(×ボタン / キャンセルボタン) document.querySelectorAll('.modal-close').forEach(btn => { btn.addEventListener('click', () => { btn.closest('.modal-overlay').classList.remove('open'); }); }); // モーダル — オーバーレイクリックで閉じる document.querySelectorAll('.modal-overlay').forEach(overlay => { overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.classList.remove('open'); }); }); // モーダル — ESCキーで閉じる document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { document.querySelectorAll('.modal-overlay.open').forEach(m => m.classList.remove('open')); } });
3. アコーディオン
使い方: 各アイテムのヘッダーをクリックで開閉します。
デフォルトで開いておきたい項目には
active クラスを付与してください。
POS画面では「FAQ」「商品カテゴリ詳細」「設定項目の折りたたみ」に使えます。
一つずつ開く排他モードにするには JS の該当行のコメントを外してください。
HTML
<!-- アコーディオン --> <div class="accordion"> <div class="accordion-item active"> <button class="accordion-header"> セクション1 <span class="accordion-icon">▼</span> </button> <div class="accordion-body"> <p>セクション1の内容がここに入ります。</p> </div> </div> <div class="accordion-item"> <button class="accordion-header"> セクション2 <span class="accordion-icon">▼</span> </button> <div class="accordion-body"> <p>セクション2の内容がここに入ります。</p> </div> </div> <div class="accordion-item"> <button class="accordion-header"> セクション3 <span class="accordion-icon">▼</span> </button> <div class="accordion-body"> <p>セクション3の内容がここに入ります。</p> </div> </div> </div>
CSS
/* アコーディオン */ .accordion { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; } .accordion-header { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 14px 16px; background: #f9f9f9; border: none; border-bottom: 1px solid #ddd; cursor: pointer; font-size: 14px; text-align: left; } .accordion-icon { font-size: 10px; transition: transform 0.3s; } .accordion-item.active .accordion-icon { transform: rotate(180deg); } .accordion-body { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; padding: 0 16px; } .accordion-item.active .accordion-body { max-height: 500px; padding: 14px 16px; }
JavaScript
// アコーディオン document.querySelectorAll('.accordion-header').forEach(header => { header.addEventListener('click', () => { const item = header.parentElement; // 排他モード(一つだけ開く)にする場合は下の2行を有効にする // const siblings = item.parentElement.querySelectorAll('.accordion-item'); // siblings.forEach(s => { if (s !== item) s.classList.remove('active'); }); item.classList.toggle('active'); }); });
4. トースト通知
使い方:
showToast('メッセージ', 'success') で呼び出します。
タイプは success / error / warning / info の4種類。
第3引数で表示時間(ms)を変更可能(デフォルト3000ms)。
POS画面では「商品追加完了」「エラー通知」「在庫警告」に使えます。
HTML
<!-- トーストコンテナ — body直下に1つ配置 --> <div id="toast-container"></div> <!-- 使用例 --> <button onclick="showToast('商品を追加しました', 'success')">成功</button> <button onclick="showToast('エラーが発生しました', 'error')">エラー</button> <button onclick="showToast('在庫が残りわずかです', 'warning')">警告</button> <button onclick="showToast('処理中です...', 'info')">情報</button>
CSS
/* トースト通知 */ #toast-container { position: fixed; top: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; } .toast { padding: 12px 20px; border-radius: 6px; color: #fff; font-size: 14px; min-width: 250px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); animation: toast-in 0.3s ease; } .toast.removing { animation: toast-out 0.3s ease forwards; } .toast--success { background: #2ecc71; } .toast--error { background: #e74c3c; } .toast--warning { background: #f39c12; } .toast--info { background: #3498db; } @keyframes toast-in { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes toast-out { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } }
JavaScript
// トースト通知 function showToast(message, type = 'info', duration = 3000) { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = `toast toast--${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.classList.add('removing'); toast.addEventListener('animationend', () => toast.remove()); }, duration); }
5. ドロップダウンメニュー
使い方: トリガーボタンをクリックでメニューを表示します。
外側のクリックやESCキーで自動的に閉じます。
POS画面では「カテゴリ選択」「操作メニュー」「ユーザーメニュー」に使えます。
メニュー項目は
<a> や <button> で構成してください。
HTML
<!-- ドロップダウン --> <div class="dropdown"> <button class="dropdown-trigger">メニュー ▼</button> <ul class="dropdown-menu"> <li><button type="button">編集</button></li> <li><button type="button">複製</button></li> <li class="dropdown-divider"></li> <li><button type="button" class="danger">削除</button></li> </ul> </div>
CSS
/* ドロップダウンメニュー */ .dropdown { position: relative; display: inline-block; } .dropdown-trigger { padding: 8px 16px; background: #fff; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-size: 14px; } .dropdown-menu { display: none; position: absolute; top: 100%; left: 0; min-width: 160px; background: #fff; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); list-style: none; padding: 4px 0; margin: 4px 0 0; z-index: 100; } .dropdown.open .dropdown-menu { display: block; } .dropdown-menu button { display: block; width: 100%; padding: 8px 16px; border: none; background: none; text-align: left; cursor: pointer; font-size: 14px; } .dropdown-menu button:hover { background: #f5f5f5; } .dropdown-menu .danger { color: #e74c3c; } .dropdown-divider { height: 1px; background: #eee; margin: 4px 0; }
JavaScript
// ドロップダウン — トグル document.querySelectorAll('.dropdown-trigger').forEach(trigger => { trigger.addEventListener('click', (e) => { e.stopPropagation(); const dd = trigger.parentElement; // 他のドロップダウンを閉じる document.querySelectorAll('.dropdown.open').forEach(d => { if (d !== dd) d.classList.remove('open'); }); dd.classList.toggle('open'); }); }); // ドロップダウン — 外側クリックで閉じる document.addEventListener('click', () => { document.querySelectorAll('.dropdown.open').forEach(d => d.classList.remove('open')); });
6. ツールチップ
使い方: 要素に
data-tooltip 属性を追加するだけで動作します(CSS only)。
JavaScriptは不要ですが、動的に内容を変えたい場合はJSで data-tooltip 属性を書き換えてください。
POS画面では「ボタンの説明」「アイコンのラベル」「入力フィールドのヒント」に使えます。
HTML
<!-- ツールチップ — data-tooltip属性を追加するだけ --> <button data-tooltip="商品を追加します">追加</button> <button data-tooltip="この操作は取り消せません" data-tooltip-pos="bottom">削除</button> <span data-tooltip="税込価格">¥1,080</span>
CSS
/* ツールチップ(CSS only) */ [data-tooltip] { position: relative; cursor: help; } [data-tooltip]::after { content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #333; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s; margin-bottom: 6px; } [data-tooltip]:hover::after { opacity: 1; } /* 下方向に表示する場合 */ [data-tooltip-pos="bottom"]::after { bottom: auto; top: 100%; margin-bottom: 0; margin-top: 6px; }
JavaScript
// ツールチップはCSS onlyで動作します // 動的にツールチップ内容を変えたい場合: function setTooltip(element, text) { element.setAttribute('data-tooltip', text); } // 使用例: // const btn = document.querySelector('#myBtn'); // setTooltip(btn, '新しいツールチップテキスト');
7. ローディングスピナー
使い方: スピナーは円形のCSSアニメーション、スケルトンはコンテンツのプレースホルダーです。
API通信やデータ読み込み時に
showLoading() で表示、完了後に hideLoading() で非表示にします。
POS画面では「商品データ読み込み」「会計処理中」「レシート印刷中」に使えます。
HTML
<!-- スピナー(全画面オーバーレイ型) --> <div id="loading-overlay" class="loading-overlay"> <div class="spinner"></div> <p>読み込み中...</p> </div> <!-- スケルトンスクリーン --> <div class="skeleton-card"> <div class="skeleton skeleton-img"></div> <div class="skeleton skeleton-line"></div> <div class="skeleton skeleton-line short"></div> </div>
CSS
/* ローディングオーバーレイ */ .loading-overlay { display: none; position: fixed; inset: 0; background: rgba(255,255,255,0.8); z-index: 9999; flex-direction: column; align-items: center; justify-content: center; gap: 12px; } .loading-overlay.show { display: flex; } /* スピナー */ .spinner { width: 40px; height: 40px; border: 4px solid #ddd; border-top-color: #e94560; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* スケルトンスクリーン */ .skeleton-card { padding: 16px; } .skeleton { background: #e0e0e0; border-radius: 4px; animation: shimmer 1.5s infinite; } .skeleton-img { width: 100%; height: 120px; margin-bottom: 12px; } .skeleton-line { height: 14px; margin-bottom: 8px; } .skeleton-line.short { width: 60%; } @keyframes shimmer { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }
JavaScript
// ローディング表示/非表示 function showLoading() { document.getElementById('loading-overlay').classList.add('show'); } function hideLoading() { document.getElementById('loading-overlay').classList.remove('show'); } // 使用例: API通信時 // showLoading(); // const res = await fetch('/api/products'); // const data = await res.json(); // hideLoading();
8. 商品カードグリッド
使い方: 商品データをグリッド表示するテンプレートです。
各カードに
data-id と data-price を設定し、「カートに追加」ボタンで商品情報を取得します。
グリッドのカラム数は minmax() の値で調整可能。画像は実際のパスに差し替えてください。
HTML
<!-- 商品カードグリッド --> <div class="product-grid"> <div class="product-card" data-id="001" data-price="350"> <div class="product-img">画像</div> <div class="product-info"> <p class="product-name">コーヒー(S)</p> <p class="product-price">¥350</p> </div> <button class="add-to-cart-btn">カートに追加</button> </div> <div class="product-card" data-id="002" data-price="500"> <div class="product-img">画像</div> <div class="product-info"> <p class="product-name">サンドイッチ</p> <p class="product-price">¥500</p> </div> <button class="add-to-cart-btn">カートに追加</button> </div> <!-- 商品カードを増やす場合はここにコピー --> </div>
CSS
/* 商品カードグリッド */ .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; } .product-card { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; transition: box-shadow 0.2s; } .product-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .product-img { height: 100px; background: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #aaa; } .product-info { padding: 12px; } .product-name { font-weight: bold; font-size: 14px; margin-bottom: 4px; } .product-price { color: #e94560; font-weight: bold; } .add-to-cart-btn { width: 100%; padding: 10px; background: #e94560; color: #fff; border: none; cursor: pointer; font-size: 13px; transition: background 0.2s; } .add-to-cart-btn:hover { background: #c73a52; }
JavaScript
// 商品カード — カートに追加(イベント委譲) document.querySelector('.product-grid').addEventListener('click', (e) => { const btn = e.target.closest('.add-to-cart-btn'); if (!btn) return; const card = btn.closest('.product-card'); const item = { id: card.dataset.id, name: card.querySelector('.product-name').textContent, price: Number(card.dataset.price), quantity: 1 }; console.log('カートに追加:', item); // ここでカート配列に追加する処理を書く // 例: cart.push(item); renderCart(); });
9. ショッピングカート
使い方: カート内の商品を一覧表示し、数量変更・削除・合計計算を行うテンプレートです。
cart 配列にデータを追加して renderCart() を呼ぶと画面が更新されます。
税率は TAX_RATE 定数で変更可能。商品カードグリッドと組み合わせて使ってください。
HTML
<!-- ショッピングカート --> <div class="cart"> <h3>カート</h3> <table class="cart-table"> <thead> <tr> <th>商品</th><th>単価</th><th>数量</th><th>小計</th><th></th> </tr> </thead> <tbody id="cart-body"> <!-- JSで動的に描画 --> </tbody> </table> <div class="cart-summary"> <p>小計: <span id="cart-subtotal">¥0</span></p> <p>消費税: <span id="cart-tax">¥0</span></p> <p class="cart-total">合計: <span id="cart-total">¥0</span></p> </div> </div>
CSS
/* ショッピングカート */ .cart-table { width: 100%; border-collapse: collapse; font-size: 14px; } .cart-table th { background: #f5f5f5; padding: 10px; text-align: left; font-size: 13px; color: #666; } .cart-table td { padding: 10px; border-bottom: 1px solid #eee; } .qty-control { display: flex; align-items: center; gap: 6px; } .qty-btn { width: 28px; height: 28px; border: 1px solid #ddd; border-radius: 50%; background: #fff; cursor: pointer; font-size: 16px; line-height: 1; } .qty-btn:hover { background: #f0f0f0; } .remove-btn { background: none; border: none; color: #e74c3c; cursor: pointer; font-size: 18px; } .cart-summary { text-align: right; padding: 16px 10px; font-size: 14px; } .cart-total { font-size: 20px; font-weight: bold; color: #e94560; }
JavaScript
// ショッピングカート const TAX_RATE = 0.10; // 消費税率 10% let cart = []; function addToCart(id, name, price) { const existing = cart.find(item => item.id === id); if (existing) { existing.quantity++; } else { cart.push({ id, name, price, quantity: 1 }); } renderCart(); } function updateQuantity(id, delta) { const item = cart.find(i => i.id === id); if (!item) return; item.quantity += delta; if (item.quantity < 1) cart = cart.filter(i => i.id !== id); renderCart(); } function removeFromCart(id) { cart = cart.filter(i => i.id !== id); renderCart(); } function renderCart() { const tbody = document.getElementById('cart-body'); let subtotal = 0; tbody.innerHTML = cart.map(item => { const itemTotal = item.price * item.quantity; subtotal += itemTotal; return `<tr> <td>${item.name}</td> <td>¥${item.price.toLocaleString()}</td> <td> <div class="qty-control"> <button class="qty-btn" onclick="updateQuantity('${item.id}', -1)">-</button> <span>${item.quantity}</span> <button class="qty-btn" onclick="updateQuantity('${item.id}', 1)">+</button> </div> </td> <td>¥${itemTotal.toLocaleString()}</td> <td><button class="remove-btn" onclick="removeFromCart('${item.id}')">×</button></td> </tr>`; }).join(''); const tax = Math.floor(subtotal * TAX_RATE); document.getElementById('cart-subtotal').textContent = `¥${subtotal.toLocaleString()}`; document.getElementById('cart-tax').textContent = `¥${tax.toLocaleString()}`; document.getElementById('cart-total').textContent = `¥${(subtotal + tax).toLocaleString()}`; }
10. テンキー入力
使い方: タッチスクリーン対応の数値入力パッドです。
ボタンサイズは最低60pxで、タッチ操作に最適化されています。
POS画面では「金額入力」「数量入力」「会員番号入力」に使えます。
onNumpadEnter コールバックで確定時の処理を実装してください。
HTML
<!-- テンキー入力 --> <div class="numpad"> <div class="numpad-display" id="numpadDisplay">0</div> <div class="numpad-keys"> <button class="numpad-btn" data-num="7">7</button> <button class="numpad-btn" data-num="8">8</button> <button class="numpad-btn" data-num="9">9</button> <button class="numpad-btn" data-num="4">4</button> <button class="numpad-btn" data-num="5">5</button> <button class="numpad-btn" data-num="6">6</button> <button class="numpad-btn" data-num="1">1</button> <button class="numpad-btn" data-num="2">2</button> <button class="numpad-btn" data-num="3">3</button> <button class="numpad-btn" data-num="00">00</button> <button class="numpad-btn" data-num="0">0</button> <button class="numpad-btn numpad-backspace" data-action="backspace">⌫</button> <button class="numpad-btn numpad-clear" data-action="clear">C</button> <button class="numpad-btn numpad-enter" data-action="enter" style="grid-column: span 2">確定</button> </div> </div>
CSS
/* テンキー */ .numpad { max-width: 280px; background: #f9f9f9; border-radius: 8px; padding: 16px; } .numpad-display { background: #1e1e2e; color: #cdd6f4; font-size: 28px; text-align: right; padding: 12px 16px; border-radius: 6px; margin-bottom: 12px; font-family: "Cascadia Code", monospace; } .numpad-keys { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } .numpad-btn { padding: 16px; font-size: 20px; border: 1px solid #ddd; border-radius: 6px; background: #fff; cursor: pointer; min-height: 60px; transition: background 0.1s; user-select: none; -webkit-tap-highlight-color: transparent; } .numpad-btn:active { background: #e0e0e0; } .numpad-clear { color: #e74c3c; } .numpad-enter { background: #e94560; color: #fff; border-color: #e94560; } .numpad-enter:active { background: #c73a52; }
JavaScript
// テンキー入力 let numpadValue = '0'; const numpadDisplay = document.getElementById('numpadDisplay'); function updateNumpadDisplay() { const num = parseInt(numpadValue) || 0; numpadDisplay.textContent = num.toLocaleString(); } // 確定時のコールバック(用途に応じて書き換え) function onNumpadEnter(value) { console.log('入力確定:', value); } document.querySelector('.numpad-keys').addEventListener('click', (e) => { const btn = e.target.closest('.numpad-btn'); if (!btn) return; const num = btn.dataset.num; const action = btn.dataset.action; if (num) { numpadValue = numpadValue === '0' ? num : numpadValue + num; } else if (action === 'backspace') { numpadValue = numpadValue.slice(0, -1) || '0'; } else if (action === 'clear') { numpadValue = '0'; } else if (action === 'enter') { onNumpadEnter(parseInt(numpadValue) || 0); numpadValue = '0'; } updateNumpadDisplay(); });
11. レシート表示
使い方: レシート風のレイアウトで注文内容を表示します。
generateReceipt(order) にオーダーデータを渡すとレシートを生成します。
window.print() で印刷可能。印刷時はレシート部分のみが出力されるCSS付き。
感熱紙サイズ(幅80mm)に合わせた max-width: 320px になっています。
HTML
<!-- レシート表示 --> <div class="receipt" id="receipt"> <div class="receipt-header"> <h3>SHOP NAME</h3> <p>〒000-0000 東京都...</p> <p>TEL: 03-0000-0000</p> </div> <div class="receipt-divider"></div> <p class="receipt-date" id="receipt-date"></p> <div class="receipt-divider"></div> <div id="receipt-items"> <!-- JSで動的に描画 --> </div> <div class="receipt-divider"></div> <div class="receipt-totals" id="receipt-totals"></div> <div class="receipt-divider"></div> <div class="receipt-footer"> <p>ありがとうございました</p> </div> </div> <button onclick="window.print()" class="print-btn">レシートを印刷</button>
CSS
/* レシート表示 */ .receipt { max-width: 320px; margin: 0 auto; background: #fff; padding: 20px; font-family: "Cascadia Code", monospace; font-size: 13px; box-shadow: 0 1px 4px rgba(0,0,0,0.1); } .receipt-header { text-align: center; } .receipt-header h3 { font-size: 18px; margin-bottom: 4px; } .receipt-header p { font-size: 11px; color: #666; } .receipt-divider { border-top: 1px dashed #aaa; margin: 10px 0; } .receipt-date { font-size: 12px; color: #666; } .receipt-item { display: flex; justify-content: space-between; margin-bottom: 4px; } .receipt-totals .receipt-item { font-size: 13px; } .receipt-totals .total-line { font-size: 18px; font-weight: bold; margin-top: 4px; } .receipt-footer { text-align: center; font-size: 12px; } .print-btn { display: block; margin: 16px auto; padding: 8px 24px; background: #333; color: #fff; border: none; border-radius: 4px; cursor: pointer; } /* 印刷時はレシートだけ表示 */ @media print { body * { visibility: hidden; } .receipt, .receipt * { visibility: visible; } .receipt { position: absolute; top: 0; left: 0; box-shadow: none; } .print-btn { display: none; } }
JavaScript
// レシート生成 function generateReceipt(order) { // order = { items: [{name, price, quantity}], taxRate: 0.10 } const now = new Date(); document.getElementById('receipt-date').textContent = now.toLocaleString('ja-JP'); // 商品リスト const itemsEl = document.getElementById('receipt-items'); let subtotal = 0; itemsEl.innerHTML = order.items.map(item => { const total = item.price * item.quantity; subtotal += total; return `<div class="receipt-item"> <span>${item.name} x${item.quantity}</span> <span>¥${total.toLocaleString()}</span> </div>`; }).join(''); // 合計 const tax = Math.floor(subtotal * (order.taxRate || 0.10)); document.getElementById('receipt-totals').innerHTML = ` <div class="receipt-item"><span>小計</span><span>¥${subtotal.toLocaleString()}</span></div> <div class="receipt-item"><span>消費税</span><span>¥${tax.toLocaleString()}</span></div> <div class="receipt-item total-line"><span>合計</span><span>¥${(subtotal + tax).toLocaleString()}</span></div> `; } // 使用例: // generateReceipt({ // items: [ // { name: 'コーヒー', price: 350, quantity: 2 }, // { name: 'サンドイッチ', price: 500, quantity: 1 } // ], // taxRate: 0.10 // });
12. バーコード入力
使い方: バーコードスキャナーからの高速入力を検知するテンプレートです。
スキャナーは高速にキーストロークを送信し最後にEnterを押します(通常50ms以内)。
手動入力とスキャナー入力を
SCAN_THRESHOLD(ms)で判別します。
onBarcodeScanned コールバックで商品検索処理を実装してください。
HTML
<!-- バーコード入力 --> <div class="barcode-input-wrapper"> <label for="barcodeInput">バーコード</label> <div class="barcode-field"> <span class="barcode-icon">☷</span> <input type="text" id="barcodeInput" placeholder="スキャンまたは入力..." autocomplete="off" autofocus> <span class="barcode-status" id="barcodeStatus"></span> </div> </div>
CSS
/* バーコード入力 */ .barcode-input-wrapper { max-width: 400px; } .barcode-input-wrapper label { display: block; font-size: 13px; color: #666; margin-bottom: 4px; } .barcode-field { display: flex; align-items: center; border: 2px solid #ddd; border-radius: 6px; padding: 0 12px; transition: border-color 0.2s; } .barcode-field:focus-within { border-color: #e94560; } .barcode-icon { font-size: 20px; color: #999; margin-right: 8px; } .barcode-field input { flex: 1; border: none; outline: none; padding: 12px 0; font-size: 16px; font-family: "Cascadia Code", monospace; } .barcode-status { font-size: 12px; padding: 2px 8px; border-radius: 10px; } .barcode-status.scanned { background: #2ecc71; color: #fff; }
JavaScript
// バーコードスキャナー入力検知 const SCAN_THRESHOLD = 50; // ms — この時間内の入力はスキャナーと判定 let lastKeyTime = 0; let isScanning = false; const barcodeInput = document.getElementById('barcodeInput'); const barcodeStatus = document.getElementById('barcodeStatus'); // スキャン完了時のコールバック function onBarcodeScanned(code) { console.log('スキャン:', code); barcodeStatus.textContent = 'スキャン完了'; barcodeStatus.className = 'barcode-status scanned'; setTimeout(() => { barcodeStatus.textContent = ''; barcodeStatus.className = 'barcode-status'; }, 2000); // ここで商品検索処理を実装 } barcodeInput.addEventListener('keydown', (e) => { const now = Date.now(); if (now - lastKeyTime < SCAN_THRESHOLD) isScanning = true; lastKeyTime = now; if (e.key === 'Enter') { e.preventDefault(); const code = barcodeInput.value.trim(); if (code) { onBarcodeScanned(code); barcodeInput.value = ''; } isScanning = false; } }); // ページ読み込み時に自動フォーカス barcodeInput.focus();
13. 会計画面レイアウト
使い方: POS会計画面の2カラムレイアウトです。
左側に商品リスト、右側に合計と支払い方法を配置します。
640px以下で1カラムに切り替わるレスポンシブ対応。
商品カード・カート・テンキーテンプレートと組み合わせて使えます。
HTML
<!-- 会計画面レイアウト --> <div class="checkout-layout"> <!-- 左: 商品リスト --> <div class="checkout-items"> <h3>注文内容</h3> <table class="checkout-table"> <thead> <tr><th>商品</th><th>数量</th><th>小計</th></tr> </thead> <tbody id="checkout-body"> <tr><td>コーヒー(S)</td><td>2</td><td>¥700</td></tr> <tr><td>サンドイッチ</td><td>1</td><td>¥500</td></tr> </tbody> </table> </div> <!-- 右: 会計サマリー --> <div class="checkout-summary"> <div class="checkout-totals"> <div class="checkout-row"><span>小計</span><span>¥1,200</span></div> <div class="checkout-row"><span>消費税(10%)</span><span>¥120</span></div> <div class="checkout-row total"><span>合計</span><span>¥1,320</span></div> </div> <div class="payment-methods"> <p>支払い方法</p> <div class="payment-buttons"> <button class="payment-btn active" data-method="cash">現金</button> <button class="payment-btn" data-method="card">カード</button> <button class="payment-btn" data-method="epay">電子マネー</button> </div> </div> <button class="checkout-btn">会計する</button> </div> </div>
CSS
/* 会計画面レイアウト */ .checkout-layout { display: grid; grid-template-columns: 1fr 360px; gap: 24px; align-items: start; } .checkout-items { background: #fff; border-radius: 8px; padding: 20px; } .checkout-table { width: 100%; border-collapse: collapse; margin-top: 12px; } .checkout-table th, .checkout-table td { padding: 10px; text-align: left; border-bottom: 1px solid #eee; font-size: 14px; } .checkout-table th { color: #888; font-size: 13px; } .checkout-summary { background: #fff; border-radius: 8px; padding: 20px; position: sticky; top: 70px; } .checkout-row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 14px; } .checkout-row.total { font-size: 24px; font-weight: bold; color: #e94560; border-top: 2px solid #333; margin-top: 8px; padding-top: 12px; } .payment-methods { margin: 20px 0; } .payment-methods p { font-size: 13px; color: #666; margin-bottom: 8px; } .payment-buttons { display: flex; gap: 8px; } .payment-btn { flex: 1; padding: 10px; border: 2px solid #ddd; border-radius: 6px; background: #fff; cursor: pointer; font-size: 13px; transition: border-color 0.2s; } .payment-btn.active { border-color: #e94560; color: #e94560; font-weight: bold; } .checkout-btn { width: 100%; padding: 16px; background: #e94560; color: #fff; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: background 0.2s; } .checkout-btn:hover { background: #c73a52; } @media (max-width: 640px) { .checkout-layout { grid-template-columns: 1fr; } .checkout-summary { position: static; } }
JavaScript
// 支払い方法の切り替え document.querySelectorAll('.payment-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.payment-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); }); }); // 会計ボタン document.querySelector('.checkout-btn').addEventListener('click', () => { const method = document.querySelector('.payment-btn.active').dataset.method; console.log('会計処理:', method); // ここに会計処理を実装 // 例: レシート生成、在庫更新、履歴保存など });
14. ステータスバッジ
使い方: 注文・取引のステータスを色分けして表示するバッジです。
setBadge(element, 'paid') でバッジの状態を動的に変更できます。
POS画面では「注文ステータス」「在庫ステータス」「取引履歴の状態表示」に使えます。
テーブルの行に組み込んで使用するのが一般的です。
HTML
<!-- ステータスバッジ --> <span class="badge badge--paid">支払済</span> <span class="badge badge--pending">未処理</span> <span class="badge badge--processing">処理中</span> <span class="badge badge--cancelled">キャンセル</span> <span class="badge badge--refunded">返金済</span> <!-- テーブル内での使用例 --> <table class="order-table"> <thead> <tr><th>注文番号</th><th>金額</th><th>ステータス</th></tr> </thead> <tbody> <tr> <td>#0001</td><td>¥1,320</td> <td><span class="badge badge--paid">支払済</span></td> </tr> <tr> <td>#0002</td><td>¥850</td> <td><span class="badge badge--pending">未処理</span></td> </tr> </tbody> </table>
CSS
/* ステータスバッジ */ .badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: bold; } .badge--paid { background: #d4edda; color: #155724; } .badge--pending { background: #fff3cd; color: #856404; } .badge--processing { background: #cce5ff; color: #004085; } .badge--cancelled { background: #f8d7da; color: #721c24; } .badge--refunded { background: #e2e3e5; color: #383d41; } /* 注文テーブル(使用例) */ .order-table { width: 100%; border-collapse: collapse; font-size: 14px; } .order-table th, .order-table td { padding: 10px; text-align: left; border-bottom: 1px solid #eee; } .order-table th { background: #f5f5f5; color: #666; font-size: 13px; }
JavaScript
// ステータスバッジの動的変更 const STATUS_MAP = { paid: { label: '支払済', className: 'badge--paid' }, pending: { label: '未処理', className: 'badge--pending' }, processing: { label: '処理中', className: 'badge--processing' }, cancelled: { label: 'キャンセル', className: 'badge--cancelled' }, refunded: { label: '返金済', className: 'badge--refunded' } }; function setBadge(element, status) { const info = STATUS_MAP[status]; if (!info) return; // 既存のバッジクラスを全て削除 Object.values(STATUS_MAP).forEach(s => element.classList.remove(s.className)); element.classList.add('badge', info.className); element.textContent = info.label; } // 使用例: // const badge = document.querySelector('.badge'); // setBadge(badge, 'paid'); // → 支払済(緑) // setBadge(badge, 'cancelled'); // → キャンセル(赤)