or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation.mdcore-components.mdindex.mdnative-bridge.mdplatform-apis.mdreact-hooks.mdstyling.mduser-interaction.md

user-interaction.mddocs/

0

# React Native User Interaction APIs

1

2

React Native provides comprehensive APIs for handling user interactions, displaying system dialogs, accessing device feedback capabilities, and platform-specific UI components.

3

4

## Installation

5

6

```bash

7

npm install react-native

8

```

9

10

## System Dialogs

11

12

### Alert

13

14

Display native alert dialogs with customizable buttons and actions.

15

16

```javascript { .api }

17

// ESM

18

import {Alert} from 'react-native';

19

20

// CommonJS

21

const {Alert} = require('react-native');

22

23

// Basic alert

24

Alert.alert('Alert Title', 'Alert message');

25

26

// Alert with single button

27

Alert.alert(

28

'Confirmation',

29

'Are you sure you want to continue?',

30

[{text: 'OK', onPress: () => console.log('OK pressed')}]

31

);

32

33

// Alert with multiple buttons

34

Alert.alert(

35

'Save Changes',

36

'Do you want to save your changes before leaving?',

37

[

38

{text: 'Cancel', style: 'cancel'},

39

{text: 'Don\'t Save', style: 'destructive', onPress: () => discardChanges()},

40

{text: 'Save', onPress: () => saveChanges()},

41

]

42

);

43

44

// Alert with custom button styles

45

Alert.alert(

46

'Delete Item',

47

'This action cannot be undone.',

48

[

49

{text: 'Cancel', style: 'cancel'},

50

{

51

text: 'Delete',

52

style: 'destructive',

53

onPress: () => deleteItem(),

54

},

55

],

56

{

57

cancelable: true, // Android: allow dismissing by tapping outside

58

onDismiss: () => console.log('Alert dismissed'),

59

}

60

);

61

62

// Prompt for text input (iOS only)

63

Alert.prompt(

64

'Enter Name',

65

'Please enter your name:',

66

[

67

{text: 'Cancel', style: 'cancel'},

68

{

69

text: 'OK',

70

onPress: (text) => {

71

console.log('Entered text:', text);

72

setUserName(text);

73

},

74

},

75

],

76

'plain-text', // Input type

77

'Default text' // Default value

78

);

79

80

// Secure text prompt (iOS only)

81

Alert.prompt(

82

'Enter Password',

83

'Please enter your password:',

84

[

85

{text: 'Cancel', style: 'cancel'},

86

{text: 'Login', onPress: (password) => login(password)},

87

],

88

'secure-text'

89

);

90

91

// Login prompt with username and password (iOS only)

92

Alert.prompt(

93

'Login',

94

'Please enter your credentials:',

95

[

96

{text: 'Cancel', style: 'cancel'},

97

{

98

text: 'Login',

99

onPress: (username, password) => {

100

login(username, password);

101

},

102

},

103

],

104

'login-password'

105

);

106

107

// Async alert with promise

108

const showAsyncAlert = () => {

109

return new Promise((resolve) => {

110

Alert.alert(

111

'Confirmation',

112

'Do you want to proceed?',

113

[

114

{text: 'No', onPress: () => resolve(false)},

115

{text: 'Yes', onPress: () => resolve(true)},

116

]

117

);

118

});

119

};

120

121

// Usage

122

const handleAction = async () => {

123

const confirmed = await showAsyncAlert();

124

if (confirmed) {

125

performAction();

126

}

127

};

128

129

// Custom alert hook

130

function useAlert() {

131

const showAlert = useCallback((title, message, buttons = []) => {

132

Alert.alert(title, message, buttons);

133

}, []);

134

135

const showConfirmation = useCallback((title, message, onConfirm, onCancel) => {

136

Alert.alert(title, message, [

137

{text: 'Cancel', style: 'cancel', onPress: onCancel},

138

{text: 'OK', onPress: onConfirm},

139

]);

140

}, []);

141

142

const showError = useCallback((message) => {

143

Alert.alert('Error', message, [{text: 'OK'}]);

144

}, []);

145

146

const showSuccess = useCallback((message) => {

147

Alert.alert('Success', message, [{text: 'OK'}]);

148

}, []);

149

150

return {

151

showAlert,

152

showConfirmation,

153

showError,

154

showSuccess,

155

};

156

}

157

```

