# HTML Templates

HTML templates use a logicless Mustache-style syntax with `{{variable}}` placeholders. They render client-side in the POS app, which means they **work offline** — no server connection needed to display or print a receipt.

This is the recommended engine for most users.

## Syntax[​](#syntax "Direct link to Syntax")

### Variables[​](#variables "Direct link to Variables")

Insert data using double curly braces:

```
<h1>{{store.name}}</h1>

<p>Order #{{order.number}}</p>

<p>Total: {{totals.total_display}}</p>
```

Use `_display` variants for pre-formatted currency values (e.g., `$12.50` instead of `12.5`). See the [Receipt Data Reference](/receipts/receipt-data.md) for all available fields.

### Sections (Loops and Conditionals)[​](#sections-loops-and-conditionals "Direct link to Sections (Loops and Conditionals)")

Use `{{#section}}...{{/section}}` to loop over arrays or conditionally show content:

```
{{#lines}}

<div class="line-item">

  <span>{{name}} × {{qty}}</span>

  <span>{{line_total_display}}</span>

</div>

{{/lines}}
```

### Inverted Sections[​](#inverted-sections "Direct link to Inverted Sections")

Use `{{^section}}...{{/section}}` to show content when a value is empty or false:

```
{{#customer.id}}

  <p>Customer: {{customer.name}}</p>

{{/customer.id}}

{{^customer.id}}

  <p>Guest checkout</p>

{{/customer.id}}
```

### Localised Labels[​](#localised-labels "Direct link to Localised Labels")

Use `{{i18n.key}}` for translatable labels that adapt to the store's language:

```
<th>{{i18n.subtotal}}</th>

<td>{{totals.subtotal_display}}</td>
```

## Common Patterns[​](#common-patterns "Direct link to Common Patterns")

### Store Header[​](#store-header "Direct link to Store Header")

The `store.address_lines` array is pre-formatted for receipts — loop it instead of stitching individual address fields together.

```
<div style="text-align: center;">

  {{#store.logo}}<img src="{{store.logo}}" alt="{{store.name}}" style="max-width: 200px;" />{{/store.logo}}

  <h1 style="margin: 8px 0;">{{store.name}}</h1>

  {{#store.address_lines}}<div>{{.}}</div>{{/store.address_lines}}

  {{#store.phone}}<div>{{store.phone}}</div>{{/store.phone}}

  {{#store.tax_ids}}<div>{{#label}}{{label}} {{/label}}{{value}}</div>{{/store.tax_ids}}

</div>
```

### Line Items with Discounts[​](#line-items-with-discounts "Direct link to Line Items with Discounts")

A line item's `discounts` field is the discount amount as a positive number, or `0` when there's no discount. Mustache treats `0` as falsy, so `{{#discounts}}...{{/discounts}}` is the right guard.

```
{{#lines}}

<div class="line-item" style="display: flex; justify-content: space-between;">

  <div>

    <div>{{name}} × {{qty}}</div>

    {{#discounts}}

      <div style="font-size: 0.9em; color: #6b7280;">

        <s>{{unit_subtotal_display}}</s> {{unit_price_display}}

      </div>

    {{/discounts}}

  </div>

  <div>{{line_total_display}}</div>

</div>

{{/lines}}
```

### Tax Summary[​](#tax-summary "Direct link to Tax Summary")

```
{{#tax_summary}}

<div class="tax-line">

  <span>{{label}} ({{rate}}%)</span>

  <span>{{tax_amount_display}}</span>

</div>

{{/tax_summary}}
```

### Payment Details[​](#payment-details "Direct link to Payment Details")

```
{{#payments}}

<div class="payment" style="display: flex; justify-content: space-between;">

  <span>{{method_title}}</span>

  <span>{{amount_display}}</span>

</div>

{{#tendered}}

  <div style="display: flex; justify-content: space-between;">

    <span>{{i18n.tendered}}</span><span>{{tendered_display}}</span>

  </div>

  <div style="display: flex; justify-content: space-between;">

    <span>{{i18n.change}}</span><span>{{change_display}}</span>

  </div>

{{/tendered}}

{{/payments}}
```

### Barcodes and QR Codes[​](#barcodes-and-qr-codes "Direct link to Barcodes and QR Codes")

Use the `<barcode>` element — the same syntax as [thermal templates](/receipts/thermal-templates.md). The value goes inside the element; the symbology is set on the `type` attribute. The renderer replaces each element with an inline SVG.

```
<!-- Code 128 barcode of the order number -->

<barcode type="code128" height="40">{{order.number}}</barcode>



<!-- QR code -->

<barcode type="qrcode" scale="3">{{order.payment_url}}</barcode>



<!-- EAN-13 -->

<barcode type="ean13">{{order.number}}</barcode>
```

The `type` attribute must be set **literally** in the template — it can't come from a placeholder. A `type` of `qr` or `qrcode` renders as a QR code in both HTML and thermal templates.

If the selected barcode type can't encode the value (for example non-numeric or wrong-length EAN-13 data), the preview shows a `Barcode error` or `QR code error` block with the original value, so you can fix the value or switch to a compatible type.

All [bwip-js symbologies](https://github.com/metafloor/bwip-js/wiki/Supported-Barcode-Types) are supported — `code128`, `qrcode`, `ean13`, `ean8`, `upca`, `pdf417`, `datamatrix`, and many more.

## Styling[​](#styling "Direct link to Styling")

Logicless templates are sanitised through WordPress's `wp_kses_post` before rendering, which **strips `<style>`, `<head>`, and document-level tags**. Use **inline styles** on each element:

```
<div style="font-family: monospace; font-size: 12px; max-width: 300px; margin: 0 auto;">

  <div style="text-align: center; margin-bottom: 16px;">

    <strong style="font-size: 16px;">{{store.name}}</strong>

  </div>

  <div style="display: flex; justify-content: space-between; font-weight: 700; border-top: 1px solid black; padding-top: 8px;">

    <span>{{i18n.total}}</span>

    <span>{{totals.total_display}}</span>

  </div>

</div>
```

The same restriction applies to `<script>`, `<link>`, and other non-content tags — they're stripped, so don't rely on external CSS or JavaScript.

Designed for black and white

Receipts are usually printed on black-and-white thermal or laser printers. Design with that in mind — use **font weight, whitespace, type contrast, and horizontal rules** for hierarchy rather than coloured fills. Coloured backgrounds and grey text either disappear on B\&W output or print as muddy halftones.

Need a thermal-paper look without a thermal printer?

Click **Use Template** on the **Narrow Receipt** card in the gallery — it's a monospace, 72mm-wide layout that prints cleanly on browser print, HTML-capable thermal printers (Sunmi/Imin WebView devices), and standard paper. Not for raw ESC/POS thermal printers — use the **Simple Thermal Receipt** XML templates for those.

## Best Practices[​](#best-practices "Direct link to Best Practices")

* **Use `_display` fields** for all currency values — they handle locale-aware formatting automatically
* **Use `{{i18n.*}}` labels** instead of hardcoding text so templates work across languages
* **Use sections for conditionals** — wrap optional fields in `{{#field}}...{{/field}}` so they're hidden when empty
* **Start from a gallery template** rather than building from scratch
* **Test with the preview** in the template editor before activating
