Skip to content
Native Animation

React Native Password Input: Show, Hide, and Validate

By Native Animation · Updated

A React Native password input should keep the draft, visibility, local rule feedback, and host validation separate. This tutorial builds a controlled Expo SDK 58 and React Native 0.88 field with a labeled show/hide button, password-manager hints, and honest strength feedback. secureTextEntry changes presentation; it does not replace server-side password policy, breached-password checks, or secure transport.

Keep the password field and security policy separate

The native TextInput owns editing, selection, paste, and the keyboard. Your host owns the password value, confirmation matching, asynchronous checks, account state, and what gets submitted. Keep the draft as a string and do not put it in analytics, logs, URLs, or error messages.

A strength score is useful immediate feedback: it tells a person which local rules are satisfied. It does not prove entropy, uniqueness, resistance to guessing, or absence from a breach corpus. The server must enforce the policy and reject a password independently of the meter.

Build a controlled password input

Toggle secureTextEntry without changing the value

Use one controlled input and a separate Pressable for visibility. Toggling secureTextEntry should never clear or rewrite the draft. The button's label must describe the action the next press will take.

Install the SDK-compatible package through Expo:

npx expo install react-native-reanimated

Here is a complete host example. The rule list is a deliberately simple product policy; replace it with the policy your server enforces.

import type { ComponentRef } from 'react';
import { useMemo, useRef, useState } from 'react';
import {
  Platform,
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
  type TextInputProps,
} from 'react-native';

type Rule = { id: string; label: string; test: (value: string) => boolean };

const rules: readonly Rule[] = [
  { id: 'length', label: 'At least 12 characters', test: (value) => value.length >= 12 },
  { id: 'upper', label: 'One uppercase letter', test: (value) => /[A-Z]/.test(value) },
  { id: 'number', label: 'One number', test: (value) => /\d/.test(value) },
  { id: 'symbol', label: 'One symbol', test: (value) => /[^A-Za-z0-9]/.test(value) },
];

type PasswordFieldProps = TextInputProps & {
  value: string;
  onChangeText: (value: string) => void;
  hostError?: string;
};

export function PasswordField({ value, onChangeText, hostError, ...props }: PasswordFieldProps) {
  const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
  const [visible, setVisible] = useState(false);
  const [focused, setFocused] = useState(false);
  const results = useMemo(
    () => rules.map((rule) => ({ ...rule, met: rule.test(value) })),
    [value],
  );
  const score = results.filter((rule) => rule.met).length;
  const label = visible ? 'Hide password' : 'Show password';

  return (
    <View style={styles.field}>
      <View style={[styles.row, focused && styles.focused]}>
        <TextInput
          {...props}
          ref={inputRef}
          accessibilityLabel={props.accessibilityLabel ?? 'Password'}
          autoCapitalize="none"
          autoComplete="new-password"
          autoCorrect={false}
          onBlur={() => setFocused(false)}
          onChangeText={onChangeText}
          onFocus={() => setFocused(true)}
          onSubmitEditing={props.onSubmitEditing}
          returnKeyType="done"
          secureTextEntry={!visible}
          textContentType={Platform.OS === 'ios' ? 'newPassword' : undefined}
          value={value}
        />
        <Pressable
          accessibilityLabel={label}
          accessibilityRole="button"
          accessibilityState={{ selected: visible }}
          hitSlop={10}
          onPress={() => setVisible((current) => !current)}
          style={styles.visibilityButton}
        >
          <Text>{visible ? 'Hide' : 'Show'}</Text>
        </Pressable>
      </View>
      <Text accessibilityLiveRegion="polite" style={styles.status}>
        {score === 0 ? 'Enter a password' : score + ' of ' + rules.length + ' rules met'}
      </Text>
      {results.map((rule) => (
        <Text key={rule.id} accessibilityLiveRegion="polite" style={rule.met ? styles.met : styles.unmet}>
          {rule.met ? '✓' : '○'} {rule.label}
        </Text>
      ))}
      {hostError ? (
        <Text accessibilityLiveRegion="assertive" style={styles.error}>
          {hostError}
        </Text>
      ) : null}
    </View>
  );
}

const styles = StyleSheet.create({
  field: { gap: 8, width: '100%' },
  row: { alignItems: 'center', borderColor: '#B9B5C7', borderRadius: 12, borderWidth: 1, flexDirection: 'row' },
  focused: { borderColor: '#6757E8' },
  visibilityButton: { alignItems: 'center', minHeight: 44, minWidth: 64, justifyContent: 'center' },
  status: { color: '#4F4C56', fontSize: 13 },
  met: { color: '#278A5C' },
  unmet: { color: '#6F6B78' },
  error: { color: '#B42318' },
});

The parent keeps the value and the host error. In a form component, pass value={password}, hostError={serverError}, and an onChangeText handler that updates the password and clears a stale server error after a new edit; do not silently weaken the policy.

Give password managers the right hints

For account creation, use autoComplete="new-password" on all platforms and add textContentType="newPassword" only on iOS when you need the native semantic hint. For sign-in, use autoComplete="current-password" and the iOS textContentType="password" equivalent. React Native 0.88 documents these as overlapping hints and says to avoid setting both at once; when both are present, textContentType takes precedence. They are password-manager hints, not validation rules, and platform behavior can differ.