158

159

```typescript { .api }

160

interface AlertStatic {

161

// Basic alert

162

alert(

163

title: string,

164

message?: string,

165

buttons?: AlertButton[],

166

options?: AlertOptions

167

): void;

168

169

// Text input prompt (iOS only)

170

prompt(

171

title: string,

172

message?: string,

173

callbackOrButtons?: ((text: string) => void) | AlertButton[],

174

type?: AlertType,

175

defaultValue?: string,

176

keyboardType?: string

177

): void;

178

}

179

180

interface AlertButton {

181

text?: string;

182

onPress?: (value?: string) => void;

183

style?: 'default' | 'cancel' | 'destructive';

184

}

185

186

interface AlertOptions {

187

cancelable?: boolean; // Android only

188

onDismiss?: () => void; // Android only

189

userInterfaceStyle?: 'unspecified' | 'light' | 'dark'; // iOS only

190

}

191

192

type AlertType = 'default' | 'plain-text' | 'secure-text' | 'login-password';

193

```

194

195

## Device Feedback APIs

196

197

### Vibration

198

199

Trigger device vibration patterns for haptic feedback.

200

201

```javascript { .api }

202

import {Vibration} from 'react-native';

203

204

// Basic vibration (400ms on iOS, default pattern on Android)

205

Vibration.vibrate();

206

207

// Custom duration (iOS only, Android uses default)

208

Vibration.vibrate(1000); // 1 second

209

210

// Vibration pattern [delay, vibrate, delay, vibrate, ...]

211

const pattern = [0, 1000, 1000, 1000]; // Vibrate 1s, pause 1s, vibrate 1s

212

Vibration.vibrate(pattern);

213

214

// Repeat vibration pattern

215

Vibration.vibrate(pattern, true); // Repeat infinitely

216

217

// Stop vibration

218

Vibration.cancel();

219

220

// Predefined patterns

221

const vibrationPatterns = {

222

short: [0, 200],

223

medium: [0, 500],

224

long: [0, 1000],

225

double: [0, 200, 100, 200],

226

triple: [0, 200, 100, 200, 100, 200],

227

heartbeat: [0, 50, 50, 50, 50, 400, 50, 400],

228

sos: [0, 200, 100, 200, 100, 200, 200, 400, 100, 400, 100, 400, 200, 200, 100, 200, 100, 200],

229

};

230

231

// Vibration feedback component

232

function VibrationFeedback({pattern = 'short', children, onPress}) {

233

const handlePress = () => {

234

Vibration.vibrate(vibrationPatterns[pattern]);

235

onPress?.();

236

};

237

238

return (

239

<TouchableOpacity onPress={handlePress}>

240

{children}

241

</TouchableOpacity>

242

);

243

}

244

245

// Haptic feedback for different interactions

246

const hapticFeedback = {

247

success: () => Vibration.vibrate([0, 50, 50, 50]),

248

error: () => Vibration.vibrate([0, 100, 100, 100, 100, 100]),

249

warning: () => Vibration.vibrate([0, 200, 100, 200]),

250

selection: () => Vibration.vibrate([0, 10]),

251

impact: () => Vibration.vibrate([0, 30]),

252

notification: () => Vibration.vibrate([0, 50, 50, 100]),

253

};

254

255

// Form validation with haptic feedback

256

function FormWithHaptics() {

257

const [email, setEmail] = useState('');

258

const [error, setError] = useState('');

259

260

const validateEmail = (value) => {

261

if (!value.includes('@')) {

262

setError('Invalid email');

263

hapticFeedback.error();

264

} else {

265

setError('');

266

hapticFeedback.success();

267

}

268

};

269

270

return (

271

<View>

272

<TextInput

273

value={email}

274

onChangeText={setEmail}

275

onBlur={() => validateEmail(email)}

276

placeholder="Email"

277

/>

278

{error && <Text style={styles.error}>{error}</Text>}

279

</View>

280

);

281

}

282

283

// Game interactions with vibration

284

function GameButton({onPress, disabled}) {

285

const handlePress = () => {

286

if (disabled) {

287

hapticFeedback.error();

288

return;

289

}

290

291

hapticFeedback.impact();

292

onPress();

293

};

294

295

return (

296

<TouchableOpacity onPress={handlePress} disabled={disabled}>

297

<Text>Game Action</Text>

298

</TouchableOpacity>

299

);

300

}

301

302

// Settings for vibration preferences

303

function VibrationSettings() {

304

const [vibrationEnabled, setVibrationEnabled] = useState(true);

305

306

const toggleVibration = (enabled) => {

307

setVibrationEnabled(enabled);

308

309

if (enabled) {

310

hapticFeedback.success();

311

}

312

};

313

314

return (

315

<View style={styles.settingRow}>

316

<Text>Enable Vibration</Text>

317

<Switch value={vibrationEnabled} onValueChange={toggleVibration} />

318

</View>

319

);

320

}

321

```

