6.8k

Building Blocks for the Web

Clean, modern building blocks. Works with all Svelte projects. Copy and paste into your apps. Open Source. Free forever.

Files
components/calendar-01.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Calendar type="single" bind:value class="rounded-lg border shadow-sm" />
A simple calendar.
calendar-01
Files
components/calendar-02.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Calendar type="single" bind:value class="rounded-lg border shadow-sm" numberOfMonths={2} />
Multiple months with single selection.
calendar-02
Files
components/calendar-03.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate, type DateValue } from "@internationalized/date";

  let value = $state<CalendarDate[]>([
    new CalendarDate(2025, 6, 12),
    new CalendarDate(2025, 7, 24),
  ]);
  // svelte-ignore state_referenced_locally
  let placeholder = $state<DateValue>(value[0]);
</script>

<Calendar
  type="multiple"
  bind:value
  bind:placeholder
  maxDays={5}
  class="rounded-lg border shadow-sm"
  numberOfMonths={2}
/>
Multiple months with multiple selection.
calendar-03
Files
components/calendar-04.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 9),
    end: new CalendarDate(2025, 6, 26),
  });
</script>

<RangeCalendar bind:value class="rounded-lg border shadow-sm" />
Single month with range selection
calendar-04
Files
components/calendar-05.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 12),
    end: new CalendarDate(2025, 7, 15),
  });
</script>

<RangeCalendar bind:value class="rounded-lg border shadow-sm" numberOfMonths={2} />
Multiple months with range selection
calendar-05
Files
components/calendar-06.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 12),
    end: new CalendarDate(2025, 6, 26),
  });
</script>

<div class="flex min-w-0 flex-col gap-2">
  <RangeCalendar bind:value minDays={5} class="rounded-lg border shadow-sm" />
  <div class="text-muted-foreground text-center text-xs">A minimum of 5 days is required</div>
</div>
Range selection with minimum days
calendar-06
Files
components/calendar-07.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 18),
    end: new CalendarDate(2025, 7, 7),
  });
</script>

<div class="flex min-w-0 flex-col gap-2">
  <RangeCalendar
    bind:value
    minDays={2}
    maxDays={20}
    numberOfMonths={2}
    class="rounded-lg border shadow-sm"
  />
  <div class="text-muted-foreground text-center text-xs">
    Your stay must be between 2 and 20 nights
  </div>
</div>
Range selection with minimum and maximum days
calendar-07
Files
components/calendar-08.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Calendar
  type="single"
  bind:value
  class="rounded-lg border shadow-sm"
  minValue={new CalendarDate(2025, 6, 12)}
/>
Calendar with disabled days
calendar-08
Files
components/calendar-09.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate, isWeekend } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 17),
    end: new CalendarDate(2025, 6, 20),
  });
</script>

<div class="flex min-w-0 flex-col gap-2">
  <RangeCalendar
    bind:value
    numberOfMonths={2}
    isDateDisabled={(date) => isWeekend(date, "en-US")}
    class="rounded-lg border shadow-sm"
    excludeDisabled
  />
  <div class="text-muted-foreground text-center text-xs">
    Your stay cannot extend through the weekend
  </div>
</div>
Calendar with disabled weekends
calendar-09
Files
components/calendar-10.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Card from "$lib/components/ui/card/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Card.Root>
  <Card.Header>
    <Card.Title>Appointment</Card.Title>
    <Card.Description>Find a date.</Card.Description>
    <Card.Action>
      <Button size="sm" variant="outline" onclick={() => (value = today(getLocalTimeZone()))}>
        Today
      </Button>
    </Card.Action>
  </Card.Header>
  <Card.Content>
    <Calendar type="single" bind:value class="bg-transparent p-0" />
  </Card.Content>
</Card.Root>
Today button
calendar-10
Files
components/calendar-11.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange>({
    start: new CalendarDate(2025, 6, 17),
    end: new CalendarDate(2025, 6, 20),
  });
</script>

<div class="flex min-w-0 flex-col gap-2">
  <RangeCalendar
    bind:value
    numberOfMonths={2}
    minValue={new CalendarDate(2025, 6, 1)}
    maxValue={new CalendarDate(2025, 7, 31)}
    class="rounded-lg border shadow-sm"
  />
  <div class="text-muted-foreground text-center text-xs">We are open in June and July only.</div>
</div>
Start and end of month
calendar-11
Files
components/calendar-12.svelte
<script lang="ts">
  import * as Card from "$lib/components/ui/card/index.js";
  import * as Select from "$lib/components/ui/select/index.js";
  import { CalendarDate } from "@internationalized/date";
  import RangeCalendar from "../ui/range-calendar/range-calendar.svelte";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange | undefined>({
    start: new CalendarDate(2025, 9, 9),
    end: new CalendarDate(2025, 9, 17),
  });

  const localizedStrings = {
    en: {
      title: "Book an appointment",
      description: "Select the dates for your appointment",
    },
    es: {
      title: "Reserva una cita",
      description: "Selecciona las fechas para tu cita",
    },
  } as const;

  let locale = $state<keyof typeof localizedStrings>("es");

  const languageOptions = [
    {
      label: "English",
      value: "en",
    },
    {
      label: "Español",
      value: "es",
    },
  ];

  const selectedLanguage = $derived(
    languageOptions.find((option) => option.value === locale)?.label ?? "Language"
  );
