# V calendar 20

> Time slot booking with available/booked slot grid and confirmation

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-calendar-20).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-calendar-20
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-calendar-20-light-fd997fa405ed72d6.png
- Registry item: https://ui.cnippet.dev/r/v-calendar-20.json
- Categories: calendar
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-calendar-20.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/calendar`

## Files

### `registry/default/variants/v-calendar-20.tsx`

```tsx
"use client";

import * as React from "react";
import { Badge } from "@/registry/default/ui/badge";
import { Button } from "@/registry/default/ui/button";
import { Calendar } from "@/registry/default/ui/calendar";

const today = new Date();

const SLOTS = [
  "9:00 AM",
  "10:00 AM",
  "11:00 AM",
  "1:00 PM",
  "2:00 PM",
  "3:00 PM",
  "4:00 PM",
];

const BOOKED: Record<string, string[]> = {
  [new Date(
    today.getFullYear(),
    today.getMonth(),
    today.getDate() + 1,
  ).toDateString()]: ["10:00 AM", "2:00 PM"],
  [new Date(
    today.getFullYear(),
    today.getMonth(),
    today.getDate() + 2,
  ).toDateString()]: ["9:00 AM", "1:00 PM", "3:00 PM"],
};

export default function Particle() {
  const [date, setDate] = React.useState<Date | undefined>();
  const [slot, setSlot] = React.useState<string | undefined>();
  const [confirmed, setConfirmed] = React.useState(false);

  const booked = date ? (BOOKED[date.toDateString()] ?? []) : [];

  function handleConfirm() {
    setConfirmed(true);
    setTimeout(() => {
      setConfirmed(false);
      setDate(undefined);
      setSlot(undefined);
    }, 2500);
  }

  return (
    <div className="flex flex-col gap-3">
      <Calendar
        disabled={{ before: today }}
        mode="single"
        onSelect={(d) => {
          setDate(d);
          setSlot(undefined);
        }}
        selected={date}
      />
      {date && (
        <div className="flex flex-col gap-2">
          <p className="text-muted-foreground text-xs">
            Available slots for{" "}
            <span className="font-medium text-foreground">
              {date.toLocaleDateString("en-US", {
                day: "numeric",
                month: "short",
              })}
            </span>
          </p>
          <div className="grid grid-cols-4 gap-1.5">
            {SLOTS.map((s) => {
              const isBooked = booked.includes(s);
              return (
                <button
                  className={`rounded-md border px-2 py-1.5 font-medium text-xs transition-colors ${
                    isBooked
                      ? "cursor-not-allowed border-dashed text-muted-foreground/40"
                      : slot === s
                        ? "border-primary bg-primary text-primary-foreground"
                        : "text-foreground hover:border-foreground/30"
                  }`}
                  disabled={isBooked}
                  key={s}
                  onClick={() => setSlot(s)}
                  type="button"
                >
                  {s}
                </button>
              );
            })}
          </div>
          <div className="flex items-center justify-between pt-1">
            <div className="flex items-center gap-1.5">
              {slot && (
                <Badge variant="secondary">
                  {date.toLocaleDateString("en-US", {
                    day: "numeric",
                    month: "short",
                  })}{" "}
                  · {slot}
                </Badge>
              )}
            </div>
            <Button
              disabled={!slot || confirmed}
              onClick={handleConfirm}
              size="sm"
            >
              {confirmed ? "Booked!" : "Book Slot"}
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}
```
