Skip to content
All Taste components

Mobile screens

Book a time slot

The date-and-time step of a salon booking, with the chosen service, a day strip and open slots.

step progress · service summary · day strip with closed day · 3×3 slots with taken · sticky summary + continue

Book appointmentStep 2 of 3 · Date & time
Haircut & beard trim

45 min · with Imran

₹650

Date

October 2026

Time

7 slots free
Fri 16 Oct · 1:00 pm

₹650 · pay at the salon

// Mobile-first page; the same markup maps to React Native via NativeWind (View/ScrollView/Pressable).
"use client";
import { useState } from "react";
import { ChevronLeft, Scissors } from "lucide-react";
import { cn } from "@/lib/utils";

const service = { name: "Haircut & beard trim", minutes: 45, stylist: "Imran", price: "₹650" };
const days = [
  { id: "2026-10-15", day: "Thu", date: 15 },
  { id: "2026-10-16", day: "Fri", date: 16 },
  { id: "2026-10-17", day: "Sat", date: 17 },
  { id: "2026-10-18", day: "Sun", date: 18 },
  { id: "2026-10-19", day: "Mon", date: 19, closed: true },
  { id: "2026-10-20", day: "Tue", date: 20 },
  { id: "2026-10-21", day: "Wed", date: 21 },
];
const slots = [
  { time: "10:00 am" }, { time: "10:45 am", taken: true }, { time: "11:30 am" },
  { time: "12:15 pm" }, { time: "1:00 pm" }, { time: "1:45 pm" },
  { time: "2:30 pm", taken: true }, { time: "3:15 pm" }, { time: "4:00 pm" },
];

export default function BookTimeScreen() {
  const [day, setDay] = useState(days[1]);
  const [slot, setSlot] = useState<string | null>("1:00 pm");
  const free = slots.filter((s) => !s.taken).length;

  return (
    <div className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
      <header className="flex h-12 items-center gap-1 px-2">
        <button type="button" aria-label="Back" className="grid size-11 place-items-center rounded-full hover:bg-surface-2"><ChevronLeft className="size-[22px]" /></button>
        <div className="flex-1 text-center leading-tight">
          <h1 className="text-[15px] font-semibold">Book appointment</h1>
          <p className="text-xs text-fg-muted">Step 2 of 3 · Date & time</p>
        </div>
        <span className="size-11" />
      </header>
      <div className="grid grid-cols-3 gap-1 px-4 pb-2" aria-hidden>
        {[1, 2, 3].map((s) => <i key={s} className={cn("h-1 rounded-full", s <= 2 ? "bg-primary" : "bg-surface-2")} />)}
      </div>

      <main className="flex-1 px-4 pt-2">
        <div className="flex items-center gap-3 rounded-lg bg-surface p-3">
          <span className="grid size-10 place-items-center rounded-full bg-primary-soft text-primary-soft-fg"><Scissors className="size-[18px]" /></span>
          <div className="min-w-0 flex-1">
            <p className="font-semibold">{service.name}</p>
            <p className="text-xs text-fg-muted">{service.minutes} min · with {service.stylist}</p>
          </div>
          <span className="font-semibold tabular-nums">{service.price}</span>
        </div>

        <div className="mt-5 flex items-baseline justify-between"><h2 className="text-base font-semibold">Date</h2><span className="text-xs text-fg-muted">October 2026</span></div>
        <div className="-mx-4 mt-2 flex gap-1.5 overflow-x-auto px-4 [scrollbar-width:none]">
          {days.map((d) => (
            <button
              key={d.id}
              type="button"
              disabled={d.closed}
              aria-pressed={day.id === d.id}
              aria-label={d.day + " " + d.date + " October" + (d.closed ? ", closed" : "")}
              onClick={() => setDay(d)}
              className={cn(
                "grid h-16 w-[46px] shrink-0 content-center justify-items-center gap-0.5 rounded-lg border border-border text-[17px] font-semibold tabular-nums transition",
                "disabled:cursor-not-allowed disabled:bg-surface disabled:text-fg-subtle",
                day.id === d.id && "border-primary bg-primary text-primary-fg",
              )}
            >
              <span className={cn("text-[11px] font-medium", day.id === d.id ? "text-primary-fg" : "text-fg-muted")}>{d.day}</span>
              {d.date}
            </button>
          ))}
        </div>

        <div className="mt-5 flex items-baseline justify-between"><h2 className="text-base font-semibold">Time</h2><span className="text-xs tabular-nums text-fg-muted">{free} slots free</span></div>
        <div className="mt-2 grid grid-cols-3 gap-2">
          {slots.map((s) => (
            <button
              key={s.time}
              type="button"
              disabled={s.taken}
              aria-pressed={slot === s.time}
              onClick={() => setSlot(s.time)}
              className={cn(
                "h-11 rounded border border-border text-sm font-medium tabular-nums transition",
                "disabled:cursor-not-allowed disabled:bg-surface disabled:text-fg-subtle disabled:line-through",
                slot === s.time && "border-primary bg-primary-soft font-semibold text-primary-soft-fg ring-1 ring-inset ring-primary",
              )}
            >
              {s.time}
            </button>
          ))}
        </div>
      </main>

      <footer className="sticky bottom-0 flex items-center gap-3 border-t border-border bg-bg px-4 pb-[max(env(safe-area-inset-bottom),16px)] pt-3">
        <div className="min-w-0 flex-1">
          <p className="font-semibold tabular-nums">{day.day} {day.date} Oct{slot ? " · " + slot : ""}</p>
          <p className="text-xs tabular-nums text-fg-muted">{service.price} · pay at the salon</p>
        </div>
        <button type="button" disabled={!slot} className="h-12 rounded bg-primary px-5 text-[15px] font-semibold text-primary-fg hover:bg-primary-hover disabled:opacity-50">
          Continue
        </button>
      </footer>
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.