---
name: anny-widget-integration
description: >
How to embed anny booking widgets and the AI chat assistant on a website. Use when integrating
anny widgets into a site, landing page, or web app. Covers script setup, widget types, seamless
layout embedding (panels in cards, no extra padding, overflow hidden, border radius),
full-page vs panel vs button vs AI chat widgets, fullscreen with nav-height for single-scroll
pages, height behavior, design tokens, login/cart placement in the top-right nav,
and asking for the correct resource/service/organization/plan/package slug before generating code.
---
# anny Widget Integration
## Quick Facts
- Widgets are **web components** (custom HTML elements).
- One `<script>` tag loads all widgets. Load it once per page.
- Widgets communicate via the iframe they contain — **no extra wrapper div or padding is needed**. Place the element directly where you want the content.
- Panels auto-resize to their content. Page widgets and maps support `fullscreen` or a fixed `height`.
- **Prefer `fullscreen="true"` + `nav-height` for full-page booking** so the page itself never scrolls — only the widget scrolls internally. This avoids the awkward double scrollbar (page scroll *and* widget scroll).
- **Put `a-login-button` and `a-cart-modal-button` in the top-right of the main navigation.** The cart should show icon + count indicator only (`show-icon="true"`, no label).
- `a-agent-chat` embeds the **anny AI chat assistant** — a launcher in the corner, an in-flow button, or an inline frame. It can drive the whole booking flow conversationally.
- UTMs and click IDs from the current page URL are forwarded automatically.
- Start in admin when possible. Admin-generated snippets are the safest source for widget type, slug wiring, and supported options.
## Ask First
Before generating integration code, make sure these inputs are known:
1. Which widget type is needed: page, panel, button, utility, or AI chat?
2. Which entity is being embedded: `resource`, `service`, `organization`, `plan`, or `package`?
3. What is the exact slug for that entity? (For `a-agent-chat`, the `organization` slug and an `assistant-id`.)
4. Is booking the main page content, part of an existing layout, or behind a CTA?
5. Are login and cart needed in the main nav (top-right)?
If the widget depends on an entity slug and the slug is missing, **ask the user for it before writing the final snippet**.
Use these prompts:
- "What is the exact resource slug?"
- "What is the exact service slug?"
- "What is the exact organization slug?"
- "What is the exact plan slug?"
- "What is the exact package slug?"
Do not guess slugs.
### When details are missing, ask for the admin snippet
If a required value isn't obvious — an `assistant-id` for `a-agent-chat`, an `idp-uuid` for SSO, or any slug — **ask the user to paste the widget code copied from the anny admin area** rather than guessing. Admin generates a ready-made snippet with the element, the correct slugs/ids, and supported options already wired.
Prompt:
- "Open this widget in the anny admin area, copy the generated embed code, and paste it here — I'll adapt it to your site."
Then keep the admin-provided slugs/ids verbatim and only adjust layout, theming, and placement around them.
## Integration Workflow
1. Ask for the missing slug if the widget is entity-based.
2. Choose the right widget type: page, panel, button, utility, or AI chat.
3. Load the widget script once.
4. Add the required entity slug attribute for that widget.
5. Place the widget directly into the page layout. For full-page booking, prefer `fullscreen="true"` + `nav-height` so only the widget scrolls.
6. If embedding inside a card, style the wrapper, not the widget.
7. Add `a-login-button` and `a-cart-modal-button` once in the top-right of the main nav when needed (cart = icon + count only).
8. Add design tokens as HTML attributes if brand customization is needed.
9. Add event listeners only if external tracking hooks such as GA4 are needed.
## Fast Decision Guide
| Need | Choose |
|---|---|
| Booking is the main content of the page | page widget (`fullscreen="true"` + `nav-height`) |
| Existing marketing/editorial page needs embedded booking flow | panel widget |
| Booking should open after a click | button widget |
| Shared auth or cart control in nav | utility widget (top-right) |
| Conversational / assisted booking, support-style helper | AI chat (`a-agent-chat`) |
Prefer admin-generated snippets when the user already has the widget configured in anny admin. Use manual HTML when building the integration directly in code or when custom layout control is needed.
## Preferred Defaults For New Websites
When building a new website from scratch, prefer these defaults unless the user asks for a different pattern:
1. Use page content plus a booking panel in the layout.
2. **For a dedicated booking page, use a fullscreen page widget with `nav-height` set to your header height.** This gives a single scroll *inside* the widget and none on the page — no double scrollbar.
3. **Put `a-login-button` and `a-cart-modal-button` in the top-right of the main navigation.** Render the cart as icon + count only (`show-icon="true"`, no `label`).
4. Use `a-subscription-button` for plans instead of embedding the plan page inline.
5. Use a fullscreen calendar page when calendar browsing is a primary use case.
6. Add `a-agent-chat` as a bottom-right launcher when the site benefits from assisted/conversational booking or a support helper.
| Scenario | Preferred pattern |
|---|---|
| Dedicated booking page | page widget with `fullscreen="true"` + `nav-height="64px"` (single in-widget scroll) |
| Landing page or marketing page with booking | content + `a-resource-booking-panel` or `a-service-booking-panel` |
| Auth + cart in site chrome | `a-login-button` + `a-cart-modal-button`, top-right of the main nav (cart = icon + count) |
| Plan / subscription sales | `a-subscription-button` |
| Calendar-first experience | `a-resource-calendar` or `a-organization-calendar` with `fullscreen="true"` + `nav-height` |
| Assisted / conversational booking | `a-agent-chat` launcher (`placement="bottom-right"`) |
Do not default every new site to a full embedded page widget *inside* a content layout. Either give booking its own fullscreen page (with `nav-height`), or embed a panel in the existing layout — pick based on whether booking is the whole page.
## Script
```html
<script src="https://cdn.anny.co/widget/annyComponents.umd.latest.min.js"></script>
```
Place once in `<head>` or before `</body>`. Do not load more than once per page.
## Widget Types
| Type | Elements | Use when |
|---|---|---|
| **Page widget** | `a-organization-page`, `a-resource-page`, `a-service-page`, `a-resource-calendar`, `a-resource-map`, `a-organization-map`, `a-organization-calendar`, `a-subscription-page`, `a-package-page`, `a-my-bookings` | Booking is the main content of the page or section |
| **Panel widget** | `a-resource-booking-panel`, `a-service-booking-panel` | You already have a page layout and want the booking flow embedded inside it |
| **Button widget** | `a-resource-button`, `a-service-button`, `a-organization-button`, `a-subscription-button`, `a-package-button`, `a-modal-button` | Booking opens on user click in a modal |
| **Utility** | `a-login-button`, `a-cart-modal-button` | Shared auth/cart controls — place top-right in the main nav |
| **AI chat** | `a-agent-chat` | Conversational / assisted booking and support — launcher, in-flow button, or inline frame |
## Minimal Page Skeleton
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Booking</title>
<script src="https://cdn.anny.co/widget/annyComponents.umd.latest.min.js"></script>
</head>
<body>
<!-- Main nav: brand on the left, links in the middle, auth + cart top-right -->
<header style="display: flex; align-items: center; gap: 16px; padding: 0 24px; height: 64px;">
<a href="/">Logo</a>
<nav style="flex: 1;"><!-- navigation links --></nav>
<a-login-button base-url="https://anny.co"></a-login-button>
<a-cart-modal-button base-url="https://anny.co" modal-layout="drawer" show-icon="true"></a-cart-modal-button>
</header>
<main>
<a-resource-booking-panel
base-url="https://anny.co"
resource="my-resource"
></a-resource-booking-panel>
</main>
</body>
</html>
```
For a **dedicated booking page** where booking is the whole page, swap the panel in `<main>` for a fullscreen page widget so only the widget scrolls:
```html
<main>
<a-resource-page
base-url="https://anny.co"
resource="my-resource"
fullscreen="true"
nav-height="64px"
></a-resource-page>
</main>
```
## Seamless Embedding Patterns
### Panel inside a card
Panels resize dynamically. Put them directly inside a card — no extra wrapper or padding needed. Apply `overflow: hidden` and `border-radius` to the card, not the widget element.
```html
<div style="border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0,0,0,.1);">
<a-resource-booking-panel
base-url="https://anny.co"
resource="my-resource"
></a-resource-booking-panel>
</div>
```
Do **not** add padding inside the card around the widget — the widget handles its own internal spacing.
### Full-page widget with a single internal scroll (preferred for booking pages)
```html
<section style="height: 100vh;">
<a-organization-page
base-url="https://anny.co"
organization="my-org"
fullscreen="true"
nav-height="64px"
></a-organization-page>
</section>
```
- `fullscreen="true"` → `height: 100vh`
- `nav-height` subtracts your sticky header: `calc(100vh - 64px)`. Match it to the real header height so the widget sits flush below the nav and fills the rest of the viewport exactly.
- The result: the **page does not scroll — only the widget scrolls internally**. This is the preferred pattern for any full-page booking, calendar, or map experience. It avoids the double-scrollbar problem where both the page and the widget scroll independently.
- If you give the widget a fixed `height` attribute, auto-resize is disabled — do not combine `height` with `fullscreen`.
Use this especially for booking-first and calendar-first pages where the widget should dominate the layout.
### Widget beside editorial content
```html
<div style="display: grid; grid-template-columns: 1fr 420px; gap: 32px; align-items: start;">
<div>
<h1>Book your space</h1>
<p>…description…</p>
</div>
<a-resource-booking-panel
base-url="https://anny.co"
resource="my-resource"
></a-resource-booking-panel>
</div>
```
Panels expand vertically as the user navigates the booking flow. Use `align-items: start` so they don't stretch.
This is the preferred default for new marketing sites and content-driven pages.
### Button widget with a custom trigger
All button widgets accept a slotted element that replaces the built-in button.
```html
<a-resource-button base-url="https://anny.co" resource="my-resource">
<button class="hero-cta">Book now</button>
</a-resource-button>
```
When a slot is provided, `label`, `button-background`, `button-text`, and `button-width` are ignored. Click and keyboard handling are still managed by the widget wrapper.
### Login and cart in the top-right of the main nav
Put `a-login-button` and `a-cart-modal-button` together in the **top-right of the main navigation** — the spot users instinctively look for account and cart. Render the cart as an **icon with a count indicator only** (no text label), like any e-commerce header.
```html
<header style="display: flex; align-items: center; gap: 16px; padding: 0 24px; height: 64px;">
<a href="/">Logo</a>
<nav style="flex: 1;">…links…</nav>
<!-- pushed to the far right by the flex:1 nav above -->
<a-login-button base-url="https://anny.co"></a-login-button>
<a-cart-modal-button
base-url="https://anny.co"
modal-layout="drawer"
show-icon="true"
></a-cart-modal-button>
</header>
```
- These are utility widgets — put them in a stable position users can always find. They sync auth state and cart across all widgets on the page automatically.
- The cart button shows its own item-count indicator; keep it icon-only (omit `label`) so it reads as a standard cart icon.
- `drawer` is a good `modal-layout` for the cart so it slides in from the right, matching its top-right anchor.
For new sites, this top-right placement should be the default.
### Plan CTA button
For plans and subscriptions, prefer a button that opens checkout instead of embedding the full plan page into a mixed-content page.
```html
<a-subscription-button
base-url="https://anny.co"
plan="my-plan"
label="Choose plan"
modal-layout="drawer"
></a-subscription-button>
```
Use `a-subscription-page` only when the whole page is dedicated to the plan checkout experience.
## AI Chat Assistant (`a-agent-chat`)
`a-agent-chat` embeds the anny **AI chat assistant**. The visitor can ask questions and the assistant can guide them through the booking flow conversationally; when it needs to show the booking UI it opens it in a modal over the host page. Login (when required) and the user's access token are handled first-party on the anny chat page — the widget only renders the launcher/panel chrome and forwards configuration.
It needs the `organization` slug and an `assistant-id` (created in anny admin). Ask for both before generating the snippet.
### Placement
Set `placement` to choose how the chat appears:
| `placement` | Result | Use when |
|---|---|---|
| `bottom-right` (default) | Fixed launcher button in the viewport corner; opens a floating panel | Site-wide support / assisted booking helper |
| `relative` / `none` | Launcher rendered in normal document flow (e.g. inside a section) | A "chat with us" button placed in your own layout |
| `inline` | No launcher — the chat frame is embedded directly | Chat *is* the page section; give the parent a fixed size |
### Floating launcher (most common)
```html
<a-agent-chat
base-url="https://anny.co"
organization="my-org"
assistant-id="my-assistant"
placement="bottom-right"
greeting-message="Need a hand? Chat with us!"
></a-agent-chat>
```
The launcher is fixed bottom-right and the panel goes fullscreen on mobile. Place it once per page, near `</body>`.
### Inline frame
When the chat is itself a page section, use `placement="inline"` and give the **parent element** a fixed width/height — the frame fills it.
```html
<div style="width: 400px; height: 560px;">
<a-agent-chat
base-url="https://anny.co"
organization="my-org"
assistant-id="my-assistant"
placement="inline"
></a-agent-chat>
</div>
```
### Chat attributes
| Attribute | Purpose |
|---|---|
| `organization` | **Required.** Organization slug whose chat to embed |
| `assistant-id` | Assistant identifier (from anny admin) |
| `placement` | `bottom-right` (default), `relative` / `none`, or `inline` |
| `start-message` | Message the conversation is pre-seeded with |
| `greeting-message` | Teaser bubble shown next to the launcher before it's opened |
| `launcher-label` | Accessible label for the launcher button |
| `launcher-color` | Launcher button background (defaults to `primary-color`, else the branded gradient) |
| `consent` | `accepted` (pre-opted-in, no banner) or `hidden` (no banner, no persistent visitor id); omit to show the chat's own consent banner |
| `supports-login` | Require login before the chat is usable (gate handled first-party) |
It also accepts the shared design tokens (`primary-color`, `primary-background`, `text-primary-color`, `text-secondary-color`, `button-text`, `logo-url`, `locale`, …) which are forwarded to the chat surface.
## Height Behavior
| Widget type | Default behavior | Override |
|---|---|---|
| Panel | Auto-resizes to content | Cannot be overridden — `height` and `fullscreen` are ignored |
| Page / map / calendar | Auto-resizes unless overridden | `height="700px"` (fixed) or `fullscreen="true"` (viewport fill) |
| Button widget | No height — opens modal | Modal size controlled by `modal-layout`, `modal-size`, `modal-width` |
## Required Attributes
Every widget needs:
- `base-url` — the anny base URL, e.g. `https://anny.co`
- The entity slug for the widget type (`resource`, `organization`, `service`, `plan`, or `package`)
Utility widgets are the exception:
- `a-login-button` only needs `base-url` unless SSO options are required
- `a-cart-modal-button` only needs `base-url`
```html
<a-resource-booking-panel
base-url="https://anny.co"
resource="my-resource-slug"
></a-resource-booking-panel>
```
### Entity slug mapping
| Widget | Required entity attribute |
|---|---|
| `a-resource-page`, `a-resource-map`, `a-resource-calendar`, `a-resource-booking-panel`, `a-resource-button` | `resource` |
| `a-service-page`, `a-service-booking-panel`, `a-service-button` | `service` |
| `a-organization-page`, `a-organization-map`, `a-organization-calendar`, `a-organization-button` | `organization` |
| `a-subscription-page`, `a-subscription-button` | `plan` |
| `a-package-page`, `a-package-button` | `package` |
| `a-modal-button` | usually `resource` or `organization` |
### Common functional attributes
| Attribute | Meaning |
|---|---|
| `locale` | force widget language |
| `should-login` | require login before booking |
| `idp-uuid` | SSO identity provider |
| `height` | fixed iframe height for page widgets |
| `fullscreen` | full viewport height for page widgets |
| `nav-height` | subtract sticky header height from fullscreen widgets |
| `default-list` | initial tab for organization widgets |
| `view` | initial organization view |
| `calendar-view` | day/week/month/list view where supported |
| `default-category` | preselect category on `a-organization-page` |
### Date parameters
Many widgets support `start` and some also support `end`.
- ISO format: `2025-06-15`
- Relative values for `start`: `today`, `tomorrow`, `this_week`, `next_week`, `this_month`, `next_month`
- `end` should be an ISO date string
- Relative values are **not** supported by `a-resource-calendar` or `a-organization-calendar`
```html
<a-resource-booking-panel
base-url="https://anny.co"
resource="my-resource"
start="tomorrow"
></a-resource-booking-panel>
```
## Design Token Pass-Through
Pass brand colors and styling directly as HTML attributes. No CSS override needed.
```html
<a-organization-page
base-url="https://anny.co"
organization="my-org"
primary-color="#0f766e"
primary-background="#f8fafc"
panel-background="#ffffff"
small-border-radius="8px"
panel-shadow="0 8px 32px rgba(0,0,0,.08)"
text-primary-color="#111827"
></a-organization-page>
```
### Color tokens
| Token | Purpose | Fallback |
|---|---|---|
| `primary-color` | Main accent color | — |
| `primary-color-rgb` | RGB triplet for transparent accent usage, e.g. `15, 118, 110` | Auto-calculated from `primary-color` |
| `primary-color-hover` | Hover state of main accent | — |
| `primary-color-overlay` | Accent overlay surface | — |
| `secondary-color` | Secondary accent | — |
| `primary-stroke-color` | Stroke / outline accent; also sets table borders | — |
### Text tokens
| Token | Purpose |
|---|---|
| `text-primary-color` | Main text color |
| `text-secondary-color` | Secondary text color |
| `text-tertiary-color` | Muted text color |
### Background tokens
| Token | Purpose | Fallback |
|---|---|---|
| `primary-background` | Page/outer background | — |
| `primary-background-rgb` | RGB triplet for layered page background | Auto-calculated from `primary-background` |
| `panel-background` | Panel surface. Also sets `primary-background` if `primary-background` is not provided. | — |
| `panel-background-rgb` | RGB triplet for panel surface | Auto-calculated from `panel-background` or `primary-background` |
| `panel-background-light` | Lighter panel variant | — |
| `panel-background-overlay` | Overlay background | — |
| `panel-background-overlay-dense` | Dense overlay background | — |
| `panel-background-dark` | Dark panel surface | — |
| `input-background` | Input field background | — |
### Border and radius tokens
| Token | Purpose | Notes |
|---|---|---|
| `small-border-radius` | Base radius unit | All larger radii are calculated from this: `1.5×`, `2×`, `2.5×`, `3×`, `4×`, `5×` |
| `panel-border-radius` | Explicit large panel radius | Overrides the scale-up from `small-border-radius` |
| `detail-border-radius` | Explicit detail card radius | Overrides the scale-up from `small-border-radius` |
| `light-border-color` | Light border color | — |
| `table-border-color` | Table and grid border color | — |
| `panel-border-style` | Border style token | — |
### Shadow tokens
| Token | Purpose |
|---|---|
| `panel-shadow` | Panel shadow; also sets dropdown shadow |
| `detail-shadow` | Detail card shadow |
### Button styling tokens
These affect the outer CTA button for button widgets, cart button, and login button.
| Token | Purpose | Fallback |
|---|---|---|
| `button-background` | Trigger fill color | Falls back to `input-background` |
| `button-text` | Trigger text and icon color | Falls back to `text-primary-color` |
| `button-width` | Trigger width | — |
| `button-height` | Trigger height | — |
| `show-icon` | Show or hide icon on login/cart | — |
### Utility tokens
| Token | Purpose |
|---|---|
| `logo-url` | Custom logo shown in the pulsing loading animation only. Does not affect logos inside the booking UI. |
### RGB tokens — auto-calculation
`primary-color-rgb`, `panel-background-rgb`, and `primary-background-rgb` are automatically calculated from their hex counterparts when not explicitly provided. You only need to pass them if you want to supply a custom RGB triplet (e.g. for a non-hex color source).
Design rules:
- Prefer configuring design in admin first. HTML token overrides are for one-off page-specific changes.
- Keep contrast high between primary actions, text, and backgrounds.
- `small-border-radius` is the simplest way to control all corner radii at once. Use `panel-border-radius` and `detail-border-radius` only to override specific levels.
- Do not combine very strong shadows, very round corners, and low-contrast text simultaneously.
## Modal Layout Options
Button widgets open content in a modal. Control the layout with `modal-layout`:
| Value | Description |
|---|---|
| `dialog` | Centered dialog. Size with `modal-size` (`sm`/`md`/`lg`) or `modal-width` |
| `drawer` | Side panel from the right — good for checkout flows |
| `fullscreen` | Full viewport — best for mobile |
```html
<a-resource-button
base-url="https://anny.co"
resource="my-resource"
modal-layout="drawer"
></a-resource-button>
```
### Modal behavior attributes
| Attribute | Purpose |
|---|---|
| `modal-layout` | `dialog`, `drawer`, or `fullscreen` |
| `modal-size` | `sm`, `md`, or `lg` for dialog layout |
| `modal-width` | custom width such as `80vw`; overrides `modal-size` |
| `modal-title` | modal header title |
| `close-on-backdrop` | allow close on outside click |
| `show-close-button` | show close button |
| `aria-label` | accessible label for modal |
## Tracking Events
Booking widgets can emit DOM custom events you can subscribe to on the widget element.
Supported event names:
- `view-page`
- `add-to-cart`
- `start-checkout`
- `complete-checkout`
Common payload fields:
- `event_name`
- `value`
- `gross_value`
- `tax`
- `currency`
- `transaction_id`
- `items`
```html
<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)
})
</script>
```
`a-login-button` and `a-cart-modal-button` are utility widgets and are not booking event emitters.
## Common Mistakes to Avoid
| Mistake | Fix |
|---|---|
| Adding padding around a panel inside a card | Apply `border-radius` + `overflow: hidden` to the card wrapper; put the widget element directly inside |
| Wrapping the widget in a flex/grid container with `align-items: stretch` | Use `align-items: start` so the panel can grow freely |
| Loading the script multiple times | One `<script>` tag per page is enough |
| Setting `height` or `fullscreen` on a panel widget | These are ignored on panels — panels always auto-resize |
| Placing `a-login-button` / `a-cart-modal-button` per entity instead of once in the nav | These are page-level shared controls — one set, top-right of the main nav |
| A full-page widget that makes the page scroll *and* the widget scroll | Use `fullscreen="true"` + `nav-height` so only the widget scrolls; don't wrap a fullscreen widget in a tall scrolling container |
| Giving the cart button a text label in the header | Keep it icon + count only (`show-icon="true"`, no `label`) like a standard cart |
| Setting `height`/`fullscreen` on `a-agent-chat` inline mode | Size the **parent element** instead — the inline chat frame fills its container |
| Styling the widget element itself with CSS borders/radius | Style the wrapper element instead; the widget manages its own internal layout |
## Attribution Forwarding
UTM params and click IDs (`gclid`, `fbclid`, `ttclid`, `li_fat_id`) from the page URL are automatically forwarded into the widget iframe. No extra setup needed for conversion tracking.
