> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Courier Web Components

> Embed the inbox, toasts, and preferences in any web app without a framework.

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

!<Doc href="/docs/assets/courier-ui-inbox-banner.webp">Inbox UI Preview</Doc>

Drop-in components for notifications in any JavaScript project:

* `<courier-inbox>`: inbox for displaying and managing messages
* `<courier-inbox-popup-menu>`: popup menu version of the inbox
* `<courier-toast>`: toasts for time-sensitive alerts
* `<courier-preferences>`: preferences center for topic subscriptions and delivery

<Tip>
  This is the latest Courier Web Components SDK, for new and existing apps.

  Coming from an earlier version? Upgrade using the
  <Doc href="/docs/sdk-libraries/courier-react-v8-migration-guide">migration guide for the React SDK</Doc>. The React SDK
  is a thin wrapper around these Web Components and exposes a similar API.
</Tip>

## Installation

Inbox, Toast, and Preferences are published as separate packages. Install only the ones you need.

<CodeGroup>
  ```bash Inbox theme={null}
  npm install @trycourier/courier-ui-inbox
  ```

  ```bash Toast theme={null}
  npm install @trycourier/courier-ui-toast
  ```

  ```bash Preferences theme={null}
  npm install @trycourier/courier-ui-preferences
  ```
</CodeGroup>

Available on GitHub and npm:
<Link href="https://github.com/trycourier/courier-web/tree/main/%40trycourier/courier-ui-inbox"><Icon icon="github" iconType="solid" /> Inbox</Link> ·
<Link href="https://www.npmjs.com/package/@trycourier/courier-ui-inbox"><Icon icon="npm" iconType="solid" /> Inbox</Link> ·
<Link href="https://github.com/trycourier/courier-web/tree/main/%40trycourier/courier-ui-toast"><Icon icon="github" iconType="solid" /> Toast</Link> ·
<Link href="https://www.npmjs.com/package/@trycourier/courier-ui-toast"><Icon icon="npm" iconType="solid" /> Toast</Link> ·
<Link href="https://github.com/trycourier/courier-web/tree/main/%40trycourier/courier-ui-preferences"><Icon icon="github" iconType="solid" /> Preferences</Link> ·
<Link href="https://www.npmjs.com/package/@trycourier/courier-ui-preferences"><Icon icon="npm" iconType="solid" /> Preferences</Link>

The Courier SDKs work with any JavaScript build system and need no extra build configuration.

<Note>
  Using React? The <Doc href="/docs/sdk-libraries/courier-react-web/">Courier React SDK</Doc> provides React components and hooks built on these Web Components.
</Note>

## Authentication

Courier authenticates with a **JWT** that your backend mints with your Courier API key, never in client code. <Doc href="/docs/in-app/authenticate-users?lang=Web%20Components">Authenticate users</Doc> covers the token flow, scope strings, reading auth state, signing out, token refresh, and EU-hosted workspaces.

```ts theme={null}
import { Courier } from "@trycourier/courier-ui-inbox";

Courier.shared.signIn({ userId, jwt });  // also accepts tenantId, apiUrls, showLogs
Courier.shared.signOut();
Courier.shared.addAuthenticationListener(({ userId }) => { /* ... */ });
```

<Tip>
  Inbox and Toast share the same `Courier.shared` instance and socket connection. Authenticate once and both components work.
</Tip>

## Inbox Web Components

***

### `<courier-inbox>`

<Frame caption="Default Courier Inbox component">
  !<Doc href="/docs/assets/courier-inbox-react-preview.webp">Default Courier Inbox component</Doc>
</Frame>

<Tip>
  Importing the Courier SDK registers Courier's Web Components (`<courier-inbox>`, `<courier-inbox-popup-menu>`).
</Tip>

```html highlight={2,5} theme={null}
<body>
  <courier-inbox id="inbox"></courier-inbox>

  <script type="module">
    import { Courier } from '@trycourier/courier-ui-inbox';

    // Generate a JWT for your user on your backend server
    const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

    // Authenticate the user with the inbox
    Courier.shared.signIn({
      userId: "user_123",
      jwt: jwt
    });
  </script>
</body>
```

