npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kiosk-keyboard-webc

v0.1.0

Published

Native web component variant of the kiosk keyboard, built on UI5 Web Components framework

Readme

Part of the ui5-keyboard monorepo. See also: ui5-lib-hotkeys and ui5-lib-kiosk-keyboard.

Native web component variant of the kiosk on-screen keyboard, built on the UI5 Web Components framework (@ui5/webcomponents-base).

<kiosk-keyboard layout="qwerty" controls="my-input"></kiosk-keyboard>

Features

  • Standards-based custom element (<kiosk-keyboard>) usable in any framework: plain HTML, React, Vue, Angular
  • SAP theming: Horizon light/dark, HCB, HCW via CSS variables (automatic theme switching)
  • UI5 app integration: consumable inside UI5 apps through the wrapper ui5-tooling-modules generates from the Custom Elements Manifest, or a hand-written WebComponent.extend() bridge
  • Multiple layouts: QWERTY, QWERTZ-DE, Japanese Romaji, Japanese Kana (plus a narrow-width compact form), Arabic, Korean Hangul, Spanish, Numeric, Numpad, Special, F-keys, Navigation. Composite variants (e.g., QWERTY + F-key row) are trivial to compose from building block rows.
  • Locale-aware: auto-selects layout based on browser locale (e.g. de → qwertz-de, ja → ja-romaji, ar → arabic, ko → ko-hangul, es → qwerty-es). Slot a <kiosk-keyboard-custom-layout slot="customLayouts" name="ja-kana" locales="ja"> into an element to switch the Japanese default to kana input for that instance.
  • Shift / Caps Lock: single-click for one-shot shift, double-click for caps lock
  • Docked mode: fixed-position keyboard at bottom of viewport with slide animation
  • Auto-show: opens/closes automatically when target inputs receive/lose focus
  • Auto-type detection: switches to Numpad for type="number", inputmode="numeric", data-keyboard-type="Numpad", etc.
  • F-key and navigation key support: configurable modes: Virtual, Native, None
  • Grapheme-aware: correct backspace/navigation for emoji and multi-code-unit characters
  • Accessible: ARIA roles, labels, live region announcements, roving tabindex, keyboard navigation, prefers-reduced-motion, forced-colors
  • i18n: built-in English/German/Japanese/Arabic, extensible via custom resolver
  • Custom layouts in markup: <kiosk-keyboard-custom-layout> children of the customLayouts slot carry a layout's rows, locales, keycap language, middleware and long-press variants, scoped to that one element

Keyboard Overview

The keyboard-type attribute selects the key set:

| Full (QWERTY layouts) | Numpad | Numeric | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | | | |

Key Types

Keys support different visual styles via the type property in KeyDefinition:

| Default | Default (hovered) | Modifier (type: "modifier") | Modifier (hovered) | | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | | | | |

  • Default: visible border, --sapButton_Background. Used for character keys.
  • Modifier: transparent background, no border (--sapButton_Lite_Background). Used for Shift, Caps Lock, layout switchers, F-keys (F1-F12), and navigation keys (Home, End, Arrows, PgUp, PgDn).
  • Action: emphasized style (--sapButton_Emphasized_Background). Used for Enter, Backspace.

Theme Preview

| sap_horizon | sap_horizon_dark | | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | | |

| sap_horizon_hcb | sap_horizon_hcw | | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | | |

Installation

Install from npm:

npm install kiosk-keyboard-webc

Framework dependencies

This package depends on the UI5 Web Components framework (@ui5/webcomponents, @ui5/webcomponents-base, @ui5/webcomponents-icons, @ui5/webcomponents-theming) as regular dependencies, so they are installed automatically with kiosk-keyboard-webc; no separate install step is required.

If your app also uses UI5 Web Components directly (e.g., @ui5/webcomponents buttons, inputs), make sure your bundler dedupes a single instance of @ui5/webcomponents-base so the framework registries (custom elements, themes, i18n) are shared and duplicate-registration errors are avoided. This package's Vite build already dedupes @ui5/webcomponents-base.

Tree-Shaking

