# Styling

> Theme Cloudlet with tokens and class names, and try them in the Style editor.

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

Cloudlet uses one external stylesheet and CSS tokens scoped to `.cloudlet-host`. The editor below lets you try them on a live Toaster and copy the CSS; every setting is a Toaster prop listed in [Toaster props](https://cloudlet.dev/toaster/docs/api/toaster).

## Style editor

Style editor at https://cloudlet.dev/toaster/docs/styling#style-editor: set the tokens on a live Toaster and copy the CSS. The query string carries the values — one parameter per token name without the `--cloudlet-` prefix (`background`, `background-dark`, `pill-radius`, `font-family`, …); dark-theme colors use the `-dark` suffix, and the preview follows the site theme.

## The stylesheet

Import `@cloudlet/toast/styles.css` once, from your global CSS entry or root layout (see [Installation](https://cloudlet.dev/toaster/docs/installation#import-the-styles)). Rules and resets are scoped to `.cloudlet-host`.

Keep it external under a strict Content Security Policy; [Content Security Policy](https://cloudlet.dev/toaster/docs/csp) has the rest of the setup.

Put overrides in a stylesheet imported after Cloudlet's. Use tokens and class names to change the look; the renderer controls width, height, position, transform and clip path.

## Tokens

Set tokens on `.cloudlet-host`. Text uses `--cloudlet-font-family` (`system-ui, sans-serif`) and `--cloudlet-font-size` (`.875rem`); descriptions and actions are smaller.

| Token | Default | Description |
|---|---|---|
| `--cloudlet-pill-height` | `44px` | Minimum height of the collapsed pill. |
| `--cloudlet-pill-radius` | `9999px` | Corner radius of the pill and its tails. |
| `--cloudlet-pill-max-width` | `22rem` | The pill grows with its title up to this width. |
| `--cloudlet-island-radius` | `24px` | Corner radius of the open Island. |
| `--cloudlet-island-max-width` | `26rem` | Width of the open Island, within the viewport. |
| `--cloudlet-tail-offset` | `4px` | How far each tail peeks out from behind the one in front. |
| `--cloudlet-tail-scale` | `.9` | Scale of the first tail relative to the pill; the second is scaled again. |
| `--cloudlet-control-min-size` | `44px` | Touch target of buttons and links. |
| `--cloudlet-viewport-gutter` | `16px` | Gap to the viewport edge; see [Offsets](https://cloudlet.dev/toaster/docs/positioning#offsets). |
| `--cloudlet-z-index` | `1000` | Stacking order of the fixed host. |

The safe-area insets are tokens too: `--cloudlet-safe-top`, `--cloudlet-safe-right`, `--cloudlet-safe-bottom` and `--cloudlet-safe-left` default to the browser's `env(safe-area-inset-*)`.

Colours, in the light and the dark theme:

| Token | Light | Dark |
|---|---|---|
| `--cloudlet-background` | `#ffffff` | `#1c1c1e` |
| `--cloudlet-foreground` | `#111113` | `#f5f5f7` |
| `--cloudlet-muted` | `#6e6e76` | `#a1a1aa` |
| `--cloudlet-focus` | `#245cdb` | `#93b4ff` |
| `--cloudlet-shadow` | a soft two-layer shadow | a deeper single shadow |
| `--cloudlet-tail-background` | the background | the foreground mixed into the background |
| `--cloudlet-tail-border` | the border | the foreground at 18% |
| `--cloudlet-border` | the foreground at 10% | the same mix |
| `--cloudlet-action-background` | the foreground at 8% | the same mix |
| `--cloudlet-success` | `oklch(0.723 0.219 142.136)` | the same |
| `--cloudlet-error` | `oklch(0.637 0.237 25.331)` | the same |
| `--cloudlet-info` | `oklch(0.685 0.169 237.323)` | the same |
| `--cloudlet-warning` | `oklch(0.795 0.184 86.047)` | the same |
| `--cloudlet-loading` | `oklch(0.556 0 0)` | the same |
| `--cloudlet-badge` | `#d70015` | the same |
| `--cloudlet-badge-foreground` | `#fff` | the same |

### Overriding tokens

Set theme-independent tokens and status colours on `.cloudlet-host`:

```css title="notifications.css"
.cloudlet-host {
  --cloudlet-font-family: 'Inter', system-ui, sans-serif;
  --cloudlet-pill-max-width: 24rem;
  --cloudlet-island-radius: 20px;
  --cloudlet-success: oklch(0.7 0.17 160);
}
```

Dark surfaces use a more specific selector, so `.cloudlet-host` alone changes light surfaces. To map both themes to your app's colour tokens, use the guarded rule below:

```css title="notifications.css"
/* Your --surface/--text/--ring are defined on :root and on your .dark root. */
@media (forced-colors: none) {
  .cloudlet-host[data-theme] {
    --cloudlet-background: var(--surface);
    --cloudlet-foreground: var(--text);
    --cloudlet-muted: var(--text-muted);
    --cloudlet-focus: var(--ring);
  }
}
```

The tokens must reach the portal: define them on `:root` or on an ancestor of the [container](https://cloudlet.dev/toaster/docs/positioning#portal-container), not on a wrapper around your React tree, which does not contain the default portal on `document.body`. This is the approach of the shadcn registry item's `toaster.css`, which maps `--popover`, `--popover-foreground`, `--muted-foreground`, `--border`, `--ring`, `--accent` and `--font-sans`.

## Theme

`<Toaster theme>` is `system` by default:

- `system` follows the user's `prefers-color-scheme`.
- `light` and `dark` force a theme, whatever the operating system prefers.

Pass your app's resolved theme to keep Cloudlet's surface and native `color-scheme` in sync with the page. The host's `data-theme` keeps the prop value; the selector below reflects the active theme after mount:

```css title="notifications.css"
.cloudlet-host[data-resolved-theme="dark"] .app-toast-action { font-weight: 500; }
```

The built-in dark surface uses `prefers-color-scheme` and works before scripts run.

In forced-colours mode (Windows High Contrast), every theme uses system colours: `Canvas`, `CanvasText` and `Highlight`, with visible borders on controls.

## Direction

`<Toaster dir>` is `ltr` or `rtl`. When it is omitted, the Toaster follows `dir="rtl"` on the `<html>` element. In right-to-left mode, text aligns to the start, the badge and the slider are mirrored, and the arrow keys move between notifications in the reading direction. Use the RTL toggle in the [Playground](https://cloudlet.dev/toaster/playground) to see it.

## Class names

`classNames` adds your classes to the parts of the renderer. They are added next to Cloudlet's own `cloudlet-*` classes and never replace them, so the stylesheet keeps working:

```tsx title="notifications.tsx"
import { CloudletProvider, createCloudlet, Toaster } from '@cloudlet/toast';
import './notifications.css';

const cloudlet = createCloudlet();

export function Notifications() {
  return (
    <CloudletProvider controller={cloudlet}>
      <Toaster
        classNames={{
          island: 'app-island',
          title: 'app-toast-title',
          action: 'app-toast-action',
        }}
      />
    </CloudletProvider>
  );
}
```

The parts are `host`, `stack`, `pill`, `tail`, `badge`, `island`, `header`, `icon`, `title`, `close`, `body`, `description`, `actions`, `action`, `indicator`, `clear` and `overflow`; [classNames](https://cloudlet.dev/toaster/docs/api/toaster#classnames) says which element each one is.

Cloudlet's own rules use two classes, such as `.cloudlet-host .cloudlet-action`, so a selector with a single class of yours loses to them on the properties they set. Write rules at least as specific, or style through the tokens:

```css title="notifications.css"
.cloudlet-host .app-toast-title { letter-spacing: -0.01em; }
.cloudlet-host .app-toast-action { font-weight: 600; }
```

Utility classes that live in a cascade layer, as Tailwind's do, lose to Cloudlet's unlayered stylesheet for every property it sets. They still apply to properties Cloudlet leaves alone.

To let utilities win, import the stylesheet into a layer of your own, ordered before `utilities`, instead of importing it from JavaScript:

```css title="globals.css"
@layer theme, base, cloudlet, components, utilities;
@import "tailwindcss";
@import "@cloudlet/toast/styles.css" layer(cloudlet);
```

The stylesheet is unlayered on purpose: then a page's own resets, such as `button { all: unset }`, cannot break the notifications. Inside a layer, every unlayered rule of the page wins over Cloudlet, so check your global styles after the switch.

The host also exposes its state as data attributes you can select on: `data-state` (`stack` or `island`), `data-island-reason`, `data-position`, `data-edge` (`top` or `bottom`), `data-theme`, `data-resolved-theme`, `data-reduced-motion` and `data-dragging`. The current notification carries `data-kind` and `data-current`.

## Next steps

- [Toaster props](https://cloudlet.dev/toaster/docs/api/toaster): every prop with its type and default.
- [Positioning & offsets](https://cloudlet.dev/toaster/docs/positioning): where the stack sits, and rendering it into an element of yours.
