Rich, customizable native toast notifications for NativePHP Mobile.
ToastKit renders toasts as native overlays — Jetpack Compose on Android, SwiftUI on iOS — so they look and feel like part of the operating system. No Blade toast component is required.
- Five variants —
success,error,warning,info, andneutral. - Rich content — title, message, and icons with per-platform overrides.
- Full customization — position, animation direction, progress, loading, swipe-to-dismiss, close control, colors, corner radius, padding, and shadow.
- Action buttons — a native action button with its own ID and pressed event.
- Queue strategy — FIFO, one toast at a time.
- Stack strategy — up to
maxVisibletoasts on screen with FIFO overflow. - Live updates — change a visible or queued toast's message, variant, icon, style, or timer without creating a new toast.
- Idempotent dismissal — dismiss by ID or dismiss everything.
- Events —
ToastShown,ToastDismissed, andToastActionPressed. - JavaScript API — a fluent
ToastAPI for web-facing apps.
Install the package with Composer:
composer require victorycodedev/toastkitThe service provider is auto-discovered by Laravel. Register the native code (only needed once per app):
php artisan vendor:publish --tag=nativephp-plugins-provider
php artisan native:plugin:register victorycodedev/toastkitVerify it is registered:
php artisan native:plugin:listThen rebuild your app, since Swift and Kotlin are compiled into it:
php artisan native:runuse Victorycodedev\ToastKit\Facades\Toast;
Toast::success('Changes saved')->show();ToastKit is fully customizable:
Toast::make('Profile updated')
->title('Success')
->success()
->icon('check')
->position('top')
->duration(3000)
->animation('spring')
->swipeToDismiss()
->show();ToastKit is controlled from PHP and renders as a native overlay — you never add a Blade toast component.
Toast::success('Saved')->show();
Toast::error('Something went wrong')->show();
Toast::warning('Storage almost full')->show();
Toast::info('Downloading...')->show();
Toast::neutral('Copied')->show();Each variant applies sensible native defaults, which any explicit style option overrides.
Toast::make('Download complete')
->title('invoice.pdf')
->success()
->icon('check')
->position('top')
->background('#111827')
->foreground('#FFFFFF')
->iconColor('#22C55E')
->cornerRadius(18)
->padding(16)
->shadow()
->animation('spring')
->swipeToDismiss()
->duration(3000)
->show();Supported options include title(), icon(), position(), duration(), persistent(), animation(), direction(), progress(), loading(), swipeToDismiss(), dismissible(), action(), and the styling methods background(), foreground(), iconColor(), actionColor(), cornerRadius(), padding(), and shadow(). See the API Reference for the full list.
icon() accepts the same icon names as NativePHP's <native:icon> component, so any icon you already use in your Blade views works unchanged:
Toast::make('Download complete')->icon('check')->show();
Toast::make('New message')->icon('email')->show();
Toast::make('Storage full')->icon('warning')->show();A shared name resolves per platform automatically — SF Symbols on iOS and Material Icons on Android.
When each platform needs a different symbol, pass the platform-native name directly:
Toast::make('Saved')
->icon(
'check',
ios: 'checkmark.circle.fill', // SF Symbol
android: 'done', // Material Icon ligature
)
->show();ios:— an SF Symbol name (dotted, e.g.house.fill,checkmark.circle.fill).android:— a Material Icon ligature name (underscored, e.g.shopping_cart,qr_code_2).
You can pass either override on its own:
Toast::make('Rated')->icon(ios: 'star.fill')->show();See NativePHP's Icon name reference for the names guaranteed to work consistently on both platforms.
Customize the message and title typography independently with text() and titleText(). Every argument is optional — anything left unset falls back to the native defaults (message: base size, medium weight, left-aligned, non-italic; title: left-aligned, non-italic, semibold weight):
Toast::make('Your download is complete')
->title('Download complete')
->text(
font: 'Inter',
size: 'sm',
weight: 'normal',
)
->titleText(
size: 'lg',
weight: 'bold',
)
->success()
->show();| Option | Values |
|---|---|
font |
A font name resolvable by the platform. |
size |
xs, sm, base, lg, xl |
weight |
normal, medium, semibold, bold |
align |
left, center, right |
italic |
true or false |
Typed enums are available too — ToastTextSize, ToastTextWeight, ToastTextAlign:
use Victorycodedev\ToastKit\Enums\ToastTextAlign;
use Victorycodedev\ToastKit\Enums\ToastTextSize;
use Victorycodedev\ToastKit\Enums\ToastTextWeight;
Toast::make('Saved')
->text(
size: ToastTextSize::Small,
weight: ToastTextWeight::Medium,
align: ToastTextAlign::Center,
)
->show();Typography updates follow the same sparse rules as the rest of update() — only the supplied values change:
Toast::update($id)
->message('Download complete')
->text(weight: 'semibold')
->success()
->show();This changes only the message weight; font, size, align, and italic are left untouched.
font delegates to NativePHP's font resolver, so it honors the fonts array in config/native-ui.php. Pass a bundled font file's basename (Inter-Bold) or a config alias (accent, body, headline, …) and ToastKit renders the typeface your app already configured:
Toast::make('Saved')->text(font: 'accent')->show();A name that can't be resolved falls back to the system font on both platforms. ToastKit does not bundle, download, or register fonts itself — it renders whatever NativePHP resolves.
update() changes a visible or queued toast in place, keeping the same ID:
$id = Toast::info('Uploading file...')
->persistent()
->show();
// Later...
Toast::update($id)
->message('Upload complete')
->success()
->icon('check')
->duration(2000)
->show();Updates are sparse:
- The toast keeps its original ID.
- Only properties you explicitly set change; everything else is preserved.
- A persistent toast becomes timed once you supply a
duration().
A message-only update is ideal for live progress:
Toast::update($id)
->message('Uploading 50%...')
->show();Use determinate progress for work with a known percentage. Values are clamped to 0–100, and updates animate without replacing the toast:
$id = Toast::info('Uploading...')->persistent()->progress(0)->show();
Toast::update($id)->progress(42.5)->show();
Toast::update($id)->progress(100)->message('Upload complete')->show();Use loading() when progress is unknown, and loading(false) to stop it:
$id = Toast::info('Connecting...')->persistent()->loading()->show();
Toast::update($id)->loading(false)->message('Connected')->duration(1500)->show();If a payload contains both progress and loading: true, determinate progress wins. This makes partial updates deterministic while preserving both values in the transport contract.
ToastKit supports fade, slide, scale, spring, snap, pop, reveal, and bounce. Direction is independent and accepts auto, left, right, top, or bottom:
Toast::success('Published')
->animation('bounce')
->direction('right')
->show();auto is the default and derives the motion from the toast position. Reduced-motion platform settings use a short fade instead of spatial or spring motion.
Define reusable presets during application boot, such as in a service provider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Victorycodedev\ToastKit\PendingToast;
use Victorycodedev\ToastKit\Facades\Toast;
final class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Toast::definePreset('syncing', fn (PendingToast $toast) => $toast
->message('Syncing...')
->info()
->persistent()
->loading());
}
}Each call creates a fresh builder, and options chained afterward override the preset:
Toast::preset('syncing')->message('Syncing contacts...')->show();Defining the same name again replaces the previous definition. Missing presets throw PresetNotFoundException. All intentional public configuration failures extend ToastKitException. Presets are PHP-only because they are registered in the Laravel application lifecycle.
Small applications can keep these definitions directly in AppServiceProvider::boot(). For larger applications, a convenient optional organization is an App\Toasts\ToastPresets class with a static register() method, called from AppServiceProvider::boot(). ToastKit does not generate or require this class; it simply keeps a longer preset catalog out of the provider. Precedence is: ToastKit defaults → preset → per-toast configuration → sparse update configuration.
namespace App\Toasts;
use Victorycodedev\ToastKit\Facades\Toast;
final class ToastPresets
{
public static function register(): void
{
Toast::definePreset('payment-success', fn ($toast) => $toast
->success()->icon('check_circle')->animation('reveal')
->position('top')->duration(3000));
Toast::definePreset('payment-failed', fn ($toast) => $toast
->error()->icon('error')->animation('snap')->direction('left')
->position('top')->duration(5000));
Toast::definePreset('uploading', fn ($toast) => $toast
->info()->loading()->persistent());
}
}Then call ToastPresets::register() from your application's AppServiceProvider::boot():
namespace App\Providers;
use App\Toasts\ToastPresets;
use Illuminate\Support\ServiceProvider;
final class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
ToastPresets::register();
}
}You may instead keep all toast presets in a dedicated service provider, such as ToastServiceProvider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Victorycodedev\ToastKit\PendingToast;
use Victorycodedev\ToastKit\Facades\Toast;
final class ToastServiceProvider extends ServiceProvider
{
public function boot(): void
{
Toast::definePreset('payment-success', fn (PendingToast $toast) => $toast
->success()
->icon('check_circle')
->animation('reveal')
->position('top')
->duration(3000));
Toast::definePreset('payment-failed', fn (PendingToast $toast) => $toast
->error()
->icon('error')
->animation('snap')
->direction('left')
->position('top')
->duration(5000));
Toast::definePreset('uploading', fn (PendingToast $toast) => $toast
->info()
->loading()
->persistent());
}
}Register that provider using your Laravel application's normal provider registration, for example in bootstrap/providers.php:
return [
App\Providers\AppServiceProvider::class,
App\Providers\ToastServiceProvider::class,
];ToastKit exposes one package-owned exception hierarchy:
RuntimeException
└── ToastKitException
├── InvalidToastConfigurationException
└── PresetNotFoundException
Catch ToastKitException when you want to handle every error intentionally raised by ToastKit:
use Victorycodedev\ToastKit\Exceptions\ToastKitException;
try {
Toast::preset('payment-success')->message('Paid')->show();
} catch (ToastKitException $exception) {
report($exception);
}Use a specific subclass when the application can recover from one particular condition, such as a missing preset:
use Victorycodedev\ToastKit\Exceptions\PresetNotFoundException;
try {
Toast::preset('payment-success');
} catch (PresetNotFoundException $exception) {
// The requested preset was not registered.
}ToastKit exceptions no longer extend PHP's InvalidArgumentException. Applications that previously caught that exception for ToastKit calls should use the package base exception instead:
// Before
catch (\InvalidArgumentException $exception) {
// ...
}
// Now
catch (\Victorycodedev\ToastKit\Exceptions\ToastKitException $exception) {
// ...
}$id = Toast::info('Syncing...')
->persistent()
->show();
Toast::dismiss($id);Dismiss everything at once:
Toast::dismissAll();Dismissals are idempotent — dismissing an ID that is already gone is a no-op.
Add a native action button and handle its press:
use Native\Mobile\Attributes\On;
use Victorycodedev\ToastKit\Events\ToastActionPressed;
Toast::error('Connection lost')
->action(
label: 'Retry',
id: 'retry',
)
->show();
#[On(ToastActionPressed::class)]
public function handleToastAction(
string $toastId,
string $actionId,
): void {
if ($actionId === 'retry') {
$this->retry();
}
}Pressing an action emits ToastActionPressed and dismisses the toast.
The other events follow the same pattern:
use Native\Mobile\Attributes\On;
use Victorycodedev\ToastKit\Events\ToastShown;
use Victorycodedev\ToastKit\Events\ToastDismissed;
#[On(ToastShown::class)]
public function handleToastShown(string $toastId): void
{
// The toast is now visible.
}
#[On(ToastDismissed::class)]
public function handleToastDismissed(string $toastId, string $reason): void
{
// $reason is one of: timeout, swipe, programmatic, action.
}See Events for the full event reference.
The default strategy is a FIFO queue — one toast at a time:
Toast::info('First')->queue()->show();
Toast::info('Second')->queue()->show();Each toast appears after the previous one finishes. Its duration only begins once it becomes visible.
The stack strategy shows up to maxVisible toasts at once:
Toast::success('Saved')
->stack()
->maxVisible(3)
->show();When the stack is full, additional toasts wait and are admitted in FIFO order as existing toasts dismiss.
ToastKit ships a JavaScript library in resources/js/ (Composer-installed at vendor/victorycodedev/toastkit/resources/js/). There is no published npm package — copy the files into your app or bundle them with your build tool.
import { Toast } from './resources/js';
Toast.success('Saved').show();Updates mirror the PHP API:
const id = await Toast.info('Uploading...')
.persistent()
.show();
await Toast.update(id)
.message('Upload complete')
.success()
.duration(2000)
.show();
await Toast.dismiss(id);
await Toast.dismissAll();Raw bridge functions are also exported:
import { Show, Update, Dismiss, DismissAll } from './resources/js';
await Show({ id: 'one', message: 'Hello' });
await Update('one', { message: 'Done' });
await Dismiss('one');
await DismissAll();<?php
namespace App\Screens;
use Native\Mobile\Attributes\On;
use Native\Mobile\Edge\NativeComponent;
use Victorycodedev\ToastKit\Events\ToastActionPressed;
use Victorycodedev\ToastKit\Facades\Toast;
class ToastDemoScreen extends NativeComponent
{
public ?string $toastId = null;
public function startUpload(): void
{
$this->toastId = Toast::info('Uploading...')
->persistent()
->show();
}
public function updateUpload(): void
{
if (! $this->toastId) {
return;
}
Toast::update($this->toastId)
->message('Uploading 50%...')
->show();
}
public function completeUpload(): void
{
if (! $this->toastId) {
return;
}
Toast::update($this->toastId)
->message('Upload complete')
->success()
->icon('check')
->duration(2000)
->show();
}
public function dismissToast(): void
{
if ($this->toastId) {
Toast::dismiss($this->toastId);
}
}
#[On(ToastActionPressed::class)]
public function handleToastAction(string $toastId, string $actionId): void
{
if ($actionId === 'retry') {
$this->startUpload();
}
}
}Blade screen using native components:
<native:scroll-view>
<native:column>
<native:text>ToastKit Demo</native:text>
<native:button
label="Start"
@press="startUpload"
/>
<native:button
label="Update"
@press="updateUpload"
/>
<native:button
label="Complete"
@press="completeUpload"
/>
<native:button
label="Dismiss"
@press="dismissToast"
/>
</native:column>
</native:scroll-view>ToastKit pairs naturally with the Fetch (Http) package for upload/download progress. Fetch is not a ToastKit dependency - this is an optional integration example.
$toastId = Toast::info('Downloading...')
->persistent()
->show();
$request = Fetch::timeout(120);
$this->requestId = $request->id();
$request->download(/* ... */);Then handle the Fetch events to drive live toast updates:
use Native\Mobile\Attributes\On;
use Victorycodedev\ToastKit\Facades\Toast;
#[On(FetchDownloadProgress::class)]
public function onDownloadProgress(
string $requestId,
int $bytesReceived,
?int $bytesTotal,
?float $progress,
): void {
if ($progress === null || ! $this->toastId) {
return;
}
Toast::update($this->toastId)
->message(
'Downloading '.(int) round($progress * 100).'%...'
)
->show();
}
#[On(FetchDownloadCompleted::class)]
public function onDownloadCompleted(): void
{
Toast::update($this->toastId)
->message('Download complete')
->success()
->icon('check')
->duration(2000)
->show();
}
#[On(FetchRequestFailed::class)]
public function onDownloadFailed(): void
{
Toast::update($this->toastId)
->message('Download failed')
->error()
->duration(3000)
->show();
}Refer to your Fetch package's documentation for its exact API surface.
ToastKit dispatches three events. Listen with NativePHP's #[On] attribute:
| Event | Payload |
|---|---|
Victorycodedev\ToastKit\Events\ToastShown |
toastId (string) |
Victorycodedev\ToastKit\Events\ToastDismissed |
toastId (string), reason (string) |
Victorycodedev\ToastKit\Events\ToastActionPressed |
toastId (string), actionId (string) |
use Native\Mobile\Attributes\On;
use Victorycodedev\ToastKit\Events\ToastDismissed;
#[On(ToastDismissed::class)]
public function handleToastDismissed(string $toastId, string $reason): void
{
// $reason is one of: timeout, swipe, programmatic, action.
}ToastDismissReason declares timeout, swipe, programmatic, action, and replaced; the native renderers currently emit timeout, swipe, programmatic, and action.
Run the PHP suite:
./vendor/bin/pestRun the JavaScript suite:
node --test resources/js/tests/*.test.jsToastKit registers FakeBridge macros so your own tests can assert on toast traffic using domain vocabulary — no emulator or device required:
use Native\Mobile\Testing\Native;
Native::test(ProfileScreen::class)
->call('save')
->assertToastShownWithMessage('Profile updated');Available macros:
| Macro | Description |
|---|---|
assertToastShown(?callable $filter = null) |
A ToastKit.Show call was made. |
assertToastShownWithMessage(string $message) |
A toast with the given message was shown. |
assertToastShownWithId(string $id) |
A toast with the given ID was shown. |
assertToastUpdated(string $id, ?callable $changesFilter = null) |
A toast was updated with the given ID. |
assertToastDismissed(string $id) |
A toast with the given ID was dismissed. |
assertAllToastsDismissed() |
ToastKit.DismissAll was called. |
To test how a screen reacts to a ToastKit event, deliver the event yourself with NativePHP's emitNative():
use Native\Mobile\Testing\Native;
use Victorycodedev\ToastKit\Events\ToastActionPressed;
Native::test(ProfileScreen::class)
->emitNative(ToastActionPressed::class, [
'toastId' => 'one',
'actionId' => 'retry',
])
->assertSet('retried', true);| Method | Description |
|---|---|
Toast::make(?string $message = null) |
Start building a toast. |
Toast::success(string $message) |
A success-variant toast. |
Toast::error(string $message) |
An error-variant toast. |
Toast::warning(string $message) |
A warning-variant toast. |
Toast::info(string $message) |
An info-variant toast. |
Toast::neutral(string $message) |
A neutral-variant toast. |
Toast::definePreset(string $name, Closure $preset) |
Define or replace a reusable PHP preset. |
Toast::preset(string $name) |
Create a fresh builder from a preset. |
Toast::update(string $id) |
Start building an update for an existing toast. |
Toast::dismiss(string $id) |
Dismiss a toast by ID. |
Toast::dismissAll() |
Dismiss all active and queued toasts. |
make() and the variant shortcuts return a PendingToast. All methods are chainable; show() sends the toast to the native bridge and returns its ID (a UUID by default, or a custom ID from id()).
| Method | Description |
|---|---|
id(string $id) |
Set a custom ID instead of the generated UUID. |
message(string $message) |
Set the message text (required). |
title(?string $title) |
Set or clear an optional title. |
text(?string $font = null, $size = null, $weight = null, $align = null, ?bool $italic = null) |
Configure message typography. See Typography. |
titleText(?string $font = null, $size = null, $weight = null, $align = null, ?bool $italic = null) |
Configure title typography. See Typography. |
success() / error() / warning() / info() / neutral() |
Set the variant. |
variant(ToastVariant|string $variant) |
Set the variant by enum or string. |
icon(?string $name = null, ?string $ios = null, ?string $android = null) |
Set an icon using string names, with optional SF Symbol (ios:) / Material Icon (android:) overrides. See Icons. |
position(ToastPosition|string $position) |
top, center, or bottom. |
duration(int $milliseconds) |
Set the visible duration (makes the toast timed). |
persistent(bool $persistent = true) |
Make the toast persistent (no timeout). |
animation(ToastAnimation|string $animation) |
fade, slide, scale, spring, snap, pop, reveal, or bounce. |
direction(ToastDirection|string $direction) |
auto, left, right, top, or bottom. |
progress(int|float $progress) |
Set determinate progress, clamped to 0–100. |
loading(bool $loading = true) |
Enable or disable indeterminate progress. |
swipeToDismiss(bool $enabled = true) |
Enable or disable swipe-to-dismiss. |
dismissible(bool $enabled = true) |
Show a visible close control. |
action(string $label, string $id) |
Add an action button with a label and ID. |
background(string $color) |
Set the background color. |
foreground(string $color) |
Set the text color. |
iconColor(string $color) |
Set the icon color. |
actionColor(string $color) |
Set the action button color. |
cornerRadius(float $radius) |
Set the corner radius. |
padding(float $padding) |
Set the inner padding. |
shadow(bool $enabled = true) |
Enable or disable the shadow. |
queue() |
Use the queue strategy (one toast at a time). |
stack() |
Use the stack strategy (multiple toasts on screen). |
strategy(ToastStrategy|string $strategy) |
Set the strategy by enum or string. |
maxVisible(int $count) |
Set the maximum visible stack size. |
show() |
Send the toast to the native bridge and return its ID. |
Toast::update($id) returns a PendingToastUpdate. It exposes the same configuration methods as PendingToast — except id(), since the ID is fixed — plus a show() method that applies the update. Only properties you explicitly set are sent; everything else is preserved.
| Enum | Values |
|---|---|
ToastVariant |
success, error, warning, info, neutral |
ToastPosition |
top, center, bottom |
ToastAnimation |
fade, slide, scale, spring, snap, pop, reveal, bounce |
ToastDirection |
auto, left, right, top, bottom |
ToastStrategy |
queue, stack |
ToastTextSize |
xs, sm, base, lg, xl |
ToastTextWeight |
normal, medium, semibold, bold |
ToastTextAlign |
left, center, right |
ToastDismissReason |
timeout, swipe, programmatic, action, replaced |
| Property | Default |
|---|---|
variant |
neutral |
position |
bottom |
duration |
3000 ms |
persistent |
false |
animation |
scale |
direction |
auto |
loading |
false |
swipe_to_dismiss |
true |
dismissible |
false |
strategy |
queue |
max_visible |
3 |
corner_radius |
16 |
padding |
16 |
shadow |
true |
| Requirement | Version |
|---|---|
| PHP | 8.4+ |
| NativePHP Mobile | 4.1+ |
| Android | API 29+ (Android 10) |
| iOS | 18.0+ |
ToastKit supports Android and iOS.
ToastKit requires no Android permissions and no iOS permission strings. It adds no third-party native dependencies — it relies on the NativePHP host toolchain and the native platform frameworks (Jetpack Compose and SwiftUI).
See CONTRIBUTING.md.
The MIT License (MIT). See LICENSE.