322

323

```typescript { .api }

324

interface VibrationStatic {

325

// Trigger vibration

326

vibrate(pattern?: number | number[], repeat?: boolean): void;

327

328

// Stop vibration

329

cancel(): void;

330

}

331

```

332

333

## Platform-Specific UI Components

334

335

### ActionSheetIOS

336

337

Display native iOS action sheets with customizable options.

338

339

```javascript { .api }

340

import {ActionSheetIOS} from 'react-native';

341

342

// Basic action sheet (iOS only)

343

const showActionSheet = () => {

344

ActionSheetIOS.showActionSheetWithOptions(

345

{

346

title: 'Choose an option',

347

message: 'Select one of the following options:',

348

options: ['Cancel', 'Option 1', 'Option 2', 'Option 3'],

349

cancelButtonIndex: 0,

350

},

351

(buttonIndex) => {

352

if (buttonIndex === 1) {

353

console.log('Option 1 selected');

354

} else if (buttonIndex === 2) {

355

console.log('Option 2 selected');

356

} else if (buttonIndex === 3) {

357

console.log('Option 3 selected');

358

}

359

}

360

);

361

};

362

363

// Action sheet with destructive option

364

const showDestructiveActionSheet = () => {

365

ActionSheetIOS.showActionSheetWithOptions(

366

{

367

title: 'Delete Item',

368

message: 'This action cannot be undone',

369

options: ['Cancel', 'Delete Item'],

370

destructiveButtonIndex: 1,

371

cancelButtonIndex: 0,

372

},

373

(buttonIndex) => {

374

if (buttonIndex === 1) {

375

deleteItem();

376

}

377

}

378

);

379

};

380

381

// Share action sheet

382

const showShareSheet = () => {

383

ActionSheetIOS.showShareActionSheetWithOptions(

384

{

385

url: 'https://example.com',

386

message: 'Check out this amazing app!',

387

subject: 'App Recommendation', // Email subject

388

},

389

(error) => {

390

console.error('Share failed:', error);

391

},

392

(success, method) => {

393

if (success) {

394

console.log(`Shared via ${method}`);

395

}

396

}

397

);

398

};

399

400

// Photo selection action sheet

401

const showPhotoActionSheet = () => {

402

ActionSheetIOS.showActionSheetWithOptions(

403

{

404

title: 'Select Photo',

405

options: ['Cancel', 'Camera', 'Photo Library'],

406

cancelButtonIndex: 0,

407

},

408

(buttonIndex) => {

409

switch (buttonIndex) {

410

case 1:

411

openCamera();

412

break;

413

case 2:

414

openPhotoLibrary();

415

break;

416

}

417

}

418

);

419

};

420

421

// Custom hook for action sheets

422

function useActionSheet() {

423

const showOptions = useCallback((options, onSelect) => {

424

if (Platform.OS !== 'ios') {

425

// Fallback for non-iOS platforms

426

Alert.alert(

427

options.title,

428

options.message,

429

options.options.map((option, index) => ({

430

text: option,

431

onPress: () => onSelect(index),

432

style: index === options.cancelButtonIndex ? 'cancel' :

433

index === options.destructiveButtonIndex ? 'destructive' : 'default',

434

}))

435

);

436

return;

437

}

438

439

ActionSheetIOS.showActionSheetWithOptions(options, onSelect);

440

}, []);

441

442

return {showOptions};

443

}

444

445

// Settings menu with action sheet

446

function SettingsMenu() {

447

const {showOptions} = useActionSheet();

448

449

const showSettingsActions = () => {

450

showOptions(

451

{

452

title: 'Settings',

453

options: ['Cancel', 'Edit Profile', 'Privacy Settings', 'Sign Out'],

454

cancelButtonIndex: 0,

455

destructiveButtonIndex: 3,

456

},

457

(buttonIndex) => {

458

switch (buttonIndex) {

459

case 1:

460

navigation.navigate('EditProfile');

461

break;

462

case 2:

463

navigation.navigate('PrivacySettings');

464

break;

465

case 3:

466

signOut();

467

break;

468

}

469

}

470

);

471

};

472

473

return (

474

<TouchableOpacity onPress={showSettingsActions}>

475

<Text>Settings</Text>

476

</TouchableOpacity>

477

);

478

}

479

```

