The keyboard.
A till or a tablet has no keyboard. Arda.keyboard() opens one over the page and resolves with what was typed. Your app opens it: nothing watches for a focused input, on purpose — a keyboard that guesses covers the wrong thing.
Asking for a value
// One value: resolves the string, or null if they cancelled
Arda.keyboard({ layout: 'full', title: 'Customer name', value: '' }).then(function (name) {
if (name === null) return; // cancelled: always check this first
setCustomer(name);
});
// Several named fields: resolves an object keyed by name
Arda.keyboard({
fields: [
{ name: 'user', caption: 'User name', flex: 2 },
{ name: 'role', caption: 'Role' }
]
}).then(function (r) {
if (r === null) return;
console.log(r.user, r.role);
});
| The person | The promise resolves |
|---|---|
| cancels | null, in every layout |
submits, and you passed fields | an object keyed by each field's name |
submits, without fields | the string typed |
- Which of the two you get depends on what you asked with, not on how many fields there are.
- It rejects (it does not resolve) when it cannot open: in a headless app, or for a
screenthe device does not have. - One keyboard at a time per screen. Opening another cancels the first; its promise resolves
null.
Layouts
layout | What it is |
|---|---|
'full' | letters, digits, symbols, caps, shift, tab, clear, space. The default when you pass fields. |
'numeric' | a 4×4 pad: digits, ., backspace, clear, tab, cancel, submit |
'pin' | centred on the screen, one masked field, digits, backspace, cancel and a tall submit. The default with no fields. |
'date', 'time', 'datetime' | pickers (below) |
- Tab and Enter move to the next field, wrapping. Submit is its own key: nothing submits by accident. Touching a field selects it.
- Clicking outside the keyboard does nothing unless you pass
closeOnOutside: true— an operator's sleeve must not throw away what they typed. - Only a PIN listens to a physical keyboard. On a machine with a keyboard,
fullandnumericignore it (otherwise it types in two places at once). Onpin, digits type, Backspace deletes, Enter submits and Escape cancels — a manager's override at a staffed till.
Arda.keyboard({ layout: 'pin', title: 'Manager PIN' }).then(function (pin) {
if (pin === null) return;
if (!checkPin(pin)) showRefused();
});
Fields
| Field key | Meaning |
|---|---|
name | the key in the result object |
caption | the label on the field |
value | the starting value |
flex | its share of the row; 1 unless you say otherwise |
secret | dots instead of characters (on by default for a PIN) |
mask | a typing pattern (below) |
prefix, suffix | display only: a currency sign, a unit. Never in the value. |
decimals, pos | numbers: a decimal limit enforced while typing; till entry (below) |
The value you get back is exactly what the field shows, minus the prefix and the suffix. An untouched field resolves '', not '0.00': "they said nothing" and "they said zero" are different answers.
Masks
Arda.keyboard({
layout: 'numeric',
fields: [
{ name: 'price', caption: 'Price each', prefix: '£', suffix: ' /kg' },
{ name: 'date', caption: 'Collect on', mask: '00/00/0000' },
{ name: 'phone', caption: 'Phone', mask: '00000 000000' }
]
}).then(function (r) {
if (r === null) return;
// r.price === '4.50' the £ and the /kg are not in it
// r.date === '12/03/2026' the slashes are; digits only: r.date.replace(/\D/g, '')
});
0one digit,Aone letter,*either, anything else a literal inserted as you reach it.\0is a literal zero (written'\\0'in a JavaScript string).- A character the mask cannot hold is dropped silently; a full mask stops accepting.
- Pass either form as the starting value:
'12032026'and'12/03/2026'mean the same. secretwins over a mask: dots, not the format.
Till entry: pos and decimals
Arda.keyboard({
layout: 'numeric',
fields: [{ name: 'amount', caption: 'Amount', prefix: '£', decimals: 2, pos: true }]
}).then(function (r) {
if (r === null || r.amount === '') return;
takePayment(Math.round(Number(r.amount) * 100)); // pence
});
pos: true is the till's way of typing money: no decimal point to press, digits shift in from the right (0.00 → 3 → 0.03 → 5 → 0.35 → 0 → 3.50). decimals limits the places while typing, rather than correcting them on submit. The exact string a pos field resolves was not run in a browser here; check it once on your screen before you store it.
Following along: onpress
Arda.keyboard({
fields: [{ name: 'note', caption: 'Note' }],
onpress: function (e) {
// e = {key, field, value} for every press, characters and actions alike
preview.textContent = e.value;
}
});
When the person cancels, the keyboard replays each changed field's original value through onpress, so an app that copies e.value into its state unwinds by itself. A throw inside onpress is logged and the keyboard carries on.
Date and time pickers
Arda.keyboard({ layout: 'date', title: 'Collect on', min: '2026-11-01', max: '2026-12-31' })
.then(function (iso) {
if (iso === null) return; // cancelled
if (iso === '') return clearDate(); // 'None' was pressed (only with none: true)
var d = new Date(iso + 'T00:00'); // local midnight; new Date('2026-11-14') would be UTC
label.textContent = d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' });
});
layout | Resolves |
|---|---|
'date' | '2026-03-12' |
'time' | '14:30' |
'datetime' | '2026-03-12T14:30' |
- A picker shows the date the way a person reads it and resolves it machine-readable. It is the one place where "what it shows is what you get" does not hold.
- Options:
value(ISO in, the same ISO out),title,min,max,minuteStep(default 5),weekStart(1 Monday, the default; 0 Sunday),none: true(a None key that resolves''— different fromnull, which is cancel). - A picker ignores
fields. Ondatetime, the Date and Time fields switch the pad. - Days outside
min/maxstay in the grid, dimmed, and cannot be pressed.
On another screen
Arda.screens.ready().then(function () {
return Arda.keyboard({ layout: 'pin', screen: 'customer' });
}).then(function (pin) { if (pin !== null) verify(pin); },
function (e) { console.warn(e.message); }); // no screen of that name
Styling, and the CSS traps
Arda.keyboard({
layout: 'numeric',
theme: { accent: '#00b3ff', key: '#3a4a5a', keySpecial: '#26313b', radius: '6px', unit: '20px' },
css: 'key { font-weight: 600 }'
});
- The keyboard lives in a shadow root. Your page's stylesheet cannot reach it, and its styles cannot reach your page.
.my-app key { … }in your CSS does nothing. themeis a closed list:accent,bg,key,keyAlt,keySpecial,keyText,fieldBg,fieldText,fieldTextOn,backdrop,radius,font,keyHeight,keyGap,unit. Any other name is refused with a console warning listing the right ones.cssis added after the theme, inside the shadow root, so it wins. The elements are custom names:keyboard,keys,keyrow,key,fields,field,fieldval. If you set the keyboard's custom properties fromcss, set them on thekeyboardelement: a value set on:hostloses to the defaults declared onkeyboard, with no error.- Size follows the screen, not your page (since v1.1.12): about 16px per unit on a 1366×768 till, 22px on 1920×1200. Your
html { font-size }does not shrink it.theme.unitscales the whole keyboard;theme.keyHeightmoves only the keys. keyGapis in pixels and should stay a whole number: a gap exists to stop two keys being touched at once.- The panel is opaque and has a backdrop, so the keyboard looks the same whatever your page draws behind it.
bgandbackdropchange both.
Checked: every option and theme name is from ArdaJS() and keyboard.css in the player source; the reject in a headless app was observed ("there is no screen on a headless device"), and the calls are shaped like those of apps running on devices today. The keyboard is drawn only in a window, so the drawn behaviour (masks, pos, pickers) was not driven by a test here; it is as the player's manual describes it.