Notifications, light as a cloud.
Cloudlet drifts in, stacks softly and opens into a card when it matters. Every motion on springs, every frame interruptible.
Life of a notification
One toast, from a soft landing to a promise kept.
- 01
Arrives softly
A pill grows out of thin air, on a spring.
- 02
Stacks, never piles
Newcomers land on top. The rest wait behind, counted.
- 03
Opens when it matters
The pill becomes an Island in one continuous shape. Text never stretches.
- 04
Speaks up
Warnings carry their own action and never steal focus.
- 05
Keeps its promises
track() shows loading while the work is in flight.
- 06
Shows real progress
The ring follows the upload, frame by frame.
- 07
Fails gracefully
Errors say what went wrong and keep the way forward.
- 08
One tap to retry
Retry is built in. No glue code.
- 09
Lands the result
Success replaces the spinner in place.
- 10
Then drifts away
Done, it folds back into the stack and floats off like a cloud.
Drifting by
Six reasons it feels this light.
- 01 / 06
Springs, not keyframes
Every motion rides a spring. Interrupt it anywhere and it carries on from its current speed.
- 02 / 06
Stack → Island
One shell that stacks, opens and slides. Swipe it, flick it on a trackpad, or use the arrow keys.
- 03 / 06
Promises built in
track() shows loading, retries attempt by attempt and cancels for real. Your promise stays yours.
- 04 / 06
Speaks up when it matters
attention opens what is important on its own, and never steals focus.
- 05 / 06
Accessible by default
Native controls, calm announcements, full keyboard, reduced motion and RTL.
- 06 / 06
Fits your stack
A shadcn registry item, tokens and classNames, SSR and Next.js, strict CSP. About 26 KB.
Get started
Install the package, mount one Toaster, call show. Guides and the API reference are in the docs.
Install
Add the package. React 18 or 19, nothing else to configure.
Mount it and call show
Import the styles once, mount one Toaster per controller, call show from anywhere below it.
notifications.tsx import '@cloudlet/toast/styles.css'; import { CloudletProvider, createCloudlet, Toaster, useCloudlet } from '@cloudlet/toast'; function SaveNotice() { const cloudlet = useCloudlet(); return <button onClick={() => cloudlet.show({ title: 'Saved', kind: 'success' })}>Notify</button>; } // The browser application bootstrap creates and owns this controller. export function Notifications({ controller }: { controller: ReturnType<typeof createCloudlet> }) { return <CloudletProvider controller={controller}> <SaveNotice /> <Toaster /> </CloudletProvider>; }
Questions
Short answers about SSR, styling, accessibility and size.
How is it different from Sonner?
Sonner is a lovely stack of toasts; Cloudlet is one stack that opens into an Island, with promises, retry and cancel built in, and every motion on interruptible springs.
Does it work with Next.js and SSR?
Yes. The server output and the first hydration render are empty, so nothing mismatches. Create the controller in a client component, import the CSS in your root layout, and anything shown before mount appears once the page hydrates.
Do I need Tailwind or shadcn?
No. The npm package needs only React 18 or 19 and its own stylesheet. The shadcn item is an optional, editable wrapper that maps your theme tokens.
Can it look like my app?
Override the scoped --cloudlet-* tokens for colours, radii and sizes, add your own classes to any part through classNames, and pick light, dark or system.
Is it accessible?
The pill is a native button and the Island a labelled carousel. Arrivals never take focus, announcements are calm and coalesced, the keyboard reaches everything, and reduced motion and RTL are respected.
How big is it?
About 29 KB min+gzip: 26 KB of JavaScript and 3 KB of CSS. It animates with Motion; in an app that does not ship Motion yet, the total is about 34 KB.
What does it cost?
Nothing. Cloudlet is MIT-licensed: use it, change it, ship it.









