ui5-lib-kiosk-keyboard
v0.1.0
Published
On-screen virtual keyboard control for SAPUI5/OpenUI5 kiosk and touch applications
Maintainers
Readme
Part of the ui5-keyboard monorepo. See also: ui5-lib-hotkeys and kiosk-keyboard-webc.
On-screen virtual keyboard control for SAPUI5/OpenUI5 kiosk and touch applications.
[!IMPORTANT] UI5 compatibility Declared floor: UI5 1.136, a long-term-maintenance (LTS) release and the lowest version SAP's UI5 tooling accepts in
manifest.json. The package is built, type-checked, and tested against 1.136. True implementation floor: UI5 1.120. The library only uses APIs available since 1.120 (DataType.registerEnum(),Localization.getLanguageTag(),Lib.init({ apiVersion: 2 })), so apps pinned to an older LTS down to 1.120 work too.Lib.init()is available from 1.118, so it does not raise the floor.
A UI5 TypeScript library (ui5.kiosk) providing a themed, accessible virtual keyboard that types into any UI5 input control. Supports multiple layouts, Shift/Caps Lock, docked mode with auto-show, and integrates with SAP Horizon theming.
Table of Contents
- Features
- Installation
- Browser Compatibility
- Getting Started
- Quick Start
- API Stability
- Custom Layouts
- KioskKeyboard Control
- Layouts
- Composition Middleware
- Function Keys (F1-F12)
- Locale-Based Default Layout
- Docked Mode
- Auto-Show
- Text Insertion
- Interop Cookbook
- Auto-Type
- controls
- Icon + Label Rendering
- Custom Target Resolver
- Mobile Keyboard Detection
- Shift & Caps Lock
- Accessibility
- Theming
- Internationalization (i18n)
- Library Enums & Constants
- Development
- Further Reading
- Troubleshooting
- When NOT to Use This Library
- License
Features
Core
- Pure UI5 Control with flat-DOM keys and event delegation (no control per key)
- Types into any UI5 input control (
sap.m.Input,sap.m.TextArea, etc.) named incontrols - Cursor-aware text insertion, backspace, and selection replacement
- Fires
liveChangeon the target for proper data binding integration - Shift toggle (single tap) and Caps Lock (double tap) with auto-release
Layouts
- Built-in layouts: QWERTY, QWERTZ-DE, Spanish QWERTY, Japanese Romaji, Japanese Kana (plus a narrow-width compact form), Korean Hangul, Arabic, numeric, special characters, numpad, function keys, navigation keys
- Locale-based default layout (auto-detects from UI5 language setting)
- Runtime layout switching via
{layout:name}keys keyboardTypeproperty for quick switching between Full, Numeric, and Numpad modes- Extensible layout definition format (
LayoutDefinitiontype) - Custom layouts, locale mappings, composition middleware and accent tables declared together on the per-control
customLayoutsaggregation - Reusable
fkey-rowandnav-rowmodules for composing custom variant layouts
Docked Mode
- Bottom-of-viewport positioning with slide-in/out animation
show()/close()API for programmatic control- Auto-show: opens when any
<input>or<textarea>receives focus, closes when focus leaves
Smart Context Detection
- Auto-type: automatically switches to numpad for Number/Tel inputs and StepInput
- Mobile keyboard detection: suppress native keyboard or defer to it on phones/tablets
- Native keyboard suppression via
inputmode="none"with ref-counted restore across keyboard instances
Integration
- SAP Horizon theming via LESS variables (all key states use
@sapUiButton*parameters) - Compact and cozy content density support
- F6 fast navigation group (
data-sap-ui-fastnavgroup) - RTL support
- Reduced motion support (
prefers-reduced-motion)
Accessibility
- Roving tabindex for keyboard (arrow key) navigation between virtual keys
role="button"on each key witharia-labelaria-pressedtoggle state on Shift keyaria-disabledon disabled state- Configurable
ariaLabelfor the keyboard group
Installation
Install from npm:
npm install ui5-lib-kiosk-keyboardIn this monorepo, dependencies are managed via npm workspaces (npm install at the root).
Browser Compatibility
The keyboard requires modern browser features for full functionality:
| Feature | Used for | Baseline |
| --------------------- | ---------------------------------- | --------------------------------------- |
| Intl.Segmenter | Grapheme-aware Backspace and caret | Chrome 87+, Firefox 125+, Safari 14.1+ |
| CSS Container Queries | Width-responsive sizing | Chrome 105+, Firefox 110+, Safari 16+ |
| CSS Cascade Layers | Keeping app CSS above library CSS | Chrome 99+, Firefox 97+, Safari 15.4+ |
| ResizeObserver | Height-responsive sizing | Chrome 64+, Firefox 69+, Safari 13.1+ |
| CSS min() / max() | Font-size capping | Chrome 79+, Firefox 75+, Safari 13.1+ |
| CSS color-mix() | Accent-variant hint tint | Chrome 111+, Firefox 113+, Safari 16.2+ |
| CSS Custom Properties | Consumer overrides | Chrome 49+, Firefox 31+, Safari 9.1+ |
Intl.Segmenter is the effective floor. It reached Baseline in April 2024, when
Firefox 125 became the last engine to ship it, so that release is the oldest
Firefox the library supports. It is also the one entry with no graceful
degradation: the segmenter is constructed at module scope, so an engine without
it throws on import rather than losing a feature. Everything else degrades -
without container queries or min() the keyboard renders at full size with no
width-responsive font scaling, without color-mix() the accent-variant hint
loses its tint, and without cascade layers the library's own rules compete with
the app's on ordinary specificity.
Getting Started
The package ships:
- the UI5 source project (
src/,ui5.yaml) for source-first development - the prebuilt distributable under
dist/resources/ui5/kiosk/ - build metadata under
dist/.ui5/build-manifest.json
That gives you 3 supported consumption modes. No project shim is required.
Before choosing a mode, declare the library dependency in your app manifest.json:
{
"sap.ui5": {
"dependencies": {
"libs": {
"ui5.kiosk": {}
}
}
}
}Ensure your app's minUI5Version (under sap.ui5.dependencies) is at least 1.136, the libraries' declared LTS floor. The code only needs 1.120, so an app pinned to an older LTS down to 1.120 also works.
1. Installed package + UI5 Tooling (default)
Recommended for published/runtime usage.
Install the package, keep the library in manifest.json, and let UI5 Tooling resolve it from node_modules.
npm install ui5-lib-kiosk-keyboardIf your app build should copy the library resources into the app dist/, add the UI5 project name to builder.settings.includeDependency:
builder:
settings:
includeDependency:
- ui5.kiosk[!NOTE]
- Use the UI5 project name
ui5.kioskhere, not the npm package nameui5-lib-kiosk-keyboard.includeDependencyis a build concern.ui5 servecan resolve the installed UI5 dependency without it.- The packaged build manifest exists so the distributable can be reused as a build result in dist-based setups instead of always rebuilding from source.
If you deploy the built app to a plain static server while bootstrapping UI5 from CDN, also map the library namespace to the copied resources/ folder:
<script
id="sap-ui-bootstrap"
src="https://sdk.openui5.org/resources/sap-ui-core.js"
data-sap-ui-resource-roots='{
"my.app": "./",
"ui5.kiosk": "./resources/ui5/kiosk/"
}'
data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
data-sap-ui-async="true"
></script>2. Source package + UI5 Tooling transpilation
Recommended for monorepos and local development when you want to work against the library source instead of the prebuilt distributable.
Enable dependency transpilation in both the build task and dev server middleware:
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
configuration:
transpileDependencies: true # source-mode only: transpiles the library's shipped src/*.ts (dist needs none)
transformTypeScript:
allowDeclareFields: true # match the library build; keeps the TS controls' typed class fields
server:
customMiddleware:
- name: ui5-tooling-transpile-middleware
afterMiddleware: compression
configuration:
transpileDependencies: true
transformTypeScript:
allowDeclareFields: true[!NOTE]
- Do not add
ui5.kioskunderframework.libraries; this is a custom UI5 dependency, not a framework library.- Keep using the
manifest.jsondependency shown above.- If your app build should include the library resources in its own
dist/, keepbuilder.settings.includeDependency: [ui5.kiosk]in addition to the transpile setup.
3. Static middleware escape hatch
Use this when you want explicit runtime serving from the dependency's distributable and do not want the dependency to participate in your app's UI5 dependency resolution.
npm install -D ui5-middleware-servestaticserver:
customMiddleware:
- name: ui5-middleware-servestatic
afterMiddleware: compression
mountPath: /resources/ui5/kiosk/
configuration:
npmPackagePath: ui5-lib-kiosk-keyboard/dist/resources/ui5/kioskNotes:
- This is mainly a dev-server/runtime option.
- If you need the library resources inside the app build output as well, prefer mode 1 with
includeDependency, or copy the resources explicitly as part of your deployment process.
Lazy loading via "lazy": true is supported and worth considering if the keyboard is only used on specific views or routes. The library includes CSS, theming, and i18n bundles, so deferring the load avoids pulling those resources at app startup:
"ui5.kiosk": { "lazy": true }When the library is used declaratively in an XML view (e.g. <kiosk:KioskKeyboard .../>), the framework loads it automatically on first view instantiation. For programmatic usage, load it explicitly before creating controls:
import Lib from "sap/ui/core/Lib";
await Lib.load({ name: "ui5.kiosk" });For kiosk terminals where the keyboard is always needed, eager loading (the default, no "lazy" flag) is simpler.
Use the control in your view or controller as shown in Quick Start below.
Quick Start
XML View:
<mvc:View xmlns:kiosk="ui5.kiosk" xmlns:m="sap.m" xmlns:mvc="sap.ui.core.mvc">
<m:Input id="myInput" value="{/text}" />
<kiosk:KioskKeyboard controls="myInput" />
</mvc:View>TypeScript:
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
import Input from "sap/m/Input";
const input = new Input({ value: "" });
const keyboard = new KioskKeyboard({
controls: [input.getId()],
});Docked with auto-show:
<kiosk:KioskKeyboard docked="true" autoShow="true" />The keyboard anchors to the bottom of the viewport and automatically opens when any input receives focus.
API Stability
Recommended stable consumer imports:
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
import { KeyboardLayout, KeyboardType, KeyName, MobileKeyboard, FKeyMode } from "ui5/kiosk/library";
import type { KioskKeyboardDomContract } from "ui5/kiosk/KioskKeyboard";
import type { KeyDefinition, LayoutDefinition } from "ui5/kiosk/types";
const DOM: KioskKeyboardDomContract = KioskKeyboard.DOM;Advanced/internal modules are available but should not be treated as a semver-stable API surface. In particular, anything under ui5/kiosk/internal/* is internal-only. This includes renderer internals and helper modules such as input operations and low-level DOM utilities. Under ui5/kiosk/layouts/*, the built-in layout definitions (e.g. qwerty, numeric, ja-kana) and the shared row fragments ui5/kiosk/layouts/fkey-row, ui5/kiosk/layouts/fkey-row-compact, ui5/kiosk/layouts/nav-row and ui5/kiosk/layouts/nav-row-compact are supported as stable consumer imports - for use as a base layout or for composing custom variants. All four row fragments set type: "modifier" on every key (transparent Lite button style); override type per key only if you want bordered/regular key styling instead.
KioskKeyboard.DOM is also a supported read-only DOM hook contract for tests and DOM assertions. Prefer it over hard-coded class names or selectors. Styling customizations should still use the public --ui5KioskKeyboard-* CSS variables rather than DOM classes.
Custom Layouts
Everything one layout is - its rows, the locales that select it, its keycap language, whether it is an auxiliary surface, its composition middleware and its long-press variants - is declared together on a CustomLayout element in the control's customLayouts aggregation. It shadows the built-in registry for that control only, without touching module-level state.
A custom layout that declares rows declares a layout. One without rows overlays the layout its name already resolves to, so a built-in can be given different variants, a different middleware or a different locale binding without restating its keys.
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
import CustomLayout from "ui5/kiosk/CustomLayout";
import { LayoutFacet, LayoutRole } from "ui5/kiosk/library";
const kb = new KioskKeyboard({
accentVariants: true,
layout: "warehouse-pos",
customLayouts: [
new CustomLayout({
name: "warehouse-pos",
rows: myPosLayout,
locales: ["de"],
keycapLang: "de",
variants: { s: ["ś", "š"] },
}),
// Rows-less overlay: give the built-in kana layout a different middleware.
new CustomLayout({ name: "ja-kana", middleware: kanaDakutenFactory }),
// A transliteration IME whose Latin-looking keys take no accent popups.
new CustomLayout({ name: "my-ime", rows: myImeLayout, suppress: [LayoutFacet.Variants] }),
// Promote the built-in secondary `numeric` to a base alphabetic layout.
new CustomLayout({ name: "numeric", layoutRole: LayoutRole.Base, rows: symbolSurface }),
],
});The aggregation carries the usual generated accessors - addCustomLayout, insertCustomLayout, removeCustomLayout, removeAllCustomLayouts, indexOfCustomLayout, destroyCustomLayouts, bindCustomLayouts - and needs no teardown: the elements are owned by the control and destroyed with it.
JavaScript callers may pass object literals (customLayouts: [{ name: "x", rows }]), which the aggregation coerces into CustomLayout elements. TypeScript callers construct new CustomLayout({...}): the interface generator does not model that coercion, so the generated type is the element union.
Fields and how they resolve
| Field | Type | Across custom layouts with the same name | Across tiers |
| ------------ | ------------------ | --------------------------------------------------------------------------- | -------------------------------------------- |
| name | string | matched after trim + lowercase; duplicates are the overlay mechanism | - |
| rows | LayoutDefinition | last declaration wins | custom layout → built-in |
| keycapLang | string | last declaration wins | custom layout → built-in |
| compact | string | last declaration wins | custom layout → built-in |
| layoutRole | LayoutRole | last declaration wins | Inherit takes the built-in's |
| locales | string[] | additive union; per prefix, last wins | custom layouts → built-in map |
| middleware | () => … | last declaration wins | custom layout → built-in |
| variants | VariantTable | additive per base letter; a letter mapped to [] drops it | built-in → defaultVariants → custom layout |
| suppress | LayoutFacet[] | discards the inherited value of each listed facet at this layout's position | - |
Custom layouts apply in aggregation order. layoutRole is a tri-state: Inherit (the default) takes the built-in layout of the same name's role, Base un-marks a built-in's secondary flag, Secondary marks an auxiliary surface. suppress is how a facet is turned off rather than replaced - a value the same custom layout declares still applies, so suppress="Variants" plus a variants table stands that table alone. suppress is comma-separated and whitespace around a name is not part of it, so suppress="Variants, Middleware" and suppress="Variants,Middleware" are the same list.
compact names the layout that renders instead of this one on a keyboard too narrow to seat its rows - the same key set in a denser arrangement, matched after trim and lowercase, and read only while the control's autoCompact is on. It may name a built-in (ja-kana ships ja-kana-compact) or another custom layout. suppress cannot turn it off: the suppressible facets are Variants and Middleware, so an overlay can replace an inherited counterpart but not remove it.
rows and variants are read by object identity: assign a new array or object to change them; mutating in place is not observed. The shape a rows array takes - and every field a key in it can carry - is in Layout Definition Format.
What the control reports
A misconfiguration is logged once per control per distinct complaint, naming the layout and the remedy, rather than resolving silently to nothing:
| Code | Trigger |
| ---------------------- | ---------------------------------------------------------------------- |
| empty-name | a custom layout whose name is empty after trim |
| invalid-rows | rows that are not a layout definition (the other facets still apply) |
| invalid-variants | variants (or defaultVariants) that are not a variant table |
| invalid-middleware | middleware that is not a function |
| invalid-locale | a locales entry that is empty after trim |
| unknown-target | facets declared with no rows, for a name no layout has |
| unknown-compact | compact that names a layout nothing declares |
| duplicate-rows | two custom layouts declare rows for one name |
| duplicate-middleware | two custom layouts declare middleware for one name |
| duplicate-locale | two custom layouts claim one BCP-47 prefix for different layouts |
An eleventh code, unknown-suppress (a suppress token naming no facet), is reachable only in the web-component twin, which parses the attribute itself; here the ui5.kiosk.LayoutFacet DataType rejects the value first.
A typo in layoutRole or suppress throws rather than being reported: they are closed enums, so ManagedObject rejects the value the way mobileKeyboard="Bogus" already does.
In an XML view
A complete layout extension is declarable with no controller code. rows and variants are object-typed, so they arrive through a model binding; middleware resolves through core:require.
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" xmlns:kiosk="ui5.kiosk">
<kiosk:KioskKeyboard id="kb" controls="name,email" accentVariants="true"
defaultVariants="{layouts>/houseAccents}">
<kiosk:customLayouts>
<!-- A whole layout: rows, locale binding, keycap language, IME, accents. -->
<kiosk:CustomLayout core:require="{ Warehouse: 'demo/middleware/warehouse' }"
name="pl-warehouse" locales="pl,pl-PL" keycapLang="pl"
rows="{layouts>/plWarehouse}"
middleware="Warehouse.createMiddleware"
variants="{layouts>/plVariants}" />
<!-- Rows-less overlay: point the Japanese locale at the BUILT-IN kana layout. -->
<kiosk:CustomLayout name="ja-kana" locales="ja" />
<!-- Opt a layout out of accents entirely. -->
<kiosk:CustomLayout name="arabic" suppress="Variants" />
<!-- Disable the built-in Hangul composer for directly-typed rows. -->
<kiosk:CustomLayout name="ko-hangul" suppress="Middleware" rows="{layouts>/hangulDirect}" />
</kiosk:customLayouts>
</kiosk:KioskKeyboard>
</mvc:View>KioskKeyboard Control
Properties
| Property | Type | Default | Description |
| ----------------- | -------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| layout | string | "qwerty" | Active layout name, written back as the active layout changes - see the note below. Auto-detected from locale when omitted. Drives the displayed surface when keyboardType="Full", and after a user {layout:X} tap regardless of keyboardType. |
| keyboardType | ui5.kiosk.KeyboardType | "Full" | Display type: Full, Numeric, or Numpad. |
| enabled | boolean | true | Whether the keyboard is interactive. |
| ariaLabel | string | "" | Accessible label for the keyboard group. Defaults to "Virtual Keyboard" from i18n when empty. |
| docked | boolean | false | Anchor to the bottom of the viewport with slide animation. |
| autoShow | boolean | false | Auto-open on input focus, auto-close when focus leaves. Requires docked. |
| autoType | boolean | false | Auto-switch between Full/Numpad based on focused input type. Requires autoShow. |
| autoCompact | boolean | false | Swap the resolved layout for its compact counterpart while the keyboard is too narrow to seat its rows, and back when the room returns. Of the built-ins only ja-kana declares a counterpart; a custom layout names its own with compact. |
| mobileKeyboard | ui5.kiosk.MobileKeyboard | "Auto" | Native keyboard behavior: Auto (device-aware), Custom (suppress), Native (defer). |
| fKeyMode | ui5.kiosk.FKeyMode | "Virtual" | F-key handling: Virtual (emit keyPress + built-in caret navigation), Native (adds a synthetic keydown + native actions), None (event only, no navigation, no native action). |
| accentVariants | boolean | false | Overlay the built-in Latin-diacritics table so any Latin base key of the resolved layout exposes a long-press / right-click accent-variant popup. The five non-Latin built-ins are excluded by default. See Accent variants. |
| controls | ui5.kiosk.ControlID[] | [] | Input control IDs for targeting. Supports single or multiple inputs. See controls. |
| defaultVariants | VariantTable \| null | null | Long-press variants applied under every layout, merged per base letter beneath anything a customLayouts entry declares. Effective only with accentVariants. See Accent variants. |
[!IMPORTANT]
layoutholds the effective layout, not the one you last set. A{layout:X}tap,setLayout(), and anautoCompactwidth swap all write it, sogetLayout()always answers "what is on screen" - the same contractkeyboardTypehas underautoType.Two consequences worth knowing before you bind it:
A two-way binding is written back.
layout="{/prefs/layout}"receives"ja-kana-compact"when the keyboard narrows, so persisting that model field persists an arrangement the user never chose - and two-way is every model's default mode, so this needs no opting in. Bind one-way (layout="{path: '/prefs/layout', mode: 'OneWay'}") when the value is a stored preference, and take user-driven changes from thelayoutChangeevent, whoseautoDetectedflag separates a width swap from a request. The control logs a warning once per instance when a width swap is about to write through a two-waylayout, so the case is never silent; a{layout:X}tap writes back without a warning, since persisting the user's own choice is the point.
keyboardTypeneeds no such care despite sharing the contract:autoTypeonly detects while the type has not been set explicitly, and a binding delivers its value throughsetKeyboardType, which marks it exactly that. Binding the property is what switches the detection off, so it has no path on which to write back.The web component splits this the other way.
<kiosk-keyboard>.layoutkeeps the layout you asked for and exposes the resolved one as the read-onlyeffectiveLayout, because a custom-element attribute is an author declaration rather than live state. PortgetLayout()toeffectiveLayout. See Requested vs. effective layout.
Associations
| Association | Type | Cardinality | Description |
| ----------------- | --------------------- | ----------- | ------------------------------------------------------------ |
| ariaLabelledBy | sap.ui.core.Control | 0..n | Additional labels announced by assistive technologies. |
| ariaDescribedBy | sap.ui.core.Control | 0..n | Additional descriptions announced by assistive technologies. |
Aggregations
| Aggregation | Type | Cardinality | Description |
| --------------- | ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| customLayouts | ui5.kiosk.CustomLayout | 0..n | Per-control layouts and overlays, applied in aggregation order. See Custom Layouts. Bindable via bindCustomLayouts. |
Events
| Event | Parameters | Description |
| --------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| keyPress | key: string, shiftKey: boolean | Fired when a virtual key is pressed, except for {shift} and {layout:*}, which switch without asking. Call preventDefault() to skip default input action. Use KeyName constants for non-character keys. |
| layoutChange | layout: string, autoDetected: boolean | Fired when the active layout changes. autoDetected marks an autoCompact width swap rather than a request. |
| keyboardTypeChange | keyboardType: string, previousKeyboardType: string, autoDetected: boolean | Fired when the keyboard type changes. |
| afterOpen | - | Fired when show() opens the docked keyboard (state/event hook, not CSS transition end). |
| afterClose | - | Fired when close() closes the docked keyboard (state/event hook, not CSS transition end). |
| activeControlChange | controlId: string | Fired when the active control changes (auto-show focus switch or programmatic target change). |
Each event has a generated TypeScript alias, exported from ui5/kiosk/KioskKeyboard: KioskKeyboard$KeyPressEvent, KioskKeyboard$LayoutChangeEvent, KioskKeyboard$KeyboardTypeChangeEvent, KioskKeyboard$AfterOpenEvent, KioskKeyboard$AfterCloseEvent and KioskKeyboard$ActiveControlChangeEvent. Type a handler with the alias rather than a hand-written Event<{ ... }>, whose parameter shape nothing checks (see UI5 TypeScript Event Typing):
import type { KioskKeyboard$KeyPressEvent } from "ui5/kiosk/KioskKeyboard";
onKeyPress(event: KioskKeyboard$KeyPressEvent): void {
const key = event.getParameter("key"); // string | undefined, from the event metadata
}Public Methods
KioskKeyboard-specific public instance methods (excluding inherited UI5 base class methods):
| Method | Returns | Description |
| -------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| setLayout(layout) | this | Set the active layout property programmatically. Effective when keyboardType="Full"; under Numeric/Numpad it sets the property but the rendered surface stays constrained until a user {layout:X} tap. |
| getBaseLayout() | string | Get the tracked base (alphabetic) layout used by {layout:base}. |
| resetLayout() | this | Switch back to the tracked base layout. |
| setKeyboardType(type) | this | Set keyboard display type (Full, Numeric, Numpad) and lock auto-type. |
| resetKeyboardType() | this | Clear explicit lock, re-enable auto-type. |
| reset() | this | Clear transient input state (shift/caps latch, in-progress composition, backspace auto-repeat, accent-variant popover) and return to the base layout. Leaves the bound value, active target, docked visibility, and all configuration untouched. |
| setAutoShow(autoShow) | this | Enable/disable focus-driven open/close behavior (docked mode). |
| setDocked(docked) | this | Enable/disable docked positioning and related open state handling. |
| setControls(ids) | this | Set the list of control IDs to target (no re-render). |
| getControls() | ControlID[] | Get the current list of control IDs. |
| getActiveControl() | Control \| null | Resolve the currently active (last focused) target control instance. |
| insertText(text) | void | Insert text at the caret of the active target (cursor-tracked, fires liveChange). No-op with no active target. Call from a keyPress handler to implement a custom {token} key. |
| deleteBackward() | boolean | Delete one grapheme before the caret of the active target. Returns whether anything was removed; no-op with no active target. |
| getActiveTargetElement() | HTMLInputElement \| HTMLTextAreaElement \| null | The resolved native input/textarea of the active target, or null. |
| show() | this | Open the docked keyboard. Idempotent. |
| close() | this | Close the docked keyboard. Idempotent. |
| isOpen() | boolean | Whether the docked keyboard is currently open. |
| refreshResponsiveState() | this | Recompute responsive width/height classes after runtime --ui5KioskKeyboard-* sizing changes inside a fixed-height host, where the rendered outer size does not change so no ResizeObserver callback fires. Usually not needed for normal container resizing. |
| setTargetResolver(fn) | this | Set an instance-level custom resolver for locating native inputs. Pass null to clear. |
| getTargetResolver() | TargetResolver \| null | Returns the instance-level target resolver, or null. |
| getFocusDomRef() | Element \| null | Returns the keycap that currently holds the roving tab stop, or null while the keyboard is disabled or renders no keys. |
| getFocusInfo() | object | Returns focus state snapshot for UI5 focus restoration. |
| applyFocusInfo(info) | this | Restores focus state snapshot previously returned by getFocusInfo(). |
| getAccessibilityInfo() | object | Returns UI5 accessibility metadata for assistive technologies. |
For full generated typings (including property/event accessors from UI5 metadata), see src/KioskKeyboard.gen.d.ts (regenerated by npm run generate).
The generated file above covers UI5 metadata accessors. The convenience/runtime methods listed here (for example show(), close(), refreshResponsiveState()) and the read-only KioskKeyboard.DOM hook contract live in src/KioskKeyboard.ts.
Static Methods
The static surface carries no layout registration; custom layouts come from the per-control customLayouts aggregation (see Custom Layouts).
| Method | Returns | Description |
| ----------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| getRegisteredLayout(name) | LayoutDefinition? | Get the definition for a built-in layout name, or undefined. |
| getRegisteredLayoutNames() | string[] | List all built-in layout names. |
| isBuiltInLayout(name) | boolean | Whether the given name is a built-in layout. |
| isSecondaryLayout(name) | boolean | Whether the layout is secondary (non-alphabetic, e.g. numeric, fkeys). |
| getLocaleLayout() | string | Detect the best built-in layout for the current UI5 locale. Falls back to "qwerty". |
| composeLayout(...sources) | LayoutDefinition | Splice rows from built-in layout names and row arrays, in order. A name no built-in has contributes nothing and warns. |
| getKeyIcon(keyValue) | string? | Default icon URI for a special key value, or undefined if none. |
| setI18nResolver(fn) | void | Set a resolver callback for i18n text overrides, or null to clear. |
| setGlobalTargetResolver(fn) | void | Set a global custom resolver for locating native inputs. Pass null to clear. |
| getGlobalTargetResolver() | TargetResolver \| null | Returns the global target resolver, or null. |
DOM Contract
Use KioskKeyboard.DOM for test selectors and DOM assertions instead of repeating raw renderer class names:
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
const DOM = KioskKeyboard.DOM;
const firstKey = keyboard.getDomRef()?.querySelector(DOM.selectors.key);This contract is read-only and stable for DOM hooks. It is not the styling API; continue to customize appearance through the documented --ui5KioskKeyboard-* CSS custom properties.
Layouts
The library ships with thirteen built-in layouts:
| Layout | Description | Rows |
| ----------------- | ----------------------------------------------------------------------- | ---- |
| qwerty | Standard QWERTY with number row | 5 |
| qwertz-de | German QWERTZ with Umlaute (ä, ö, ü, ß) | 5 |
| numeric | Numbers with basic operators | 4 |
| special | Special characters and symbols | 4 |
| numpad | Compact numeric keypad (calculator) | 5 |
| fkeys | Function keys F1-F12 (standalone) | 3 |
| nav | Navigation keys (arrows, Home/End, Pg) | 4 |
| ja-romaji | Japanese Romaji (QWERTY base with JIS punctuation) | 5 |
| ja-kana | Japanese Kana direct-input (JIS X 6002) | 5 |
| ja-kana-compact | Japanese Kana for narrow keyboards, no row wider than twelve key widths | 5 |
| arabic | Arabic (standard Arabic 101 layout) | 5 |
| ko-hangul | Korean Hangul Dubeolsik (KS X 5002) | 5 |
| qwerty-es | Spanish QWERTY with accented vowels and ñ | 5 |
Layout switching is driven by special key values in the layout definition:
// A key that switches to the numeric layout when tapped
{ value: "{layout:numeric}", label: "123", type: "modifier" }The keyboardType property provides a shortcut for common configurations:
Full: renders the activelayoutproperty (default: QWERTY)Numeric: renders the numeric layout regardless of thelayoutpropertyNumpad: renders the numpad layout regardless of thelayoutproperty
keyboardType is a constraint on the default rather than a hard lock. A user-initiated {layout:X} tap (e.g. a custom {layout:special} key added through a customLayouts entry on the numpad surface) takes precedence and shows the user's pick. A subsequent {layout:base} tap re-engages the keyboardType constraint and returns to the constrained default. setLayout, setKeyboardType, resetKeyboardType, auto-type detection, and a switch to another target input all clear the user pick. A cleared pick returns to the tracked base layout, so Full -> Numpad -> Full lands on the base; a pick of a primary layout (for example {layout:qwertz-de}) is itself the base and stays.
While the Numpad/Numeric constraint is active (even on a user-driven pick that overrides it), a rendered layout reshapes its {layout:base} key. Where the key is useless it is dropped: on the constrained layout itself (tapping it would re-render the same surface), and on a layout that also carries a {layout:numpad}/{layout:numeric} key matching the constraint (there the key would sit dead next to one reaching the same numbers surface, e.g. next to "123" on the numeric keyboard's symbols layout). Where {layout:base} is instead the only route back to the constrained default (on the numpad's symbols layout "123" leads to the numeric layout, so {layout:base} is the only way back to the numpad; likewise the nav and fkeys layouts), the key is kept but relabeled: under the constraint it returns to the number surface rather than letters, so it renders as a back icon (accessible name "Return to numbers") instead of the misleading "ABC" text.
Programmatic base-layout helpers make layout round-trips explicit:
const kb = this.byId("keyboard") as KioskKeyboard;
kb.setLayout("qwertz-de");
kb.setLayout("numeric");
kb.getBaseLayout(); // "qwertz-de"
kb.resetLayout(); // back to qwertz-deResponsive Behavior Overview
| Scenario | Detection | Adapts automatically? | Consumer CSS needed? |
| ------------------------------------------------------------ | -------------------------------------------------------------------------- | -------------------------- | --------------------------------------------- |
| Width (any container width) | CSS @container queries at 30rem / 20rem | Yes | No |
| Layout rows (a layout with a compact counterpart) | ResizeObserver on the keyboard's own box, at 22rem | With autoCompact | No |
| Height (flex/grid parent with fixed height) | Root element inherits constraint via max-height: 100%; min-height: 0 | Yes | No |
| Height (explicit constraint on root) | max-height or height on the keyboard root | Yes | No |
| Height (height: auto parent, unconstrained) | max-height: 100% resolves to no constraint | Correctly stays full size | No |
| Height (deeply nested ancestor constraint, no flex/grid) | Intermediate height: auto ancestors break max-height: 100% propagation | No | max-height or height on the keyboard root |
| Docked mode | Viewport-driven, fixed positioning | Skipped (always full size) | No |
| Compact density | sapUiSizeCompact CSS class | Yes | No |
Constrained Containers and Popovers
The keyboard root element sets max-height: 100%; min-height: 0; overflow: hidden by default, so placing it inside a flex or grid parent with a fixed height automatically triggers responsive scaling without any additional CSS.
| Container height | Behavior |
| ---------------- | ----------------------------------------------------- |
| Above 16 rem | Full layout (default key sizes) |
| 12-16 rem | Compact layout (cqShort, reduced key height) |
| Below 12 rem | Minimal layout (cqTiny, further reduced key height) |
The thresholds are configurable via CSS custom properties (--ui5KioskKeyboard-cqShortThreshold, --ui5KioskKeyboard-cqTinyThreshold).
<!-- Automatic: flex parent constrains the keyboard -->
<VBox height="250px">
<kiosk:KioskKeyboard controls="myInput" />
</VBox>
<!-- Manual CSS needed: Popover wraps content in height: auto divs that break propagation -->
<Popover contentWidth="24rem" contentHeight="18rem">
<kiosk:KioskKeyboard controls="myInput" class="myConstrainedKeyboard" />
</Popover>
<!-- .myConstrainedKeyboard { height: 15rem; } -->[!TIP] You can also fine-tune key sizes via
--ui5KioskKeyboard-keyHeightand other CSS custom properties to fit more content into a smaller container without relying solely on the automatic breakpoints.
Layout Definition Format
Layouts are arrays of rows, where each row is an array of KeyDefinition objects. This is the shape a CustomLayout's rows takes:
import type { LayoutDefinition, KeyDefinition } from "ui5/kiosk/types";
const myLayout: LayoutDefinition = [
[
{ value: "1" },
{ value: "2" },
{ value: "3" },
{ value: "{backspace}", label: "", icon: "sap-icon://arrow-left", width: "2", type: "action" },
],
[{ value: "{enter}", label: "Enter", width: "2", type: "action" }],
];KeyDefinition fields:
| Field | Type | Description |
| --------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value | string | Character or action ({backspace}, {enter}, {shift}, {layout:name}, {fkey:name}) |
| label | string | Display label. Omit to resolve automatically (i18n for special keys, value for regular keys). Set to "" to suppress (icon-only). When icon is also present, both render together. |
| shiftLabel | string | Label when Shift is active. |
| shiftValue | string | Value when Shift is active (defaults to uppercase of value). |
| capsLockLabel | string | Label for {shift} key when Caps Lock is active. Omit for i18n "Caps Lock". Set to "" to suppress. Only meaningful on {shift} keys. |
| capsLockIcon | string | Icon for {shift} key when Caps Lock is active. Independent of icon. Defaults to sap-icon://locked. Only meaningful on {shift} keys. |
| width | KeyWidth | Proportional key width, from the closed set "1.25" | "1.5" | "1.75" | "2" | "2.25" | "2.75" | "space". Omit for a standard 1x key; any other string renders at 1x. |
| type | KeyType | "default" | "modifier" (subdued) | "action" (prominent) | "space". Styling, except that modifier, action and space keys never take an accent-variant table. |
| icon | string | SAP icon URI or Unicode character. Renders inline with label when both are present (customizable via --ui5KioskKeyboard-dualDirection). Set label="" for icon-only. |
| variants | string[] | Long-press / right-click accent-variant popup glyphs for this key. Overrides the built-in accentVariants table; [] suppresses the popup. E.g. ["ä", "à", "á", "â"]. |
| ariaLabel | string | Explicit accessible name for the key; highest priority in the name-resolution chain (else label, else i18n, else value). |
Accent variants (German umlauts)
The qwertz-de layout ships dedicated ä / ö / ü keys and ß, and a German-locale app selects it automatically (de → qwertz-de, see Locale-Based Default Layout). To reach accented letters from any Latin layout, a key can carry a long-press popup of variants.
Long-press / right-click popup. Press and hold a key (or right-click it) to open a small popup of accent variants; tap, drag-and-release, or arrow-and-Enter to insert one, Escape to dismiss. A plain tap still inserts the key's base character. When Shift or Caps Lock is active, the popup surfaces the uppercase forms, including the capital sharp S ẞ for s/ß.
Accessibility. A variant key carries a corner-triangle hint and advertises the popup to assistive technology with aria-haspopup="dialog". Keyboard users open it with the context-menu gesture (the Menu key, or Shift+F10) on the focused key; the key's own Enter/Space still types its base character, so the key carries no aria-expanded state.
Built-in Latin-diacritics table. Set the accentVariants property to merge a broad Latin-diacritics table (à á â ä, ç, è é ê ë, ñ, ö œ ø, ß, ü, …) onto every matching base letter of the resolved layout, so umlauts and accents work on any Latin layout without editing layout data:
<kiosk:KioskKeyboard accentVariants="true" controls="myInput" />Per-key variants. Author or override the popup for a single key with the variants field on its KeyDefinition. An explicit variants always wins over the built-in table; the key's own value stays the tap default and is not repeated in the list:
const myLayout: LayoutDefinition = [
[
{ value: "a", variants: ["ä", "à", "á", "â"] },
{ value: "o", variants: ["ö", "ø"] },
],
];Because an explicit variants wins, declaring variants: [] suppresses the popup on a single key the built-in table would otherwise cover, e.g. to skip a diacritic already reachable as its own dedicated key on the layout.
Per-layout variant tables.
With accentVariants on, the built-in Latin table is resolved through two further tiers before it is applied: the host's defaultVariants table, which applies under every layout, and the variants of the customLayouts entries naming that layout. The tiers are layered built-in → defaultVariants → custom layout, each merged onto the one below per base letter, so a tier extends the one under it rather than replacing it and only the letters it names change. This targets a locale's layout without editing layout data, e.g. Polish variants on the layout the current locale resolves to:
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
import CustomLayout from "ui5/kiosk/CustomLayout";
const kb = new KioskKeyboard({
accentVariants: true,
customLayouts: [
new CustomLayout({
name: KioskKeyboard.getLocaleLayout(),
variants: { s: ["ś", "š"], z: ["ż", "ź", "ž"] },
}),
],
});Base letters must be lowercase; a table with an uppercased or padded letter is rejected as a whole and reported as invalid-variants, rather than silently arming nothing.
Three levels of opt-out, narrowest first:
| Recipe | Effect |
| ----------------------------------- | -------------------------------------------------------- |
| variants: { s: [] } on the entry | Drops one base letter, leaving the rest of the table |
| suppress: [LayoutFacet.Variants] | Opts that layout out of variants entirely |
| variants: [] on a KeyDefinition | Suppresses the popup on that one key, whatever the table |
The defaultVariants tier only ever adds; it has no suppression spelling. Turn the whole affordance off with accentVariants="false", which is the default.
The five non-Latin built-in layouts (ja-romaji, ja-kana, ja-kana-compact, arabic, ko-hangul) resolve the built-in table to nothing, so accentVariants adds no popups there; supply a variants table on a custom layout (or a defaultVariants table) to opt one back in, and because there is no built-in tier to merge onto, those tiers stand alone. That exclusion list is only the shipped default for those built-ins; it never locks you out. A custom layout whose Latin-looking keys should not surface accent popups (a transliteration IME, say) opts out with suppress="Variants", which discards defaultVariants along with the built-in tier. Action, modifier, and space keys never take table variants even when a table is keyed to their value.
LATIN_DIACRITIC_VARIANTS is re-exported from ui5/kiosk/library for inspection (to read what the defaults are, or to build a table from them); a supplied table merges onto it per base letter rather than replacing it.
Declaring a variant table while accentVariants is off applies nothing, and logs a warning saying so.
Composition Middleware
Some scripts require processing between key press and text insertion. For example, Japanese Kana needs dakuten/handakuten composition (ka + dakuten = ga), and Korean Hangul needs jamo-to-syllable composition (individual consonants and vowels combine into syllable blocks).
The UI5 library includes composition middleware that activates automatically when the associated layout is active. No configuration needed. The middleware is always available in the library preload.
Built-in Middleware
| Middleware | Layout | Behavior |
| ---------------- | ---------------------------- | -------------------------------------------------------- |
| kana-dakuten | ja-kana, ja-kana-compact | Composes base kana + dakuten/handakuten into voiced kana |
| hangul-compose | ko-hangul | Composes jamo into Hangul syllable blocks with preedit |
Custom Middleware
Implement the CompositionMiddleware interface and supply the factory on the customLayouts entry for that layout:
import KioskKeyboard from "ui5/kiosk/KioskKeyboard";
import CustomLayout from "ui5/kiosk/CustomLayout";
import type { CompositionMiddleware } from "ui5/kiosk/types";
function createMyMiddleware(kb: KioskKeyboard): CompositionMiddleware {
return {
handleKey(key, target) {
// Return true if consumed (keyboard skips default handling).
// Return false to pass through to default behavior.
const caret = target.selectionStart ?? target.value.length;
if (key === "~" && target.value[caret - 1] === "n") {
kb.deleteBackward();
kb.insertText("ñ");
return true;
}
return false;
},
commit() {
// Finalize any in-progress composition.
return null;
},
reset() {
// Clear state without committing.
},
};
}
const kb = new KioskKeyboard({ controls: ["myInput"] });
kb.addCustomLayout(new CustomLayout({ name: "my-layout", rows: myRows, middleware: () => createMyMiddleware(kb) }));
kb.setLayout("my-layout");The handleKey method receives:
key: the raw key value from the layout definition (e.g.,"a","{backspace}","{enter}")target: the DOM input element the keyboard is typing into
When handleKey returns true, the keyboard skips default handling. The factory takes no arguments, so close over the control, as above, and edit through its public insertText(text) and deleteBackward(), which keep maxlength and the browser undo stack and fire UI5 liveChange for the model binding; assigning target.value skips all of that. Replacing the character before the caret takes a deleteBackward() and an
