You need a popup that accepts typed input, such as a name, access code, or password. React Native’s standard Alert.alert() has buttons for users to tap.
React Native’s Alert.prompt() adds a text field on iOS. React Native does not implement Alert.prompt() for Android, so Android releases need a separate input path.
This guide helps you choose the smallest reliable solution for your platforms, input rules, and submission flow.
React Native prompt: the short answer
React Native’s Alert.prompt() is its built-in iOS method for a native alert with a text field.
The right approach depends on whether the screen must behave the same on iOS and Android. Start with platform coverage, then decide how much control the prompt needs.
- Native iOS prompt: Use
Alert.prompt()when the flow only needs Apple’s native text-input alert. - Prompt package: Use a package when both platforms need a similar dialog and an added dependency fits the project.
- Custom modal: Use a React Native
Modalwhen layout, validation, or submission behavior must be controlled inside the app.
All three paths can collect text, but they differ in platform coverage, validation, layout, and dismissal behavior.
Does Alert.prompt() work on Android?
No. React Native’s Alert.prompt() API is available on iOS only, so Android needs a different text-input interface.
On Android, Alert.alert() has a title, message, and tappable buttons. The native Android alert does not include the editable field that appears in the iOS prompt.
Keep Alert.prompt() when the feature is intentionally iOS-only. In shared code, guard the call and route Android users to a package dialog or custom modal.
import React, { useState } from 'react';
import { Alert, Button, Platform, View } from 'react-native';
import { PromptDialog } from './PromptDialog';
export function NamePrompt() {
const [promptVisible, setPromptVisible] = useState(false);
const handleName = (name: string) => {
console.log(name);
};
return (
<View>
<Button
title="Edit name"
onPress={() => {
if (Platform.OS === 'ios') {
Alert.prompt('Name', 'Enter your display name', handleName);
} else {
setPromptVisible(true);
}
}}
/>
<PromptDialog
visible={promptVisible}
title="Name"
message="Enter your display name"
placeholder="e.g. Taylor"
onCancel={() => setPromptVisible(false)}
onSubmit={(name) => {
handleName(name);
setPromptVisible(false);
}}
/>
</View>
);
}
Choose your implementation: native, package, or custom
Choose Alert.prompt() for a simple iOS-only field. Use a package for a shared dialog API, or a custom Modal when Android support, validation, async submission, or UI control matters.
Use these four criteria to confirm the fit before you commit:
- Platform support: Use
Alert.prompt()for iOS. If Android users also need to enter text, use a prompt package or customModal. - Visual control: Use the native prompt for standard iOS styling. Choose a package or custom
Modalwhen the prompt must share one layout across platforms. - Maintenance: A package needs dependency, React Native, and native-build compatibility checks. A custom
Modalleaves focus, keyboard, dismissal, and accessibility behavior to your app. - Submit behavior: The native prompt dismisses after a button tap, which makes inline validation and failed-request retries awkward. A custom
Modalcan stay open until submission succeeds.
Basic Alert.prompt() example on iOS
On iOS, Alert.prompt() shows a native alert with a text field. Pass a title, message, button array, and the plain-text type for a standard field.
import { Alert } from 'react-native';
export function showUsernamePrompt(onSubmit) {
Alert.prompt(
'Username',
'Enter your new username below',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Submit',
onPress: onSubmit,
},
],
'plain-text'
);
}
The call uses four arguments:
- Title:
'Username'names the prompt. - Message:
'Enter your new username below'tells the user what to enter. - Buttons: The array creates Cancel and Submit actions. The Submit handler receives the entered text as
onSubmit’s argument. - Type:
'plain-text'sets a standard text field.
Customizing the prompt: buttons, secure input, and defaults
On iOS, Alert.prompt() lets you change buttons and text-field settings. Later arguments control the input type, initial value, and keyboard.
Buttons and callback behavior
Pass the buttons array as the third argument. Each button has a label and can run an onPress function; a confirm handler receives the typed text.
function showUsernamePrompt(saveUsername: (value: string) => void) {
const buttons = [
{ text: 'Cancel', style: 'cancel' as const },
{
text: 'Submit',
onPress: (value?: string) => saveUsername(value ?? ''),
},
];
Alert.prompt('Username', 'Enter a username', buttons);
}
Each button can include:
textfor its labelonPressfor its handlerstyleon iOS:default,cancel, ordestructive
Secure text entry for passwords
Use secure-text as the fourth argument to mask password characters.
Alert.prompt('Password', 'Enter your password', handlePassword, 'secure-text');
Default values and keyboard type
Use the fifth argument to prefill the field and the sixth to choose the keyboard type.
Alert.prompt(
'Email',
'Confirm your email address',
handleEmail,
'plain-text',
'[email protected]',
'email-address'
);
For an iOS alert with username and password fields, React Native also provides the login-password input type. A full sign-in flow is usually clearer as a screen, sheet, or custom modal.
Dependency-free option: a custom Modal prompt
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
KeyboardAvoidingView,
Modal,
Platform,
Pressable,
StyleSheet,
Text,
TextInput,
type TextInputProps,
View,
} from 'react-native';
type PromptDialogProps = {
visible: boolean;
title: string;
message?: string;
placeholder?: string;
defaultValue?: string;
cancelText?: string;
submitText?: string;
keyboardType?: TextInputProps['keyboardType'];
secureTextEntry?: boolean;
validate?: (value: string) => string | null;
onCancel: () => void;
onSubmit: (value: string) => void | Promise<void>;
};
export function PromptDialog({
visible,
title,
message,
placeholder,
defaultValue = '',
cancelText = 'Cancel',
submitText = 'Save',
keyboardType = 'default',
secureTextEntry = false,
validate,
onCancel,
onSubmit,
}: PromptDialogProps) {
const [value, setValue] = useState(defaultValue);
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const submitLock = useRef(false);
useEffect(() => {
if (!visible) return;
setValue(defaultValue);
setError(null);
setSubmitting(false);
submitLock.current = false;
}, [defaultValue, visible]);
const handleSubmit = useCallback(async () => {
if (submitLock.current) return;
const validationError = validate?.(value) ?? null;
if (validationError) {
setError(validationError);
return;
}
submitLock.current = true;
setSubmitting(true);
setError(null);
try {
await onSubmit(value);
} catch (caughtError) {
const message =
caughtError instanceof Error
? caughtError.message
: 'Could not submit. Try again.';
setError(message);
} finally {
submitLock.current = false;
setSubmitting(false);
}
}, [onSubmit, validate, value]);
const handleCancel = () => {
if (!submitting) onCancel();
};
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={handleCancel}
>
<KeyboardAvoidingView
style={styles.overlay}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<View accessibilityViewIsModal style={styles.card}>
<Text style={styles.title}>{title}</Text>
{message ? <Text style={styles.message}>{message}</Text> : null}
<TextInput
accessibilityLabel={title}
autoFocus
editable={!submitting}
keyboardType={keyboardType}
placeholder={placeholder}
onChangeText={(nextValue) => {
setValue(nextValue);
if (error) setError(null);
}}
onSubmitEditing={handleSubmit}
returnKeyType="done"
secureTextEntry={secureTextEntry}
style={[styles.input, error ? styles.inputError : null]}
value={value}
/>
{error ? (
<Text accessibilityLiveRegion="polite" style={styles.error}>
{error}
</Text>
) : null}
<View style={styles.actions}>
<Pressable
accessibilityRole="button"
accessibilityState={{ disabled: submitting }}
disabled={submitting}
onPress={handleCancel}
style={styles.button}
>
<Text style={styles.cancelLabel}>{cancelText}</Text>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityState={{ busy: submitting, disabled: submitting }}
disabled={submitting}
onPress={handleSubmit}
style={[styles.button, styles.submitButton]}
>
{submitting ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.submitLabel}>{submitText}</Text>
)}
</Pressable>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
);
}
type ProfileNameEditorProps = {
saveDisplayName: (name: string) => Promise<void>;
};
export function ProfileNameEditor({
saveDisplayName,
}: ProfileNameEditorProps) {
const [promptVisible, setPromptVisible] = useState(false);
return (
<>
<Pressable onPress={() => setPromptVisible(true)}>
<Text>Edit display name</Text>
</Pressable>
<PromptDialog
visible={promptVisible}
title="Display name"
message="Enter the name shown on your profile."
placeholder="e.g. Taylor"
cancelText="Not now"
submitText="Save"
validate={(name) =>
name.trim() ? null : 'Display name is required.'
}
onCancel={() => setPromptVisible(false)}
onSubmit={async (name) => {
await saveDisplayName(name.trim());
setPromptVisible(false);
}}
/>
</>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
backgroundColor: 'rgba(0, 0, 0, 0.45)',
},
card: {
width: '100%',
maxWidth: 420,
padding: 20,
borderRadius: 16,
backgroundColor: '#fff',
},
title: { fontSize: 20, fontWeight: '700', color: '#111827' },
message: { marginTop: 8, fontSize: 15, color: '#4b5563' },
input: {
marginTop: 16,
paddingHorizontal: 12,
paddingVertical: 10,
borderWidth: 1,
borderColor: '#9ca3af',
borderRadius: 8,
fontSize: 16,
color: '#111827',
},
inputError: { borderColor: '#dc2626' },
error: { marginTop: 8, color: '#dc2626' },
actions: {
flexDirection: 'row',
justifyContent: 'flex-end',
gap: 8,
marginTop: 20,
},
button: {
minWidth: 88,
minHeight: 44,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 16,
borderRadius: 8,
},
submitButton: { backgroundColor: '#2563eb' },
cancelLabel: { color: '#374151', fontWeight: '600' },
submitLabel: { color: '#fff', fontWeight: '700' },
});
Building a reusable PromptDisalog component
PromptDialog is a controlled Modal. Its props control visibility and input behavior, while local TextInput state resets when the dialog opens.
accessibilityRole="button"
The component handles platform behavior in four parts:
Modaluses a transparent overlay and centered card on both platforms.KeyboardAvoidingViewadds iOS padding when the keyboard opens. Test Android with your app’s window-soft-input configuration, especially on smaller screens.autoFocusopens the keyboard, andonSubmitEditingroutes the return key through the same submit handler as the button.placeholder,keyboardType, andsecureTextEntrypass directly toTextInputfor email, numeric, or password prompts.
Before shipping, test screen-reader labels, return focus to the trigger after closing, and decide whether backdrop taps should dismiss the dialog. If your React Native version does not support gap, use margins between buttons.
Adding validation and async submit handling
Validate before closing the Modal. Show an inline error for invalid values and disable buttons while onSubmit is pending.
The validator receives the controlled TextInput value. The caller decides whether to normalize it; the example uses trim() for validation and before saving.
Editing clears an old error. The next button press or return-key event sends the updated value to handleSubmit for validation.
The ref lock prevents two rapid events from starting duplicate requests before React updates submitting.
The parent controls visibility. ProfileNameEditor closes the dialog after saveDisplayName resolves; if it rejects, the controls return and the error appears in the existing error slot.
Which prompt approach fits your app?
Alert.prompt() fits simple iOS input. Use a package when a shared imperative prompt is enough. Choose a custom Modal for consistent cross-platform UI, validation, or async submission.
| Approach | Platform fit | Choose it when | Main tradeoff |
|---|---|---|---|
Alert.prompt() | iOS only | A native system dialog and callback are enough | It cannot remain open to show inline validation or a failed request |
| Prompt package | iOS and Android | You want one prompt API and accept a dependency | Android behavior needs separate testing |
Custom Modal | iOS and Android | You need custom layout, extra fields, inline errors, or async submit | You own the component code and accessibility checks |
For one simple iOS value, stay native. If the flow must survive validation or request failures on both platforms, a custom Modal is the most predictable option.
Common prompt errors and how to fix them
Common prompt bugs involve Android support, button callbacks, and Modal input state.
- No input appears on Android:
Alert.prompt()is unsupported there. Guard the iOS call withPlatform.OS === 'ios', then open a verified package dialog or aModalwithTextInput. - Buttons dismiss the prompt but run the wrong action: Give Cancel and Submit separate
onPresshandlers. On iOS, read the typed string from the Submit callback; the cancel handler should dismiss without processing a value. - Secure entry or the wrong keyboard appears: The
secure-textinput type and prompt-level keyboard setting belong to iOSAlert.prompt(). In a custom dialog, setsecureTextEntryandkeyboardTypeonTextInput, and passdefaultValueas a string. - A custom prompt loses text or submits twice: Keep the field controlled, and disable Submit while the request is pending. Use a screen, sheet, or full form for multiple fields and detailed validation.
Build and publish the prompt flow with Bilt
If you want the input flow without maintaining dialog code and deployment yourself, describe it in Bilt. Bilt generates a real React Native app for iOS and Android.

