# Booking widget

The booking widget puts a rental's whole booking flow on the rental's own site: the search, the car types, insurance, extras, the driver's details, the payment and the confirmation. It is one script and one tag. It works on any site that takes HTML: plain pages, WordPress, Squarespace, Webflow, Next.js, Nuxt, and pages from a CMS such as Prismic or Sanity.

```html
<script type="module" src="https://mllw.is/widget/1/mellow.js"></script>
<mellow-booking rental="your-rental" auth="pk_live_…"></mellow-booking>
```

A booking made through the widget lands on the rental's Website channel, at that channel's prices, as any other booking in Mellow.

## Before you start

1. In Mellow, open **Settings · Widget** and press **Add widget**. Pick the Website channel the site sells for. Mellow makes the widget's keys.
2. Under **Sites**, add each site that shows the widget, such as `https://www.example.com`. A widget takes at most 10 sites, https only. The live key works only from these sites.
3. The **Install** card gives the code above with your rental's slug and live key. **Copy code** copies it.
4. The **Test** card gives the same code with the test key. Use it on your own machine first: see [Test key](/widget/test-key).

The live key needs an online payment processor on the rental in Mellow, as the customer pays on the last step.

## The flow

1. **Search.** Pickup and return location, dates and times, and an optional driver age and coupon.
2. **Car type.** The car types for the search, each with its price.
3. **Insurance.** Levels, packages and add-on covers, laid out from what the rental sells, with the excess for each peril and the deposit.
4. **Extras.** Extras, and packages that hold no insurance.
5. **Driver.** The customer's details.
6. **Pay.** A bot check, then a **hold** on one car at a fixed total, then the payment on the hold in the processor's own form.
7. **Done.** The processor's paid webhook makes the booking from the hold. The page shows the reference and links to manage the booking and to check in online.

Before Pay, the order is a cart in the browser. It keeps no car. Pay holds one car for 15 minutes.

## A search on the home page

The search can stand alone and send the customer to the booking page. Give it the rental and the key, and the booking page's path in `action`:

```html
<!-- the home page -->
<mellow-search rental="your-rental" auth="pk_live_…" action="/book" layout="row"></mellow-search>

<!-- /book -->
<mellow-booking rental="your-rental" auth="pk_live_…"></mellow-booking>
```

The search goes to `/book` in the address, and the booking opens on the car types.

## Your own markup

An element with no child elements draws Mellow's default content. An element with child elements draws nothing of its own: it binds your children. Thus you can start with the one tag, and later take over one part at a time. This page gives the car types your own card and leaves every other step as it is:

```html
<mellow-booking rental="your-rental" auth="pk_live_…">
  <mellow-progress></mellow-progress>
  <mellow-step when="search"><mellow-search></mellow-search></mellow-step>
  <mellow-step when="car-type">
    <mellow-car-types>
      <template>
        <article class="car">
          <mellow-image index="0"></mellow-image>
          <h3><mellow-value of="name"></mellow-value></h3>
          <mellow-price of="payNow"></mellow-price>
          <mellow-choose>Choose</mellow-choose>
        </article>
      </template>
    </mellow-car-types>
  </mellow-step>
  <mellow-step when="insurance"><mellow-insurance></mellow-insurance></mellow-step>
  <mellow-step when="extras"><mellow-products></mellow-products></mellow-step>
  <mellow-step when="driver"><mellow-driver></mellow-driver></mellow-step>
  <mellow-step when="pay"><mellow-pay></mellow-pay></mellow-step>
  <mellow-step when="done"><mellow-confirmation></mellow-confirmation></mellow-step>
  <mellow-error></mellow-error>
  <mellow-back></mellow-back>
  <mellow-next></mellow-next>
  <mellow-summary></mellow-summary>
</mellow-booking>
```

[Elements](/widget/elements) lists every part.

## One car type on its own page

A car type's own page gets a search for that car type only. Give `<mellow-search>` the car type's slug, id or code in `car-type`, and the booking page in `action`. Once a location, both dates and both times are set, it shows the price, such as "ISK 82,800 for 5 days", with **Book**. Book opens the booking page with the search and the car type chosen, on the insurance step. When the car type is not free on those dates, the search says so and offers **See other campers** (or cars), which opens the booking page's car type step for the same dates. The customer can also change the dates.

```html
<mellow-car-type id="camper-4x4">
  <mellow-image index="0"></mellow-image>
  <mellow-features group="camping"></mellow-features>
</mellow-car-type>

<mellow-search
  rental="your-rental"
  auth="pk_live_…"
  action="/book"
  car-type="camper-4x4"
></mellow-search>
```

`<mellow-car-type>` and the other parts outside a booking or a search read the first one on the page, here the search. Put `<mellow-booking>` on `/book`.

To book on the car type's page itself, put the `<mellow-car-type>` in a booking that has no car type step. It chooses itself, and the steps follow on the same page:

```html
<mellow-booking
  rental="your-rental"
  auth="pk_live_…"
  steps="search insurance extras driver pay done"
>
  <mellow-car-type id="camper-4x4"></mellow-car-type>
  …the steps…
</mellow-booking>
```

## The script

`https://mllw.is/widget/1/mellow.js` defines every element. Any Mellow host serves the same script, and the script calls the API on its own host. The `1` is the version of the elements. A new build of version 1 reaches every site within 5 minutes, and the address does not change. Two copies of the script on one page do no harm.

The script is about 118 kB, 35 kB with gzip. Load it with `type="module"`: a module script waits for the page, so it never blocks the first paint.

## Languages

`locale` sets the language: `en`, `is`, `de`, `fr`, `es` or `it`. Without it the widget reads the page's `<html lang>`, and then takes English. Prices and dates follow the language, and the money is the rental's currency.

## Types for React and Vue

The tags and their attributes come as TypeScript types, for JSX in React 19 and for Vue templates:

- `https://mllw.is/widget/1/types/elements.d.ts`: the attributes and the events.
- `https://mllw.is/widget/1/types/react.d.ts`: the tags in React's JSX.
- `https://mllw.is/widget/1/types/vue.d.ts`: the tags in Vue templates, with `@mellow-booked` for an event.

Save the three files in your project, in one folder. The [Next.js](/widget/recipes/nextjs) and [Nuxt](/widget/recipes/nuxt) recipes use them.

The browser key goes in `auth`. React and Vue both keep `key` for themselves and never put it on an element, so `auth` works in every framework. An older snippet with `key` still works in plain HTML.

## A site with a Content-Security-Policy

The bot check at Pay runs in a small frame on Mellow's host, `https://mllw.is/widget/1/check`. A site whose `Content-Security-Policy` names its frames must allow that host in `frame-src`. Without it the frame does not load, and Pay stops with "The security check did not pass." The script, the Plain preset's stylesheet and the API come from the same host, so `script-src`, `style-src` and `connect-src` take it too:

```
frame-src https://mllw.is; script-src https://mllw.is; style-src https://mllw.is 'unsafe-inline'; connect-src https://mllw.is
```

The Plain preset writes the rental's look in a `<style>` tag, which `'unsafe-inline'` allows. The processor's card form adds its own hosts. The site no longer needs Cloudflare's Turnstile host: the frame loads Turnstile itself.

The frame opens only on the widget's own sites, and on localhost for the test key.

## Next

- [Elements](/widget/elements): every element, its attributes and its state.
- [Events and the store](/widget/events): what the widget tells your page, and how your code drives it.
- [Look](/widget/look): the Plain preset, its tokens, and your own CSS.
- [Pages and paths](/widget/pages): one page for each step, and your own router.
- [Recipes](/widget/recipes/html): the widget on each kind of site.
