# Installation

> Add Cloudlet from npm or the shadcn registry, check the peers, and import the CSS once.

Source: https://cloudlet.dev/toaster/docs/installation

Cloudlet installs as one package, `@cloudlet/toast`, or as a shadcn registry item that adds a small wrapper you own on top of the same package; pick the one that matches how your project manages components.

## Install

**npm.** Add the package. React 18 or 19, nothing else to configure.

```sh title="Terminal"
npm install @cloudlet/toast
```

Import the styles once, mount one Toaster per controller, call show from anywhere below it.

```tsx title="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>;
}
```

**shadcn.** Adds a thin Toaster wrapper and a CSS bridge to your shadcn tokens, plus the package itself.

```sh title="Terminal"
npx shadcn@latest add https://cloudlet.dev/r/toaster.json
```

A client component owns the controller; the wrapper lives in components/ui and is yours to edit.

```tsx title="notification-owner.tsx"
"use client";

import { useEffect, useState } from "react";
import {
  createCloudlet,
  useOptionalCloudlet,
  type ReactCloudletController,
} from "@cloudlet/toast";
import { Toaster } from "@/components/ui/toaster";

function SaveButton() {
  const controller = useOptionalCloudlet();
  return (
    <button
      disabled={!controller}
      onClick={() => controller?.show({ title: "Saved", kind: "success" })}
    >
      Notify
    </button>
  );
}

export function NotificationOwner() {
  const [controller, setController] = useState<ReactCloudletController | null>(null);
  useEffect(() => {
    const instance = createCloudlet();
    setController(instance);
    return () => instance.dispose();
  }, []);
  // null on the server and in the first client render: the button renders, the Toaster waits.
  return <Toaster controller={controller}><SaveButton /></Toaster>;
}
```

## Requirements

- **React and React DOM** `^18.2.0 || ^19.0.0` are peer dependencies. Use a matching pair: the same version of `react` and `react-dom`.
- **ESM only.** The package ships ES modules with TypeScript declarations; there is no CommonJS or UMD build. Deep imports into its internals are closed on purpose: everything public comes from `@cloudlet/toast`.
- **No runtime dependencies.** The springs and tweens that move the stack and the Island are Cloudlet's own.
- **No Tailwind or shadcn/ui requirement.** The npm package styles itself with plain CSS and custom properties; the shadcn item only bridges them to your tokens.
- **Browsers.** Chromium and Edge 111, Firefox 114, Safari and iOS 16.4 or newer. The automated suite runs on current versions, and the minimum versions are checked by hand ([What is tested](https://cloudlet.dev/toaster/docs/accessibility#what-is-tested)).
- **TypeScript.** The declarations work with `strict` and `exactOptionalPropertyTypes`; with the latter, leave an optional field out rather than passing `undefined`.

## Import the styles

Cloudlet's CSS is one explicit file. Import it once, where your application imports its global CSS: the client entry of a single-page app, or the root layout of a Next.js App Router project.

```tsx title="app/layout.tsx"
import '@cloudlet/toast/styles.css';
```

In the Next.js Pages Router, global CSS must come from the custom App, so the import goes into `pages/_app.tsx`. The stylesheet does not need a build plugin; under a strict Content Security Policy keep it external, as described in [Content Security Policy](https://cloudlet.dev/toaster/docs/csp).

## The shadcn registry item

The `toaster` item copies two files into your project and adds `@cloudlet/toast` as a dependency:

- **`components/ui/toaster.tsx`**: a thin `"use client"` adapter that renders the Provider and the Toaster for a controller you pass in. Every public Toaster option passes through unchanged.
- **`components/ui/toaster.css`**: a theme bridge that maps your shadcn tokens (`--popover`, `--popover-foreground`, `--muted-foreground`, `--border`, `--ring`, `--accent`, `--font-sans`) to Cloudlet's tokens.

Both files are yours to edit. The notification runtime itself stays in the package: the item copies no controller, renderer or animation code.

The wrapper deliberately does not create a controller: a client component owns it, as `notification-owner.tsx` in the shadcn tab above does, for the reasons in [SSR & Next.js](https://cloudlet.dev/toaster/docs/ssr#nextjs-app-router).

A few details of the bridge:

- The wrapper imports `@cloudlet/toast/styles.css` and then `./toaster.css`; keep that order. In the Pages Router, move both imports into `pages/_app.tsx` in the same order and remove them from the wrapper.
- Define light tokens on `:root` and dark tokens on your `.dark` root, then pass the matching `theme="light"` or `theme="dark"` to the Toaster so the native `color-scheme` agrees.
- The tokens must be inherited by the portal container. A wrapper element around your React tree does not theme the default portal on `document.body`.

## Next steps

- [Quick start](https://cloudlet.dev/toaster/docs/quick-start): create the controller, mount the Toaster and show the first notification.