Include the copy, field types, validation, and each button action. A single iOS value may fit Alert.prompt(), while a cross-platform sign-in flow needs multiple fields and validation that Bilt can build into the app.
Sample prompt
Build a form with email and secure-password fields. Label the buttons “Sign in” and “Cancel,” and show a validation message when either field is empty.
Then work through the generated flow:
- Preview it: Open the live iOS or Android preview and test the fields, keyboard, validation, and button actions.
- Check a real device: Use the QR code to test typing and screen behavior on your phone.
- Refine in chat: Ask the Bilt agent to change the copy, field rules, or button actions.
- Keep or export the code: Continue refining the project in Bilt, or export the React Native code when you need to work with a developer.
- Test and publish: Test the iOS build through TestFlight before release, then use Bilt’s publishing workflow to submit to the Apple App Store and Google Play Console.

- Once the flow works, keep the same Bilt project through store submission. Check keyboard behavior, validation, loading states, icons, store details, and release builds for both platforms before you submit.
Bilt keeps preview, device testing, refinement, and publishing in one project. You can test the generated flow on a device, change it in chat, then prepare builds for each store.
Bilt turns your description into a real native app you can preview, refine, and publish on iOS and Android.
Bilt’s free backend can handle authentication and login flows when your app needs them, with no code required.
Start building free. No credit card required.
FAQs
How do you show a prompt-style input on Android?
React Native core does not support Alert.prompt() on Android. Open a custom Modal for validation or custom UI, or use a verified dialog package when you need an imperative prompt API.
Keep the platform branch in one shared helper. It can call Alert.prompt() on iOS and open the Android implementation through the same app-level function.
Can you customize how a React Native alert looks?
React Native core alerts follow operating-system styling, so you cannot restyle the dialog itself. You can still configure its title, message, buttons, and supported input options.
A custom dialog can match the app's colors, spacing, typography, and button layout. The tradeoff is ownership: your component must also handle focus, keyboard avoidance, accessibility labels, and dismissal behavior.
Does Alert.prompt() work in Expo apps?
Yes, on iOS. Expo uses React Native’s core Alert.prompt() API, including in Expo Go; Android still needs a custom Modal or another Android input interface.
A custom Modal works in Expo Go because it uses React Native components. Use that approach when the same input flow must work on iOS and Android.