The package declares a sideEffects field in package.json so that bundlers (Vite/Rollup, webpack) can correctly handle side-effectful modules during tree-shaking (see Rollup side effects). Only the genuinely side-effectful modules are listed: the component entries that register the custom elements (KioskKeyboard, CustomLayout), theme/i18n asset registration (Assets, generated/**), and the convenience bundle entries (bundle.esm, kiosk-keyboard.bundle).

[!NOTE] All built-in layouts and their composition middleware (kana, Hangul) are pure data/factory modules that the registries (core/layout-registry, core/middleware-registry) statically import and reference. They are therefore always included in the bundle through normal tree-shaking, so no sideEffects marker is required.

In this monorepo, install all workspace dependencies once at the repository root:

npm install

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+ | | 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 Custom Properties | Consumer overrides | Chrome 49+, Firefox 31+, Safari 9.1+ | | CSS color-mix() | Theme-adaptive shadows | Chrome 111+, Firefox 113+, Safari 16.2+ | | Shadow DOM v1 | Component encapsulation | Chrome 53+, Firefox 63+, Safari 10+ |

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 element 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, and without color-mix() the shadows stop adapting to the theme.

Consumption Modes

1. Standalone via kiosk-keyboard-webc/bundle (recommended)

Recommended for plain HTML, React, Vue, Angular, and most non-UI5 apps.

kiosk-keyboard-webc/bundle is the convenience entry point. It imports Assets, registers the custom element, loads theme and i18n assets, and inserts the SAP "72" font face.

[!NOTE] The examples below use bare package specifiers (kiosk-keyboard-webc/…), which require a bundler (Vite, webpack, etc.) or an import map. For plain <script> usage without a build step, replace the specifier with the resolved path to dist/kiosk-keyboard.bundle.js (for example ./node_modules/kiosk-keyboard-webc/dist/kiosk-keyboard.bundle.js).

[!NOTE] The element is client-only: importing the bundle runs customElements.define() and registers the SAP font at module load, which throws during server-side rendering (Next.js, Nuxt, Analog). In an SSR framework, import it on the client only - e.g. Next.js dynamic(() => import("kiosk-keyboard-webc/bundle"), { ssr: false }), or import inside useEffect / onMounted.

<script type="module">
  import "kiosk-keyboard-webc/bundle";
</script>

<input id="my-input" type="text" />
<kiosk-keyboard layout="qwerty" controls="my-input"></kiosk-keyboard>
import { KioskKeyboard } from "kiosk-keyboard-webc/bundle";

2. Advanced ESM via kiosk-keyboard-webc + kiosk-keyboard-webc/Assets

Use this when your app already manages UI5 Web Components dependencies and you want the bare component class entry point.

import "kiosk-keyboard-webc/Assets";
import KioskKeyboard from "kiosk-keyboard-webc";

Use this mode when you want to avoid the convenience bundle and keep control over how the component is composed into your app build. Assets is required here because the bare kiosk-keyboard-webc entry exports the component class only.

[!IMPORTANT] Font loading:

kiosk-keyboard-webc/bundle already imports kiosk-keyboard-webc/Assets, which in turn loads the SAP "72" font via @ui5/webcomponents-base/dist/FontFace.js and registers theme/i18n assets. If you use the bare kiosk-keyboard-webc entry directly, import kiosk-keyboard-webc/Assets yourself and make sure the "72" font is available (for example via the UI5 framework, FontFace.js, or a custom @font-face declaration).

Custom fonts:

If you override --sapFontFamily or set a custom font-family on the keyboard, the default key sizing may not fit the new font's glyph metrics. You may need to adjust --kiosk-keyboard-key-height, --kiosk-keyboard-key-font-size, or --kiosk-keyboard-key-padding to prevent clipping or excessive whitespace.

3. Inside a UI5 app

This package is not a native UI5 library, so the UI5-app story is different from ui5-lib-hotkeys / ui5-lib-kiosk-keyboard.

For UI5 apps, make sure your app can resolve npm ESM packages via ui5-tooling-modules in ui5.yaml. There are two integration paths:

3a. Auto-Generated Wrapper (CEM-driven, recommended)

Published builds include dist/custom-elements.json (the Custom Elements Manifest), and the package declares the customElements field in package.json. The ui5-tooling-modules middleware reads this manifest and auto-generates a sap.ui.core.webc.WebComponent wrapper at serve/build time. No manual wrapper code needed.

# ui5.yaml - config for CEM-driven web component consumption
builder:
  customTasks:
    - name: ui5-tooling-modules-task
      afterTask: replaceVersion
      configuration:
        addToNamespace: true
server:
  customMiddleware:
    - name: ui5-tooling-transpile-middleware
      afterMiddleware: compression
    - name: ui5-tooling-modules-middleware
      afterMiddleware: ui5-tooling-transpile-middleware
      configuration:
        addToNamespace: true
        useRelativeModulePaths: true

Then use the component directly in XML views:

<mvc:View xmlns:kb="kiosk-keyboard-webc">
  <kb:KioskKeyboard layout="qwerty" docked="true" />
</mvc:View>

In workspace development, run npm run build (or at least npm run generateAPI) in the webc package first so that dist/custom-elements.json exists. The CEM is produced by generateAPI; npm run generate only emits the CSS and i18n assets, not the manifest.

The framework version declared in ui5.yaml must be >= 1.120.0 for the CEM-driven web component transformation to activate. The SAP-samples/uxc-integration project is the official reference for the build-time configuration (addToNamespace: true on the task).

useRelativeModulePaths: true is needed for dev serve. Without it, the middleware redirects module requests to a namespace-prefixed path (demo/hotkeys/thirdparty/...) that only exists after ui5 build. During dev serve, modules are stored under their original npm names and the redirect target does not resolve. This is intentional middleware design (ui5-community/ui5-ecosystem-showcase#1049); the namespace rewriting of entry-point modules is a build-only step. useRelativeModulePaths: true skips the redirect and serves modules at their npm paths directly. See UI5-WEBCOMPONENT-CONSUMPTION-RESEARCH.md for a full path-mapping reference.

The demo app in this repository uses Path A (CEM-driven) for the kiosk-keyboard web component. The manual bridge (Path B below) is documented as a reference for consumers who need explicit metadata control.

3b. WebComponent.extend() bridge (reference)

For full control over the UI5 metadata surface, create a manual bridge using WebComponent.extend(). This gives explicit property/event/method/association mappings and typed UI5 events. Event names in metadata.events auto-convert from camelCase to kebab-case DOM events (e.g. keyPress maps to key-press), so explicit mapping: { to: "..." } on events is not needed.

[!IMPORTANT] The bridge below binds tag: "kiosk-keyboard", the canonical unscoped name. The module-system import registers that tag only if scoping is off (pluginOptions.webcomponents.scoping: false) on both the ui5-tooling-modules task and its middleware - the configuration in 3a above does not set it. With scoping left on, the middleware intercepts the import and registers kiosk-keyboard-<hash> instead; customElements.get("kiosk-keyboard") then finds nothing and the element stays unupgraded, with no error. Either turn scoping off, or load dist/kiosk-keyboard.bundle.js from a <script> tag outside /resources/ so it registers the canonical tag without interception. See CONSUMPTION.md.

import WebComponent from "sap/ui/core/webc/WebComponent";
import "kiosk-keyboard-webc/bundle"; // requires scoping: false (see note above)

const KioskKeyboardWebc = WebComponent.extend("my.control.KioskKeyboard", {
  metadata: {
    tag: "kiosk-keyboard",
    properties: {
      layout: { type: "string", defaultValue: "", mapping: { type: "property", to: "layout" } },
      docked: { type: "boolean", defaultValue: false, mapping: { type: "property", to: "docked" } },
      // ... additional property/event/method/association mappings
    },
  },
});

Use the bridge when you want predictable XML view metadata, typed UI5 events, or imperative methods such as show() / close() exposed as a UI5 control API.

Choosing between the UI5 control and the web component

| Criterion | ui5-lib-kiosk-keyboard (UI5 control) | kiosk-keyboard-webc (web component) | | ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ | | Framework | SAPUI5 / OpenUI5 only | Any (plain HTML, React, Vue, Angular, UI5 via wrapper/bridge) | | Theming | LESS variables (@sapUiButton*) | CSS custom properties + SAP theme token fallbacks | | i18n | UI5 ResourceBundle + setI18nResolver() callback | Built-in EN/DE/JA/AR + setI18nResolver() callback | | Target inputs | controls property + setControls() + getActiveControl() | controls attribute + setTargetElement() + getActiveTargetElement() | | Density | UI5 content density (sapUiSizeCompact) | data-ui5-compact-size attribute |

Both packages share the same layout definitions (KeyDefinition, LayoutDefinition), the same custom-layout model (one element per layout carrying its rows, locales, keycap language, role, middleware and variants, plus a defaultVariants table on the host), and the same special-key syntax ({shift}, {backspace}, {layout:name}). The UI5 control collects those elements in a customLayouts aggregation, the web component in a customLayouts slot; the fields, their merge rules and the diagnostics are identical.

Event naming follows platform conventions: keyPress (camelCase) in the UI5 control vs key-press (kebab-case) in the web component. layout-change and keyboard-type-change carry the same payload on both sides. The others do not, so a handler written against one twin needs adapting for the other:

| Event | ui5-lib-kiosk-keyboard | kiosk-keyboard-webc | | --------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------- | | Key event payload | key, shiftKey | key, shiftKey, char | | key for a character | the resolved character ("A" while shifted) | the layout's raw value ("a"), with char carrying the resolved one | | key for Backspace / Enter | "Backspace" / "Enter" (KeyName constants) | "{backspace}" / "{enter}" (the raw token) | | Key event for {shift} | not fired | fired; cancelling it vetoes the toggle | | Key event for {layout:*} | not fired | fired with the wrapped form; cancelling it vetoes the switch | | Composition middleware | runs before the key event, so a consumed key never reaches a handler | runs after it, so the handler sees every key | | Active-control event | activeControlChange with controlId | active-control-change with activeElement | | Open / close events | afterOpen / afterClose, no parameters | after-open / after-close with activeElement | | change on the target | on Enter, on close, and on a target switch (modified inputs only) | on Enter only |

One-shot Shift is spent on the same set of keys on both: any key that acts on the target - a character, an unknown {...} token, {backspace}, {enter}, an {fkey:*}, a committed accent variant, or a key the composition middleware consumed. A latched modifier is spent by the next non-modifier key, which is also what XKB, AccessX and Sticky Keys do.

Vetoing key-press does not change that set on either twin: the latch is consumed to produce the payload, so preventDefault() cancels the insertion, not the spend.

Caps Lock is sticky on both and never auto-releases. {shift} and {layout:*} spend nothing; selecting a layout resets the whole typing context, Caps Lock included.

Activating a focused keycap from the physical keyboard differs in one respect: the web component activates Space on release, following native <button> semantics, while the UI5 control routes Space through UI5's sapselect, a keydown pseudo-event, and so activates on press. Neither twin repeats on a held key, and {backspace} repeats only from pointer input. Both treat Shift+Enter and Shift+Space as "type the shifted glyph" and both reject Ctrl, Alt and Meta. Both also read that Shift from the press, whichever key is released first, so the two agree on the glyph for every release ordering.

See UI5-WEBCOMPONENT-CONSUMPTION-RESEARCH.md for general guidance on web component consumption patterns inside UI5 apps.

API Stability

Recommended stable consumer entry points and imports:

import {
  KioskKeyboard,
  CustomLayout,
  FKeyMode,
  KeyboardType,
  LayoutFacet,
  LayoutRole,
  MobileKeyboard,
} from "kiosk-keyboard-webc/bundle";

import type {
  KeyPressEventDetail,
  LayoutChangeEventDetail,
  KeyboardTypeChangeEventDetail,
  ActiveControlChangeEventDetail,
  OpenStateChangeEventDetail,
  KeyDefinition,
  KeyRow,
  LayoutDefinition,
  CustomLayoutSpec,
  KeyWidth,
  KeyType,
  SpecialKeyValue,
  CompositionMiddleware,
  I18nResolver,
  TargetResolver,
  KioskKeyboardDomContract,
} from "kiosk-keyboard-webc/bundle";

For most applications, prefer kiosk-keyboard-webc/bundle. The bare kiosk-keyboard-webc entry point is also supported for advanced setups when paired with kiosk-keyboard-webc/Assets. The bundle registers <kiosk-keyboard-custom-layout> alongside <kiosk-keyboard>; the bare entry registers <kiosk-keyboard> only, so import kiosk-keyboard-webc/CustomLayout next to it to use the customLayouts slot.

Customization is per element via the customLayouts slot and the defaultVariants property. The static methods on KioskKeyboard are read-only inspectors (getRegisteredLayout, getRegisteredLayoutNames, isBuiltInLayout, isSecondaryLayout, getLocaleLayout), the global setI18nResolver, and composeLayout for splicing rows together. KioskKeyboard.DOM is stable alongside them, see DOM Contract. Import the class and call them directly:

import { KioskKeyboard } from "kiosk-keyboard-webc/bundle";

const qwerty = KioskKeyboard.getRegisteredLayout("qwerty");
KioskKeyboard.setI18nResolver((key) => undefined);

Internal modules under core/* (e.g. shift-state, dom-utils, input-operations, layout-registry) are implementation details and may change without notice. The one carve-out is core/latin-variants, published as the stable kiosk-keyboard-webc/variants subpath. Layout definitions under layouts/* and middleware factories under middleware/* are stable imports, for reading a built-in's rows or middleware into a <kiosk-keyboard-custom-layout>; built-ins are already bundled, so a layout you only want to render is asked for by name through the layout attribute. Their two composition helpers, layouts/symbol-common and layouts/default-layout, are internal. The shared row modules (kiosk-keyboard-webc/layouts/fkey-row, kiosk-keyboard-webc/layouts/fkey-row-compact, kiosk-keyboard-webc/layouts/nav-row, kiosk-keyboard-webc/layouts/nav-row-compact) are stable imports for composing custom variant layouts. Their keys are declared as type: "modifier" (the transparent Lite button style); override type on individual keys if you want the default bordered style instead. nav-row-compact seats the same eight nav keys as two rows of four, for keyboards narrower than about 20rem where one row of eight leaves each key around 30px wide; fkey-row-compact does the same for the twelve function keys, as two rows of six.

[!NOTE] See the API Stability Policy for full details on stable vs internal import boundaries across all packages.

Attributes / Properties

| Attribute | Property | Type | Default | Description | | --------------------- | ----------------- | ---------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | layout | layout | string | "" | Layout asked for (e.g. qwerty, qwertz-de). Empty = auto-detect from locale. Not rewritten by what renders; read effectiveLayout for that. A secondary layout named here (numeric, special, ...) renders, while {layout:base} and reset() return to the locale layout. | | (read-only) | effectiveLayout | string | - | The layout actually rendering, after the locale default, a {layout:*} key, a keyboard-type constraint and the auto-compact width tier. See Requested vs. effective layout. | | keyboard-type | keyboardType | string | "Full" | "Full", "Numpad", or "Numeric". Any value other than "Full" disables auto-type detection until resetKeyboardType(). | | open | open | boolean | false | Opens/closes the docked keyboard. Equivalent to show()/close(). | | docked | docked | boolean | false | Fixed-position mode at bottom of viewport. | | auto-show | autoShow | boolean | false | Auto open/close when target inputs gain/lose focus (requires docked). | | auto-type | autoType | boolean | false | Auto-detect keyboard type from focused input's type/inputmode. | | auto-compact | 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. | | disabled | disabled | boolean | false | Disables all key interaction. | | controls | controls | string | "" | Comma-separated IDs of target elements. Supports single or multiple inputs. | | accessible-name | accessibleName | string | "" | Custom ARIA label for the keyboard. Falls back to i18n "Virtual Keyboard". | | mobile-keyboard | mobileKeyboard | string | "Auto" | "Auto" (defer to native on touch), "Custom", or "Native". | | f-key-mode | fKeyMode | string | "Virtual" | "Virtual" (fire event + move cursor), "Native" (dispatch keydown), "None". | | accent-variants | 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. | | (programmatic only) | defaultVariants | VariantTable \| null | null | Long-press variants applied under every layout, merged per base letter beneath anything a slotted <kiosk-keyboard-custom-layout> declares. Effective only with accent-variants. See Accent variants. |

keyboard-type, mobile-keyboard and f-key-mode reject a value outside their enum: the element logs a warning naming the valid values and the property falls back to its default, whether the value arrives as an attribute, before connection, or programmatically.

Requested vs. effective layout

layout is a declaration and effectiveLayout is a resolved value, the same split the platform draws between src and currentSrc, and that @ui5/webcomponents-base draws between dir and UI5Element.effectiveDir.

Four things change what renders without writing to layout: the locale default when layout is empty, a {layout:*} key the user taps, a keyboard-type of Numpad or Numeric pinning its own surface, and an auto-compact width swap. Leaving the declaration alone is what lets each of them be undone - auto-compact needs the layout you asked for in order to restore it when the room comes back.

const kb = document.querySelector("kiosk-keyboard");
kb.layout; // "ja-kana"          - what you asked for
kb.effectiveLayout; // "ja-kana-compact"  - what is on screen right now

effectiveLayout is read-only and has no attribute. To be told when it changes rather than polling it, listen for layout-change, whose autoDetected flag separates a width swap from a request.

[!NOTE] The UI5 control splits this differently: KioskKeyboard#getLayout() returns the effective layout, because a UI5 control property is live control state rather than an author declaration. Port kb.effectiveLayout to kb.getLayout(), not to the control's layout setting. See Custom Layouts in the control's README.

Slots

| Slot | Accepts | Description | | --------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | customLayouts | <kiosk-keyboard-custom-layout> | Per-element layouts and overlays, applied in DOM order. Never projected, so they render nothing. See Custom Layouts. |

Keyboard type override via data-keyboard-type

When auto-type is enabled, the keyboard detects the type from inputmode and type attributes. For cases where these don't convey the right keyboard type (e.g., composite web component hosts), you can set an explicit override via the data-keyboard-type attribute on the input or any ancestor element:

<!-- Force Numpad for a custom control -->
<my-amount-field data-keyboard-type="Numpad">
  <input type="text" />
</my-amount-field>

<!-- Force Full keyboard even for type="number" -->
<input type="number" data-keyboard-type="Full" />

Valid values: "Full", "Numpad". This attribute takes priority over inputmode and type detection.

Events

| Event | Detail | Description | | ----------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key-press | { key: string, shiftKey: boolean, char?: string } | Fired on key click. Cancelable. char is the resolved character (after shift); undefined for action/F-keys. | | after-open | { activeElement: HTMLInputElement \| HTMLTextAreaElement \| null } | Fired when the docked keyboard enters the open state. activeElement is the input that's now active. State-change hook only; not a CSS transition-end event. | | after-close | { activeElement: HTMLInputElement \| HTMLTextAreaElement \| null } | Fired when the docked keyboard enters the closed state. activeElement is the input that was active just before closing. State-change hook only; not a CSS transition-end event. | | layout-change | { layout: string, autoDetected: boolean } | Fired when layout switches. autoDetected marks an auto-compact width swap rather than a request. | | keyboard-type-change | { keyboardType: string, previousKeyboardType: string, autoDetected: boolean } | Fired when keyboard type changes. | | active-control-change | { activeElement: HTMLInputElement \| HTMLTextAreaElement \| null } | Fired when the active control changes (auto-show focus switch or programmatic setTargetElement). |

Text Insertion

Keys write into the target the way the platform does. While the target input holds focus, the keyboard selects the range it is about to replace and performs the edit through document.execCommand("insertText" | "delete"), so the browser applies maxlength itself and records the edit on its own undo stack - Ctrl+Z in the target reverts keyboard input exactly as it reverts physical typing. The grapheme cluster Backspace removes is still resolved in JS beforehand, because the engines disagree on where one ends. An input inside an open shadow root qualifies; the focus check descends shadow roots to find it.

The platform path needs a focused target whose input type supports selection. When the target does not hold focus - after setTargetElement() without a focus move, for instance - or its type refuses setSelectionRange() (type="number" and type="email" throw), or the command is unavailable or declines it, the value is assigned instead and maxlength is applied in JS. The resulting text is the same on both paths; what dispatches the input event is not.

| | Platform edit | Assignment | | ------------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------- | | maxlength | Applied by the browser | Applied in JS | | Browser undo stack | Edit recorded | Not recorded | | input event | One, dispatched by the platform | One synthesized InputEvent (inputType of insertText, insertLineBreak or deleteContentBackward) |

Either path produces exactly one input event per edit, so a listener on the target sees every edit regardless. An edit that a saturated maxlength leaves empty writes nothing and dispatches nothing. Read-only and disabled targets are never written to.

Docked Mode and Auto-Show

With docked set, the keyboard is fixed to the bottom of the viewport and slides in and out. open, show() and close() drive it; after-open and after-close fire as the state changes, not when the transition ends. Pressing Escape anywhere on the page closes an open docked keyboard, unless an accent-variant popup is open, which takes the Escape first.

show() on a keyboard with no active target focuses and targets the element controls names, when it names exactly one. With several ids there is nothing to pick, and the keyboard opens without a target until one of them takes focus.

Auto-show

auto-show (with docked, and while not disabled) listens for focusin and focusout on the whole document, in the capture phase:

  1. Opens when focus lands on an element an <input> or <textarea> resolves from: the element itself, one in its light DOM, or one up to three shadow roots down (through setTargetResolver() when one is set). The input becomes the active target. Any <input> qualifies, so set controls on a page with checkboxes or other non-text inputs.
  2. Filters by controls when it is set: only a focused element that carries one of the listed ids, or has an ancestor within five levels that does, opens the keyboard. A UI5 view prefix (container-app---view--) is stripped before comparing, so controls="myInput" matches a UI5-rendered id.
  3. Leaves another keyboard's input alone. An input is skipped when another live keyboard already targets it or lists it in its own controls. A keyboard that is disabled, disconnected, not rendered, or docked and closed never blocks another.
  4. Closes one animation frame after focus leaves, unless focus has moved into the keyboard or onto another input this keyboard would open for. A show() in that frame cancels the close.

Auto-type

With auto-type on, each auto-show target is inspected, first match wins:

  1. data-keyboard-type="Full" or "Numpad" on the input or any ancestor, across shadow roots (see Keyboard type override)
  2. inputmode of numeric, decimal or tel (case-insensitive) → Numpad
  3. type of number or tel → Numpad
  4. Otherwise Full

A keyboard-type set explicitly switches detection off until resetKeyboardType(). Refocusing the input the open keyboard already targets does not re-detect: that input carries the inputmode="none" written on its previous focus, which would hide an authored numeric.

Native keyboard

mobile-keyboard decides whether the element opens at all:

| Value | Behavior | | ---------- | -------------------------------------------------------------------------------------------------- | | "Auto" | Defers to the native on-screen keyboard while (pointer: coarse) matches; opens otherwise. | | "Custom" | Always opens. | | "Native" | Never opens: show(), open and auto-show all leave it closed and the native keyboard in charge. |

While it is open, the keyboard writes inputmode="none" on the target so the native keyboard stays down, and restores the original value on close, on a target switch and on disconnect. The restore is reference-counted per input, so two keyboards targeting one input do not clobber each other's saved value.

Methods

| Method | Description | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | show() | Opens the docked keyboard (sets open = true) when the current mobileKeyboard mode allows custom rendering. Logs a warning if docked is false. | | close() | Closes the docked keyboard (sets open = false). | | setTargetElement(el) | Programmatically sets the target input/textarea. | | setTargetResolver(fn) | Sets a custom resolver to locate the native input/textarea inside a host element. Pass null to clear. | | resetKeyboardType() | Resets keyboard type to "Full" and re-enables auto-type detection. Fires keyboard-type-change with autoDetected: false; a keyboardType assigned from inside that handler stands. | | reset() | Clears transient input state (shift/caps latch, in-progress composition, backspace auto-repeat, variant popover) and returns to the base layout. Leaves the bound value and configuration untouched. | | refreshResponsiveState() | Recomputes responsive height classes after runtime styling changes that do not emit a reliable resize signal. Usually not needed for normal container resizing. | | insertText(text) | Inserts text at the caret of the active target (cursor-tracked, dispatches a native input event). No-op with no active target. Call from a key-press handler to implement a custom {token} key. | | deleteBackward() | Deletes one grapheme before the caret of the active target. Returns whether anything was removed; no-op with no active target. | | getActiveTargetElement() | Returns the resolved native input/textarea of the active target, or null (re-resolves each call). Mirrors the UI5 control's method of the same name. |

Static API

The static surface carries no layout registration; custom layouts come from the customLayouts slot (see Custom Layouts).

| Method | Description | | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | KioskKeyboard.getRegisteredLayout(name) | Returns a built-in layout definition by name. | | KioskKeyboard.getRegisteredLayoutNames() | Returns all built-in layout names. | | KioskKeyboard.isBuiltInLayout(name) | Checks if a layout is built-in. | | KioskKeyboard.isSecondaryLayout(name) | Checks if a layout is secondary (non-alphabetic). | | KioskKeyboard.getLocaleLayout() | Returns the layout for the active UI5 Web Components locale (configured language, falling back to the browser locale). | | KioskKeyboard.setI18nResolver(fn) | Sets a custom i18n resolver callback. | | KioskKeyboard.composeLayout(...sources) | Splices rows from built-in layout names and row arrays, in order, into one layout definition. |

KioskKeyboard.DOM is a supported read-only DOM hook contract for tests and DOM assertions. Prefer it over hard-coded shadow selectors. Styling customizations should still use the documented host attributes and public --kiosk-keyboard-* CSS custom properties.

DOM Contract

Use KioskKeyboard.DOM when you need stable selectors for tests or DOM assertions:

import { KioskKeyboard } from "kiosk-keyboard-webc/bundle";
import type { KioskKeyboardDomContract } from "kiosk-keyboard-webc/bundle";

const DOM: KioskKeyboardDomContract = KioskKeyboard.DOM;
const firstKey = keyboard.shadowRoot?.querySelector(DOM.selectors.key);

The contract is intentionally read-only. It is not the styling API; continue to customize appearance through the documented public CSS custom properties.

Built-in Layouts

| Name | Description | | ----------------- | ----------------------------------------------------------------------- | | qwerty | Standard US QWERTY | | qwertz-de | German QWERTZ with umlauts and ss | | ja-romaji | Japanese Romaji (QWERTY base with JIS punctuation) | | ja-kana | Japanese Kana direct-input (JIS X 6002) | | ja-kana-compact | Japanese Kana for narrow keyboards, no row wider than twelve key widths | | arabic | Arabic (standard Arabic 101 layout) | | numeric | Numbers + common symbols | | special | Extended symbols (#+=, currencies) | | numpad | Calculator-style number pad | | fkeys | F1-F12 function keys | | nav | Navigation keys (arrows, Home, End, etc.) | | ko-hangul | Korean Hangul Dubeolsik (KS X 5002) | | qwerty-es | Spanish QWERTY with accented vowels and ñ |

Combined variants (e.g., QWERTY + F-key row) are not built-in. They are trivial compositions - see Layout Composition below.

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 <kiosk-keyboard-custom-layout> element in the customLayouts slot of a <kiosk-keyboard>. It shadows the built-in registry for that element 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.

name, keycap-lang, compact, locales, layout-role and suppress are string attributes, so an overlay is a plain markup declaration:

<script type="module">
  import "kiosk-keyboard-webc/bundle";
</script>

<input id="my-input" type="text" />
<kiosk-keyboard controls="my-input" accent-variants>
  <!-- Point the Japanese locale at the BUILT-IN kana layout. -->
  <kiosk-keyboard-custom-layout slot="customLayouts" name="ja-kana" locales="ja"></kiosk-keyboard-custom-layout>

  <!-- Opt a layout out of accents entirely. -->
  <kiosk-keyboard-custom-layout slot="customLayouts" name="arabic" suppress="Variants"></kiosk-keyboard-custom-layout>

  <!-- Type the Hangul keycaps directly, without the built-in composer. -->
  <kiosk-keyboard-custom-layout
    slot="customLayouts"
    name="ko-hangul"
    suppress="Middleware"
  ></kiosk-keyboard-custom-layout>
</kiosk-keyboard>

Every child carries slot="customLayouts". The children render nothing: the shadow template has no <slot name="customLayouts"> for them, so a custom layout is never projected and never affects layout or styling.

rows, variants and middleware are object-typed properties with no attribute, so a full layout is assembled in script:

import { KioskKeyboard } from "kiosk-keyboard-webc/bundle";
import type CustomLayout from "kiosk-keyboard-webc/CustomLayout";
import { createKanaDakutenMiddleware } from "kiosk-keyboard-webc/middleware/kana-dakuten";
import warehousePos from "./warehouse-pos-layout";

const kb = document.createElement("kiosk-keyboard") as KioskKeyboard;
kb.setAttribute("controls", "my-input");
kb.accentVariants = true;
kb.layout = "warehouse-pos";

const pos = document.createElement("kiosk-keyboard-custom-layout") as CustomLayout;
pos.slot = "customLayouts";
pos.name = "warehouse-pos";
pos.rows = warehousePos;
pos.locales = "de,de-AT";
pos.keycapLang = "de";
pos.variants = { s: ["ś", "š"] };

// Rows-less overlay: give the built-in kana layout a different middleware.
const kana = document.createElement("kiosk-keyboard-custom-layout") as CustomLayout;
kana.slot = "customLayouts";
kana.name = "ja-kana";
kana.middleware = createKanaDakutenMiddleware;

kb.append(pos, kana);
document.body.appendChild(kb);

Assemble the subtree before mounting the host, as above, and a slotted layout resolves on the keyboard's first paint. Afterwards the slot stays live: adding, removing, reordering or editing a custom layout re-folds the maps and re-renders.

No teardown is needed: the custom layouts are children of the element and are released with it. The component maintains no window-global mutable customization state, so multiple apps sharing the same page (Fiori Launchpad, micro-frontends) cannot pollute each other through the keyboard.

Each key of a rows array is a KeyDefinition:

interface KeyDefinition {
  value: string; // Character to insert, or action like "{shift}", "{enter}", "{backspace}", "{layout:numeric}", "{fkey:F5}"
  label?: string; // Display label; omit for auto-resolve (i18n for special keys, value for regular); "" suppresses
  shiftLabel?: string; // Label when shifted
  shiftValue?: string; // Value when shifted (defaults to value.toUpperCase() for single chars)
  capsLockLabel?: string; // Label for {shift} key in Caps Lock state; omit for i18n "Caps Lock"; "" suppresses
  capsLockIcon?: string; // Icon for {shift} key in Caps Lock state; independent of icon; defaults to locked icon
  width?: KeyWidth; // "1.25" | "1.5" | "1.75" | "2" | "2.25" | "2.75" | "space"
  type?: KeyType; // "default" | "modifier" | "action" | "space"
  icon?: string; // SAP icon URI or Unicode char/emoji; renders inline with label when both present
  ariaLabel?: string; // Accessible name when the key has no visible label (icon-only); resolution: ariaLabel -> label -> built-in i18n. Set this for icon-only custom keys ({paste} etc.)
  variants?: string[]; // Long-press / right-click accent-variant glyphs; overrides the built-in accent-variants table, [] suppresses the popup (e.g. ["ä", "à", "á", "â"])
}

Fields and how they resolve

| Property | Attribute | Type | Across custom layouts with the same name | Across tiers | | ------------ | ------------- | ------------------ | --------------------------------------------------------------------------- | -------------------------------------------- | | name | name | string | matched after trim + lowercase; duplicates are the overlay mechanism | - | | rows | (none) | LayoutDefinition | last declaration wins | custom layout → built-in | | keycapLang | keycap-lang | string | last declaration wins | custom layout → built-in | | compact | compact | string | last declaration wins | custom layout → built-in | | layoutRole | layout-role | LayoutRole | last declaration wins | Inherit takes the built-in's | | locales | locales | token string | additive union; per prefix, last wins | custom layouts → built-in map | | middleware | (none) | () => … | last declaration wins | custom layout → built-in | | variants | (none) | VariantTable | additive per base letter; a letter mapped to [] drops it | built-in → defaultVariants → custom layout | | suppress | suppress | token string | discards the inherited value of each listed facet at this layout's position | - |

Custom layouts apply in DOM 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. Nothing can throw on an attribute here, so an unrecognised value inherits rather than promoting the layout to a base 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.

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 host's auto-compact 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.

locales and suppress are token strings rather than arrays, split on commas and whitespace alike: locales="pl,pl-PL" and locales="pl pl-PL" are the same declaration, as are suppress="Variants,Middleware" and suppress="Variants Middleware".

rows and variants are read by object identity: assign a new array or object to change them; mutating in place is not observed.

What the element reports

A misconfiguration is logged once per element 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 token that is empty after trim | | unknown-target | facets declared with no rows, for a name no layout has | | unknown-suppress | a suppress token outside Variants / Middleware | | 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 |

Any other element in the slot is ignored with a warning naming its tag, so a stray child cannot quietly take part in the fold.

In a framework

The string attributes work the same in every framework; only the three object-typed properties need the framework's escape hatch for setting a DOM property rather than an attribute.

TypeScript - the package ships no tag typings, so document.createElement("kiosk-keyboard") returns a plain HTMLElement and a TSX template does not know the tags. Declare them once in the app:

// kiosk-keyboard.d.ts
import type { KioskKeyboard } from "kiosk-keyboard-webc/bundle";
import type CustomLayout from "kiosk-keyboard-webc/CustomLayout";
import type { DetailedHTMLProps, HTMLAttributes } from "react";

declare global {
  interface HTMLElementTagNameMap {
    "kiosk-keyboard": KioskKeyboard;
    "kiosk-keyboard-custom-layout": CustomLayout;
  }
}

// React 19 types; with React 18 types, augment the global `JSX` namespace instead.
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "kiosk-keyboard": DetailedHTMLProps<HTMLAttributes<KioskKeyboard>, KioskKeyboard> & {
        layout?: string;
        controls?: string;
      };
      "kiosk-keyboard-custom-layout": DetailedHTMLProps<HTMLAttributes<CustomLayout>, CustomLayout> & {
        name?: string;
        locales?: string;
        suppress?: string;
      };
    }
  }
}

Drop the react half outside React. Hyphenated attributes such as keycap-lang and auto-show need no entry: TypeScript does not check JSX attribute names that contain a dash.

React - reach the element with a ref and assign the property in an effect:

import { useEffect, useRef } from "react";
import "kiosk-keyboard-webc/bundle";
import type CustomLayout from "kiosk-keyboard-webc/CustomLayout";
import warehousePos from "./warehouse-pos-layout";

export function Keyboard() {
  const custom = useRef<CustomLayout>(null);

  useEffect(() => {
    if (custom.current) custom.current.rows = warehousePos;
  }, []);

  return (
    <kiosk-keyboard layout="warehouse-pos" controls="my-input">
      <kiosk-keyboard-custom-layout
        ref={custom}
        slot="customLayouts"
        name="warehouse-pos"
        keycap-lang="de"
        locales="de"
      />
    </kiosk-keyboard>
  );
}

Vue - tell the template compiler the two tags are custom elements, or it warns that it cannot resolve them as components:

// vite.config.ts
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("kiosk-keyboard") } } });

The .prop modifier binds a DOM property; markRaw keeps the rows out of Vue's reactivity system, since the element reads them by identity and would otherwise receive a proxy:

<script setup lang="ts">
import { markRaw } from "vue";
import "kiosk-keyboard-webc/bundle";
import warehousePos from "./warehouse-pos-layout";

const rows = markRaw(warehousePos);
</script>

<template>
  <kiosk-keyboard layout="warehouse-pos" controls="my-input">
    <kiosk-keyboard-custom-layout slot="customLayouts" name="warehouse-pos" keycap-lang="de" :rows.prop="rows" />
  </kiosk-keyboard>
</template>

Angular - property binding already writes a DOM property, so [rows] is enough (declare CUSTOM_ELEMENTS_SCHEMA on the component or module):

<kiosk-keyboard layout="warehouse-pos" controls="my-input">
  <kiosk-keyboard-custom-layout
    slot="customLayouts"
    name="warehouse-pos"
    keycap-lang="de"
    [rows]="warehousePos"
  ></kiosk-keyboard-custom-layout>
</kiosk-keyboard>

Accent variants (German umlauts)

The qwertz-de layout ships dedicated ä / ö / ü keys and ß, and a German-locale page selects it automatically (de → qwertz-de). 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 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. The accent-variants attribute merges a broad Latin-diacritics ta