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

# Widget Embedding - Advanced

> This guide explains how to control layout, events, modal behavior, and HTML attributes in a targeted way.

## HTML page structure

A minimal page structure with a header, an optional sidebar, and a widget in the main area.

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>My booking page</title>
  <script src="https://cdn.anny.co/widget/annyComponents.umd.latest.min.js"></script>
</head>
<body>

  <header>
    <a href="/">Your logo</a>
    <nav><!-- Navigation --></nav>
    <a-login-button base-url="https://anny.co"></a-login-button>
    <a-cart-modal-button base-url="https://anny.co" modal-layout="drawer"></a-cart-modal-button>
  </header>

  <main>
    <aside><!-- optional sidebar --></aside>

    <div>
      <a-resource-booking-panel
        base-url="https://anny.co"
        resource="my-resource"
      ></a-resource-booking-panel>

      <!-- further page content -->
    </div>
  </main>

</body>
</html>
```

* The script is loaded once in the `<head>`.
* `a-login-button` and `a-cart-modal-button` belong in the header, so they stay visible on all pages.
* Replace the panel with a page widget using `fullscreen="true"` and `nav-height` if the booking should fill the entire viewport height (see [Full-page widgets](#full-page-widgets) further below).

## Full-page widgets

Page widgets are suitable when the widget is the main content of the page.

Page widgets:

* `a-organization-page`
* `a-organization-map`
* `a-organization-calendar`
* `a-resource-page`
* `a-resource-map`
* `a-resource-calendar`
* `a-service-page`
* `a-subscription-page`
* `a-package-page`
* `a-my-bookings`

## Recommended integration for full-page layouts

Use `fullscreen="true"` so the widget fills the available viewport height. If your website has a fixed header, also pass `nav-height` so the widget is not rendered underneath it.

```html theme={null}
<a-resource-calendar
  base-url="https://anny.co"
  resource="my-resource"
  fullscreen="true"
  nav-height="72px"
></a-resource-calendar>
```

Notes:

* `fullscreen="true"` corresponds to `height: 100vh`.
* `nav-height` changes this to `calc(100vh - nav-height)`.
* If you set `height`, automatic resizing is disabled and the iframe has a fixed height.
* For `*-page`, map, calendar, and `a-my-bookings` widgets, full-page layouts are usually the cleanest integration.

## Organization map and organization calendar

These are page widgets, not modal buttons. Treat them like a full page section.

```html theme={null}
<a-organization-map
  base-url="https://anny.co"
  organization="my-org"
  fullscreen="true"
></a-organization-map>

<a-organization-calendar
  base-url="https://anny.co"
  organization="my-org"
  calendar-view="week"
  fullscreen="true"
></a-organization-calendar>
```

Use the organization map when the map is the primary interface. Use the organization calendar when availability navigation is the focus. If you want the full explore flow instead, use `a-organization-page`.

## Panels

Booking panels are deliberately kept compact and lock the navigation.

Panel widgets:

* `a-resource-booking-panel`
* `a-service-booking-panel`

Important behavior:

* Panels always use dynamic height.
* External `height` and `fullscreen` values are ignored.
* Panels are meant to embed the booking flow into your own page layout.

```html theme={null}
<a-service-booking-panel
  base-url="https://anny.co"
  service="intro-call"
></a-service-booking-panel>
```

## Popups from the widget

The widget opens a **separate browser window** for some steps. This is not only about payment — **login and logout always need this window**, even if you do not offer external payment methods.

The widget opens a popup for:

* **Login and logout** via the login button, or when a booking requires sign-in
* **External payments** such as PayPal, iDEAL, giropay, Sofort, Bancontact, EPS, Przelewy24, or TWINT
* **Card verification (3-D Secure)** when the bank asks for an extra confirmation

Your website must allow these windows. Otherwise the window stays blank, or the browser shows **This content is blocked. Contact the site owner to fix the issue**. Login and payment then cannot finish.

<Warning>
  A common blocker is the HTTP header **Cross-Origin-Opener-Policy** set to **same-origin**. The browser then isolates every window the widget opens from the iframe.
</Warning>

### What you need to set on your website

1. Set **Cross-Origin-Opener-Policy** to **same-origin-allow-popups** — or remove the header.
2. Allow pop-ups for your booking page (browser and, if applicable, company policy).
3. Do **not** change `X-Frame-Options` or `frame-ancestors` just for this. Those control whether *your* page may be embedded, not whether the widget may open a window.

<Note>
  The anny booking page on anny.co is not affected. Until the header change is live, customers can book there instead of in the website widget.
</Note>

## External payments in the widget

When customers choose a payment method in embedded checkout that leaves anny (PayPal, iDEAL, giropay, Sofort, Bancontact, EPS, Przelewy24, TWINT), the widget opens the payment page in a **centered popup**. Card and SEPA stay in the widget (Stripe overlay). Prerequisites for that window: [Popups from the widget](#popups-from-the-widget).

The checkout hint reads: **Your booking must be paid online. For this purpose, a new window will open where you can make the payment. After that the window will close automatically.**

If the browser blocks the popup, the widget shows **Your browser blocked the payment window** and the **Open payment page** button. The widget does not load the payment page inside itself. The host page is not navigated.

## Login button and cart button

These two components are configured under Organization → Settings → Booking page → Global components. They are intended as shared controls that are usually located in the site header or in a fixed navigation bar.

### Login button

Use `a-login-button` when you need a shared login/logout element that stays in sync with the widget session.

```html theme={null}
<a-login-button
  base-url="https://anny.co"
  locale="en"
  idp-uuid="your-idp-uuid"
