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

# Studies

> Present hosted Pillow studies with the browser SDK

Use `presentStudy(...)` to prepare and open a hosted study for the current browser user. For the concepts behind this, see [About presenting studies](/sdk/concepts/presenting-studies).

These presentation APIs are part of the `@trypillow/web` package.

## Present a study

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.presentStudy({ id: 'demo-study' });
```

The SDK bootstraps the audience session, prepares the study, and opens the Pillow widget shell.

## Study options

`presentStudy` accepts an options object as second argument:

| Option                 | Type      | Default | Description                                                                         |
| ---------------------- | --------- | ------- | ----------------------------------------------------------------------------------- |
| `skipIfAlreadyExposed` | `boolean` | `false` | Skip presenting if the user has already seen this study. Emits `studySkip` instead. |
| `forceFreshSession`    | `boolean` | `false` | Ignore any saved session and start a new interview.                                 |
| `presentation`         | `object`  | None    | Layout and appearance options (see below).                                          |

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.presentStudy(
  { id: 'demo-study' },
  {
    forceFreshSession: true,
    skipIfAlreadyExposed: true,
    presentation: {
      mode: 'modal',
      width: 720,
      height: 720,
    },
  },
);
```

## Presentation options

Control how the study window is displayed by passing `presentation` in the study options.

| Option         | Type                              | Default          | Description                                                                                                                            |
| -------------- | --------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`         | `'modal' \| 'overlay' \| 'embed'` | `'modal'`        | How the study is displayed.                                                                                                            |
| `position`     | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Corner position. Applies to `overlay` mode.                                                                                            |
| `zIndex`       | `number`                          | `999999`         | CSS `z-index` for the widget container.                                                                                                |
| `width`        | `number \| string`                | Mode-dependent   | Width of the study window. Numbers are pixels. Strings are used as-is (e.g. `'80vw'`). Defaults: `400px` modal, `360px` overlay/embed. |
| `height`       | `number \| string`                | Mode-dependent   | Height of the study window. Defaults: `680px` modal, `540px` overlay, `520px` embed.                                                   |
| `borderRadius` | `number \| string`                | Mode-dependent   | Corner radius. Defaults: `24px` modal/embed, `20px` overlay.                                                                           |
| `bottom`       | `number \| string`                | `20px`           | Bottom offset. Applies to `overlay` mode.                                                                                              |
| `top`          | `number \| string`                | None             | Top offset. Applies to `overlay` mode.                                                                                                 |
| `left`         | `number \| string`                | None             | Left offset. Applies to `overlay` mode.                                                                                                |
| `right`        | `number \| string`                | None             | Right offset. Applies to `overlay` mode.                                                                                               |
| `target`       | `string \| HTMLElement \| null`   | `null`           | Mount target for `embed` mode. Pass a CSS selector or a DOM element.                                                                   |
| `backdrop`     | `object`                          | See below        | Backdrop overlay for `modal` mode.                                                                                                     |

### Presentation modes

* **`modal`**: Centered dialog over a full-page backdrop. Dismissed by clicking the backdrop or pressing Escape.
* **`overlay`**: Floating corner panel anchored to `position`. Dismissed via the close button or Escape.
* **`embed`**: Inline inside a `target` element. No backdrop or positioning; the study fills the target container.

<Tip>If you omit `mode`, the SDK defaults to `modal`.</Tip>

### Backdrop options

Control the overlay behind the study window in `modal` mode.

| Option  | Type     | Default     | Description                                              |
| ------- | -------- | ----------- | -------------------------------------------------------- |
| `blur`  | `number` | `6`         | Backdrop blur radius in pixels.                          |
| `color` | `string` | `'#0f172a'` | Backdrop tint color (any CSS color value).               |
| `alpha` | `number` | `0.42`      | Backdrop opacity from `0` (transparent) to `1` (opaque). |

### Examples

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
// Modal with custom size and backdrop
await sdk.presentStudy({ id: 'demo-study' }, {
  presentation: {
    mode: 'modal',
    width: 720,
    height: 720,
    backdrop: { blur: 10, alpha: 0.55, color: '#020617' },
  },
});

// Overlay in the bottom-left corner
await sdk.presentStudy({ id: 'demo-study' }, {
  presentation: {
    mode: 'overlay',
    position: 'bottom-left',
    bottom: 32,
    left: 32,
  },
});

// Embedded inside a container
await sdk.presentStudy({ id: 'demo-study' }, {
  presentation: {
    mode: 'embed',
    target: '#study-container',
    width: '100%',
    height: '100%',
    borderRadius: 0,
  },
});
```

