6.9k
New Components: Field, Input Group, Item and more

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 start-2.5 size-4 select-none"
        />
        <Input
          id="time-from"
          type="time"
          step="1"
          value="10:30:00"
          class="appearance-none ps-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 start-2.5 size-4 select-none"
        />
        <Input
          id="time-to"
          type="time"
          step="1"
          value="12:30:00"
          class="appearance-none ps-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="flex gap-2 border-t px-4 !pt-4 *:[div]:w-full">
    <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:pe-48">
    <div class="p-6">
      <Calendar
        type="single"
        bind:value
        isDateUnavailable={(date) => bookedDates.some((d) => d.compare(date) === 0)}
        class="bg-transparent p-0 [--cell-size:--spacing(10)] data-unavailable:line-through data-unavailable:opacity-100 md:[--cell-size:--spacing(12)] [&_[data-outside-month]]:hidden"
        weekdayFormat="short"
      />
    </div>
    <div
      class="no-scrollbar inset-y-0 end-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-s 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 px-6 !py-5 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:ms-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="flex flex-col border-b @md/card:grid">
    <Card.Title>Web Analytics</Card.Title>
    <Card.Description>Showing total visitors for this month.</Card.Description>
    <Card.Action class="mt-2 @md/card:mt-0">
      <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 pe-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 end-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 pe-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 end-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 ps-6 text-sm after:absolute after:inset-y-2 after:start-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