Native port of beUI Input. A text field with a label, leading and trailing icons, an error shake, and a check that draws itself on success.
import { Input } from "./src";
<Input
label="Email"
leftIcon={Mail}
value={email}
onChangeText={setEmail}
error={valid ? undefined : "Enter a valid email address."}
reserveErrorLine
/>
<Input label="Search" leftIcon={Search} success={found} />
<Input label="Password" secureTextEntry rightIcon={<ToggleButton />} />- Field: a 44-point, fully rounded field. Its border fades between idle, focused and error colours over 200 ms. Focus and error also show a 2-point ring, like the web's
ring-2. - Error: a truthy
errorshakes the field (6, 4 and 2 points over 450 ms) and turns it red. A stringerroralso shows a message that fades and drops in, and is announced to screen readers.reserveErrorLinekeeps one line of space so content below doesn't jump. - Success:
successdraws a check at the trailing edge in place ofrightIcon. - Icons:
leftIconandrightIcontake a component given{ size, color }(such as a lucide icon) or a prebuilt element. A trailing control, such as a show-password button, sits in a 44-point square so it stays easy to tap. - State: pass
valuewithonChangeTextto control it, ordefaultValueto let it manage itself. Every otherTextInputprop passes through, andrefreaches theTextInput. - Accessibility: the label is the field's spoken name, and pressing the label focuses the field. The error message is read as the hint and reported as invalid.
- Reduced motion: when the system asks for it, or
disableAnimationis set, colours change instantly, nothing shakes, and the check appears fully drawn.
classNames styles the root, label, field, input and error message individually.
- Theme: the keyboard follows the app's light or dark theme on iOS. Pass
keyboardAppearanceto override it. - Text: text and placeholder sit centred in the 44-point field on every platform. The field sets a font size without a line height, because a line height on a
TextInputpushes the text down on iOS. - Moving between fields:
refreaches theTextInput, so setreturnKeyType="next",submitBehavior="submit"andonSubmitEditing={() => nextRef.current?.focus()}. - Autofill: pass
autoCompleteandtextContentTypeso iOS and Android can fill passwords, emails and one-time codes. - Staying above the keyboard: that's the screen's job, not the field's. Since Android became edge-to-edge, the window no longer resizes when the keyboard opens, so use react-native-keyboard-controller: wrap the app in
KeyboardProvider, put forms in aKeyboardAwareScrollViewwith abottomOffset, and addKeyboardToolbarfor previous, next and done controls. The example app does exactly this.
Copy this directory, hooks/use-presence.ts, hooks/use-reduced-motion.ts, hooks/use-shake.ts, primitives/drawn-path.tsx, primitives/render-icon.tsx, styles/motion.ts, and the semantic tokens in styles/theme.css.