Skip to main content

Signature

toast.custom(
  content: ReactNode | CustomContentRenderFn,
  options?: CustomToastOptions
): string

Parameters

content
ReactNode | CustomContentRenderFn
required
ReactNode or render function that receives { id, dismiss, type, isExiting }
options
CustomToastOptions
Optional configuration for duration, dismissible, style, etc.

Custom content render function

When passing a function as content, it receives the following props:
id
string
Toast ID
dismiss
() => void
Function to dismiss this toast
type
ToastType
Toast type (defaults to “info” if not specified in options)
isExiting
boolean
Whether the toast is currently exiting

Custom toast options

The options object supports the following properties:
type
ToastType
Toast type: “success” | “error” | “info” | “loading” (default: “info”)
id
string
Stable key for deduplication
description
string
Description text (not rendered in custom content, but can be used for accessibility)
duration
number
Duration in milliseconds
style
ViewStyle
Style overrides for the toast container
dismissible
boolean
Whether this toast can be dismissed via swipe
showCloseButton
boolean
Whether to show the close button (note: custom content must render its own close button)
deduplication
boolean
Enable deduplication for this toast

Return value

Returns the toast ID as a string, which can be used to dismiss the toast later.

Examples

Simple custom content

import { View, Text } from 'react-native';

toast.custom(
  <View style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
    <Text style={{ fontSize: 16 }}>🎉</Text>
    <Text style={{ marginLeft: 8 }}>Custom toast!</Text>
  </View>
);

With render function for dismiss access

import { View, Text, Button } from 'react-native';

toast.custom(({ dismiss }) => (
  <View style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
    <Text>Custom toast!</Text>
    <Button title="Close" onPress={dismiss} />
  </View>
));

With options

toast.custom(
  <MyCustomToast />,
  {
    duration: 5000,
    dismissible: false,
    type: 'success',
  }
);

Custom action toast

import { View, Text, TouchableOpacity } from 'react-native';

toast.custom(
  ({ dismiss }) => (
    <View style={{ 
      flexDirection: 'row', 
      alignItems: 'center', 
      padding: 16,
      backgroundColor: '#1f2937',
      borderRadius: 8,
    }}>
      <Text style={{ flex: 1, color: 'white' }}>
        Changes saved locally
      </Text>
      <TouchableOpacity 
        onPress={() => {
          syncToServer();
          dismiss();
        }}
        style={{ marginLeft: 12, paddingHorizontal: 12, paddingVertical: 6 }}
      >
        <Text style={{ color: '#60a5fa', fontWeight: '600' }}>Sync</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={dismiss} style={{ marginLeft: 8 }}>
        <Text style={{ color: '#9ca3af' }}></Text>
      </TouchableOpacity>
    </View>
  ),
  {
    duration: 8000,
  }
);

Undo toast

import { View, Text, TouchableOpacity } from 'react-native';

const handleDelete = (item) => {
  // Delete the item
  deleteItem(item.id);
  
  // Show undo toast
  toast.custom(
    ({ dismiss }) => (
      <View style={{
        flexDirection: 'row',
        alignItems: 'center',
        padding: 16,
        backgroundColor: '#374151',
        borderRadius: 8,
      }}>
        <Text style={{ flex: 1, color: 'white' }}>
          Item deleted
        </Text>
        <TouchableOpacity
          onPress={() => {
            restoreItem(item);
            dismiss();
          }}
          style={{ paddingHorizontal: 12 }}
        >
          <Text style={{ color: '#fbbf24', fontWeight: '600' }}>Undo</Text>
        </TouchableOpacity>
      </View>
    ),
    {
      duration: 5000,
      type: 'info',
    }
  );
};

Progress toast

import { View, Text } from 'react-native';
import { useState, useEffect } from 'react';

const ProgressToast = ({ dismiss }) => {
  const [progress, setProgress] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setProgress(p => {
        if (p >= 100) {
          clearInterval(interval);
          dismiss();
          return 100;
        }
        return p + 10;
      });
    }, 500);
    
    return () => clearInterval(interval);
  }, []);
  
  return (
    <View style={{ padding: 16 }}>
      <Text style={{ marginBottom: 8 }}>Uploading...</Text>
      <View style={{ 
        height: 4, 
        backgroundColor: '#e5e7eb', 
        borderRadius: 2,
        overflow: 'hidden',
      }}>
        <View style={{
          height: '100%',
          width: `${progress}%`,
          backgroundColor: '#3b82f6',
        }} />
      </View>
      <Text style={{ marginTop: 4, fontSize: 12, color: '#6b7280' }}>
        {progress}%
      </Text>
    </View>
  );
};

toast.custom(
  <ProgressToast />,
  {
    duration: Infinity,  // Don't auto-dismiss
    dismissible: false,
  }
);

Using the toast ID

// Save the toast ID to dismiss it later
const toastId = toast.custom(
  <MyPersistentToast />,
  {
    duration: Infinity,
  }
);

// Later, dismiss it manually
toast.dismiss(toastId);

Responding to exit state

toast.custom(({ isExiting }) => (
  <View style={{
    padding: 16,
    backgroundColor: '#1f2937',
    borderRadius: 8,
    opacity: isExiting ? 0.5 : 1,  // Fade out when exiting
  }}>
    <Text style={{ color: 'white' }}>Custom toast</Text>
  </View>
));

Notes

  • Custom content fills the entire toast container and receives entry/exit animations
  • When using custom content, the default icon, title, description, and close button are not rendered
  • You are responsible for implementing your own close button if needed (use the dismiss function from the render props)
  • The custom content receives the same container styles as standard toasts (unless overridden with the style option)
  • Use duration: Infinity for toasts that should persist until manually dismissed

Build docs developers (and LLMs) love