# Quickstart

A working booking form in a Next.js app in about ten minutes, using @clndr-pro/react and a publishable key.

By the end of this page a guest can open `/book` on your Next.js site, pick a slot, and book a meeting that shows up under **Meetings** in your clndr.pro dashboard. You need a clndr.pro account and a Next.js 14+ app using the App Router.

**Set your hours**

In the dashboard, open [**Availability**](https://www.clndr.pro/availability) and set the hours you take meetings. Slots only exist inside these hours, so a page with no availability shows no times at all.

Connect Google Calendar when the dashboard asks. clndr.pro then keeps your existing events off the slot list and can add Meet links to bookings. It works without Google too: bookings still land in **Meetings**.

**Create a booking page**

Open [**Booking pages**](https://www.clndr.pro/booking), choose **New booking page**, and note its **Link**, the slug at the end of its URL. A page at `www.clndr.pro/ada/intro-call` has the slug `intro-call`.

Leave **Confirmation** on *Instant* for now, so test bookings confirm immediately. *Needs approval* is covered in [How booking works](/concepts#booking-types).

**Create a publishable key**

Open [**API keys**](https://www.clndr.pro/api-keys), choose **Create API Key**, pick **Publishable key** and give it a name like "Marketing site". Copy the key: it starts with `clndr_pk_` and is shown once.

A publishable key is meant to sit in browser code. It can read your booking pages and slots and create bookings, nothing else. [API keys](/api-keys) explains the two key types.

**Install the React package**

```bash
npm install @clndr-pro/react
```

Then put the key in `.env.local`:

```bash title=".env.local"
NEXT_PUBLIC_CLNDR_PUBLISHABLE_KEY=clndr_pk_...
```

Use `@clndr-pro/react` 0.2.1 or later. Earlier versions call `https://clndr.pro`, which redirects to `www` and loses the key on the way, so every request fails with `401 Missing API key`. If you're stuck on an older version, pass `baseUrl="https://www.clndr.pro"` to `ClndrProvider`.

**Add the provider**

`ClndrProvider` holds the API client every component and hook uses. It's a client component, so give it its own file instead of turning your root layout into one:

```tsx title="app/providers.tsx"
'use client';

import { ClndrProvider } from '@clndr-pro/react';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ClndrProvider publishableKey={process.env.NEXT_PUBLIC_CLNDR_PUBLISHABLE_KEY!}>
      {children}
    </ClndrProvider>
  );
}
```

```tsx title="app/layout.tsx" {1,7}
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

**Render the booking form**

```tsx title="app/book/page.tsx"
import { BookingWidget } from './booking-widget';

export const metadata = { title: 'Book a call' };

export default function BookPage() {
  return (
    <main className="mx-auto max-w-xl px-4 py-12">
      <BookingWidget />
    </main>
  );
}
```

```tsx title="app/book/booking-widget.tsx"
'use client';

import { BookingInline } from '@clndr-pro/react';

export function BookingWidget() {
  return (
    <BookingInline
      slug="intro-call"
      onBooked={(bookingId) => console.log('booked', bookingId)}
    />
  );
}
```

Replace `intro-call` with your slug. The page itself stays a Server Component; only the widget runs in the browser.

**Book a test meeting**

Run `npm run dev`, open [localhost:3000/book](http://localhost:3000/book), pick a date and a time, and confirm with your own email address. You'll get the confirmation email, and the meeting appears under [**Meetings**](https://www.clndr.pro/meetings).

## Make it look like your site

The form ships with plain, unstyled markup. Hand it your own `Button`, `Input` and `Label` and it renders with them:

```tsx title="app/book/booking-widget.tsx"
'use client';

import { BookingInline } from '@clndr-pro/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';

export function BookingWidget() {
  return (
    <BookingInline
      slug="intro-call"
      components={{ Button, Input, Label }}
      classNames={{
        root: 'space-y-6',
        heading: 'text-2xl font-semibold',
        muted: 'text-sm text-muted-foreground',
        slotGrid: 'mt-4 grid grid-cols-3 gap-2',
      }}
    />
  );
}
```

[Styling and slots](/react/styling) lists every slot and class name. To design the whole flow yourself (a calendar, grouped time slots, your own form), use the hooks instead: the [shadcn booking widget](/tutorials/shadcn-booking-widget) tutorial builds one.

## Next

* Keep the guest's details off the browser-to-clndr.pro path with [a server action](/tutorials/server-actions).
* Approve or decline requests from your own admin screen: [Approve bookings in your admin](/tutorials/approvals-dashboard).
* Something not working? [Troubleshooting](/troubleshooting) covers the usual suspects: `401`, empty slot lists, and dates a day off.