></a-login-button>
```

Behavior:

* Loads its own login iframe.
* Opens login and logout in a popup. Your website must allow that window, see [Popups from the widget](#popups-from-the-widget).
* Automatically switches to the logged-in avatar state.
* Forwards auth changes so neighboring widgets update their authentication state.

Good placements:

* Main site header
* Mobile navigation drawer
* Top bar on booking landing pages

### Cart button

Use `a-cart-modal-button` when users should add items from booking widgets and check out later.

```html theme={null}
<a-cart-modal-button
  base-url="https://anny.co"
  modal-layout="drawer"
  label="Cart"
></a-cart-modal-button>
```

Behavior:

* Shows a live badge with the item count.
* Stays in sync with neighboring widgets on the same page.
* Opens the checkout in a modal overlay.

Common combinations:

* `a-resource-booking-panel` together with `a-cart-modal-button`
* Several booking widgets on one page with a shared cart button

## Events and tracking

Most embed widgets forward booking lifecycle events as DOM custom events. This applies to everything except the login button.

Supported event names:

* `view-page`
* `add-to-cart`
* `start-checkout`
* `complete-checkout`

Common fields in the event payload:

* `event_name`
* `value`
* `gross_value`
* `tax`
* `currency`
* `transaction_id`
* `items`

Example:

```html theme={null}
<a-resource-page
  id="booking-widget"
  base-url="https://anny.co"
  resource="my-resource"
></a-resource-page>

<script>
  const widget = document.getElementById('booking-widget')

  widget.addEventListener('complete-checkout', (event) => {
    const data = event.detail
    console.log('Checkout complete', data.transaction_id, data.gross_value, data.currency)
  })
</script>
```

GA4 example:

```html theme={null}
<script>
  const widget = document.getElementById('booking-widget')

  widget.addEventListener('complete-checkout', (event) => {
    const data = event.detail

    gtag('event', 'purchase', {
      currency: data.currency,
      value: data.gross_value ?? data.value,
      transaction_id: data.transaction_id,
      items: (data.items || []).map((item) => ({
        item_id: item.id,
        item_name: item.name,
        quantity: item.quantity,
        price: item.price,
      })),
    })
  })
</script>
```

Notes:

* `a-login-button` and `a-cart-modal-button` are helper widgets and do not send booking events.
* Page widgets, maps, calendars, booking panels, and checkout-focused pages send booking events.
* Subscription and package widgets send checkout-focused events. In practice, `view-page`, `start-checkout`, and `complete-checkout` are the most relevant.

### UTM parameters and click IDs

You don't need to do anything else for attribution. When a widget loads, it reads the query string of the **parent page URL** and automatically forwards these parameters into the embedded iframe:

| Parameter | Platform |
| - | - |
| `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` | All (Google Analytics, among others) |
| `gclid` | Google Ads |
| `fbclid` | Meta / Facebook Ads |
| `ttclid` | TikTok Ads |
| `li_fat_id` | LinkedIn Ads |

So if a user arrives on your page via `?utm_campaign=spring&gclid=abc123`, the widget carries these values through the entire booking flow. Conversion tracking works without any additional configuration.

No HTML attribute is required. The forwarding happens automatically on every widget load.

## Start and end date

Most widgets accept `start` (and sometimes `end`) to pre-navigate the calendar or map to a specific date range.

**ISO 8601 date string** for an exact date:

```html theme={null}
<a-resource-page
  base-url="https://anny.co"
  resource="my-resource"
  start="2025-06-15"
></a-resource-page>
```

**Relative keyword value** that is resolved at render time:

| Value | Meaning |
| - | - |
| `today` | Current day |
| `tomorrow` | Next day |
| `this_week` | Start of the current week |
| `next_week` | Start of the next week |
| `this_month` | Start of the current month |
| `next_month` | Start of the next month |

```html theme={null}
<a-resource-booking-panel
  base-url="https://anny.co"
  resource="my-resource"
  start="tomorrow"
