# Styling and slots

Restyle the @clndr-pro/react booking form with your own components (components prop) and class names (classNames prop), with complete shadcn/ui and plain-CSS examples and Markdown description rendering.

`BookingForm`, `BookingInline` and `BookingModal` ship plain HTML with a `clndr-*` class on every part and no CSS at all. Two props change that:

* `components` swaps the elements themselves: your `Button`, `Input`, `Label`.
* `classNames` swaps the class on each part.

Use both with a component library, `classNames` alone with Tailwind, or neither and write CSS against the default classes.

## Slots: the components prop

| Slot               | Default                      | Renders                                                               |
| ------------------ | ---------------------------- | --------------------------------------------------------------------- |
| `Root`             | `<div>`                      | The outer wrapper, in every state                                     |
| `Card`             | `<div>`                      | The details form, and the success message                             |
| `Heading`          | `<h2>`                       | The page title, and the success heading                               |
| `Muted`            | `<div>`                      | Host and duration, the picked time, loading and empty messages        |
| `Error`            | `<div>`                      | Load and booking errors                                               |
| `Button`           | `<button>`                   | Every button: time slots, **Back** and **Confirm**                    |
| `Input`            | `<input>`                    | The date input, name, email, and `text` / `email` / `phone` questions |
| `Textarea`         | `<textarea>`                 | `textarea` questions                                                  |
| `Select`           | `<select>`                   | `select` questions, with the options as `<option>` children. 0.2.1+   |
| `Label`            | `<label>`                    | Every field label                                                     |
| `MarkdownRenderer` | Lazy-loaded `react-markdown` | The page description. Gets `{ content, className }`.                  |

Each slot receives the native props for its element (`className`, `onClick`, `value`, `htmlFor`, …), so any component that forwards them works. The checkbox for `checkbox` questions is always a native `<input type="checkbox">`: design-system `Input`s are text fields.

One `Button` slot covers three kinds of button, told apart only by class. To make time slots outlined while **Confirm** stays solid, give them different `classNames` (below) rather than different components.

## Class names: the classNames prop

A class you pass **replaces** the default for that part; it isn't appended. Pass `input: 'w-full'` and the `clndr-input` class is gone from inputs.

| Key               | Default                         | On                                   |
| ----------------- | ------------------------------- | ------------------------------------ |
| `root`            | `clndr-root`                    | `Root`                               |
| `heading`         | `clndr-heading`                 | `Heading`                            |
| `muted`           | `clndr-muted`                   | `Muted`                              |
| `error`           | `clndr-error`                   | `Error`                              |
| `description`     | `clndr-description`             | The wrapper `MarkdownRenderer` draws |
| `label`           | `clndr-label`                   | Every `Label`                        |
| `dateInput`       | `clndr-date-input`              | The date `Input`                     |
| `slotGrid`        | `clndr-slot-grid`               | The `<div>` around the time buttons  |
| `slotButton`      | `clndr-slot-btn`                | Each time `Button`                   |
| `card`            | `clndr-card`                    | `Card`                               |
| `input`           | `clndr-input`                   | Name, email and question `Input`s    |
| `textarea`        | `clndr-textarea`                | `Textarea`                           |
| `select`          | same as `input`                 | `Select`. 0.2.1+                     |
| `checkbox`        | `clndr-checkbox`                | Checkbox questions. 0.2.1+           |
| `buttonSecondary` | `clndr-btn clndr-btn-secondary` | **Back**                             |
| `buttonPrimary`   | `clndr-btn clndr-btn-primary`   | **Confirm**                          |

`slotButtonSelected` (`clndr-slot-btn--selected`) is in the type but nothing uses it yet: picking a time moves straight on to the form, so there's no selected state to draw. The row holding **Back** and **Confirm** has an inline `display: flex; gap: 8px` you can't override from `classNames`.

## shadcn/ui

With shadcn's `Button`, `Input`, `Label` and `Textarea`, the form matches the rest of a shadcn app, including dark mode:

```bash
npx shadcn@latest add button input label textarea
```

```tsx title="components/booking/shadcn-booking-form.tsx"
'use client';

import { BookingForm, type BookingFormProps } from '@clndr-pro/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';

/** The SDK's booking flow, drawn with your shadcn components. */
export function ShadcnBookingForm(props: Omit<BookingFormProps, 'components' | 'classNames'>) {
  return (
    <BookingForm
      {...props}
      components={{ Button, Input, Label, Textarea }}
      classNames={{
        root: 'space-y-5',
        heading: 'text-lg font-semibold',
        muted: 'text-sm text-muted-foreground',
        label: 'mb-2 block',
        dateInput: 'w-full',
        slotGrid: 'mt-4 grid grid-cols-3 gap-2',
        slotButton: 'bg-background text-foreground border hover:bg-accent hover:text-accent-foreground',
        card: 'space-y-4',
        buttonSecondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        error: 'text-sm text-destructive',
        description: 'text-sm text-muted-foreground',
      }}
    />
  );
}
```

shadcn's `Button` renders its default (solid) variant here, because the slot can't pick a variant. The `slotButton` and `buttonSecondary` classes turn that into outline and secondary looks: shadcn merges class names with `tailwind-merge`, so `bg-background` wins over the variant's `bg-primary`. If your `Button` doesn't merge classes, wrap it instead: `Button: (props) => <Button variant="outline" {...props} />` makes every button outlined.

From 0.2.1 you can pass a `Select` slot too. shadcn's `Select` is a Radix component with a different API from `<select>` (`onValueChange`, no `<option>` children), so it doesn't fit the slot directly; a styled native select does. `classNames.select` takes `input`'s classes by default, which is usually what you want.