480

481

```typescript { .api }

482

interface ActionSheetIOSStatic {

483

// Show action sheet

484

showActionSheetWithOptions(

485

options: ActionSheetOptions,

486

callback: (buttonIndex: number) => void

487

): void;

488

489

// Show share sheet (iOS only)

490

showShareActionSheetWithOptions(

491

options: ShareActionSheetOptions,

492

failureCallback: (error: Error) => void,

493

successCallback: (success: boolean, method: string) => void

494

): void;

495

}

496

497

interface ActionSheetOptions {

498

title?: string;

499

message?: string;

500

options: string[];

501

cancelButtonIndex?: number;

502

destructiveButtonIndex?: number | number[];

503

anchor?: number; // iPad popover anchor

504

tintColor?: string;

505

userInterfaceStyle?: 'light' | 'dark';

506

}

507

508

interface ShareActionSheetOptions {

509

message?: string;

510

url?: string;

511

subject?: string;

512

anchor?: number; // iPad popover anchor

513

tintColor?: string;

514

excludedActivityTypes?: string[];

515

}

516

```

517

518

### ToastAndroid

519

520

Display native Android toast messages for brief notifications.

521

522

```javascript { .api }

523

import {ToastAndroid} from 'react-native';

524

525

// Basic toast (Android only)

526

const showToast = (message) => {

527

if (Platform.OS === 'android') {

528

ToastAndroid.show(message, ToastAndroid.SHORT);

529

}

530

};

531

532

// Different toast durations

533

const showShortToast = (message) => {

534

ToastAndroid.show(message, ToastAndroid.SHORT); // ~2 seconds

535

};

536

537

const showLongToast = (message) => {

538

ToastAndroid.show(message, ToastAndroid.LONG); // ~3.5 seconds

539

};

540

541

// Toast with gravity positioning

542

const showPositionedToast = (message) => {

543

ToastAndroid.showWithGravity(

544

message,

545

ToastAndroid.SHORT,

546

ToastAndroid.CENTER

547

);

548

};

549

550

// Toast with custom positioning

551

const showCustomPositionToast = (message) => {

552

ToastAndroid.showWithGravityAndOffset(

553

message,

554

ToastAndroid.SHORT,

555

ToastAndroid.BOTTOM,

556

25, // x offset

557

50 // y offset

558

);

559

};

560

561

// Cross-platform toast utility

562

const toast = {

563

show: (message, duration = 'short') => {

564

if (Platform.OS === 'android') {

565

const androidDuration = duration === 'long'

566

? ToastAndroid.LONG

567

: ToastAndroid.SHORT;

568

ToastAndroid.show(message, androidDuration);

569

} else {

570

// iOS fallback using Alert

571

Alert.alert('', message);

572

}

573

},

574

575

success: (message) => {

576

toast.show(`✅ ${message}`);

577

},

578

579

error: (message) => {

580

toast.show(`❌ ${message}`);

581

},

582

583

warning: (message) => {

584

toast.show(`⚠️ ${message}`);

585

},

586

587

info: (message) => {

588

toast.show(`ℹ️ ${message}`);

589

},

590

};

591

592

// Form submission with toast feedback

593

function FormWithToast() {

594

const [loading, setLoading] = useState(false);

595

596

const handleSubmit = async (formData) => {

597

setLoading(true);

598

599

try {

600

await submitForm(formData);

601

toast.success('Form submitted successfully!');

602

} catch (error) {

603

toast.error('Failed to submit form');

604

} finally {

605

setLoading(false);

606

}

607

};

608

609

return (

610

<View>

611

{/* Form content */}

612

<Button

613

title="Submit"

614

onPress={handleSubmit}

615

disabled={loading}

616

/>

617

</View>

618

);

619

}

620

621

// Network status with toast

622

function NetworkStatusToast() {

623

useEffect(() => {

624

const unsubscribe = NetInfo.addEventListener(state => {

625

if (!state.isConnected) {

626

toast.warning('No internet connection');

627

} else if (state.isConnected) {

628

toast.success('Connected to internet');

629

}

630

});

631

632

return unsubscribe;

633

}, []);

634

635

return null;

636

}

637

638

// Custom toast hook

639

function useToast() {

640

const show = useCallback((message, options = {}) => {

641

if (Platform.OS === 'android') {

642

const duration = options.duration === 'long'

643

? ToastAndroid.LONG

644

: ToastAndroid.SHORT;

645

646

const gravity = options.position === 'top'

647

? ToastAndroid.TOP

648

: options.position === 'center'

649

? ToastAndroid.CENTER

650

: ToastAndroid.BOTTOM;

651

652

ToastAndroid.showWithGravity(message, duration, gravity);

653

} else {

654

// iOS fallback

655

Alert.alert('', message);

656

}

657

}, []);

658

659

return {

660

show,

661

success: (message) => show(`✅ ${message}`),

662

error: (message) => show(`❌ ${message}`),

663

warning: (message) => show(`⚠️ ${message}`),

664

info: (message) => show(`ℹ️ ${message}`),

665

};

666

}

667

```

