# Widget reference

URL: https://better-helpdesk.com/docs/widget/reference/

Props, attributes, DOM events and CSS custom properties of HelpdeskWidget, HelpdeskConversations, their custom elements and the script tag.



The widget is the customer side of the helpdesk. It comes in two forms over the same `widget/*` routes:

| Form                                                   | React                   | Custom element             | Registers it                    |
| ------------------------------------------------------ | ----------------------- | -------------------------- | ------------------------------- |
| A launcher and a floating panel                        | `HelpdeskWidget`        | `<helpdesk-widget>`        | `defineHelpdeskWidget()`        |
| The panel in the page, always open, without a launcher | `HelpdeskConversations` | `<helpdesk-conversations>` | `defineHelpdeskConversations()` |

All four exports come from `better-helpdesk/widget`, which starts with `'use client'`. Each element renders into its own open shadow root, so the host's styles do not reach it. Related guides: [Add the widget](/docs/widget/add), [Add a support page](/docs/widget/support-page), [Theme the widget and the agent UI](/docs/customize/theming).

## React props [#react-props]

`HelpdeskWidget` takes every prop below. `HelpdeskConversations` takes all but `label`.

<TypeTable
  type="{
  api: {
    type: 'string',
    default: &#x22;'/api/helpdesk'&#x22;,
    description: <>Base URL of the handler. Absolute when the widget runs on another origin.</>,
  },
  inbox: {
    type: 'string',
    default: &#x22;'support'&#x22;,
    description: 'Key of the inbox new conversations go to.',
  },
  locale: {
    type: 'string',
    default: 'the browser language',
    description: <>Anything starting with <code>de</code> is German, with <code>fr</code> French and with <code>it</code> Italian; everything else is English.</>,
  },
  types: {
    type: 'string[]',
    default: 'every configured type',
    description: <>The conversation types offered. Types the server does not know are left out.</>,
  },
  orgId: {
    type: 'string',
    description: <>Files new conversations under this organization of the signed-in user. Must be one of the <code>orgs</code> their identity lists. Also what <code>resolveTier</code> is asked about when the inbox offers priorities by tier.</>,
  },
  identityToken: {
    type: 'string',
    description: <>An HS256 JWT signed with <code>identityTokenSecret</code>, sent as <code>x-helpdesk-identity</code>, for a widget on an origin where <code>identify</code> cannot see the session.</>,
  },
  appVersion: {
    type: 'string',
    description: <>Stored as <code>appVersion</code> in the conversation's context.</>,
  },
  context: {
    type: 'Record<string, string>',
    description: <>Host context, stored as <code>host</code> in the context of each <code>bug</code>, <code>feature</code> or <code>question</code> conversation the widget opens. Up to 20 keys of up to 100 characters, values up to 500 characters; more is refused with 400.</>,
  },
  label: {
    type: 'string',
    description: <>Text beside the launcher icon, such as <code>Questions? Write to us</code>. Widget only.</>,
  },
  theme: {
    type: &#x22;'auto'&#x22;,
    description: <>Follows the operating system's dark mode, as the <code>theme</code> attribute does.</>,
  },
  ref: {
    type: 'Ref<HelpdeskWidgetElement>',
    description: <>The element, for its <a href=&#x22;#element-methods&#x22;><code>open</code> method</a>.</>,
  },
}"
/>

```tsx title="app/layout.tsx"
import { HelpdeskWidget } from 'better-helpdesk/widget';

<HelpdeskWidget inbox="support" locale="en" context={{ plan: 'team' }} />
```

## Element attributes [#element-attributes]

`<helpdesk-widget>` and `<helpdesk-conversations>` read the same values as attributes. A changed attribute takes effect at once.

| Attribute        | Prop            | Value                                                                                            |
| ---------------- | --------------- | ------------------------------------------------------------------------------------------------ |
| `api`            | `api`           | URL                                                                                              |
| `inbox`          | `inbox`         | Inbox key                                                                                        |
| `locale`         | `locale`        | `en`, `de`, `fr`, `it`, or a browser language tag                                                |
| `types`          | `types`         | Comma-separated list                                                                             |
| `org`            | `orgId`         | Organization id                                                                                  |
| `identity-token` | `identityToken` | JWT                                                                                              |
| `app-version`    | `appVersion`    | String                                                                                           |
| `label`          | `label`         | String, `<helpdesk-widget>` only                                                                 |
| `theme`          | none            | `auto` follows the operating system's dark mode. The React wrappers take it as the `theme` prop. |