></a-resource-booking-panel>
```

Relative values are supported everywhere, **except** for `a-resource-calendar` and `a-organization-calendar`, which require ISO 8601.

`end` always expects an ISO 8601 date string.

## Customization options by widget type

This section documents the public HTML attributes. Admin-generated snippets usually already cover the most important ones.

### Common attributes for embed widgets

Applies to page widgets and panel widgets. Not applicable to button widgets.

| Attribute | Purpose |
| - | - |
| `base-url` | Required. anny base URL, e.g. `https://anny.co` |
| `locale` | Force the widget language. If omitted, the browser language is used |
| `height` | Fixed iframe height, e.g. `700px` or `80vh`. Disables automatic resizing. Not for button widgets. |
| `fullscreen` | Fill the viewport height (`100vh`). Not for button widgets. |
| `nav-height` | Offset from the viewport edge when using `fullscreen`, e.g. `64px` |
| `should-login` | Require login before booking |
| `idp-uuid` | UUID of the SSO identity provider |
| `logo-url` | Custom logo for the loading animation |

All embed widgets additionally accept the design attributes from `WIDGET_DESIGNING.md`.

## Organization widgets

### `a-organization-page`

| Attribute | Purpose |
| - | - |
| `organization` | Required organization slug |
| `default-list` | Initial tab: `resources`, `services`, `subscriptions`, or `packages` |
| `view` | `grid`, `list`, or `calendar` |
| `calendar-view` | `day`, `week`, `month`, or `list` |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |
| `default-category` | Preselect a resource category |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |
| `hide-organization-filters` | Hide the filter bar |
| `hide-organization-map` | Hide the map toggle |

Note:

* The "Calendar board" option in the admin corresponds to `calendar-view="list"`.

### `a-organization-calendar`

| Attribute | Purpose |
| - | - |
| `organization` | Required organization slug |
| `calendar-view` | `day`, `week`, `month`, or `list` |
| `category` | Category slug |
| `start` | Initial start date (ISO 8601 only) |
| `end` | Initial end date |
| `hide-organization-filters` | Hide the filter bar |

### `a-organization-map`

| Attribute | Purpose |
| - | - |
| `organization` | Required organization slug |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

## Resource widgets

### `a-resource-page`

| Attribute | Purpose |
| - | - |
| `resource` | Required resource slug |
| `service` | Preselect a service |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |
| `hide-calendar` | Hide the calendar |

### `a-resource-calendar`

| Attribute | Purpose |
| - | - |
| `resource` | Required resource slug |
| `service` | Preselect a service |
| `calendar-view` | `day`, `week`, `month`, or `list` |
| `start` | Initial start date (ISO 8601 only) |
| `end` | Initial end date |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

### `a-resource-map`

| Attribute | Purpose |
| - | - |
| `resource` | Required resource slug |
| `service` | Preselect a service |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

### `a-resource-booking-panel`

| Attribute | Purpose |
| - | - |
| `resource` | Required resource slug |
| `service` | Preselect a service |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |

## Service widgets

### `a-service-page`

| Attribute | Purpose |
| - | - |
| `service` | Required service slug |
| `resource` | Optionally preselect a resource |

### `a-service-booking-panel`

| Attribute | Purpose |
| - | - |
| `service` | Required service slug |
| `resource` | Optionally preselect a resource |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `end` | Initial end date |

## Subscription and package widgets

### `a-subscription-page`

| Attribute | Purpose |
| - | - |
| `plan` | Required plan slug |

### `a-subscription-button`

| Attribute | Purpose |
| - | - |
| `plan` | Required plan slug |
| `label` | Label of the trigger button |

### `a-package-page`

| Attribute | Purpose |
| - | - |
| `package` | Required package slug |

### `a-package-button`

| Attribute | Purpose |
| - | - |
| `package` | Required package slug |
| `label` | Label of the trigger button |

## Button widgets

Applies to:

* `a-organization-button`
* `a-resource-button`
* `a-service-button`
* `a-subscription-button`
* `a-package-button`
* `a-modal-button`
* `a-cart-modal-button`

## Common modal attributes

| Attribute | Purpose |
| - | - |
| `modal-layout` | `dialog`, `drawer`, or `fullscreen` |
| `modal-size` | `sm`, `md`, or `lg` for the dialog layout |
| `modal-width` | Custom dialog width, e.g. `80vw` |
| `modal-title` | Title in the modal header |
| `close-on-backdrop` | Allow closing by clicking outside |
| `show-close-button` | Show the close button |
| `aria-label` | Accessible label for the modal dialog |

## Trigger attributes for entity buttons

Applies to:

* `a-organization-button`
* `a-resource-button`
* `a-service-button`
* `a-subscription-button`
* `a-package-button`

| Attribute | Purpose |
| - | - |
| `label` | Default label of the button |
| `button-width` | Width of the trigger |
| `button-background` | Background color of the trigger |
| `button-text` | Text color of the trigger |

