Skip to content
Document DB v7: Temporal Support Feed The Machine Here

On-Screen Keyboard

A focused on-screen keyboard for touch tablets and kiosks. Auto-shows when an Entry / Editor (MAUI) or <input> / <textarea> (Blazor) gains focus, docks along the bottom edge, and types into whatever’s focused — without stealing focus when keys are tapped.

Two packages ship the same shape:

  • Shiny.Maui.Controls.Desktop — MAUI desktop (Windows + macOS AppKit + MacCatalyst + Linux GTK). Bundled with Tray Icon and Docking in the desktop-only add-on. Namespace: Shiny.Maui.Controls.Desktop.OnScreenKeyboard.
  • Shiny.Blazor.Controls.Kiosk — Blazor (browser, Blazor Server, BlazorWebView). Bundled with Docking under the kiosk-shaped add-on. Namespace: Shiny.Blazor.Controls.Kiosk.OnScreenKeyboard.

Intentionally limited in scope: English US-QWERTY layout, dispatch into the host app’s own text fields, no IME / dead-key composition. The 80% case for touch kiosks, not a replacement for the OS on-screen keyboard.

  • NuGet downloads for Shiny.Maui.Controls.Desktop
  • NuGet downloads for Shiny.Blazor.Controls.Kiosk
Frameworks
.NET MAUI
Blazor
Operating Systems
Windows
macOS
Linux
Shiny.Maui.Controls.DesktopNuGet package Shiny.Maui.Controls.Desktop
Terminal window
dotnet add package Shiny.Maui.Controls.Desktop

In MauiProgram.cs:

using Shiny;
using Shiny.Maui.Controls.Desktop.OnScreenKeyboard;
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseOnScreenKeyboard(opts =>
{
opts.AutoShowOnFocus = true; // show when an Entry / Editor gains focus
opts.AutoHideOnBlur = true;
opts.Height = 280;
opts.PushContent = true; // shrinks the page above by Height
opts.Theme = OnScreenKeyboardTheme.Light;
});

Resolve IOnScreenKeyboard from DI to control visibility from code, or use it inline as a View:

public class MyPageViewModel(IOnScreenKeyboard keyboard)
{
public void OnLaunchKioskMode()
{
keyboard.Show();
}
}
<!-- Inline use, e.g. in a kiosk-style page that always shows the keyboard -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:osk="clr-namespace:Shiny.Maui.Controls.Desktop.OnScreenKeyboard;assembly=Shiny.Maui.Controls.Desktop">
<Grid RowDefinitions="*,Auto">
<!-- your page content -->
<osk:OnScreenKeyboardView Grid.Row="1" Height="280" />
</Grid>
</ContentPage>
Terminal window
dotnet add package Shiny.Blazor.Controls.Kiosk

Program.cs:

using Shiny.Blazor.Controls.Kiosk.OnScreenKeyboard;
builder.Services.AddShinyOnScreenKeyboard(opts =>
{
opts.AutoShowOnFocus = true;
opts.AutoHideOnBlur = true;
opts.HeightPx = 280;
opts.PushContent = true; // adds body { padding-bottom: var(--shiny-osk-height); }
});

_Imports.razor:

@using Shiny.Blazor.Controls.Kiosk.OnScreenKeyboard

Place once at the root layout (typically MainLayout.razor):

<OnScreenKeyboardHost />

Or inject IOnScreenKeyboardService into any component to drive visibility from code.

Identical shape on both renderers — only the View / RenderFragment type differs.

public interface IOnScreenKeyboard // IOnScreenKeyboardService on Blazor
{
bool IsVisible { get; }
event EventHandler<bool>? VisibilityChanged;
void Show();
void Hide();
void Toggle();
}
public sealed class OnScreenKeyboardOptions
{
public bool AutoShowOnFocus { get; set; } = true;
public bool AutoHideOnBlur { get; set; } = true;
public double Height { get; set; } = 280; // HeightPx on Blazor
public bool PushContent { get; set; } = true; // false = overlay above content
public OnScreenKeyboardTheme Theme { get; set; } = OnScreenKeyboardTheme.Light;
public TimeSpan AutoRepeatDelay { get; set; } = TimeSpan.FromMilliseconds(400);
public TimeSpan AutoRepeatInterval { get; set; } = TimeSpan.FromMilliseconds(50);
}