The `context` property, not an attribute, carries the host context: `element.context = { plan: 'team' }`.

```ts
import { defineHelpdeskWidget } from 'better-helpdesk/widget';

defineHelpdeskWidget();
```

`defineHelpdeskWidget(tag = 'helpdesk-widget')` and `defineHelpdeskConversations(tag = 'helpdesk-conversations')` register the elements under another tag name when given one. Each does nothing on the server or when the tag is already registered.

## Script tag [#script-tag]

`widget.js` in the published package (`better-helpdesk/widget.js`, the file `dist/widget.js`) is a standalone build with React included. The handler does not serve it: the host serves the file itself, in the example below from the root of its site ([Add the widget](/docs/widget/add) copies it into `public/`). The script registers `<helpdesk-widget>`, then appends one to `document.body` with an attribute for each `data-*` attribute of the script tag: `data-api` becomes `api`, `data-identity-token` becomes `identity-token`, `data-theme` becomes `theme`.

```html
<script
  src="https://app.example.com/widget.js"
  data-api="https://app.example.com/api/helpdesk"
  data-inbox="sales"
  data-locale="de"
  async
></script>
```

Only the element the script creates comes alive. A `<helpdesk-widget>` already in the page's markup stays inert. Loading the script again while its widget is still in the page adds no second one.

## Element methods [#element-methods]

| Method                                | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open({ type?, subject?, message? })` | Opens the panel. With `type`, such as `'bug'`, it opens on that type's form when the inbox offers it, and on its first view otherwise; focus moves into the form. `subject` and `message` go into the form's fields the customer has left empty, and the customer can change them before sending; the subject field shows on bug, feature and question forms. On a panel that is already open, it keeps a draft the customer is writing unless `type` names another form, and it never leaves a conversation the customer is replying in. |

`<helpdesk-widget>` and `<helpdesk-conversations>` both have it. Code that holds the element calls it directly, for example from a button of the host's own:

```ts
document.querySelector('helpdesk-widget')?.open({
  type: 'bug',
  message: 'The export button did nothing.',
});
```

With the React wrapper, pass a `ref`:

```tsx
import { HelpdeskWidget, type HelpdeskWidgetElement } from 'better-helpdesk/widget';
import { useRef } from 'react';