`a-modal-button` is different:

* no `label` prop
* no built-in trigger styling props
* use your own element in the slot as the trigger

## Custom trigger (slot)

All button widgets accept a default slot that replaces the built-in trigger. Use this when you want to style the trigger yourself or use a different element as the button.

```html theme={null}
<!-- Custom button as trigger -->
<a-resource-button
  base-url="https://anny.co"
  resource="my-resource"
>
  <button class="my-cta-button">Book a desk</button>
</a-resource-button>

<!-- Link as trigger -->
<a-organization-button
  base-url="https://anny.co"
  organization="my-org"
>
  <a href="#">View availability</a>
</a-organization-button>

<!-- a-modal-button always requires a slot trigger -->
<a-modal-button
  base-url="https://anny.co"
  resource="my-resource"
>
  <button>Open booking</button>
</a-modal-button>
```

When a slot is used, `label`, `button-background`, `button-text`, and `button-width` are ignored. Click and keyboard handling are still managed by the widget wrapper.

### `a-resource-button`

| Attribute | Purpose |
| - | - |
| `resource` | Required resource slug |
| `service` | Optional service |
| `start` | Initial start date (see [Start and end date](#start-and-end-date)) |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

### `a-service-button`

| Attribute | Purpose |
| - | - |
| `service` | Required service slug |
| `resource` | Optionally preselect a resource |

### `a-organization-button`

| Attribute | Purpose |
| - | - |
| `organization` | Required organization slug |
| `default-list` | Initial organization tab |
| `view` | `grid`, `list`, or `calendar` |
| `calendar-view` | `day`, `week`, `month`, or `list` |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

### `a-modal-button`

Use this button when you need a custom modal trigger without an entity-specific button component.

| Attribute | Purpose |
| - | - |
| `resource` | Open the resource booking page |
| `service` | Preselect a service (together with `resource`) |
| `organization` | Open the organization explore page |
| `default-list` | Initial organization tab |
| `view` | Initial organization view |
| `calendar-view` | Initial organization calendar view |
| `hide-resource-header` | Hide the resource header |
| `hide-organization-header` | Hide the organization header |

## Helper widgets

### `a-cart-modal-button`

| Attribute | Purpose |
| - | - |
| `label` | Button label |
| `aria-label` | Accessible dialog label |
| `modal-layout` | `dialog`, `drawer`, or `fullscreen` |
| `modal-size` | Dialog size preset |
| `modal-width` | Custom dialog width |
| `modal-title` | Title in the modal header |
| `close-on-backdrop` | Close by clicking outside |
| `show-close-button` | Show the close button |
| `button-width` | Trigger width |
| `button-height` | Trigger height |
| `button-background` | Trigger background |
| `button-text` | Trigger text/icon color |
| `show-icon` | Show or hide the icon |

### `a-login-button`

| Attribute | Purpose |
| - | - |
| `locale` | Language of the login button |
| `idp-uuid` | UUID of the SSO provider |
| `button-width` | Trigger width |
| `button-height` | Trigger height |
| `button-background` | Trigger background |
| `button-text` | Trigger text/icon color |
| `small-border-radius` | Trigger corner radius |
| `show-icon` | Show or hide the icon |

### `a-my-bookings`

| Attribute | Purpose |
| - | - |
| `base-url` | Required |
| `locale` | Optional language |
| `height` | Fixed height |
| `fullscreen` | Fill the viewport height |
| `nav-height` | Header offset for fullscreen |
| `idp-uuid` | UUID of the SSO provider |

Note:

* `a-my-bookings` always requires authentication. `should-login` is not available, since login is mandatory.

## External payment in embedded checkout

When customers pay in the widget with an external payment method (for example PayPal, iDEAL, Giropay, Sofort, Bancontact, EPS, Przelewy24, or TWINT), anny opens the payment page in its own centered window. Your website and the widget stay open, and checkout keeps the data already entered.

Card and SEPA payments stay in the widget. If the payment window is blocked, the **Open payment page** button appears — payment does not continue inside the widget iframe. If someone closes the window before payment finishes, they see a message that the payment could not be processed. For login and payment to open a window reliably, see [Popups from the widget](#popups-from-the-widget).

## Recommendations

* Use the admin-generated snippet first and only refine it manually when needed.
* Use page widgets for dedicated booking pages.
* Use panels for editorial pages around the booking flow.
* Use button widgets when booking should only become visible after a click.
* Place `a-login-button` and `a-cart-modal-button` in stable, clearly visible positions in the UI.
* Make sure your website allows popups from the widget — login needs them even without payment. See [Popups from the widget](#popups-from-the-widget).
* Prefer `drawer` for desktop-side checkout flows and `fullscreen` for mobile experiences.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.