A customizable native keyboard for React Native. Familiar by default; colors, fonts, and key shapes are yours.
Status: early iOS and Android preview. Includes UIKit and Kotlin keyboards, plus an Expo showcase and native device regression screens. This is our own keyboard, not a reskinned Apple keyboard. The package remains private and unpublished.
Shared feature support means support on both iOS and Android. A capability available on only one platform is not shared support. Platform-native features may differ and are identified explicitly.
- UIKit keys hosted in a native input accessory with an empty input view on iOS; Kotlin keys with an owned
EditTexton Android. - Android clipboard paste, Back dismissal, and native ripple feedback.
- English QWERTY, numbers, punctuation, shift, caps lock, space, and return.
- Native text insertion, selection replacement, deletion, and held-delete repetition.
- Immediate key highlights, character popups, optional haptics, and accessibility labels.
- Custom word suggestions and automatic word replacements are disabled. System mode uses the installed keyboard.
- Light, dark, and transparent themes; customizable colors, fonts, shapes, and raised depth.
- Platform-specific accent holds, iOS held-space/Android space-slide cursor movement, sentence capitalization, and double-space punctuation.
Apple/Gboard predictive models, swipe typing, dictation, stickers, and multilingual composition engines are not implemented. Use system mode for system keyboard features. Visual metrics are tuned against the iOS 26 keyboard and the tested Android 16 Gboard; this is not a version-adaptive replica. Keyflow has distinct phone/tablet and portrait/landscape geometry profiles.
import { KeyflowTextInput } from 'react-native-keyflow';
<KeyflowTextInput
placeholder="Write something…"
defaultValue=""
keyboardAppearance="light"
keyboardTheme={{ keyBackground: '#FFFFFF', fontSize: 22 }}
onChangeText={(text) => console.log(text)}
style={{ height: 52 }}
/>;Text is owned by the native editor. This preview supports defaultValue, not a controlled value; changing defaultValue after mount does not reset text. Remount with a new React key to reset. The ref exposes async focus(), blur(), and setKeyboardMode("custom" | "system") methods. Await the mode method before requesting focus when switching imperatively; it cancels active holds and resets the custom page while retaining text and selection. onSubmitEditing receives the text and return dismisses the keyboard.
Use keyboardMode="system" to show the user's system keyboard. Subscribe to onKeyboardModeChange when controlling this prop so native system-switch actions update your state. keyboardAppearance selects Keyflow's light/dark theme and otherwise follows device appearance. keyboardTheme overrides it. The input's React Native style controls its outer layout, not the keyboard keys or native text-field typography.
Additional props: autoCorrect (default true; set false for literal text entry), autoFocus, editable, inputAccessibilityLabel, hapticsEnabled (default false), and showSecondaryKeyLabels (default true). Set showSecondaryKeyLabels={false} to hide Android's visible 1–0 long-press legends without disabling their shortcuts. iOS accepts the same prop and has no secondary legends to hide. This is a single-line input, not a complete replacement for every React Native TextInput prop.
createKeyboardTheme(overrides, base) validates and freezes a theme. lightKeyboardTheme, darkKeyboardTheme, androidKeyboardTheme, androidDarkKeyboardTheme, and transparentKeyboardTheme are included.
For new themes, group overrides by section. Set the shared font once; override only what differs:
<KeyflowTextInput
keyboardTheme={{
font: { family: 'system-rounded', weight: 'medium' },
keys: { background: '#FFFFFF', color: '#182D40' },
}}
/>The same keyboardTheme object applies on iOS and Android, on phones and
tablets, and in portrait and landscape. There are no phone, tablet, or
orientation-specific theme branches. Keyflow selects and resizes the native
layout internally while keeping the configured colors, font, material, opacity,
and section styles. Pass the same props when the device rotates; do not detect
the device class in application code.
Sections inherit the global theme and font. color also sets icon and pressed foreground colors unless you override those explicitly. Existing flat tokens still work; section settings take precedence. createKeyboardTheme(overrides, base) merges partial sections without discarding their other settings. Use fontFamily: null inside a section to restore the system font there.
Available sections are keys, specialKeys, deleteKey, returnKey, preview, selection, and toolbar. Use keyboard: { background, material } for the surface and font: { family, size, weight } for shared typography.
Material-specific options stay with the material:
keyboardTheme={{
keyboard: {
material: {
type: 'raised',
depth: 4,
shadowColor: '#00304C',
},
},
}}Use material: { type: 'flat' } when no depth is wanted. depth and shadowColor only exist on the raised object. Foreground, background, pressed colors, borders, radius, typography, and opacity remain section settings because they work with both materials.
Each section accepts background, color, placeholderColor, iconColor, pressedBackground, pressedColor, borderColor, borderWidth, cornerRadius, fontFamily, fontSize, fontWeight, and iconSize, where applicable to its content. Section font sizes are 10–32, icon sizes 12–28, border widths 0–3, and radii 0–24 logical pixels. Labels fit within fixed key and control bounds; styling cannot change row geometry or touch targets. This is visual customization, not arbitrary keyboard layout.
Apply transparency directly to the default theme, or to any preset:
keyboardTheme={{
keyboard: { backgroundOpacity: 0.35, keyOpacity: 0.7 },
}}Both controls accept 0–1 on iOS and Android:
backgroundOpacitychanges only the keyboard panel. Use0withkeyOpacity: 1for solid keys over your app background.keyOpacitychanges keycaps, controls, press previews, long-press selection, borders, and themed shadows. Use0.5withbackgroundOpacity: 1for translucent keys over a solid panel. Text and icons keep their configured colors.
Omit either setting to keep its theme value. backgroundOpacity replaces the panel color's alpha; keyOpacity multiplies each key surface's existing alpha. You can also set individual section colors with alpha, such as keys: { background: '#FFFFFF80' }.
keyboard.surfaceOpacity remains an optional multiplier for all surfaces together, applied after these settings. Reusing or restyling the theme does not compound opacity.
transparentKeyboardTheme includes bordered keycaps, a defined space bar, and dark blue text. It starts with a 35% tinted background and 70% key opacity. The example's Make room for your style screen has separate Background and Keys sliders from 0% to 100%, over a coastal wallpaper or plain background. Keep your backdrop outside KeyflowAvoidingView so it continues beneath the keyboard.
Additional tokens include fontWeight (regular, medium, bold), specialKeyForeground, and deleteKeyBackground. Raised material depth is limited to 0–6 logical pixels.
The example's Story Studio screen shows a complete app-specific theme defined in the example. It uses the same public theme API as every other screen and adds no library preset, keyboard type, or fixed size.
| Token | Meaning |
|---|---|
background |
Keyboard surface |
keyBackground, keyForeground |
Character keys |
specialKeyBackground |
Shift, delete, mode keys |
pressedKeyBackground |
Pressed face |
actionKeyBackground, actionKeyForeground |
Return key |
fontFamily |
Registered native font name or system design alias; null uses default |
fontSize |
Preferred character size, 12–32 logical pixels |
keyCornerRadius |
Key face radius in points |
Colors use #RRGGBB or #RRGGBBAA (alpha last). Missing native fonts fall back to the system font. Action labels have separate fitted sizes; icons retain their platform sizes. Keyflow does not bundle proprietary fonts or keyboard artwork.
On iOS, fontSize is the base capital/digit size. Lowercase letters and small punctuation receive optical size adjustments, matching the proportions of the iOS keyboard (22-point capitals and 25-point lowercase at the default). All sizes remain clamped and fitted inside fixed key faces. The default uses a calibrated public system font; it is an approximation of Apple's keyboard typeface. A supplied fontFamily preserves that font's design and width. Android scales the default letter proportions to its responsive phone geometry (22 at 320 points wide, 28 at 411), then fits the chosen font inside the key. Action icons retain their platform rendering. See the Android comparison and device checks for the calibrated Gboard version and remaining differences.
Use a font your app has already registered by passing its name: keyboardTheme={{ font: { family: 'Quicksand_600SemiBold' } }}. If loading it with useFonts, wait until loading finishes before mounting the input. Choose the registered face for a specific weight. The Your app. Your type. example bundles three Quicksand weights and compares them with the system font; see its font loader and screen.
No theme factory is required for a few overrides:
<KeyflowTextInput
keyboardTheme={{
fontFamily: 'system-rounded',
fontSize: 24,
fontWeight: 'bold',
keyCornerRadius: 12,
}}
/>system-rounded, system-serif, and system-monospace work on both platforms. For a custom font, register it before mounting the input using your app's native font setup or expo-font, then pass the registered name. Use Platform.select if the two platforms use different names. The example bundles JetBrains Mono and demonstrates runtime loading. Missing fonts fall back to the platform font.
The exported keyboardThemeLimits define accepted ranges: font size 12–32, radius 0–24, and raised depth 0–6 logical pixels. Invalid values throw before reaching native code. Native labels shrink to fit their available width and height; radius is additionally bounded to the key face. Theme props never change row heights, key widths, or touch regions. This prevents the measured geometry failures covered by the device tests; it cannot guarantee the appearance of every font, color combination, device, or accessibility configuration.
Open Customize fonts & test layouts to test 76 combinations, including boundary sizes, all materials, bundled fonts, and a missing font. ref.current.getKeyboardMetrics() reports native key count, dimensions, and layout violations for regression tests. iOS reports resolved font names; Android reports requested families.
Use Node from .nvmrc, Yarn 4 through Corepack, and the official stim package. The legacy stim-cli package is not supported:
npm install --global stimThen install and run the project:
corepack yarn install
corepack yarn check
cd example
stim start
stim ios
# or stim androidBoth examples use Expo SDK 57, React Native 0.86.3, React 19.2.3, and TypeScript 6.0.3. The library uses Expo Modules as its bridge. It requires a native development or production build and Expo modules installed in the consuming React Native app; Expo Go and web are not supported. The SDK 57 example targets iOS 16.4+ and Android 7.0+ (API 24).
The package declares Expo 55+, React Native 0.83+, and React 19.2+ peer dependencies to preserve the earlier SDK 55 integration. Those ranges are minimum requirements, not a guarantee that every future SDK release works. Use the React/React Native versions selected by your Expo SDK. The library podspec minimum remains iOS 15.1; the consuming app must satisfy the higher minimum required by its Expo/RN stack.
yarn build emits ESM and TypeScript declarations. The example includes platform-style, Story Studio, custom-font, and transparent demos with system-keyboard comparison. yarn example:export creates a standalone Git repository in artifacts/keyflow-example-repo, with a bundled library package. Native edits need a rebuild; TypeScript example edits use Fast Refresh.
src/ React API, public types, validated themes, tests
ios/ UIKit keys, keyboard presentation, Expo module bridge
example/ Expo demonstration app
android/ Kotlin keyboard, EditText, Expo module bridge
docs/ Architecture, source audit, verification notes
.github/workflows/ TypeScript, tests, formatting, package build
Organization follows react-native-ease: root library, TypeScript, Builder Bob, platform folders, Yarn workspaces, and Expo example. The iOS prototype adapts native editing and touch/timer patterns from the downloaded Dartwords module; see source audit and notices.
See verification notes for tested behavior and architecture for limitations and follow-up work. Keyflow is the chosen working name; npm availability has not been checked.
Android uses an app-owned bottom panel. Connect onKeyboardFrameChange to KeyflowAvoidingView, or consume the frame in your own layout. English/French layouts, portrait/landscape, and number pads are supported; custom word suggestions are disabled.
The example has explicit Keyflow/Apple comparison buttons and a shared dark toggle. keyboardAppearance also controls Apple’s system keyboard.
Use KeyflowAvoidingView for keyboard avoidance on both iOS and Android, and connect the active input's onKeyboardFrameChange to its keyboardFrame prop. This is the shared integration used throughout the example, including the custom-font screen. This replaces manual fixed-height spacers; do not combine both avoidance approaches on the same content. See integration and regression tests.
A frame describes the keyboard's occupied area: its top (screenY), height, and whether it is visible, measured in logical screen coordinates. The wrapper handles platform-specific notifications and frames to keep content above the keyboard. Regular React Native KeyboardAvoidingView is not a supported shared integration for Keyflow because it cannot detect the Android custom panel by itself.
The example's Test keyboard transitions screen runs show/hide, switching, layout-restoration, and interrupted-transition checks against a real system-keyboard baseline.
Compare native interactions supplies repeatable text cases, keyboard-state inspection, and six consecutive mode-handoff checks. Test keyboard transitions compares a real React Native TextInput, Keyflow, and system mode, including keyboard avoidance and interrupted presentation. See the current native parity report for commands, captured references, and limits.
Tablet selection is native and does not depend on a model-name list. iOS uses UIDevice.current.userInterfaceIdiom == .pad; Android uses Configuration.smallestScreenWidthDp >= 600. Sizes are derived from the current viewport's short edge and safe-area inset, with separate landscape proportions. This covers conventional full-screen phone and tablet sizes. Split-screen, iPad floating/split keyboards, and an Android IME's user-selected floating or resized mode are separate system layouts and are not reproduced by the custom panel.
On Android, keyboardMode="system" requests the selected system IME and retries while the window gains focus. The OS can still suppress or float it when a physical keyboard is attached or Gboard is saved in compact/floating mode. The tablet device suite now requires real native keys to be visible, so a toolbar-only handoff fails with an actionable setup message instead of producing a misleading comparison capture.
Pressed previews and long-press popups inherit the keyboard material. Customize the focused choice with selectedKeyBackground and selectedKeyForeground, independently of pressedKeyBackground. See press materials and accent verification.
The visual feature suite adds held/released key
captures, native/custom screenshot pairs, gesture recordings, customization
boundary checks, and explicit pixel regressions. Run yarn test:features with the
platform, agent-device session, and device ID; yarn test:features:report builds
the review gallery at artifacts/features/index.html.
Dedicated number-pad, decimal-pad, and phone-pad layouts and phone landscape
mode are available through keyboardType. Open Compare layouts & rotation
for native/custom comparisons. See keyboard layouts and tests.
The layout customization suite tests 58 themes on each of these layouts and QWERTY, in portrait and both landscape directions. The example includes Run 58 and flat/raised visual profiles for manual review.
The custom keyboard supports English and French, with a globe switcher and QWERTY/AZERTY templates. Open English & French keyboards in the example to try both, or omit keyboardLanguages to discover supported device preferences. Layout variants cannot always be read from the OS; explicit configuration selects the template. Custom word suggestions and automatic replacements are disabled. See language configuration and tests.
<KeyflowTextInput
keyboardLanguages={[{ language: 'en' }, { language: 'fr', layout: 'azerty' }]}
/>With keyboardMode="system", the actual iOS or installed Android keyboard owns all enabled languages, layout preferences, candidates, and composition. Non-Latin languages remain system-keyboard features; Keyflow does not reproduce their composition engines.
Custom word suggestions are disabled on both platforms. iOS omits the suggestion strip; Android retains its paste/system/dismiss toolbar. autoCorrect is a system-keyboard hint only; it does not enable custom word replacement. Use the toolbar theme section to style the Android paste/system/dismiss controls.
The example groups checks by Behavior, Layouts and Appearance. See test categories and commands.