Free referenceMotionary
React Native Password Input
A React Native password input is a TextInput with secureTextEntry plus a show and hide toggle, and the autofill props that let password managers fill and save it.
Installation
Reanimated is only there for the strength bar. Drop the meter and this is core React Native with no dependencies.
npx expo install react-native-reanimated react-native-worklets
Usage
Paste this into components/PasswordInput.tsx. Use
mode="login" on a sign-in screen and mode="new" when
someone is choosing a password, because password managers treat the two
differently.
// PasswordInput.tsx
import { useState, type ComponentProps } from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
type PasswordInputProps = Omit<
ComponentProps<typeof TextInput>,
'value' | 'onChangeText' | 'style' | 'secureTextEntry'
> & {
value: string;
onChangeText: (text: string) => void;
mode?: 'login' | 'new';
};
const AUTOFILL = {
login: { autoComplete: 'current-password', textContentType: 'password' },
new: {
autoComplete: 'new-password',
textContentType: 'newPassword',
passwordRules: 'minlength: 12; required: lower; required: upper; required: digit;',
},
} as const;
const LEVELS = [
{ label: 'Too short', color: '#D92D20' },
{ label: 'Weak', color: '#D92D20' },
{ label: 'Okay', color: '#B54708' },
{ label: 'Good', color: '#067647' },
{ label: 'Strong', color: '#067647' },
];
function scorePassword(value: string): number {
if (value.length < 8) return 0;
let score = 1;
if (value.length >= 12) score += 1;
if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score += 1;
if (/\d/.test(value) && /[^A-Za-z0-9]/.test(value)) score += 1;
return score;
}
export function PasswordInput({
value,
onChangeText,
mode = 'login',
...inputProps
}: PasswordInputProps) {
const [visible, setVisible] = useState(false);
const showMeter = mode === 'new' && value.length > 0;
const score = scorePassword(value);
const level = LEVELS[score];
const barStyle = useAnimatedStyle(() => ({
width: withTiming(`${(Math.max(score, 1) / 4) * 100}%`, { duration: 180 }),
backgroundColor: withTiming(level.color, { duration: 180 }),
}));
return (
<View style={styles.root}>
<View style={styles.box}>
<TextInput
{...AUTOFILL[mode]}
{...inputProps}
value={value}
onChangeText={onChangeText}
secureTextEntry={!visible}
autoCapitalize="none"
autoCorrect={false}
spellCheck={false}
accessibilityLabel={mode === 'new' ? 'New password' : 'Password'}
style={styles.input}
/>
<Pressable
onPress={() => setVisible((v) => !v)}
hitSlop={12}
accessibilityRole="button"
accessibilityLabel={visible ? 'Hide password' : 'Show password'}
>
<Text style={styles.toggle}>{visible ? 'Hide' : 'Show'}</Text>
</Pressable>
</View>
{showMeter ? (
<View accessible accessibilityLabel={`Password strength: ${level.label}`}>
<View style={styles.track}>
<Animated.View style={[styles.bar, barStyle]} />
</View>
<Text style={[styles.level, { color: level.color }]}>{level.label}</Text>
</View>
) : null}
</View>
);
}
const styles = StyleSheet.create({
root: { gap: 8 },
box: {
height: 52,
flexDirection: 'row',
alignItems: 'center',
gap: 12,
borderRadius: 12,
borderWidth: 1.5,
borderColor: '#E4E4E7',
backgroundColor: '#FFFFFF',
paddingHorizontal: 14,
},
input: {
flex: 1,
height: '100%',
padding: 0,
fontSize: 16,
color: '#18181B',
includeFontPadding: false,
},
toggle: { fontSize: 14, fontWeight: '600', color: '#18181B' },
track: {
height: 4,
borderRadius: 2,
overflow: 'hidden',
backgroundColor: '#F4F4F5',
},
bar: { height: '100%', borderRadius: 2 },
level: { marginTop: 4, fontSize: 13, fontWeight: '500' },
});
How it works
secureTextEntry does the masking and it is driven by one boolean,
so the toggle is a Pressable that flips it. A tap on the
Pressable does not take focus from the input, so the keyboard
stays up while someone checks what they typed.
The AUTOFILL table is the part most tutorials leave out.
textContentType tells iOS whether this is an existing password or
a new one, autoComplete does the same on Android, and
passwordRules tells iOS what a generated password has to contain.
Without them the field still masks text, but the password manager stays quiet.
The strength bar is a rough hint from length and character variety, and that is
all it should claim to be. Enforce your real password policy on the server, and
if the meter matters to you, @zxcvbn-ts/core estimates strength
from actual guessing patterns.
Gotchas
iOS clears the field when you start typing again
This is standard UIKit behaviour for secure fields: when a secure input regains focus and the user types, the old value is wiped and replaced by the new character. It looks like a bug and people report it as one, but it is what native iOS apps do too. Don't try to fight it unless your design really needs you to.
Autofill needs your domain linked to your app
The props are only half of it. For iOS to offer the saved password for your
site, the app needs an Associated Domains entry of the form
webcredentials:yourdomain.com, plus an
apple-app-site-association file on that domain. In Expo that goes
in ios.associatedDomains in your app config. Android's equivalent
is a Digital Asset Links file.
Pair it with a username field
Password managers save the pair, so the field just before this one should be a
text field with
textContentType="username" or "emailAddress". A
password field alone often produces no save prompt at all.
secureTextEntry does not work with multiline
React Native's docs are explicit about this: a secure field has to be single
line. If masking seems to have stopped working, look for a stray
multiline prop, which turns the field into a
text area.
Accessibility
The toggle needs accessibilityRole="button" and a label that
changes with its state, otherwise VoiceOver just reads the word on screen with
no context. The meter is grouped into one element so it is announced as a
single sentence instead of a stray bar and word.
How do I hide password text in React Native?
Set secureTextEntry on TextInput. It replaces each
character with a dot on both iOS and Android. It only works on single-line
inputs, so it can't be combined with multiline.
How do I show and hide a password in React Native?
Keep a visible boolean in state, pass
secureTextEntry={!visible} to the input, and flip it from a
Pressable beside the field. Give that button a label that changes
between "Show password" and "Hide password" so screen readers report the state.
How do I enable password autofill in React Native?
Set autoComplete="current-password" with
textContentType="password" on sign-in, or
autoComplete="new-password" with
textContentType="newPassword" on sign-up. Then link your domain to
your app with Associated Domains on iOS and Digital Asset Links on Android so
saved credentials for your site are offered.
Why does my password field clear on iOS?
iOS secure text fields clear their contents when the user types after the field regains focus. It is native behaviour rather than a React Native bug, and standard iOS apps do the same thing.
After the password, most sign-in flows ask for a code, which is an OTP input. If you want that step animated and finished, the OTP code input drop is ready to paste in.
Depends onreact-native-reanimatedreact-native-worklets
Every component on this page is free to copy. Motionary sells the polished, production versions over at the catalog.