</script>

<Card.Root>
  <Card.Header>
    <Card.Title>{localizedStrings[locale].title}</Card.Title>
    <Card.Description>{localizedStrings[locale].description}</Card.Description>
    <Card.Action>
      <Select.Root type="single" bind:value={locale}>
        <Select.Trigger class="w-[100px]" aria-label="Select language">
          {selectedLanguage}
        </Select.Trigger>
        <Select.Content align="end">
          {#each languageOptions as option (option.value)}
            <Select.Item value={option.value}>{option.label}</Select.Item>
          {/each}
        </Select.Content>
      </Select.Root>
    </Card.Action>
  </Card.Header>
  <Card.Content>
    <RangeCalendar
      bind:value
      numberOfMonths={2}
      {locale}
      class="bg-transparent p-0"
      buttonVariant="outline"
    />
  </Card.Content>
</Card.Root>
Localized calendar
calendar-12
Files
components/calendar-13.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Select from "$lib/components/ui/select/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { CalendarDate } from "@internationalized/date";
  import type { ComponentProps } from "svelte";

  let value = $state<CalendarDate>(new CalendarDate(2025, 6, 12));
  let dropdown = $state<ComponentProps<typeof Calendar>["captionLayout"]>("dropdown");

  const dropdownOptions = [
    {
      label: "Month and Year",
      value: "dropdown",
    },
    {
      label: "Month Only",
      value: "dropdown-months",
    },
    {
      label: "Year Only",
      value: "dropdown-years",
    },
  ];

  const selectedDropdown = $derived(
    dropdownOptions.find((option) => option.value === dropdown)?.label ?? "Dropdown"
  );

  const id = $props.id();
</script>

<div class="flex flex-col gap-4">
  <Calendar
    type="single"
    bind:value
    class="rounded-lg border shadow-sm"
    captionLayout={dropdown}
  />
  <div class="flex flex-col gap-3">
    <Label for="{id}-dropdown" class="px-1">Dropdown</Label>
    <Select.Root type="single" bind:value={dropdown}>
      <Select.Trigger id="{id}-dropdown" size="sm" class="bg-background w-full">
        {selectedDropdown}
      </Select.Trigger>
      <Select.Content align="center">
        {#each dropdownOptions as option (option.value)}
          <Select.Item value={option.value}>{option.label}</Select.Item>
        {/each}
      </Select.Content>
    </Select.Root>
  </div>
</div>
With Month and Year Dropdown
calendar-13
Files
components/calendar-14.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));

  const bookedDates = Array.from({ length: 12 }, (_, i) => new CalendarDate(2025, 6, 15 + i));
</script>

<Calendar
  type="single"
  bind:value
  class="rounded-lg border shadow-sm"
  isDateUnavailable={(date) => bookedDates.some((d) => d.compare(date) === 0)}
/>
With Booked/Unavailable Days
calendar-14
Files
components/calendar-15.svelte
<script lang="ts">
  import Construction from "@lucide/svelte/icons/construction";
</script>

<div class="flex h-full flex-col items-center justify-center gap-4">
  <Construction class="size-10" />
  <span>This block is under construction. Check back soon!</span>
</div>
With Week Numbers
calendar-15
Files
components/calendar-16.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import * as Card from "$lib/components/ui/card/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import Clock2Icon from "@lucide/svelte/icons/clock-2";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Card.Root class="w-fit py-4">
  <Card.Content class="px-4">
    <Calendar type="single" bind:value class="bg-transparent p-0" />
  </Card.Content>
  <Card.Footer class="flex flex-col gap-6 border-t px-4 !pt-4">
    <div class="flex w-full flex-col gap-3">
      <Label for="time-from">Start Time</Label>
      <div class="relative flex w-full items-center gap-2">
        <Clock2Icon
          class="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none"
        />
        <Input
          id="time-from"
          type="time"
          step="1"
          value="10:30:00"
          class="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
        />
      </div>
    </div>
    <div class="flex w-full flex-col gap-3">
      <Label for="time-to">End Time</Label>
      <div class="relative flex w-full items-center gap-2">
        <Clock2Icon
          class="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none"
        />
        <Input
          id="time-to"
          type="time"
          step="1"
          value="12:30:00"
          class="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
        />
      </div>
    </div>
  </Card.Footer>
