Reference

UI components

The pos-* elements available to Worker UI extensions: properties, events and rules.

View Markdown

Applies to api_version: "2026-01".

Worker UI extensions (blocks, modal/fullscreen actions, render: "worker" pages) build their UI only from the pos-* elements below. POS renders each element with its own components, so the UI always matches POS.

Iframe pages do not use this list; they use free-form HTML/React and can add @pancake-pos/ui to look like POS.

General rules

  • Only use elements from this list. Plain HTML tags (div, span, img, table, script…) and unknown elements are not rendered; the extension logs an error in the app's error log.
  • Text is only displayed inside pos-text, pos-heading, pos-badge, pos-link, pos-button, pos-banner.
  • No style, className or raw HTML. Colors, spacing and fonts follow POS.
  • URLs (href, src) must be https://.
  • Events pass values through event.detail. In JSX use onClick, onChange… as with regular components. In plain JS, the event name is the part after on in lowercase: onPageChange → addEventListener('pagechange', …).
  • Limits: 2,000 elements per extension; pos-table renders at most 500 rows at a time (use pagination).
  • Array/object properties (options, columns, rows, pagination) are passed directly in JSX; no JSON.stringify needed.
TSX
<pos-stack gap="small">
  <pos-text-field label="Ticket code" value={code} onChange={(e) => setCode(e.detail)} />
  <pos-button variant="primary" loading={saving} onClick={save}>Save</pos-button>
</pos-stack>

Without a framework, build the tree with the runtime's DOM API (see Worker environment):

JavaScript
let code = '';

const field = document.createElement('pos-text-field');
field.label = 'Ticket code';
field.addEventListener('change', (e) => { code = e.detail; });

const button = document.createElement('pos-button');
button.variant = 'primary';
button.textContent = 'Save';
button.addEventListener('click', () => save(code));

const stack = document.createElement('pos-stack');
stack.gap = 'small';
stack.append(field, button);
document.body.append(stack);

Layout

pos-stack

Stacks children vertically or horizontally.

Property Type Default Description
direction 'vertical' | 'horizontal' 'vertical' Stacking direction
gap 'none' | 'small' | 'medium' | 'large' 'medium' Space between children
align 'start' | 'center' | 'end' | 'stretch' 'stretch' Cross-axis alignment
justify 'start' | 'center' | 'end' | 'space-between' 'start' Main-axis alignment
wrap boolean false Allow wrapping (with horizontal)

pos-grid

A grid of equal columns; collapses to one column on narrow screens.

Property Type Default Description
columns 1–6 2 Number of columns
gap 'none' | 'small' | 'medium' | 'large' 'medium' Spacing

pos-section

A titled container, typically the outer shell of a block.

Property Type Description
heading string Title
description string Short text below the title

Slot actions: buttons shown at the right of the title.

TSX
<pos-section heading="Customer debt">
  <pos-button slot="actions" variant="tertiary" onClick={reload}>Reload</pos-button>
  <pos-text>...</pos-text>
</pos-section>

pos-divider

A horizontal rule. No properties.

Display

pos-text

Property Type Default
tone 'default' | 'subdued' | 'success' | 'warning' | 'critical' 'default'
size 'small' | 'medium' | 'large' 'medium'
weight 'regular' | 'semibold' | 'bold' 'regular'

pos-heading

Property Type Default
level 1–4 3

pos-badge

A short status label.

Property Type Default
tone 'default' | 'info' | 'success' | 'warning' | 'critical' 'default'

pos-money

Displays an amount using the shop's currency and number format.

Property Type Description
value number Amount
currency string ISO 4217 code; defaults to the shop currency
tone same as pos-text Text color
Property Type Description
href string https:// URL, always opens in a new tab

onClick event: use it when the link performs an action in the app instead of opening a URL (omit href).

pos-image

Property Type Default Description
src string An https:// URL on a domain listed in network.allowed_origins, or public/<file name> for a file in the project's public/ folder
alt string Alternative text
width, height number Size (px)
fit 'cover' | 'contain' 'cover' How the image fits

