Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Keyboard Accessory

A bar docked to the top edge of the soft keyboard while a TextEntry has focus. It belongs to the keyboard, not to the entry: it appears when the keyboard comes up, moves with it, and goes away with it. Nothing is rendered inside the text field.

Frameworks
.NET MAUI

The reason it exists: the iOS numeric keypad has no return key. Give a user a numeric field and no Done button and there is genuinely no way for them to put the keyboard away.

The quickest version — no bar markup at all:

<shiny:TextEntry Placeholder="Amount"
Keyboard="Numeric"
Text="{Binding Amount, Mode=TwoWay}"
AccessoryPreset="NavigationAndDone" />
Preset Bar
None No bar. The default
Done A trailing “Done” that dismisses the keyboard
Navigation Leading previous/next field arrows
NavigationAndDone Arrows on the left, “Done” on the right

Accessory takes a KeyboardAccessoryView. Items is its content property, so items can be listed directly inside it:

<shiny:TextEntry Placeholder="Notes" Text="{Binding Notes, Mode=TwoWay}">
<shiny:TextEntry.Accessory>
<shiny:KeyboardAccessoryView>
<shiny:KeyboardNavigationItem Direction="Previous" />
<shiny:KeyboardNavigationItem Direction="Next" />
<shiny:KeyboardAccessorySpacer />
<shiny:KeyboardAccessoryItem Text="#tag" Command="{Binding InsertTagCommand}" />
<shiny:KeyboardDismissItem />
</shiny:KeyboardAccessoryView>
</shiny:TextEntry.Accessory>
</shiny:TextEntry>

A KeyboardAccessorySpacer takes whatever width is left over, which is how items are pushed apart — everything before it stays left, everything after it goes right.

For something that is not a row of buttons at all, set BarContent to any View and it replaces the item row wholesale. Items lays out as one auto-width column per item, so a long row gets squeezed rather than scrolled — when there are more items than fit the bar, BarContent with a horizontal ScrollView (and a KeyboardDismissItem pinned beside it) is the shape you want. Any KeyboardAccessoryItem nested inside BarContent is wired to the focused field exactly like one listed in Items, however deep it sits.

Type Purpose
KeyboardAccessoryView The bar itself
KeyboardAccessoryItem Icon/label button — Icon, Text, ToolColor, FontSize, IconSize, Command, CommandParameter, Clicked
KeyboardNavigationItem Direction="Previous" / "Next". Moves focus and dims itself to 0.35 opacity at the ends of the run
KeyboardDismissItem “Done” — dismisses the keyboard. Set Text for a non-English bar; the default no longer overwrites a text already assigned
KeyboardAccessorySpacer Flexible gap
Property Type Default Description
Items IList<View> empty Bar contents, left to right (content property)
BarContent View? null Replaces the item row with your own layout
BarHeight double 44 Bar height
BarBackgroundColor Color? surface-container-high token Bar fill
BarBorderColor Color? outline-variant token The hairline along the top edge
ItemSpacing double 4 Gap between items
CurrentOwner TextEntry? Read-only. The field the bar is serving right now

KeyboardNavigationItem walks the fields on the page through KeyboardFieldNavigator, which collects every enabled, visible, non-read-only TextEntry / InputView under the field’s page in depth-first visual-tree order — for a form laid out top to bottom, the order it reads in. MAUI has no TabIndex to honour (that was a Xamarin.Forms concept), so declaration order is the order.

Set FieldGroup on the entries to navigate within a subset rather than the whole page:

<shiny:TextEntry Placeholder="Card number" FieldGroup="payment" AccessoryPreset="NavigationAndDone" />
<shiny:TextEntry Placeholder="Expiry" FieldGroup="payment" AccessoryPreset="NavigationAndDone" />
<shiny:TextEntry Placeholder="CVV" FieldGroup="payment" AccessoryPreset="NavigationAndDone" />

A disabled arrow stays disabled-looking when it is tapped. That sounds like nothing, but MAUI’s TapGestureRecognizer disregards IsEnabled on several platforms, so the tap does reach the item — the move is refused, and the press-flash that used to run alongside it ended by restoring full opacity, undoing the dim. The flash is skipped while the item is disabled.

KeyboardFieldNavigator is public and platform-free, so it can be driven from your own items:

KeyboardFieldNavigator.CanMove(entry, KeyboardNavigationDirection.Next);
KeyboardFieldNavigator.Move(entry, KeyboardNavigationDirection.Next);
KeyboardFieldNavigator.Collect(entry); // the ordered run

Limitation: virtualized containers (CollectionView, TableView, DataGrid) only realize the rows currently on screen, so navigation cannot reach a field that has not been realized yet.

This is a deliberately platform-only feature, in the same category as Desktop being MAUI-only.

Platform Behaviour
iOS / Mac Catalyst The real UIResponder.InputAccessoryView. The OS owns the bar, so it rides the keyboard’s own animation exactly. On Catalyst proper, where there is no soft keyboard, it generally will not appear at all
Android Android has no accessory API — the IME runs in a different process and only an IME app can draw inside it. The same bar is rendered in the activity’s content view and pushed up by however much the IME actually overlaps it, measured rather than assumed so adjustResize, adjustPan and forced edge-to-edge (API 35+) are all correct. On API 30+ it is frame-synced to the IME animation via WindowInsetsAnimation; below that it snaps into place when the insets land
Windows / macOS / Linux No soft keyboard. Accessory compiles and does nothing
Blazor No equivalent, on purpose — see below

Because the Android bar tracks the IME insets rather than focus, a device with a hardware keyboard attached (which raises no IME) correctly shows no bar.

iOS Safari already draws its own bar above the keyboard and will not let a page remove it, so ours would stack on top of it and the user would see two. Chrome on Android draws none, so the behaviour would diverge across the only two browsers where it matters. Building one would be parity theatre.

The bar serves any InputView, not only single-line entries — this is how MarkdownEditor puts its formatting toolbar on the keyboard, and it is the supported way to build anything similar. On a multi-line editor a KeyboardDismissItem is close to mandatory: the return key inserts a newline, so nothing else puts the keyboard away.

  • One bar instance serves one field. AccessoryPreset materializes a bar per entry automatically; if you share a single KeyboardAccessoryView across fields, remember that on iOS a UIView can only have one superview.
  • The bar lives outside the control’s visual tree, so it is torn down explicitly when the field is unloaded — navigating away does not leave a bar behind.
  • Swapping the bar while the field is already focused reloads the responder’s input views, so the change is visible immediately.