</Card.Root>
With time picker
calendar-16
Files
components/calendar-17.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";
  import * as Card from "$lib/components/ui/card/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { Input } from "$lib/components/ui/input/index.js";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Card.Root class="w-fit py-4">
  <Card.Content class="px-4">
    <Calendar
      type="single"
      bind:value
      class="bg-transparent p-0 [--cell-size:--spacing(10.5)]"
    />
  </Card.Content>
  <Card.Footer class="*:[div]:w-full flex gap-2 border-t px-4 !pt-4">
    <div>
      <Label for="time-from" class="sr-only">Start Time</Label>
      <Input
        id="time-from"
        type="time"
        step="1"
        value="10:30:00"
        class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
    <span>-</span>
    <div>
      <Label for="time-to" class="sr-only">End Time</Label>
      <Input
        id="time-to"
        type="time"
        step="1"
        value="12:30:00"
        class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
  </Card.Footer>
</Card.Root>
With time picker inline
calendar-17
Files
components/calendar-18.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Calendar
  type="single"
  bind:value
  class="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"
/>
Variable size
calendar-18
Files
components/calendar-19.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as Card from "$lib/components/ui/card/index.js";

  let todayDate = today(getLocalTimeZone());
  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Card.Root class="max-w-[300px] py-4">
  <Card.Content class="px-4">
    <Calendar
      type="single"
      bind:value
      class="bg-transparent p-0 [--cell-size:--spacing(9.5)]"
    />
  </Card.Content>
  <Card.Footer class="flex flex-wrap gap-2 border-t px-4 !pt-4">
    {#each [{ label: "Today", value: 0 }, { label: "Tomorrow", value: 1 }, { label: "In 3 days", value: 3 }, { label: "In a week", value: 7 }, { label: "In 2 weeks", value: 14 }] as preset (preset.value)}
      <Button
        variant="outline"
        size="sm"
        class="flex-1"
        onclick={() => {
          value = todayDate?.add({ days: preset.value });
        }}
      >
        {preset.label}
      </Button>
    {/each}
  </Card.Footer>
</Card.Root>
With presets
calendar-19
Files
components/calendar-20.svelte
<script lang="ts">
  import { Button } from "$lib/components/ui/button/index.js";
  import * as Card from "$lib/components/ui/card/index.js";
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { CalendarDate, getLocalTimeZone } from "@internationalized/date";

  let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
  let selectedTime = $state<string | null>("10:00");

  const bookedDates = Array.from({ length: 3 }, (_, i) => new CalendarDate(2025, 6, 17 + i));
  const timeSlots = Array.from({ length: 37 }, (_, i) => {
    const totalMinutes = i * 15;
    const hour = Math.floor(totalMinutes / 60) + 9;
    const minute = totalMinutes % 60;
    return `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
  });
</script>

<Card.Root class="gap-0 p-0">
  <Card.Content class="relative p-0 md:pr-48">
    <div class="p-6">
      <Calendar
        type="single"
        bind:value
        isDateUnavailable={(date) => bookedDates.some((d) => d.compare(date) === 0)}
        class="data-unavailable:line-through data-unavailable:opacity-100 bg-transparent p-0 [--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)] [&_[data-outside-month]]:hidden"
        weekdayFormat="short"
      />
    </div>
    <div
      class="no-scrollbar inset-y-0 right-0 flex max-h-72 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t p-6 md:absolute md:max-h-none md:w-48 md:border-l md:border-t-0"
    >
      <div class="grid gap-2">
        {#each timeSlots as time (time)}
          <Button
            variant={selectedTime === time ? "default" : "outline"}
            onclick={() => (selectedTime = time)}
            class="w-full shadow-none"
          >
            {time}
          </Button>
        {/each}
      </div>
    </div>
  </Card.Content>
  <Card.Footer class="flex flex-col gap-4 border-t !py-5 px-6 md:flex-row">
    <div class="text-sm">
      {#if value && selectedTime}
        Your meeting is booked for
        <span class="font-medium">
          {value.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
            weekday: "long",
            day: "numeric",
            month: "short",
          })}
        </span>
        at <span class="font-medium">{selectedTime}</span>.
      {:else}
        Select a date and time for your meeting.
      {/if}
    </div>
    <Button
      disabled={!value || !selectedTime}
      class="w-full md:ml-auto md:w-auto"
      variant="outline"
    >
      Continue
    </Button>
  </Card.Footer>
</Card.Root>
With time presets
calendar-20
Files
components/calendar-21.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import RangeCalendarDay from "$lib/components/ui/range-calendar/range-calendar-day.svelte";
  import { CalendarDate, isWeekend } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  let value = $state<DateRange | undefined>({
    start: new CalendarDate(2025, 6, 12),
    end: new CalendarDate(2025, 6, 17),
  });
</script>

<RangeCalendar
  bind:value
  class="rounded-lg border shadow-sm [--cell-size:--spacing(11)] md:[--cell-size:--spacing(13)]"
  monthFormat="long"
  captionLayout="dropdown"
>
  {#snippet day({ day, outsideMonth })}
    {@const dayIsWeekend = isWeekend(day, "en-US")}
    <RangeCalendarDay class="flex flex-col items-center">
      {day.day}
      {#if !outsideMonth}
        <span>
          {dayIsWeekend ? "$220" : "$100"}
        </span>
      {/if}
    </RangeCalendarDay>
  {/snippet}
</RangeCalendar>
Custom days and formatters
calendar-21
Files
components/calendar-22.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import { getLocalTimeZone, today, type CalendarDate } from "@internationalized/date";

  const id = $props.id();

  let open = $state(false);
  let value = $state<CalendarDate | undefined>();
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-date" class="px-1">Date of birth</Label>
  <Popover.Root bind:open>
    <Popover.Trigger id="{id}-date">
      {#snippet child({ props })}
        <Button {...props} variant="outline" class="w-48 justify-between font-normal">
          {value ? value.toDate(getLocalTimeZone()).toLocaleDateString() : "Select date"}
          <ChevronDownIcon />
        </Button>
      {/snippet}
    </Popover.Trigger>
    <Popover.Content class="w-auto overflow-hidden p-0" align="start">
      <Calendar
        type="single"
        bind:value
        captionLayout="dropdown"
        onValueChange={() => {
          open = false;
        }}
        maxValue={today(getLocalTimeZone())}
      />
    </Popover.Content>
  </Popover.Root>
</div>
Date picker
calendar-22
Files
components/calendar-23.svelte
<script lang="ts">
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import { getLocalTimeZone } from "@internationalized/date";
  import type { DateRange } from "bits-ui";

  const id = $props.id();

  let open = $state(false);
  let value = $state<DateRange | undefined>();
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-dates" class="px-1">Select your stay</Label>
  <Popover.Root bind:open>
    <Popover.Trigger id="{id}-dates">
      {#snippet child({ props })}
        <Button {...props} variant="outline" class="w-56 justify-between font-normal">
          {value?.start && value?.end
            ? `${value.start.toDate(getLocalTimeZone()).toLocaleDateString()} - ${value.end.toDate(getLocalTimeZone()).toLocaleDateString()}`
            : "Select date"}
          <ChevronDownIcon />
        </Button>
      {/snippet}
    </Popover.Trigger>
    <Popover.Content class="w-auto overflow-hidden p-0" align="start">
      <RangeCalendar bind:value captionLayout="dropdown" />
    </Popover.Content>
  </Popover.Root>
</div>
Date range picker
calendar-23
Files
components/calendar-24.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import { getLocalTimeZone } from "@internationalized/date";
  import type { CalendarDate } from "@internationalized/date";

  const id = $props.id();

  let open = $state(false);
  let value = $state<CalendarDate | undefined>();
</script>

<div class="flex gap-4">
  <div class="flex flex-col gap-3">
    <Label for="{id}-date" class="px-1">Date</Label>
    <Popover.Root bind:open>
      <Popover.Trigger id="{id}-date">
        {#snippet child({ props })}
          <Button {...props} variant="outline" class="w-32 justify-between font-normal">
            {value
              ? value.toDate(getLocalTimeZone()).toLocaleDateString()
              : "Select date"}
            <ChevronDownIcon />
          </Button>
        {/snippet}
      </Popover.Trigger>
      <Popover.Content class="w-auto overflow-hidden p-0" align="start">
        <Calendar
          type="single"
          bind:value
          onValueChange={() => {
            open = false;
          }}
          captionLayout="dropdown"
        />
      </Popover.Content>
    </Popover.Root>
  </div>
  <div class="flex flex-col gap-3">
    <Label for="{id}-time" class="px-1">Time</Label>
    <Input
      type="time"
      id="{id}-time"
      step="1"
      value="10:30:00"
      class="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
    />
  </div>
</div>
Date and Time picker
calendar-24
Files
components/calendar-25.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import { getLocalTimeZone } from "@internationalized/date";
  import type { CalendarDate } from "@internationalized/date";

  const id = $props.id();

  let open = $state(false);
  let value = $state<CalendarDate | undefined>();
</script>

<div class="flex flex-col gap-6">
  <div class="flex flex-col gap-3">
    <Label for="{id}-date" class="px-1">Date</Label>
    <Popover.Root bind:open>
      <Popover.Trigger id="{id}-date">
        {#snippet child({ props })}
          <Button {...props} variant="outline" class="w-full justify-between font-normal">
            {value
              ? value.toDate(getLocalTimeZone()).toLocaleDateString()
              : "Select date"}
            <ChevronDownIcon />
          </Button>
        {/snippet}
      </Popover.Trigger>
      <Popover.Content class="w-auto overflow-hidden p-0" align="start">
        <Calendar
          type="single"
          bind:value
          captionLayout="dropdown"
          onValueChange={() => {
            open = false;
          }}
        />
      </Popover.Content>
    </Popover.Root>
  </div>
  <div class="flex gap-4">
    <div class="flex flex-col gap-3">
      <Label for="{id}-time-from" class="px-1">From</Label>
      <Input
        type="time"
        id="{id}-time-from"
        step="1"
        value="10:30:00"
        class="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
    <div class="flex flex-col gap-3">
      <Label for="{id}-time-to" class="px-1">To</Label>
      <Input
        type="time"
        id="{id}-time-to"
        step="1"
        value="12:30:00"
        class="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
  </div>
</div>
Date and Time range picker
calendar-25
Files
components/calendar-26.svelte
<script lang="ts">
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import { getLocalTimeZone } from "@internationalized/date";
  import type { CalendarDate } from "@internationalized/date";

  const id = $props.id();

  let openFrom = $state(false);
  let openTo = $state(false);
  let valueFrom = $state<CalendarDate | undefined>();
  let valueTo = $state<CalendarDate | undefined>();
</script>

<div class="flex flex-col gap-6">
  <div class="flex gap-4">
    <div class="flex flex-1 flex-col gap-3">
      <Label for="{id}-date-from" class="px-1">Check-in</Label>
      <Popover.Root bind:open={openFrom}>
        <Popover.Trigger id="{id}-date-from">
          {#snippet child({ props })}
            <Button
              {...props}
              variant="outline"
              class="w-full justify-between font-normal"
            >
              {valueFrom
                ? valueFrom.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
                    day: "2-digit",
                    month: "short",
                    year: "numeric",
                  })
                : "Select date"}
              <ChevronDownIcon />
            </Button>
          {/snippet}
        </Popover.Trigger>
        <Popover.Content class="w-auto overflow-hidden p-0" align="start">
          <Calendar
            type="single"
            bind:value={valueFrom}
            captionLayout="dropdown"
            onValueChange={() => {
              openFrom = false;
            }}
          />
        </Popover.Content>
      </Popover.Root>
    </div>
    <div class="flex flex-col gap-3">
      <Label for="{id}-time-from" class="invisible px-1">From</Label>
      <Input
        type="time"
        id="{id}-time-from"
        step="1"
        value="10:30:00"
        class="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
  </div>
  <div class="flex gap-4">
    <div class="flex flex-1 flex-col gap-3">
      <Label for="{id}-date-to" class="px-1">Check-out</Label>
      <Popover.Root bind:open={openTo}>
        <Popover.Trigger id="{id}-date-to">
          {#snippet child({ props })}
            <Button
              {...props}
              variant="outline"
              class="w-full justify-between font-normal"
            >
              {valueTo
                ? valueTo.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
                    day: "2-digit",
                    month: "short",
                    year: "numeric",
                  })
                : "Select date"}
              <ChevronDownIcon />
            </Button>
          {/snippet}
        </Popover.Trigger>
        <Popover.Content class="w-auto overflow-hidden p-0" align="start">
          <Calendar
            type="single"
            bind:value={valueTo}
            captionLayout="dropdown"
            onValueChange={() => {
              openTo = false;
            }}
            isDateDisabled={(date) => {
              return (valueFrom && date.compare(valueFrom) < 0) ?? false;
            }}
          />
        </Popover.Content>
      </Popover.Root>
    </div>
    <div class="flex flex-col gap-3">
      <Label for="{id}-time-to" class="invisible px-1">To</Label>
      <Input
        type="time"
        id="{id}-time-to"
        step="1"
        value="12:30:00"
        class="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
      />
    </div>
  </div>
</div>
Date range picker with time
calendar-26
Files
components/calendar-27.svelte
<script lang="ts">
  import CalendarIcon from "@lucide/svelte/icons/calendar";
  import * as Chart from "$lib/components/ui/chart/index.js";
  import * as Card from "$lib/components/ui/card/index.js";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { BarChart, Highlight, type ChartContextValue } from "layerchart";
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import type { DateRange } from "bits-ui";
  import { CalendarDate, getLocalTimeZone } from "@internationalized/date";
  import { scaleBand } from "d3-scale";
  import { cubicInOut } from "svelte/easing";

  let value = $state<DateRange | undefined>({
    start: new CalendarDate(2025, 6, 5),
    end: new CalendarDate(2025, 6, 20),
  });

  const chartData = [
    { date: new Date("2025-06-01"), visitors: 178 },
    { date: new Date("2025-06-02"), visitors: 470 },
    { date: new Date("2025-06-03"), visitors: 103 },
    { date: new Date("2025-06-04"), visitors: 439 },
    { date: new Date("2025-06-05"), visitors: 88 },
    { date: new Date("2025-06-06"), visitors: 294 },
    { date: new Date("2025-06-07"), visitors: 323 },
    { date: new Date("2025-06-08"), visitors: 385 },
    { date: new Date("2025-06-09"), visitors: 438 },
    { date: new Date("2025-06-10"), visitors: 155 },
    { date: new Date("2025-06-11"), visitors: 92 },
    { date: new Date("2025-06-12"), visitors: 492 },
    { date: new Date("2025-06-13"), visitors: 81 },
    { date: new Date("2025-06-14"), visitors: 426 },
    { date: new Date("2025-06-15"), visitors: 307 },
    { date: new Date("2025-06-16"), visitors: 371 },
    { date: new Date("2025-06-17"), visitors: 475 },
    { date: new Date("2025-06-18"), visitors: 107 },
    { date: new Date("2025-06-19"), visitors: 341 },
    { date: new Date("2025-06-20"), visitors: 408 },
    { date: new Date("2025-06-21"), visitors: 169 },
    { date: new Date("2025-06-22"), visitors: 317 },
    { date: new Date("2025-06-23"), visitors: 480 },
    { date: new Date("2025-06-24"), visitors: 132 },
    { date: new Date("2025-06-25"), visitors: 141 },
    { date: new Date("2025-06-26"), visitors: 434 },
    { date: new Date("2025-06-27"), visitors: 448 },
    { date: new Date("2025-06-28"), visitors: 149 },
    { date: new Date("2025-06-29"), visitors: 103 },
    { date: new Date("2025-06-30"), visitors: 446 },
  ];

  const total = chartData.reduce((acc, curr) => acc + curr.visitors, 0);

  const chartConfig = {
    visitors: {
      label: "Visitors",
      color: "var(--color-primary)",
    },
  } satisfies Chart.ChartConfig;

  const filteredData = $derived.by(() => {
    if (!value?.start || !value?.end) return chartData;
    return chartData.filter(({ date }) => {
      const dateObj = new Date(date);
      if (!value) return true;
      const startDate = value.start!.toDate(getLocalTimeZone());
      const endDate = value.end!.toDate(getLocalTimeZone());
      // set end date to end of day to include the full day
      endDate.setHours(23, 59, 59, 999);
      return dateObj >= startDate && dateObj <= endDate;
    });
  });

  let context = $state<ChartContextValue>();
</script>

<Card.Root class="@container/card w-full max-w-xl">
  <Card.Header class="@md/card:grid flex flex-col border-b">
    <Card.Title>Web Analytics</Card.Title>
    <Card.Description>Showing total visitors for this month.</Card.Description>
    <Card.Action class="@md/card:mt-0 mt-2">
      <Popover.Root>
        <Popover.Trigger>
          {#snippet child({ props })}
            <Button {...props} variant="outline">
              <CalendarIcon />
              {value?.start && value?.end
                ? `${value.start.toDate(getLocalTimeZone()).toLocaleDateString()} - ${value.end.toDate(getLocalTimeZone()).toLocaleDateString()}`
                : "June 2025"}
            </Button>
          {/snippet}
        </Popover.Trigger>
        <Popover.Content class="w-auto overflow-hidden p-0" align="end">
          <RangeCalendar
            class="w-full"
            fixedWeeks
            bind:value
            minValue={new CalendarDate(2025, 6, 1)}
            maxValue={new CalendarDate(2025, 6, 31)}
          />
        </Popover.Content>
      </Popover.Root>
    </Card.Action>
  </Card.Header>
  <Card.Content class="px-4">
    <Chart.Container config={chartConfig} class="aspect-auto h-[250px] w-full">
      <BarChart
        bind:context
        data={filteredData}
        xScale={scaleBand().padding(0.25)}
        x="date"
        axis="x"
        y="visitors"
        props={{
          bars: {
            stroke: "none",
            rounded: "all",
            radius: 4,
            // use the height of the chart to animate the bars
            initialY: context?.height,
            initialHeight: 0,
            motion: {
              x: { type: "tween", duration: 500, easing: cubicInOut },
              width: { type: "tween", duration: 500, easing: cubicInOut },
              height: { type: "tween", duration: 500, easing: cubicInOut },
              y: { type: "tween", duration: 500, easing: cubicInOut },
            },
          },
          xAxis: { format: (d) => d.toLocaleDateString("en-US", { day: "numeric" }) },
        }}
      >
        {#snippet belowMarks()}
          <Highlight area={{ class: "fill-muted" }} />
        {/snippet}
        {#snippet tooltip()}
          <Chart.Tooltip
            class="w-[150px]"
            nameKey="visitors"
            labelFormatter={(d) =>
              d.toLocaleDateString("en-US", {
                month: "short",
                day: "numeric",
                year: "numeric",
              })}
          />
        {/snippet}
      </BarChart>
    </Chart.Container>
  </Card.Content>
  <Card.Footer class="border-t">
    <div class="text-sm">
      You had
      <span class="font-semibold">{total.toLocaleString()}</span>
      visitors for the month of June.
    </div>
  </Card.Footer>
</Card.Root>
Chart filter
calendar-27
Files
components/calendar-28.svelte
<script lang="ts">
  import CalendarIcon from "@lucide/svelte/icons/calendar";
  import { CalendarDate, getLocalTimeZone, type DateValue } from "@internationalized/date";
  import { untrack } from "svelte";
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import { Input } from "$lib/components/ui/input/index.js";
  import { Label } from "$lib/components/ui/label/index.js";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";

  function formatDate(date: DateValue | undefined) {
    if (!date) return "";
    return date.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
      day: "2-digit",
      month: "long",
      year: "numeric",
    });
  }

  function isValidDate(date: Date | undefined): date is Date {
    if (!date) return false;
    return !isNaN(date.getTime());
  }

  const id = $props.id();
  let value = $state<DateValue | undefined>(new CalendarDate(2025, 6, 1));
  let open = $state(false);
  let inputValue = $state(untrack(() => formatDate(value)));
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-date" class="px-1">Subscription Date</Label>
  <div class="relative flex gap-2">
    <Input
      id="{id}-date"
      placeholder="June 01, 2025"
      class="bg-background pr-10"
      bind:value={
        () => inputValue,
        (v) => {
          const date = new Date(v);
          inputValue = v;
          if (isValidDate(date)) {
            value = new CalendarDate(
              date.getFullYear(),
              date.getMonth(),
              date.getDate()
            );
          }
        }
      }
      onkeydown={(e) => {
        if (e.key === "ArrowDown") {
          e.preventDefault();
          open = true;
        }
      }}
    />
    <Popover.Root bind:open>
      <Popover.Trigger id="{id}-date-picker">
        {#snippet child({ props })}
          <Button
            {...props}
            variant="ghost"
            class="absolute right-2 top-1/2 size-6 -translate-y-1/2"
          >
            <CalendarIcon class="size-3.5" />
            <span class="sr-only">Select date</span>
          </Button>
        {/snippet}
      </Popover.Trigger>
      <Popover.Content
        class="w-auto overflow-hidden p-0"
        align="end"
        alignOffset={-8}
        sideOffset={10}
      >
        <Calendar
          type="single"
          bind:value
          captionLayout="dropdown"
          onValueChange={(v) => {
            inputValue = formatDate(v);
            open = false;
          }}
        />
      </Popover.Content>
    </Popover.Root>
  </div>
</div>
Input with date picker
calendar-28
Files
components/calendar-29.svelte
<script lang="ts">
  import { Label } from "$lib/components/ui/label/index.js";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Calendar } from "$lib/components/ui/calendar/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import CalendarIcon from "@lucide/svelte/icons/calendar";
  import { parseDate } from "chrono-node";
  import { CalendarDate, getLocalTimeZone, type DateValue } from "@internationalized/date";
  import { untrack } from "svelte";

  function formatDate(date: DateValue | undefined) {
    if (!date) return "";

    return date.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
      day: "2-digit",
      month: "long",
      year: "numeric",
    });
  }

  const id = $props.id();

  let open = $state(false);
  let inputValue = $state("In 2 days");
  let value = $state<DateValue | undefined>(
    untrack(() => {
      const date = parseDate(inputValue);
      if (date)
        return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
      return undefined;
    })
  );
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-date" class="px-1">Schedule Date</Label>
  <div class="relative flex gap-2">
    <Input
      id="date"
      bind:value={
        () => inputValue,
        (v) => {
          inputValue = v;
          const date = parseDate(v);
          if (date) {
            value = new CalendarDate(
              date.getFullYear(),
              date.getMonth() + 1,
              date.getDate()
            );
          }
        }
      }
      placeholder="Tomorrow or next week"
      class="bg-background pr-10"
      onkeydown={(e) => {
        if (e.key === "ArrowDown") {
          e.preventDefault();
          open = true;
        }
      }}
    />
    <Popover.Root bind:open>
      <Popover.Trigger id="{id}-date-picker">
        {#snippet child({ props })}
          <Button
            {...props}
            variant="ghost"
            class="absolute right-2 top-1/2 size-6 -translate-y-1/2"
          >
            <CalendarIcon class="size-3.5" />
            <span class="sr-only">Select date</span>
          </Button>
        {/snippet}
      </Popover.Trigger>
      <Popover.Content class="w-auto overflow-hidden p-0" align="end">
        <Calendar
          type="single"
          bind:value
          captionLayout="dropdown"
          onValueChange={(v) => {
            inputValue = formatDate(v);
            open = false;
          }}
        />
      </Popover.Content>
    </Popover.Root>
  </div>
  <div class="text-muted-foreground px-1 text-sm">
    Your post will be published on
    <span class="font-medium">{formatDate(value)}</span>.
  </div>
</div>
Natural language date picker
calendar-29
Files
components/calendar-30.svelte
<script lang="ts">
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import RangeCalendar from "$lib/components/ui/range-calendar/range-calendar.svelte";
  import { Label } from "$lib/components/ui/label/index.js";
  import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
  import type { DateRange } from "bits-ui";
  import { CalendarDate, getLocalTimeZone, type DateValue } from "@internationalized/date";
  import { formatDateRange } from "little-date";

  const id = $props.id();

  let value = $state<DateRange | undefined>({
    start: new CalendarDate(2025, 6, 4),
    end: new CalendarDate(2025, 6, 10),
  });

  function formatRange(start: DateValue, end: DateValue) {
    return formatDateRange(start.toDate(getLocalTimeZone()), end.toDate(getLocalTimeZone()), {
      includeTime: false,
    });
  }
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-dates" class="px-1">Select your stay</Label>
  <Popover.Root>
    <Popover.Trigger id="{id}-dates">
      {#snippet child({ props })}
        <Button {...props} variant="outline" class="w-56 justify-between font-normal">
          {#if value?.start && value?.end}
            {formatRange(value.start, value.end)}
          {:else}
            Select date
          {/if}
          <ChevronDownIcon />
        </Button>
      {/snippet}
    </Popover.Trigger>
    <Popover.Content class="w-auto overflow-hidden p-0" align="start">
      <RangeCalendar bind:value captionLayout="dropdown" />
    </Popover.Content>
  </Popover.Root>
</div>
With little-date
calendar-30
Files
components/calendar-31.svelte
<script lang="ts">
  import { formatDateRange } from "little-date";
  import PlusIcon from "@lucide/svelte/icons/plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Card from "$lib/components/ui/card/index.js";
  import { CalendarDate, getLocalTimeZone, type DateValue } from "@internationalized/date";

  const events = [
    {
      title: "Team Sync Meeting",
      start: "2025-06-12T09:00:00",
      end: "2025-06-12T10:00:00",
    },
    {
      title: "Design Review",
      start: "2025-06-12T11:30:00",
      end: "2025-06-12T12:30:00",
    },
    {
      title: "Client Presentation",
      start: "2025-06-12T14:00:00",
      end: "2025-06-12T15:00:00",
    },
  ];

  let value = $state<DateValue | undefined>(new CalendarDate(2025, 6, 12));
</script>

<Card.Root class="w-fit py-4">
  <Card.Content class="px-4">
    <Calendar type="single" bind:value class="bg-transparent p-0" preventDeselect />
  </Card.Content>
  <Card.Footer class="flex flex-col items-start gap-3 border-t px-4 !pt-4">
    <div class="flex w-full items-center justify-between px-1">
      <div class="text-sm font-medium">
        {value?.toDate(getLocalTimeZone()).toLocaleDateString("en-US", {
          day: "numeric",
          month: "long",
          year: "numeric",
        })}
      </div>
      <Button variant="ghost" size="icon" class="size-6" title="Add Event">
        <PlusIcon />
        <span class="sr-only">Add Event</span>
      </Button>
    </div>
    <div class="flex w-full flex-col gap-2">
      {#each events as event (event.title)}
        <div
          class="bg-muted after:bg-primary/70 relative rounded-md p-2 pl-6 text-sm after:absolute after:inset-y-2 after:left-2 after:w-1 after:rounded-full"
        >
          <div class="font-medium">{event.title}</div>
          <div class="text-muted-foreground text-xs">
            {formatDateRange(new Date(event.start), new Date(event.end))}
          </div>
        </div>
      {/each}
    </div>
  </Card.Footer>
</Card.Root>
With event slots
calendar-31
Files
components/calendar-32.svelte
<script lang="ts">
  import { getLocalTimeZone, type DateValue } from "@internationalized/date";
  import CalendarPlusIcon from "@lucide/svelte/icons/calendar-plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import Calendar from "$lib/components/ui/calendar/calendar.svelte";
  import * as Drawer from "$lib/components/ui/drawer/index.js";
  import { Label } from "$lib/components/ui/label/index.js";

  let open = $state(false);
  let value = $state<DateValue | undefined>();
  const id = $props.id();

  const triggerLabel = $derived.by(() => {
    if (value) return value.toDate(getLocalTimeZone()).toLocaleDateString();
    return "Select date";
  });
</script>

<div class="flex flex-col gap-3">
  <Label for="{id}-date" class="px-1">Date of birth</Label>
  <Drawer.Root bind:open>
    <Drawer.Trigger id="{id}-date">
      {#snippet child({ props })}
        <Button {...props} variant="outline" class="w-48 justify-between font-normal">
          {triggerLabel}
          <CalendarPlusIcon />
        </Button>
      {/snippet}
    </Drawer.Trigger>
    <Drawer.Content class="w-auto overflow-hidden p-0">
      <Drawer.Header class="sr-only">
        <Drawer.Title>Select date</Drawer.Title>
        <Drawer.Description>Set your date of birth</Drawer.Description>
      </Drawer.Header>
      <Calendar
        type="single"
        bind:value
        captionLayout="dropdown"
        onValueChange={(v) => {
          if (v) {
            open = false;
          }
        }}
        class="mx-auto [--cell-size:clamp(0px,calc(100vw/7.5),52px)]"
      />
    </Drawer.Content>
  </Drawer.Root>
  <div class="text-muted-foreground px-1 text-sm">This example works best on mobile.</div>
</div>
Date picker in a drawer
calendar-32