DOCS LLMs

RailsFast Native - Web Forms & The Keyboard

Your RailsFast app renders its forms as web pages inside a native WebView. That is what makes RailsFast fast β€” but the WebView is a browser engine, and browser engines have opinions about the on-screen keyboard that surprise people the first time.

The short version, verified on both shells:

IMPORTANT

Focusing a field from code β€” the HTML autofocus attribute, Turbo's post-visit autofocus, or a JS element.focus() β€” focuses the field on every platform, but does NOT raise the software keyboard on iOS or Android. The on-screen keyboard only comes up on a real user gesture (a tap). This is deliberate browser-engine behavior, not a RailsFast bug, and it is the same on both native shells.

So a chat composer, a search box, or any "land here ready to type" screen will open with the caret in the field but the keyboard down until the user taps. On desktop web there is no soft keyboard, so autofocus is purely a focus nicety there.

Why β€” it's a browser-engine user-activation policy

Platform autofocus focuses the field? Soft keyboard rises on open?
iOS (WKWebView) Yes No β€” tap to raise
Android (WebView / Chromium) Yes No β€” tap to raise
Desktop web Yes n/a (no soft keyboard)

You can prove it to yourself on Android: load a page with <textarea autofocus> and read the IME state with adb shell dumpsys input_method | grep mInputShown. On page load it reports mInputShown=false (field focused, keyboard down); tap the field and it flips to mInputShown=true.

The iOS 26 trap β€” do NOT set keyboardDisplayRequiresUserAction

There is a legacy, undocumented WKPreferences key, keyboardDisplayRequiresUserAction, that older guides tell you to flip via KVC to make programmatic focus raise the keyboard:

// ☠️ DO NOT DO THIS β€” crashes on iOS 26.
configuration.preferences.setValue(false, forKey: "keyboardDisplayRequiresUserAction")
WARNING

As of iOS 26, WKPreferences no longer has this key. Setting it via KVC throws NSUnknownKeyException and crashes your app on every WebView creation (i.e. immediately on launch). It was already unreliable on iOS β‰₯ 11.3. Do not add it.

RailsFast does not force the keyboard β€” on purpose

The RailsFast native shells (railsfast-ios, railsfast-android) ship no keyboard-on-programmatic-focus override, and the recommendation is to leave it that way:

  • On iOS the only remaining lever is a private-API swizzle of WKWebView's _elementDidFocus: internal method, whose signature changes across iOS versions β€” a wrong guess crashes exactly like the KVC above.
  • On Android it is doable with public APIs, but still needs a JSβ†’native focus bridge and native showSoftInput plumbing.

That is an asymmetric, fragile, engine-policy-fighting feature to bake into every app by default. The pragmatic default is: add autofocus to your primary input (the field opens focused, and the keyboard rises on the user's first tap β€” one tap, not a workaround), and accept the platform behavior.

<%# The composer/search field opens focused. On phones the keyboard
    comes up on first tap (browser-engine policy); on desktop it's a
    focus nicety. %>
<%= form.text_area :body, autofocus: true, ... %>

Opt-in: if you really want keyboard-on-open

If a specific screen genuinely needs the keyboard up on arrival and one tap is unacceptable, here is the shape of it β€” as an app-level addition, not something to enable globally.

Android (public API, cleaner): add a small bridge component that, when a web field reports focus, calls into native:

// On a "composer connected / field focused" bridge message:
webView.requestFocus()
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(webView, InputMethodManager.SHOW_IMPLICIT)

iOS (private API, fragile β€” guard it so it can only ever no-op): swizzle WKContentView's focus callback to report userIsInteracting = true, and guard on the method's runtime type encoding so an unrecognized iOS signature safely does nothing instead of crashing:

// Only proceed if the private selector exists AND its type encoding
// matches what you verified on your target iOS β€” otherwise no-op.
// This is private API: re-verify on every iOS major.
let sel = NSSelectorFromString("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
// ...method_getTypeEncoding guard, then method_setImplementation to force the BOOL...
CAUTION

Treat the iOS side as private-API surface you own and must re-verify on every iOS release. RailsFast keeps it out of the templates for exactly this reason. If you adopt it, isolate it (RailsFast iOS already centralizes its one private-WebKit workaround β€” the input-accessory removal β€” in RailsFast/Shell/WebViewController.swift; put this next to it) and keep the guard so it degrades to "focused, no keyboard" rather than crashing.

Sources