Define rules and report strength honestly

Use local rules for immediate feedback

Run cheap deterministic checks while editing and show which requirements are met. Keep the rules readable and align them with the server policy. If the policy changes, update both together and treat the server as authoritative. A score such as “3 of 4 rules met” is clearer than a color alone.

Keep host validation and server errors separate

Local rules can catch an empty value, a short value, or a missing character class. The host still decides whether confirmation matches, whether a password was previously used, whether a breach check failed, and whether the account request succeeded. Render those host errors separately and leave them visible until the host clears them. Never claim that a green meter means the password is secure.

Submit the value over your authenticated HTTPS request and avoid logging it. If the server rejects it, preserve the draft so the person can correct one rule instead of starting over.

Handle keyboard, focus, and submission

Passwords use the normal text keyboard: keyboardType="default", autoCapitalize="none", and autoCorrect={false} are sensible defaults. returnKeyType="done" can submit a single-password form; onSubmitEditing should call the host validator and request, not infer success from the animation.

Use the input ref to focus after navigation or to move from a confirmation field. Keep the field above the keyboard with the screen's layout strategy, and test deletion, selection, paste, and hardware keyboards on both platforms. Revealing the text should preserve focus and the selection; do not remount the input when the eye button is pressed.

Make visibility and strength feedback accessible

Give the input an honest accessibilityLabel and a useful accessibilityHint. The visibility control is a real button with a 44-point target, a changing label (“Show password” / “Hide password”), and selected state that describes whether the value is currently visible. A decorative eye icon alone is not an accessible control.

Expose strength changes through a polite live region and keep host failures in a separate error announcement. Do not announce every animation frame or every inserted character. Respect reduced-motion preferences in any animated meter; immediate rule updates are still available when motion is reduced. Verify VoiceOver and TalkBack on physical devices because web output cannot prove native announcement timing.

When to use Password Strength Meter

Password Strength Meter is the catalog implementation when you want a controlled or uncontrolled password field, four animated rule segments, a requirement checklist, and a built-in visibility button. Its public props are value/defaultValue, onChange, requirements, placeholder, levelColors, inputHeight, backgroundColor, textColor, fontFamily, colorScheme, duration, accessibilityLabel, showAccessibilityLabel, hideAccessibilityLabel, inputProps, style, and testID. It also exports scorePassword and a ref with focus, clear, show, and hide. The current component is configured for account creation: it supplies new-password/newPassword internally, and its inputProps cannot replace those or its controlled value, change handler, or visibility setting. For a sign-in field, use the same host pattern with a native TextInput, or adapt the copied component's source to the sign-in hints.

Use inputProps for native hints such as textContentType, autoComplete, onSubmitEditing, and accessibilityHint; the component reserves its controlled value, change handler, and secureTextEntry. It has no server validator or host error prop, so wrap it with your own form state. Each published piece is reviewed on physical iPhone and Android devices with Expo Go before release.

For generic labels, focus, and form error timing, see React Native Text Input. If the same form also collects a phone number, use React Native Phone Number Input for country selection and E.164 boundaries instead of repeating them here.

Common pitfalls

  • Treating the meter as a security test. A local score cannot check entropy, reuse, breaches, or server policy.
  • Using the wrong password-manager hint. Choose new-password for account creation and current-password/password for sign-in.
  • Remounting on reveal. Toggle secureTextEntry on the mounted input so focus and selection survive.
  • Hiding errors inside the meter. Keep host and local feedback separate so server rejection remains visible.
  • Logging the draft. Redact passwords from analytics, crash reports, URL parameters, and request logs.
  • Relying on color alone. Pair color with text and announced rule state.
  • Over-announcing. Use polite, meaningful status changes instead of speaking every animation frame.

For platform behavior, see the React Native 0.88 TextInput reference, the React Native 0.88 accessibility guide, and the Expo SDK 58 Reanimated reference.

FAQ

How do I show and hide a password in React Native?

Keep one controlled TextInput mounted and toggle its secureTextEntry value from a labeled button. Do not replace the input or alter its text when visibility changes.

Which textContentType and autoComplete values should a password field use?

Use autoComplete="new-password" for creation and autoComplete="current-password" for sign-in; add the matching iOS textContentType value only on iOS. These are platform hints for autofill, not enforcement or validation.

Does a password strength meter make a password secure?

No. It reports the local rules you chose. The host and server must enforce policy, check reuse or breaches when required, and accept or reject the password independently.

How should I validate password rules and show server errors?

Evaluate inexpensive local rules while editing, then submit to the host's validator and server. Render server errors separately, preserve the draft, and clear the error only after a new edit or a successful retry.

Which keyboard and accessibility props should a password input use?

Use keyboardType="default", autoCapitalize="none", autoCorrect={false}, one platform-appropriate password-manager hint (with the iOS semantic equivalent when needed), plus an honest label, hint, and a real show/hide button with a 44-point target. Test announcements and autofill on physical devices.