668

669

```typescript { .api }

670

interface ToastAndroidStatic {

671

// Constants

672

SHORT: number;

673

LONG: number;

674

TOP: number;

675

BOTTOM: number;

676

CENTER: number;

677

678

// Show methods

679

show(message: string, duration: number): void;

680

showWithGravity(message: string, duration: number, gravity: number): void;

681

showWithGravityAndOffset(

682

message: string,

683

duration: number,

684

gravity: number,

685

xOffset: number,

686

yOffset: number

687

): void;

688

}

689

```

690

691

## Accessibility and Interaction Helpers

692

693

### Accessibility Features

694

695

```javascript { .api }

696

// Accessibility-aware interactions

697

function AccessibleButton({onPress, children, accessibilityLabel, accessibilityHint}) {

698

return (

699

<TouchableOpacity

700

onPress={onPress}

701

accessible={true}

702

accessibilityRole="button"

703

accessibilityLabel={accessibilityLabel}

704

accessibilityHint={accessibilityHint}

705

accessibilityState={{disabled: false}}

706

>

707

{children}

708

</TouchableOpacity>

709

);

710

}

711

712

// Screen reader announcements

713

function AccessibilityAnnouncements() {

714

const announce = (message) => {

715

// Announce message to screen readers

716

AccessibilityInfo.announceForAccessibility(message);

717

};

718

719

const handleSave = () => {

720

// Perform save operation

721

saveData().then(() => {

722

announce('Data saved successfully');

723

}).catch(() => {

724

announce('Failed to save data');

725

});

726

};

727

728

return (

729

<Button

730

title="Save"

731

onPress={handleSave}

732

accessibilityLabel="Save data"

733

accessibilityHint="Saves your current progress"

734

/>

735

);

736

}

737

738

// Accessibility info detection

739

function AccessibilityInfo() {

740

const [isScreenReaderEnabled, setScreenReaderEnabled] = useState(false);

741

const [isReduceMotionEnabled, setReduceMotionEnabled] = useState(false);

742

743

useEffect(() => {

744

// Check screen reader status

745

AccessibilityInfo.isScreenReaderEnabled().then(setScreenReaderEnabled);

746

747

// Check reduce motion preference

748

AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotionEnabled);

749

750

// Listen for changes

751

const screenReaderSubscription = AccessibilityInfo.addEventListener(

752

'screenReaderChanged',

753

setScreenReaderEnabled

754

);

755

756

const reduceMotionSubscription = AccessibilityInfo.addEventListener(

757

'reduceMotionChanged',

758

setReduceMotionEnabled

759

);

760

761

return () => {

762

screenReaderSubscription.remove();

763

reduceMotionSubscription.remove();

764

};

765

}, []);

766

767

return (

768

<View>

769

<Text>Screen Reader: {isScreenReaderEnabled ? 'Enabled' : 'Disabled'}</Text>

770

<Text>Reduce Motion: {isReduceMotionEnabled ? 'Enabled' : 'Disabled'}</Text>

771

</View>

772

);

773

}

774

```