<Tip>
  **Sample App**: See a complete working example in the [Web Components example app](https://github.com/trycourier/courier-web/tree/main/examples/web-js).
</Tip>

<Note>
  If you're using <Doc href="/docs/tenants/overview">tenants</Doc>, you can scope requests to a particular
  tenant by passing its ID to the `signIn` request.

  ```js theme={null}
  Courier.shared.signIn({
    userId: "user_123-id",
    jwt: jwt,
    tenantId: "my-tenant-id"
  });
  ```

  For the full reference of sign in parameters, see the <Doc href="/docs/sdk-libraries/courier-js-web#courierclient-options">Courier JS docs</Doc>.
</Note>

***

### `<courier-inbox-popup-menu>`

<Frame caption="Default Courier Inbox Popup Menu component">
  !<Doc href="/docs/assets/courier-inbox-popupmenu.webp">Default Courier Inbox Popup Menu component</Doc>
</Frame>

```html highlight={3,7} theme={null}
<body>
  <div style="padding: 24px;">
    <courier-inbox-popup-menu id="inbox"></courier-inbox-popup-menu>
  </div>

  <script type="module">
    import { Courier } from '@trycourier/courier-ui-inbox';

    // Generate a JWT for your user on your backend server
    const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

    // Authenticate the user with the inbox
    Courier.shared.signIn({
      userId: "user_123",
      jwt: jwt
    });
  </script>
</body>
```

<Tip>
  **Sample App**: See a complete working example in the [Web Components example app](https://github.com/trycourier/courier-web/tree/main/examples/web-js).
</Tip>

***

### Tabs and feeds

The `<courier-inbox>` element takes feeds through `setFeeds()`, or a `feeds` attribute holding the same array as JSON. The concept, every filter field, and the `datasetId` uniqueness rule are on <Doc href="/docs/in-app/tabs-and-feeds?lang=Web%20Components">Tabs and feeds</Doc>.

`selectFeed()` and `selectTab()` pick one in code. See <Doc href="/docs/in-app/web-components#feed-and-tab-selection">Feed and tab selection</Doc> below.

### Handle clicks and presses

Handle clicks and presses with `onMessageClick()`, `onMessageActionClick()`, and `onMessageLongPress()`.

<Tip>
  `onMessageLongPress()` is only applicable on devices that support touch events.
</Tip>

```html highlight={2,18-40} wrap theme={null}
<body>
  <courier-inbox id="inbox"></courier-inbox>

  <!-- Uncomment the line below to use the popup menu instead -->
  <!-- <courier-inbox-popup-menu id="inbox"></courier-inbox-popup-menu> -->

  <script type="module">
    import { Courier } from '@trycourier/courier-ui-inbox';

    // Generate a JWT for your user on your backend server
    const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

    // Authenticate the user with the inbox
    Courier.shared.signIn({
      userId: "user_123",
      jwt: jwt
    });

    const inbox = document.getElementById('inbox');

    // Handle message clicks
    inbox.onMessageClick(({ message, index }) => {
      alert("Message clicked at index " + index +
            ":\n" + JSON.stringify(message, null, 2));
    });

    // Handle message action clicks (these are buttons on individual messages)
    inbox.onMessageActionClick(({ message, action, index }) => {
      alert(
        "Message action clicked at index " + index + ":\n" +
        "Action: " + JSON.stringify(action, null, 2) + "\n" +
        "Message: " + JSON.stringify(message, null, 2)
      );
    });

    // Handle message long presses (useful for mobile web)
    inbox.onMessageLongPress(({ message, index }) => {
      alert("Message long pressed at index " + index +
            ":\n" + JSON.stringify(message, null, 2));
    });
  </script>
</body>
```

***

### Styles and theming

Call `setLightTheme()` / `setDarkTheme()` with a `CourierInboxTheme`, or set the `light-theme` / `dark-theme` attributes to a JSON string of one. The object is identical on every web SDK, so every field is in <Doc href="/docs/in-app/customize-the-inbox?lang=Web%20Components#courierinboxtheme-reference">the CourierInboxTheme reference</Doc>.

### Popup alignment, position, and dimensions

`popup-alignment`, `popup-width`, `popup-height`, and the `top` / `right` / `bottom` / `left` offsets position the panel. The nine alignment values and their defaults are in <Doc href="/docs/in-app/add-an-inbox?lang=Web%20Components#size-and-position-the-popup">Size and position the popup</Doc>.

### Custom elements

Customize parts of the inbox by passing factory functions that return HTML elements.

<Expandable title="Custom list item">
  ```html theme={null}
  <body>
    <courier-inbox id="inbox"></courier-inbox>

    <script type="module">
      const inbox = document.getElementById('inbox');

      inbox.setListItem(({ message, index }) => {
        const pre = document.createElement('pre');
        pre.style.padding = '24px';
        pre.style.borderBottom = '1px solid #e0e0e0';
        pre.style.margin = '0';
        pre.textContent = JSON.stringify({ message, index }, null, 2);

        return pre;
      });
    </script>
  </body>
  ```

  <Frame caption="Custom inbox message list item displaying the message object">
    !<Doc href="/docs/assets/courier-inbox-list-items.webp">Custom inbox message list item displaying the message object</Doc>
  </Frame>
</Expandable>

<Expandable title="Custom header">
  Call `removeHeader()` to remove the header entirely.

  The `setHeader` callback receives a `props` object with a `feeds` array. Each feed includes selection state, tabs with unread counts, and filter information.

  ```html highlight={2,8-46} wrap theme={null}
  <body>
    <courier-inbox id="inbox"></courier-inbox>

    <script type="module">
      import { Courier } from '@trycourier/courier-ui-inbox';

      const inbox = document.getElementById('inbox');

      inbox.setHeader((props) => {
        const headerDiv = document.createElement('div');
        headerDiv.style.background = 'red';
        headerDiv.style.fontSize = '20px';
        headerDiv.style.color = 'white';
        headerDiv.style.padding = '20px';
        headerDiv.style.width = '100%';
        headerDiv.style.fontFamily = 'monospace';

        const selectedFeed = props.feeds.find(feed => feed.isSelected);
        const selectedTab = selectedFeed?.tabs.find(tab => tab.isSelected);

        let feedPart = selectedFeed ? `${selectedFeed.title}` : 'No Feed Selected';
        let tabPart = selectedTab ? `${selectedTab.title}` : 'No Tab Selected';
        let unreadPart = typeof selectedTab?.unreadCount === 'number' ? `Unread: ${selectedTab.unreadCount}` : '';

        headerDiv.textContent = `${feedPart} / ${tabPart} ${unreadPart && '- ' + unreadPart}`;

        return headerDiv;
      });

      // Set up feeds and authenticate...
    </script>
  </body>
  ```

  <Frame caption="Custom inbox header">
    !<Doc href="/docs/assets/courier-inbox-custom-header.webp">Custom inbox header</Doc>
  </Frame>

  #### Header factory props

  ```ts theme={null}
  {
    feeds: [
      {
        feedId: string;
        title: string;
        iconSVG?: string;
        tabs: [
          {
            datasetId: string;
            title: string;
            unreadCount: number;
            isSelected: boolean;
            filter: {
              tags?: string[];
              archived?: boolean;
              status?: 'read' | 'unread';
            };
          }
        ];
        isSelected: boolean;
      }
    ];
  }
  ```
</Expandable>

<Expandable title="Custom popup menu button">
  ```html highlight={2,10-16} wrap theme={null}
  <body>
    <div style="display: flex; justify-content: center; align-items: center; padding: 100px;">
      <courier-inbox-popup-menu id="inbox"></courier-inbox-popup-menu>
    </div>

    <script type="module">
      const inbox = document.getElementById('inbox');

      inbox.setMenuButton(({ unreadCount }) => {
        const button = document.createElement('button');
        button.textContent = `Open the Inbox Popup. Unread message count: ${unreadCount}`;
        return button;
      });
    </script>
  </body>
  ```

  <Frame caption="Custom inbox popup menu button">
    !<Doc href="/docs/assets/courier-inbox-menu-button.webp">Custom inbox popup menu button</Doc>
  </Frame>
</Expandable>

<Expandable title="Custom loading, empty, error, and pagination states">
  <Tip>
    The inbox loads the next page automatically when the user scrolls to the bottom, so the pagination component may only flash briefly.
  </Tip>

  ```html wrap theme={null}
  <body>
    <courier-inbox id="inbox"></courier-inbox>

    <script type="module">
      const inbox = document.getElementById('inbox');

      inbox.setLoadingState(props => {
        const loading = document.createElement('div');
        loading.style.padding = '24px';
        loading.textContent = 'Custom Loading State';
        return loading;
      });

      inbox.setEmptyState(props => {
        const empty = document.createElement('div');
        empty.style.padding = '24px';
        empty.textContent = 'Custom Empty State';
        return empty;
      });

      inbox.setErrorState(props => {
        const error = document.createElement('div');
        error.style.padding = '24px';
        error.textContent = 'Custom Error State';
        return error;
      });

      inbox.setPaginationItem(props => {
        const pagination = document.createElement('div');
        pagination.style.padding = '24px';
        pagination.style.textAlign = 'center';
        pagination.textContent = 'Loading the next page of messages';
        return pagination;
      });
    </script>
  </body>
  ```

  <Frame caption="Custom pagination state">
    !<Doc href="/docs/assets/courier-inbox-pagination.webp">Custom pagination state</Doc>
  </Frame>
</Expandable>

***

### Programmatic control

`<courier-inbox>` exposes methods to manage feeds, tabs, actions, and data refresh at runtime.

#### Feed and tab selection

| Method                   | Description                                                                  |
| :----------------------- | :--------------------------------------------------------------------------- |
| `selectFeed(feedId)`     | Switch to the specified feed and load its data.                              |
| `selectTab(tabId)`       | Switch to the specified tab within the current feed.                         |
| `getFeeds()`             | Returns the current array of configured feeds.                               |
| `currentFeedId` (getter) | Returns the ID of the currently selected feed.                               |
| `refresh()`              | Forces a reload of inbox data, bypassing the cache. Returns `Promise<void>`. |

#### Header actions

| Method                        | Description                                                                   |
| :---------------------------- | :---------------------------------------------------------------------------- |
| `setActions(actions)`         | Set header actions. Action IDs: `'readAll'`, `'archiveRead'`, `'archiveAll'`. |
| `setListItemActions(actions)` | Set list item actions. Action IDs: `'read_unread'`, `'archive_unarchive'`.    |

#### Popup menu control (on `<courier-inbox-popup-menu>`)

| Method         | Description                                                 |
| :------------- | :---------------------------------------------------------- |
| `showPopup()`  | Open the popup programmatically with transition animation.  |
| `closePopup()` | Close the popup programmatically with transition animation. |

#### Static helper methods

| Method                                  | Description                                    |
| :-------------------------------------- | :--------------------------------------------- |
| `CourierInbox.defaultFeeds()`           | Returns the default feeds (Inbox and Archive). |
| `CourierInbox.defaultActions()`         | Returns the default header actions.            |
| `CourierInbox.defaultListItemActions()` | Returns the default list item actions.         |

<Expandable title="Programmatic feed/tab selection example">
  ```html theme={null}
  <body>
    <courier-inbox id="inbox"></courier-inbox>

    <script type="module">
      import { Courier } from '@trycourier/courier-ui-inbox';

      const inbox = document.getElementById('inbox');

      inbox.setFeeds([
        {
          feedId: 'notifications',
          title: 'Notifications',
          tabs: [
            { datasetId: 'all', title: 'All', filter: {} },
            { datasetId: 'unread', title: 'Unread', filter: { status: 'unread' } }
          ]
        },
        {
          feedId: 'archive',
          title: 'Archive',
          tabs: [
            { datasetId: 'archived', title: 'Archived', filter: { archived: true } }
          ]
        }
      ]);

      Courier.shared.signIn({ userId: "user_123", jwt: '...' });

      // Programmatically select a feed and tab
      inbox.selectFeed('notifications');
      inbox.selectTab('unread');

      // Get current state
      console.log('Current feed:', inbox.currentFeedId);
      console.log('All feeds:', inbox.getFeeds());
    </script>
  </body>
  ```
</Expandable>

<Expandable title="Actions configuration example">
  ```html theme={null}
  <body>
    <courier-inbox id="inbox"></courier-inbox>

    <script type="module">
      import { Courier } from '@trycourier/courier-ui-inbox';

      const inbox = document.getElementById('inbox');

      // Configure header actions
      inbox.setActions([
        { id: 'readAll', iconSVG: '...', text: 'Mark All Read' },
        { id: 'archiveRead', iconSVG: '...', text: 'Archive Read' }
      ]);

      // Configure list item actions
      inbox.setListItemActions([
        { 
          id: 'read_unread',
          readIconSVG: '...',
          unreadIconSVG: '...'
        },
        {
          id: 'archive_unarchive',
          archiveIconSVG: '...',
          unarchiveIconSVG: '...'
        }
      ]);
    </script>
  </body>
  ```
</Expandable>

***

### Attribute vs method usage

Most options can be set with HTML attributes or with methods:

* **HTML attributes**: Best for initial, static configuration
* **Programmatic methods**: Best for dynamic, runtime configuration

```html theme={null}
<!-- Using HTML attributes -->
<courier-inbox 
  mode="light"
  light-theme='{"inbox": {"backgroundColor": "#fff"}}'>
</courier-inbox>

<!-- Using programmatic methods -->
<courier-inbox id="inbox"></courier-inbox>
<script type="module">
  const inbox = document.getElementById('inbox');
  inbox.setMode('light');
  inbox.setLightTheme({
    inbox: { backgroundColor: '#fff' }
  });
</script>
```

Some features are methods only and have no attribute: `selectFeed()`, `refresh()`, `getFeeds()`.

## Toast Web Components

Toasts are short-lived notifications that prompt users to act. The Toast component reads the Courier Inbox message feed.

<Tip>
  Toasts sync with the Inbox message feed. Use both components together for persistent and temporary notifications.
</Tip>

***

### `<courier-toast>`

<Frame caption="Courier Toast component">
  <img width="552px" src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-toast-stack.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=aa904cfe129eb6cfc4492b609abb057f" data-path="assets/courier-toast-stack.webp" />
</Frame>

<Tip>
  Importing `@trycourier/courier-ui-toast` registers Courier's Web Components (`<courier-toast>`).
</Tip>

```html lines highlight={2,5} theme={null}
<body>
  <courier-toast></courier-toast>

  <script type="module">
    import { Courier } from "@trycourier/courier-ui-toast";

    // Generate a JWT for your user on your backend server
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

    // Authenticate the user
    Courier.shared.signIn({
      userId: "user_123",
      jwt: jwt
    });
  </script>
</body>
```

<Tip>
  **Sample App**: See a complete working example in the [Web Components example app](https://github.com/trycourier/courier-web/tree/main/examples/web-js).
</Tip>

***

### HTML attributes

<Tip>
  Terminology: **toast** is the whole stack managed by `<courier-toast>`. A **toast item** is one toast shown for one message.
</Tip>

| Attribute                 | Type                                         | Default     | Description                                                                                                                                                 |
| :------------------------ | :------------------------------------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auto-dismiss`            | `boolean`                                    | `false`     | Whether toast items should auto-dismiss.                                                                                                                    |
| `auto-dismiss-timeout-ms` | `integer`                                    | `5000`      | If `auto-dismiss` is enabled, the timeout in milliseconds before dismissal.                                                                                 |
| `dismiss-button`          | `"visible" \| "hidden" \| "hover" \| "auto"` | `"auto"`    | Display option for the dismiss button. `"auto"` makes the button always visible if `auto-dismiss` is false, and visible on hover if `auto-dismiss` is true. |
| `light-theme`             | `json`                                       | `undefined` | JSON-stringified `CourierToastTheme` applied in light mode. Merged with defaults.                                                                           |
| `dark-theme`              | `json`                                       | `undefined` | JSON-stringified `CourierToastTheme` applied in dark mode. Merged with defaults.                                                                            |
| `mode`                    | `"light" \| "dark" \| "system"`              | `"system"`  | Theme mode for the toast component.                                                                                                                         |

<Frame caption="Toast component with auto-dismiss enabled.">
  <img width="552px" src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-toast-auto-dismiss.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=fcd4fbe6c877b43afda07896026427b6" data-path="assets/courier-toast-auto-dismiss.webp" />
</Frame>

With `auto-dismiss` set, the dismiss button (**x**) appears only on hover and each toast item dismisses itself. A countdown bar shows the time remaining.

***

### Handle clicks

| API Method                        | Description                                              |
| :-------------------------------- | :------------------------------------------------------- |
| `onToastItemClick(handler)`       | Called when a toast item is clicked.                     |
| `onToastItemActionClick(handler)` | Called when an action button on a toast item is clicked. |

<Frame caption="Courier Toast with action buttons">
  <img width="552px" src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-toast-action-buttons.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=483d244496cf995bd58018ab1eeea56b" data-path="assets/courier-toast-action-buttons.webp" />
</Frame>

If a message contains <Doc href="/docs/design/templates/design-studio#buttons">actions</Doc>, each toast item gets a button per action. These buttons do nothing until you wire them up.

```html index.html lines highlight={12-15} theme={null}
<body>
  <courier-toast id="my-toast"></courier-toast>

  <script type="module">
    import {
      Courier,
      CourierToastDatastore
    } from "@trycourier/courier-ui-toast";

    const toast = document.getElementById("my-toast");

    toast.onToastItemClick(({ message, toastItem }) => {
      window.open(message.actions[0].href);
    });

    toast.onToastItemActionClick(({ message, action }) => {
      window.open(action.href);
      CourierToastDatastore.shared.removeMessage(message);
    });

    Courier.shared.signIn({ userId, jwt });
  </script>
</body>
```

<Expandable title="Click event types">
  ```ts theme={null}
  type CourierToastItemClickEvent = {
    message: InboxMessage;
    toastItem: CourierToastItem | HTMLElement;
  };

  type CourierToastItemActionClickEvent = {
    message: InboxMessage;
    action: InboxAction;
  };
  ```
</Expandable>

***

### Styles and theming

Call `setLightTheme()` / `setDarkTheme()` with a `CourierToastTheme`, or set `light-theme` / `dark-theme` to a JSON string of one. Every field, including the per-style action variants, is in <Doc href="/docs/in-app/customize-toasts?lang=Web%20Components#couriertoasttheme-reference">the CourierToastTheme reference</Doc>.

**The theme has no `toast` wrapper.** `setLightTheme({ item: { ... } })` is the shape. An object nested under a `toast` key parses without error and applies nothing.

### Custom elements

| Method                         | Description                                                     |
| :----------------------------- | :-------------------------------------------------------------- |
| `setToastItemContent(factory)` | Customize the content area only, keeping default stack styling. |
| `setToastItem(factory)`        | Fully replace each toast item for complete control.             |

<Expandable title="Custom toast content example">
  ```html index.html lines highlight={6,37-42} theme={null}
  <html>
  <head>
    <link href="./styles.css" rel="stylesheet">
  </head>
  <body>
    <courier-toast id="my-toast" dismiss-button="hidden"></courier-toast>

    <script type="module">
      import { CourierToastDatastore } from "@trycourier/courier-ui-toast";

      const toast = document.getElementById("my-toast");

      toast.setToastItemContent(({ message }) => {
        const content = document.createElement("div");
        content.className = "toast-content";

        const icon = document.createElement("img");
        icon.className = "toast-icon";
        icon.src = "./toast-icon.svg";
        icon.width = 24;
        icon.height = 24;

        const textContainer = document.createElement("div");
        textContainer.className = "toast-text";

        const title = document.createElement("strong");
        title.className = "toast-title";
        title.textContent = message.title;

        const body = document.createElement("p");
        body.className = "toast-body";
        body.textContent = message.body;

        textContainer.appendChild(title);
        textContainer.appendChild(body);

        const dismissButton = document.createElement("button");
        dismissButton.className = "toast-dismiss";
        dismissButton.textContent = "\u00d7";
        dismissButton.addEventListener("click", () => {
          CourierToastDatastore.shared.removeMessage(message);
        });

        content.appendChild(icon);
        content.appendChild(textContainer);
        content.appendChild(dismissButton);

        return content;
      });

      Courier.shared.signIn({ userId, jwt });
    </script>
  </body>
  ```

  <Frame caption="Toast with custom item content">
    <img width="552px" src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-toast-custom-item-content.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=c92c48ff62db652c8fd2d15e9e470b7a" data-path="assets/courier-toast-custom-item-content.webp" />
  </Frame>
</Expandable>

<Expandable title="Fully custom toast item example">
  `auto-dismiss` and `auto-dismiss-timeout-ms` remain valid when using custom items. If `auto-dismiss` is `true`, custom items are automatically removed after the timeout.

  ```html index.html lines theme={null}
  <html>
  <head>
    <link href="./styles.css" rel="stylesheet">
  </head>
  <body>
    <courier-toast id="my-toast"></courier-toast>

    <script type="module">
      const toast = document.getElementById("my-toast");

      toast.setToastItem((props) => {
        const { message, dismiss } = props;

        const container = document.createElement("div");
        container.className = "toast-content";

        const messageDiv = document.createElement("div");
        messageDiv.className = "toast-message";

        const title = document.createElement("strong");
        title.className = "toast-title";
        title.textContent = message.title;

        const body = document.createElement("p");
        body.className = "toast-body";
        body.textContent = message.body;

        messageDiv.appendChild(title);
        messageDiv.appendChild(body);

        const actionsDiv = document.createElement("div");
        actionsDiv.className = "toast-actions";

        if (message.actions) {
          message.actions.forEach(action => {
            const button = document.createElement("button");
            button.className = "toast-action-button";
            button.textContent = action.content;
            button.onclick = () => {
              if (action.href) {
                window.open(action.href);
              }
            };
            actionsDiv.appendChild(button);
          });
        }

        container.appendChild(messageDiv);
        container.appendChild(actionsDiv);

        return container;
      });

      Courier.shared.signIn({ userId, jwt });
    </script>
  </body>
  </html>
  ```

  <Frame caption="Toast with a fully custom item">
    <img width="552px" src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-toast-custom-item.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=5dfe7f525390632b38f1466127e9137a" data-path="assets/courier-toast-custom-item.webp" />
  </Frame>
</Expandable>

***

### Programmatic control

| Method                        | Description                                                                     |
| :---------------------------- | :------------------------------------------------------------------------------ |
| `enableAutoDismiss()`         | Enable auto-dismiss for toast items.                                            |
| `disableAutoDismiss()`        | Disable auto-dismiss. Items remain visible until manually dismissed.            |
| `setAutoDismissTimeoutMs(ms)` | Set the auto-dismiss timeout in milliseconds.                                   |
| `setDismissButton(option)`    | Set dismiss button visibility: `'visible'`, `'hidden'`, `'hover'`, or `'auto'`. |
| `setMode(mode)`               | Set theme mode: `'light'`, `'dark'`, or `'system'`.                             |
| `setLightTheme(theme)`        | Set the light theme programmatically.                                           |
| `setDarkTheme(theme)`         | Set the dark theme programmatically.                                            |

***

### Toast datastore

`CourierToastDatastore` holds the Inbox messages that `<courier-toast>` displays and dismisses. It is a singleton, accessed through `CourierToastDatastore.shared`.

| Method                   | Description                                                             |
| :----------------------- | :---------------------------------------------------------------------- |
| `addMessage(message)`    | Add a message to display as a toast. Messages must include `messageId`. |
| `removeMessage(message)` | Remove a message, dismissing any displayed toast for it.                |

```ts theme={null}
import { CourierToastDatastore } from "@trycourier/courier-ui-toast";

// Add a test message (useful for prototyping)
CourierToastDatastore.shared.addMessage({
  title: "Lorem ipsum dolor sit",
  body: "Lorem ipsum dolor sit amet",
  messageId: "abcd-1234-abcd-1234",
  actions: [{ "content": "Click me!" }]
});

// Remove a message (dismiss its toast)
CourierToastDatastore.shared.removeMessage(message);
```

***

## Preferences Web Components

The Preferences component lets users manage their topic subscriptions inside your app. They also control how each topic is delivered, including per-channel routing and digest schedules.

<Tip>
  Preferences ship in their own package, `@trycourier/courier-ui-preferences` (see [Installation](#installation)). It does not require the inbox or toast packages.

  ```bash theme={null}
  npm install @trycourier/courier-ui-preferences
  ```
</Tip>

### `<courier-preferences>`

<Tip>
  Importing `@trycourier/courier-ui-preferences` registers the `<courier-preferences>` Web Component.
</Tip>

```html lines highlight={2,5} theme={null}
<body>
  <courier-preferences id="preferences"></courier-preferences>

  <script type="module">
    import { Courier } from "@trycourier/courier-ui-preferences";

    // Generate a JWT for your user on your backend server
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

    // Authenticate the user
    Courier.shared.signIn({
      userId: "user_123",
      jwt: jwt
    });
  </script>
</body>
```

<Note>
  Preferences use the same [authentication](#authentication) mechanism as the inbox, but the JWT must include the `read:preferences` and `write:preferences` scopes.
</Note>

***

### Preferences HTML attributes

| Attribute     | Type                            | Default     | Description                                                                             |
| :------------ | :------------------------------ | :---------- | :-------------------------------------------------------------------------------------- |
| `light-theme` | `json`                          | `undefined` | JSON-stringified `CourierPreferencesTheme` applied in light mode. Merged with defaults. |
| `dark-theme`  | `json`                          | `undefined` | JSON-stringified `CourierPreferencesTheme` applied in dark mode. Merged with defaults.  |
| `mode`        | `"light" \| "dark" \| "system"` | `"system"`  | Theme mode for the component.                                                           |
| `tenant-id`   | `string`                        | `undefined` | Scope preferences to a specific tenant (multi-tenant apps).                             |
| `brand-id`    | `string`                        | `undefined` | Render preferences using a specific brand's styling.                                    |

***

### Preferences styling and theming

Call `setLightTheme()` / `setDarkTheme()` with a `CourierPreferencesTheme`, or set `light-theme` / `dark-theme` to a JSON string of one. Setting `primaryColor` alone carries the accent through the toggles, radios, and chips. Every field is in <Doc href="/docs/in-app/customize-preferences?lang=Web%20Components#courierpreferencestheme-reference">the CourierPreferencesTheme reference</Doc>.

### Custom channel labels

Topics can be delivered over multiple channels. Use `setChannelLabels()` to rename those channels in the UI.

```html index.html lines highlight={9-13} theme={null}
<body>
  <courier-preferences id="prefs"></courier-preferences>

  <script type="module">
    import { Courier } from "@trycourier/courier-ui-preferences";

    const prefs = document.getElementById("prefs");

    prefs.setChannelLabels({
      email: "E-mail",
      push: "Mobile Push",
      sms: "Text Message"
    });

    Courier.shared.signIn({ userId, jwt });
  </script>
</body>
```

The default labels are:

| Channel key      | Default label |
| :--------------- | :------------ |
| `direct_message` | Chat          |
| `email`          | Email         |
| `push`           | Push          |
| `sms`            | SMS           |
| `webhook`        | Webhook       |
| `inbox`          | Inbox         |

## EU and regional endpoints

Only needed if your workspace uses the <Doc href="/docs/workspaces/security#regions-and-data-residency">EU datacenter</Doc>. `@trycourier/courier-ui-inbox` re-exports `EU_COURIER_API_URLS`, `DEFAULT_COURIER_API_URLS`, and `getCourierApiUrlsForRegion` from `@trycourier/courier-js`. Pass the result as `apiUrls` on sign-in. For the hostnames, the helper semantics, and the same-region JWT requirement, see <Doc href="/docs/in-app/authenticate-users?lang=Web%20Components#eu-hosted-workspaces">EU-hosted workspaces</Doc>.

## Related documentation

<CardGroup cols={2}>
  <Card title="React SDK" icon="react" href="/docs/sdk-libraries/courier-react-web">
    React components and hooks built on the Web Components.
  </Card>

  <Card title="Inbox Theme Reference" icon="paintbrush" href="/docs/in-app/customize-the-inbox?lang=Web%20Components#courierinboxtheme-reference">
    The full CourierInboxTheme type definition for the inbox.
  </Card>
</CardGroup>