A hand-tuned US-QWERTY with three switchable layers:

LayerKeys
Lowercase ` 1 2 3 4 5 6 7 8 9 0 - = ⌫ / Tab Q W E R T Y U I O P [ ] \ / Caps A S D F G H J K L ; ’ Enter / Shift Z X C V B N M , . / Shift / Ctrl Alt [Space] Alt ◀ ▼ ▲ ▶
ShiftCapital letters + shifted symbols (!@#$%^&*()_+, etc.) — visual modifier indicator stays lit until released
123 / SymbolsNumeric pad + punctuation + arrow keys — tap-toggle modifier; tap again to return to letters

Modifier keys behave like a real keyboard: Shift is momentary unless Caps Lock is engaged. The Numbers/Symbols toggle is sticky. State is fully visible in the rendered key chrome.

Two implementation details that make or break a touch OSK:

1. No focus stealing. Every key uses pointerdown + preventDefault() rather than click (Blazor) or sets Focusable = false and intercepts PointerPressed (MAUI). If you let the OS take focus when a key is tapped, the target input loses its caret the moment the user starts typing. This is the single biggest cause of “the OSK doesn’t work” bugs.

2. Caret-position tracking. Mutating Text at CursorPosition (MAUI) or dispatching insertText via execCommand (Blazor) works cleanly, but selection-replace edge cases (user selects “abc” and types “x” → result is “x”, not “abcx”) need bookkeeping. The implementation tracks SelectionStart/SelectionLength and replaces the range when present.

  • MAUI inputs only / DOM inputs only. The OSK dispatches into its host’s text inputs. Won’t inject into popups owned by other apps, native windows inside a WebView, or focused windows belonging to other processes. For kiosk apps this is the desired behaviour. Opt-in system-wide dispatch arrives in v0.4.
  • Shadow DOM (some Web Components with internal <input> elements) — focusin doesn’t pierce shadow roots. v0.1 skips this.
  • Rich editors (Quill, ProseMirror, Monaco) — execCommand('insertText') works against <input> / <textarea> / simple contenteditable but selection behaviour can get weird inside complex editor frameworks. v0.1 documents this as best-effort.
  • Enter key dispatches a keydown/keyup for Enter (so form submit fires) — it does NOT insert \n. Use Shift+Enter (configurable) for a literal newline in <textarea> / multi-line Editor.
  • No IME, no dead-key composition, no language switching until v0.5 / v0.3 respectively.

MAUI: ResourceDictionary keys — OnScreenKeyboardKeyBrush, OnScreenKeyboardModifierBrush, OnScreenKeyboardPressedBrush, OnScreenKeyboardBackgroundBrush, OnScreenKeyboardForegroundBrush. Override the resource keys in your app theme to restyle.

Blazor: CSS custom properties — --shiny-osk-key-bg, --shiny-osk-key-fg, --shiny-osk-key-pressed-bg, --shiny-osk-modifier-bg, --shiny-osk-bg, --shiny-osk-height. Override on a parent element, or on <OnScreenKeyboardHost> directly.

Both renderers honour reduced-motion preferences — animation duration is a theme token, not a constant.

Every key exposes the appropriate platform automation role:

  • Windows MAUI: AutomationPeer with Name = label and LocalizedControlType = "key"
  • macOS / AppKit / Catalyst: NSAccessibilityRole.Button with a localized description
  • Linux GTK: ATK role KEY via the underlying GtkButton
  • Blazor: ARIA role="button" + aria-keyshortcuts matching the displayed glyph, contained inside role="application" so screen readers don’t fight the typing flow

The OSK is designed so switch-input users can step through keys with a single-button scanner — designed in v0.1 rather than retrofitted, since the AutomationPeer tree is hard to add cleanly later.

  • Tray Icon — ships in the same Shiny.Maui.Controls.Desktop package
  • Docking — once v0.4 floating windows land, the OSK can be hosted in a floating dock window for “always on top” kiosk use