Join the waitlist

<BookingCalendar />

Drop the Slotr booking experience into any React app with one component.

React component

<BookingCalendar> is the whole Slotr booking flow — pick a day, pick a time, enter details, confirm — rendered as a single React component. You pass it one thing, a booking id, and it takes care of the rest.

Read this page + ".md" and use it to add @slotr/react to my app. Follow the component's API and props from that page exactly.

Booking.tsx
Loading preview…
import { BookingCalendar } from "@slotr/react/booking-calendar";
import "@slotr/react/styles.css";
function App() {
return <BookingCalendar id={DEMO_ID} />;
}

The id is opaque — your backend issues it (today an encrypted token). The preview above uses a signed demo id; pass your own to show real availability.

How it works

Under the hood <BookingCalendar> renders an <iframe> pointing at book.slotr.dev. That matters: the booking logic — availability, pricing, time-zones, payment — runs on Slotr, not in your bundle. Your app just places the component and passes props.

Three things happen automatically:

  • Self-sizing. The widget reports its height to the parent via a postMessage (slotr:resize); the component listens and resizes the iframe, so there is never an inner scrollbar or a guessed fixed height.
  • Theming from props. theme, accent, radius and lang are forwarded to the widget as URL parameters. Change a prop and the booking UI restyles — no rebuild, no CSS from you.
  • An opaque id. Whatever id you pass is sent through untouched. There is no client-side encryption or serialization in the component, so when the id representation changes server-side, your code does not.

You never touch the iframe, the resize listener, or the query string — that is exactly what the component removes.

Try it

Change the props and watch the real component update live:

Theme
Accent

Out-of-the-box modal

Most products open booking in a dialog. <BookingCalendar.Dialog> is that dialog, already built: a transparent, full-viewport canvas over a dimmed overlay, so the widget's own open/close animations play instead of being boxed in.

import { useState } from "react";

function Example({ id }: { id: string }) {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Book now</button>
      <BookingCalendar.Dialog open={open} onOpenChange={setOpen}>
        <BookingCalendar id={id} />
      </BookingCalendar.Dialog>
    </>
  );
}

Two ways to use it

Keep going: Usage walks every real scenario with a live example; Props is the full contract.

On this page