react-native-system-toast
v1.0.0
Published
System toasts for React Native. Real Android Toast, a matching iOS presenter, no root component.
Maintainers
Readme
react-native-system-toast
A real android.widget.Toast on Android, and a matching presenter on iOS. One
function, nothing to mount, no dependencies.
import { toast } from 'react-native-system-toast';
toast('Saved');That is the whole thing. There is no <Toast /> to put at the root of your app,
no provider, and no context to be inside — so this works from an error handler, a
retry callback, a saga, or module scope before React has rendered anything.
| | | |---|---| | Install size | 14 kB packed, 25 files | | Runtime dependencies | none | | Classes surviving R8 | 7 | | Architecture | New Architecture Turbo Native Module |
Install
npm install react-native-system-toast
cd ios && pod installRequires the New Architecture. No linking, no manifest changes, no permissions.
API
Four things are exported. That is deliberate — everything below is the complete surface, not a selection.
toast(message, options?)
Shows a toast. Returns nothing; it never throws and never rejects.
import { toast } from 'react-native-system-toast';
// The common case
toast('Saved');
// Longer, for something the user needs to actually read
toast('Could not reach the server. Your changes are saved locally.', {
duration: 'long',
});
// Top of the screen — honoured on iOS, see the Android note below
toast('Copied to clipboard', { position: 'top' });interface ToastOptions {
duration?: 'short' | 'long'; // default 'short'
position?: 'top' | 'bottom'; // default 'bottom'
}'short' is 2000 ms and 'long' is 3500 ms — Android's own values, matched
exactly on iOS so one call behaves the same on both platforms.
Real use, outside React entirely:
// api.ts — no component, no hook, no provider
import { toast } from 'react-native-system-toast';
export async function save(draft: Draft) {
try {
await client.put('/drafts', draft);
toast('Saved');
} catch {
toast('Saved offline. It will sync when you reconnect.', {
duration: 'long',
});
}
}toast.hide()
Dismisses whatever is on screen. Safe to call when there is nothing showing.
import { toast } from 'react-native-system-toast';
toast('Uploading…', { duration: 'long' });
const response = await upload(file);
toast.hide(); // the upload finished early
toast(`Uploaded ${response.name}`);Useful when a toast announces something in progress that might end before its duration does.
getToastCapabilities()
Synchronous and free — no bridge call, so it is safe to read during render.
import { getToastCapabilities } from 'react-native-system-toast';
const { platform, positionControl, durationMs } = getToastCapabilities();interface ToastCapabilities {
platform: 'ios' | 'android';
positionControl: boolean; // false on Android 11+
durationMs: { short: number; long: number }; // 2000 / 3500
}Branch on positionControl rather than on Platform.OS or an API level, so the
reason a placement was ignored stays visible in your own code:
const { positionControl } = getToastCapabilities();
// Only offer the choice where it means something
{positionControl && (
<Switch value={preferTop} onValueChange={setPreferTop} />
)}
toast('Copied', { position: positionControl && preferTop ? 'top' : 'bottom' });And to time something that has to agree with the toast:
const { durationMs } = getToastCapabilities();
toast('Undo available');
const timer = setTimeout(commit, durationMs.short); // not a guessed 2000Types
import type {
ToastOptions,
ToastDuration,
ToastPosition,
ToastCapabilities,
} from 'react-native-system-toast';Two behaviours that are not the platform default
Both are deliberate. Both are things a raw Toast gets wrong.
Only one toast exists at a time
A raw Android Toast queues. Call it ten times and the user watches ten
toasts in sequence, for fifteen seconds. This package keeps a single toast and
replaces its text in place, restarting the timer:
// One toast. Reads "Message 5 of 5". Gone ~2s later.
for (let i = 1; i <= 5; i++) toast(`Message ${i} of 5`);A burst collapses into a single show
Every Toast.show() is an IPC to NotificationManagerService. Firing several in
one tick is enough to take down system_server — an Android 17 emulator answered
a five-call loop with "Process system isn't responding", wedging the whole
device rather than just the app.
So calls inside a 60 ms window collapse to one, keeping the last message. Only the last one in a burst is readable anyway. The window is invisible in normal use and you never need to debounce at the call site.
Platform differences
Read these before deciding this package fits.
position: 'top' does nothing on Android 11+
Android 11 made Toast.setGravity() a no-op for text toasts:
"The following methods are no-ops, so your app shouldn't use them:
setGravity()" — Android 11 behaviour changes
React Native's own ToastAndroid carries the same warning. This is not something
a library can work around while still using a real system toast — the system owns
placement now.
So on API 30 and above this package does not call setGravity() at all,
reports positionControl: false, and warns once in __DEV__ with a link to that
page. On API 29 and below, 'top' works.
If you need a top-anchored message on modern Android, you want a Snackbar or a JavaScript toast library. That is a real trade, and pretending otherwise would just move the surprise to your users.
Dark mode
| | Follows the system theme |
|---|---|
| iOS | Yes — .systemChromeMaterial and .label, no theme code in this library |
| Android | Whatever the OS does. Verified on Android 17: the toast stays light even with system dark mode on |
The Android toast is rendered by system_server, not by your app, so its
appearance is entirely the platform's business. That is the cost of using the
real widget instead of drawing a lookalike — you get the system's behaviour
including where it is inconsistent.
iOS has no toast API
There is no UIToast, in UIKit or SwiftUI, and the HUD the system shows for
silent mode or AirPods is private. So the iOS side is drawn — but drawn out of
system parts, so it tracks the platform rather than freezing one moment of it
into hard-coded colours:
UIVisualEffectViewwith.systemChromeMaterial, and.labelfor text, so light and dark are handled by the OS- Dynamic Type, including at accessibility sizes
- Real safe-area insets, so it clears the Dynamic Island and the home indicator
- No travel when Reduce Motion is on, only a fade
- A VoiceOver announcement, because a view that appears without taking focus is otherwise silent
- Non-interactive, in a passthrough window — taps reach your UI underneath, the same as Android
Size and release builds
The Android side needs no ProGuard or R8 keep rules. android/consumer-rules.pro
is intentionally empty and records why: no reflection, no name-based lookup, and
no resources resolved by name, so resource shrinking has nothing of this
library's to delete.
Verified rather than assumed — example/ builds with minifyEnabled and
shrinkResources on, and toasts work in that build. Seven classes survive
into the release dex.
iOS has no equivalent step. Swift and Objective-C compile ahead of time and Xcode
dead-strips release builds already. The library does ship a
PrivacyInfo.xcprivacy, accurately empty: it collects nothing, makes no network
calls, and uses no required-reason APIs.
Example app
example/ exercises every option, including the ones that fail:
yarn example android
yarn example iosIt reads getToastCapabilities() and labels its own Position section
accordingly, so running it on an Android 11+ device shows you the limitation
rather than describing it.
Comparison
| | This | react-native-toast-message | burnt |
|---|---|---|---|
| Native system toast on Android | yes | no, drawn in JS | yes |
| Root component required | no | <Toast /> | no |
| Runtime dependencies | 0 | 0 | 2 |
| Custom styling | none, on purpose | extensive | some |
If you want branded toasts with your own colours, icons and animations, use
react-native-toast-message — that is what it is for. Use this one when you want
what the platform already does.
Author
Built by Balamurugan V.
GitHub · LinkedIn · [email protected]
Issues and pull requests are welcome — particularly reports from devices whose OEM styles the system toast differently.
Licence
MIT