775

776

## Interaction Patterns

777

778

### Long Press and Context Menus

779

780

```javascript { .api }

781

// Long press interactions

782

function LongPressItem({item, onEdit, onDelete}) {

783

const showContextMenu = () => {

784

if (Platform.OS === 'ios') {

785

ActionSheetIOS.showActionSheetWithOptions(

786

{

787

options: ['Cancel', 'Edit', 'Delete'],

788

destructiveButtonIndex: 2,

789

cancelButtonIndex: 0,

790

},

791

(buttonIndex) => {

792

if (buttonIndex === 1) onEdit(item);

793

if (buttonIndex === 2) onDelete(item);

794

}

795

);

796

} else {

797

Alert.alert('Context Menu', 'Choose an action', [

798

{text: 'Cancel'},

799

{text: 'Edit', onPress: () => onEdit(item)},

800

{text: 'Delete', style: 'destructive', onPress: () => onDelete(item)},

801

]);

802

}

803

};

804

805

return (

806

<TouchableOpacity onLongPress={showContextMenu}>

807

<View style={styles.item}>

808

<Text>{item.title}</Text>

809

</View>

810

</TouchableOpacity>

811

);

812

}

813

814

// Haptic feedback with interactions

815

function HapticButton({onPress, children, feedbackType = 'impact'}) {

816

const handlePress = () => {

817

// Provide haptic feedback

818

if (Platform.OS === 'ios') {

819

// iOS haptic feedback would go here if available

820

Vibration.vibrate(50);

821

} else {

822

Vibration.vibrate(30);

823

}

824

825

onPress?.();

826

};

827

828

return (

829

<TouchableOpacity onPress={handlePress}>

830

{children}

831

</TouchableOpacity>

832

);

833

}

834

835

// Double tap detection

836

function DoubleTapView({onSingleTap, onDoubleTap, children}) {

837

const lastTap = useRef(null);

838

839

const handleTap = () => {

840

const now = Date.now();

841

const DOUBLE_PRESS_DELAY = 300;

842

843

if (lastTap.current && (now - lastTap.current) < DOUBLE_PRESS_DELAY) {

844

onDoubleTap?.();

845

} else {

846

setTimeout(() => {

847

if (lastTap.current && (Date.now() - lastTap.current) >= DOUBLE_PRESS_DELAY) {

848

onSingleTap?.();

849

}

850

}, DOUBLE_PRESS_DELAY);

851

}

852

853

lastTap.current = now;

854

};

855

856

return (

857

<TouchableWithoutFeedback onPress={handleTap}>

858

{children}

859

</TouchableWithoutFeedback>

860

);

861

}

862

```

863

864

This comprehensive user interaction documentation provides developers with all the tools needed to create engaging, accessible, and platform-appropriate user experiences in React Native applications.