## Launch studies

Launch studies are audience-targeted studies configured in the dashboard. Instead of hard-coding which study to show, call `presentLaunchStudyIfAvailable()` at a safe moment in your app and the backend decides which study (if any) to present.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.presentLaunchStudyIfAvailable();
```

If no launch study is pending for the current user, the call is a no-op.

Good places to call it:

* After the app finishes initializing and the main view is mounted
* On route changes in a single-page app
* After a user action where a modal or overlay is appropriate

<Warning>
  The browser SDK does not auto-present launch studies. You must call `presentLaunchStudyIfAvailable()` yourself. The heartbeat only refreshes which study is pending; it never opens the widget on its own.
</Warning>

### How it works

The SDK refreshes the pending launch study assignment whenever it heartbeats with the backend, on initialization and when the tab returns to the foreground (throttled to at most once per minute). `presentLaunchStudyIfAvailable()` uses the latest assignment and clears it after presenting.

### Overriding server-configured display

When you configure a distribution in the dashboard, the server sends display settings (mode, size, backdrop) to the SDK. These are applied automatically. If you pass `presentation` options to `presentLaunchStudyIfAvailable()`, your values override the server defaults:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.presentLaunchStudyIfAvailable({
  skipIfAlreadyExposed: true,
  presentation: {
    mode: 'overlay',
    position: 'bottom-right',
  },
});
```

### Events

Launch studies emit the same lifecycle events as manual `presentStudy()` calls: `studyPresent`, `studySkip`, `studyFinish`, and `studyError`.

<Tip>
  To set up audience targeting and launch distributions, see the [audience targeting guide](/guides/audience-targeting).
</Tip>

## Distribution

You can ship the browser SDK in either of these ways:

* Import `@trypillow/web` from your frontend app.
* Load `https://unpkg.com/@trypillow/web/dist/pillow-sdk.min.js` with a script tag.

## Lifecycle events

Register listeners before calling `presentStudy(...)` to capture every event.

| Event          | Payload            | When                                                         |
| -------------- | ------------------ | ------------------------------------------------------------ |
| `studyPresent` | `{ study }`        | The study widget has opened.                                 |
| `studySkip`    | `{ study }`        | The study was skipped (e.g. `skipIfAlreadyExposed` matched). |
| `studyFinish`  | `{ study }`        | The participant completed the study.                         |
| `studyError`   | `{ study, error }` | Something went wrong preparing or presenting the study.      |

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
sdk.on('studyPresent', ({ study }) => {
  console.log('Presented', study.id);
});

sdk.on('studyFinish', ({ study }) => {
  console.log('Finished', study.id);
});

sdk.on('studySkip', ({ study }) => {
  console.log('Skipped', study.id);
});

sdk.on('studyError', ({ study, error }) => {
  console.error('Failed', study.id, error);
});

await sdk.presentStudy({ id: 'demo-study' });
```

### Unsubscribing

`on(...)` returns an unsubscribe function. Call it when you no longer need the listener, for example when a component unmounts.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const unsubscribe = sdk.on('studyFinish', ({ study }) => {
  console.log('Finished', study.id);
});

// Later, when you no longer need this listener:
unsubscribe();
```

You can also remove a specific listener with `off(...)`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
function handleFinish({ study }) {
  console.log('Finished', study.id);
}

sdk.on('studyFinish', handleFinish);

// Later:
sdk.off('studyFinish', handleFinish);
```

## What's next

<CardGroup cols={2}>
  <Card title="Audience targeting" icon="target" href="/guides/audience-targeting">
    Target specific user cohorts with automatic study launches.
  </Card>

  <Card title="Getting started" icon="rocket" href="/sdk/web/getting-started">
    Review the full browser SDK setup flow.
  </Card>

  <Card title="Session management" icon="refresh-cw" href="/sdk/web/session-management">
    Clear or reset sessions when the user logs out or restarts a study.
  </Card>
</CardGroup>