const widget = useRef<HelpdeskWidgetElement>(null);
// <HelpdeskWidget ref={widget} />
// later: widget.current?.open({ type: 'bug' });
```

`HelpdeskWidgetElement` is the element's type and `OpenRequest` the options of `open()`; the package also registers both tags with TypeScript, so `document.querySelector('helpdesk-widget')` is typed without a cast.

## DOM events [#dom-events]

The element dispatches these on itself. They bubble and cross the shadow boundary.

| Event                      | `detail`                                                               | When                                       |
| -------------------------- | ---------------------------------------------------------------------- | ------------------------------------------ |
| `helpdesk:open`            | none                                                                   | The panel opens. `<helpdesk-widget>` only. |
| `helpdesk:message-sent`    | `{ inbox: string, type: string, segment?: string, reference: string }` | A new conversation is sent.                |
| `helpdesk:booking-clicked` | `{ reference: string }`                                                | The customer follows the booking link.     |

```ts
document.addEventListener('helpdesk:message-sent', event => {
  const { reference } = (event as CustomEvent<{ reference: string }>).detail;
});
```

Key presses in the widget's text fields stop at the element, so they do not reach the host page's shortcuts.

## What the widget captures [#what-the-widget-captures]

A message sent with **Send us a message** is a `chat` and carries the page URL and locale. A ticket for a `bug`, `feature` or `question` is a `ticket`, and its context holds the page URL and title, user agent, viewport, locale, `appVersion`, the last 10 errors and unhandled rejections on the page (300 characters each), and the host context as `host`. The form lists each item, and the customer can leave any of them out. On an inbox with [`priorities`](/docs/inbox/inboxes#inboxconfig), a ticket form also asks a signed-in customer of a tier the inbox offers them to to pick one, named by `priorityLabels` and explained by the inbox's text; a visitor is never asked. Once sent, the thread says by when the team aims to reply when the conversation falls under a [response target](/docs/inbox/sla), and after resolution asks for a rating: good or bad, or 1 to 5 with `ratingScale: 5`. For any other type, it holds the page URL, the locale, the first page of the visit, the external referrer and the UTM parameters of that first page. The fields and their limits are on [HTTP routes](/docs/reference/routes#context).

## Browser storage [#browser-storage]

| Key                       | Storage          | Holds                                                                      |
| ------------------------- | ---------------- | -------------------------------------------------------------------------- |
| `helpdesk-visitor:{api}`  | `localStorage`   | An anonymous visitor's token, per API base URL without its trailing slash. |
| `helpdesk-visitor`        | `localStorage`   | Read when the key above is empty; written by earlier versions.             |
| `helpdesk-landing`        | `sessionStorage` | The first page of the visit.                                               |
| `helpdesk-referrer`       | `sessionStorage` | The external referrer of the visit, or an empty string.                    |
| `helpdesk-searches:{api}` | `localStorage`   | The last five documentation searches, shown on Home.                       |

## CSS custom properties [#css-custom-properties]

Set on the element or any ancestor; the values inherit into the shadow root.

| Property                     | Default                                            | Styles                                                              |
| ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------------- |
| `--helpdesk-font`            | `system-ui, -apple-system, 'Segoe UI', sans-serif` | Font family                                                         |
| `--helpdesk-bg`              | `#ffffff`                                          | Panel background                                                    |
| `--helpdesk-fg`              | `#0c2034`                                          | Text; shadows are tinted from it                                    |
| `--helpdesk-muted`           | `#486581`                                          | Secondary text                                                      |
| `--helpdesk-border`          | `#d9e2ec`                                          | Borders                                                             |
| `--helpdesk-subtle`          | `#f0f4f8`                                          | Quiet backgrounds                                                   |
| `--helpdesk-accent`          | `#0c2034`                                          | Buttons, links, unread dots                                         |
| `--helpdesk-accent-hover`    | `#243b53`                                          | Accent on hover                                                     |
| `--helpdesk-accent-fg`       | `#f0f4f8`                                          | Text on the accent                                                  |
| `--helpdesk-danger`          | `#cf1124`                                          | Errors                                                              |
| `--helpdesk-focus`           | `#f55068`                                          | Focus ring                                                          |
| `--helpdesk-radius`          | `12px`                                             | Corners; controls step down from it and dialogs step up             |
| `--helpdesk-launcher-bg`     | the accent                                         | Launcher background                                                 |
| `--helpdesk-launcher-fg`     | the accent foreground                              | Launcher icon and label                                             |
| `--helpdesk-panel-header-bg` | the accent                                         | Panel header background                                             |
| `--helpdesk-panel-header-fg` | the accent foreground                              | Panel header text                                                   |
| `--helpdesk-panel-border`    | `1px solid` the border color                       | Panel border                                                        |
| `--helpdesk-shadow`          | a soft shadow tinted from the text color           | Shadows of the launcher, the panel and pop-ups; `none` removes them |
| `--helpdesk-offset-bottom`   | `20px`                                             | Distance of the widget from the bottom of the viewport              |
| `--helpdesk-page-height`     | `640px`                                            | Height of `<helpdesk-conversations>`                                |

With `theme="auto"` and a dark operating system theme, unset properties take dark defaults: background `#102a43`, text `#f0f4f8`, muted `#bcccdc`, border `#334e68`, subtle `#243b53`, accent `#f0f4f8`, accent hover `#d9e2ec`, accent foreground `#0c2034`, panel header `#243b53` on `#f0f4f8`.

The agent UI reads most of the same properties; see [Agent UI](/docs/inbox/agent-ui#css-custom-properties).