For a calendar, times grouped by day, or a layout these slots can't reach, drop down to the hooks: the [shadcn booking widget](/tutorials/shadcn-booking-widget) tutorial builds one with shadcn's `Calendar`.

## Plain CSS

Without Tailwind, keep the default classes and style them. This stylesheet gives a clean, neutral form; change the variables at the top to match your site:

```css title="styles/clndr.css"
.clndr-root {
  --clndr-fg: #18181b;
  --clndr-muted: #71717a;
  --clndr-border: #e4e4e7;
  --clndr-bg: #ffffff;
  --clndr-accent: #2563eb;
  --clndr-danger: #dc2626;
  --clndr-radius: 8px;

  display: grid;
  gap: 1.25rem;
  max-width: 36rem;
  color: var(--clndr-fg);
  font: inherit;
}

@media (prefers-color-scheme: dark) {
  .clndr-root {
    --clndr-fg: #fafafa;
    --clndr-muted: #a1a1aa;
    --clndr-border: #3f3f46;
    --clndr-bg: #18181b;
  }
}

.clndr-heading { margin: 0; font-size: 1.5rem; font-weight: 600; line-height: 1.25; }
.clndr-muted { color: var(--clndr-muted); font-size: 0.875rem; }
.clndr-error { color: var(--clndr-danger); font-size: 0.875rem; }
.clndr-description { font-size: 0.9375rem; line-height: 1.6; }
.clndr-description a { color: var(--clndr-accent); }

.clndr-label { display: block; margin-bottom: 0.375rem; font-size: 0.875rem; font-weight: 500; }

.clndr-input,
.clndr-textarea,
.clndr-date-input {
  box-sizing: border-box;
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--clndr-border);
  border-radius: var(--clndr-radius);
  background: var(--clndr-bg);
  color: inherit;
  font: inherit;
}
.clndr-textarea { min-height: 6rem; resize: vertical; }

.clndr-input:focus-visible,
.clndr-textarea:focus-visible,
.clndr-date-input:focus-visible,
.clndr-btn:focus-visible,
.clndr-slot-btn:focus-visible {
  outline: 2px solid var(--clndr-accent);
  outline-offset: 2px;
}

.clndr-slot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: 0.5rem;
  margin-top: 1rem;
}

.clndr-slot-btn {
  padding: 0.5rem;
  border: 1px solid var(--clndr-border);
  border-radius: var(--clndr-radius);
  background: var(--clndr-bg);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.clndr-slot-btn:hover { border-color: var(--clndr-accent); color: var(--clndr-accent); }

.clndr-card {
  display: grid;
  gap: 1rem;
  padding: 1.25rem;
  border: 1px solid var(--clndr-border);
  border-radius: calc(var(--clndr-radius) + 4px);
}

.clndr-btn {
  padding: 0.625rem 1rem;
  border: 1px solid transparent;
  border-radius: var(--clndr-radius);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.clndr-btn-primary { flex: 1; background: var(--clndr-accent); color: #fff; }
.clndr-btn-secondary { background: transparent; border-color: var(--clndr-border); color: inherit; }
.clndr-btn:disabled { opacity: 0.6; cursor: progress; }

.clndr-checkbox { width: 1rem; height: 1rem; accent-color: var(--clndr-accent); }
```

Import it once (`import './clndr.css'` in a client component, or from your global stylesheet) and render the form with no `classNames` at all.

## The description

Hosts write booking page descriptions in Markdown, and the form renders them: headings, lists, links (opened in a new tab), tables, code. The renderer, `react-markdown` with `remark-gfm`, loads in its own chunk the first time a description appears, so pages without one never download it.

Its elements carry shadcn/Tailwind classes (`text-primary`, `bg-muted`, `border-border`, `text-muted-foreground`), so they pick up a shadcn theme. Two catches:

* **Tailwind only generates classes it finds in your source**, and it skips `node_modules`. Unless your own code uses the same utilities, tell Tailwind to scan the package. In Tailwind v4, add this to the CSS file that imports Tailwind:

  ```css title="app/globals.css"
  @source "../node_modules/@clndr-pro/react/dist";
  ```

  In Tailwind v3, add `'./node_modules/@clndr-pro/react/dist/**/*.{js,mjs}'` to `content`.
* **Without Tailwind** the classes do nothing and the description renders as plain HTML; style it through `.clndr-description` like the stylesheet above.

To use your own renderer (MDX, a `prose` component you already have), pass `MarkdownRenderer`. Yours is bundled normally and the built-in chunk is never requested:

```tsx title="components/booking/booking-with-markdown.tsx"
'use client';

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

export function WithOwnMarkdown() {
  return (
    <BookingInline
      slug="intro-call"
      components={{
        MarkdownRenderer: ({ content, className }) => (
          <div className={className}>
            <Markdown>{content}</Markdown>
          </div>
        ),
      }}
      classNames={{ description: 'prose prose-sm dark:prose-invert max-w-none' }}
    />
  );
}
```

The `prose` classes come from `@tailwindcss/typography`. Without the plugin, use your own typography classes.

## Dark mode

The form has no colours of its own, so it follows whatever you give it: shadcn components and tokens switch with your `.dark` class, and the plain-CSS example switches on `prefers-color-scheme` (swap that media query for your own dark selector if you have a toggle). The exception is `BookingModal`'s built-in white panel; pass `contentClassName` and `overlayClassName` to restyle it, or [use your own dialog](/tutorials/shadcn-booking-dialog).