POS loads images through a proxy on the sandbox: image types only, up to 5 MB. Images from undeclared domains are not shown (development mode logs a warning).

Inputs

Properties shared by every input:

Property Type Description
label string Label
value depends on the component Current value (controlled component)
disabled boolean Disable the input
required boolean Show the required marker (does not validate)
error string Error message below the input
help string Hint below the input

Shared events: onChange (event.detail is the new value), onBlur.

POS shows what the user types immediately and then sends onChange to the app, so inputs never lag while the app is busy. If the app sets a value different from what the user just entered (for example uppercasing it or clearing the field), POS shows the app's value. Set value directly in onChange; changing it after an await may overwrite what the user is typing.

pos-text-field

Property Type Description
value string
placeholder string
multiline boolean | number Multi-line input; a number sets the visible rows
maxLength number Character limit

pos-number-field

Property Type Description
value number | null
min, max, step number
suffix string Unit shown after the number, for example "%"

pos-select

Property Type Description
options { label: string; value: string; disabled?: boolean }[] Choices
value string | string[] An array with multiple
multiple boolean Multiple selection
placeholder string
searchable boolean Allow typing to filter

pos-checkbox

Property Type Description
checked boolean
label string

onChange: event.detail is a boolean.

pos-date-picker

Property Type Description
value string | null Date as YYYY-MM-DD
min, max string Selectable range, as YYYY-MM-DD

Feedback

pos-banner

A prominent message inside the extension.

Property Type Default Description
tone 'info' | 'success' | 'warning' | 'critical' 'info'
heading string Title
dismissible boolean false Show a close button

onDismiss event.

pos-spinner

Property Type Default
size 'small' | 'medium' | 'large' 'medium'

Data and actions

pos-button

Property Type Default Description
variant 'primary' | 'secondary' | 'tertiary' | 'danger' 'secondary' Button style
size 'small' | 'medium' 'medium'
loading boolean false Shows a busy state and blocks clicks
disabled boolean false

onClick event.

pos-table

A data table. Cells only hold values (strings, numbers); POS formats them according to the column format.

Property Type Default Description
columns Column[] Column definitions
rows object[] [] Data, up to 500 rows
rowKey string 'id' Field used as the row key
loading boolean false
pagination { page: number; pageSize: number; total: number } | null null App-side pagination
emptyText string Content when there are no rows
TypeScript
type Column = {
  key: string;                       // field name in the row
  title: string;
  align?: 'left' | 'center' | 'right';
  width?: number;                    // px
  format?: 'text' | 'number' | 'money' | 'date' | 'datetime';
};

Events:

  • onPageChange: event.detail is { page, pageSize }.
  • onRowClick: event.detail is the clicked row.
TSX
<pos-table
  columns={[
    { key: 'system_id', title: 'Order' },
    { key: 'total_price', title: 'Total', format: 'money', align: 'right' },
  ]}
  rows={orders}
  rowKey="system_id"
  pagination={{ page, pageSize: 20, total }}
  onPageChange={(e) => setPage(e.detail.page)}
/>

pos-modal

A dialog opened from inside an extension (for example from a button in a block).

Property Type Description
open boolean Open state
heading string Title

Slot footer for the dialog buttons. The onClose event fires when the user closes it or clicks outside.

TSX
<pos-modal open={open} heading="Confirm" onClose={() => setOpen(false)}>
  <pos-text>Push this order to the ERP?</pos-text>
  <pos-button slot="footer" onClick={() => setOpen(false)}>Cancel</pos-button>
  <pos-button slot="footer" variant="primary" onClick={push}>Push</pos-button>
</pos-modal>

Actions with presentation: "modal" already get a POS modal; do not wrap them in pos-modal.

Planned additions

pos-page, pos-tabs, pos-filter-bar, pos-chart (POS draws the chart, the app only passes data), for full-screen Worker UI pages.