Data Display

Calendar

A date picker supporting single, multiple, and range selection. Built on @internationalized/date for timezone-aware dates.

Playground

Experiment with different props.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Basic Usage

Single date selection with bind:value.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<script lang="ts">
  import { Calendar } from 'sv5ui';
  import { today, getLocalTimeZone } from '@internationalized/date';

  let value = $state(today(getLocalTimeZone()));
</script>

<Calendar bind:value />

Variants

4 visual styles.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<Calendar variant="solid" />
<Calendar variant="outline" />
<Calendar variant="soft" />
<Calendar variant="subtle" />

Sizes

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<Calendar size="xs" />
<Calendar size="sm" />
<Calendar size="md" />
<Calendar size="lg" />
<Calendar size="xl" />

Colors

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<Calendar color="primary" />
<Calendar color="secondary" />
<Calendar color="tertiary" />
<Calendar color="success" />
<Calendar color="warning" />
<Calendar color="error" />
<Calendar color="info" />
<Calendar color="surface" />

Multiple Selection

Select multiple dates with type="multiple".

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Selected: 0 date(s)

<script lang="ts">
  import { Calendar } from 'sv5ui';
  import type { DateValue } from '@internationalized/date';

  let values = $state<DateValue[]>([]);
</script>

<Calendar type="multiple" bind:value={values} />

Multiple with maxDays

Cap how many dates the user can pick in type="multiple" mode with maxDays. Once the limit is reached, additional cells become unselectable.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Selected: 0 / 5 date(s)

<script lang="ts">
  import { Calendar } from 'sv5ui';
  import type { DateValue } from '@internationalized/date';

  let values = $state<DateValue[]>([]);
</script>

<!-- Cap the selection to 5 dates -->
<Calendar type="multiple" maxDays={5} bind:value={values} />
<p>Selected: {values.length} / 5 date(s)</p>

Highlighted Dates

Pass an isDateHighlightable(date) predicate to visually mark special dates such as holidays or events. Marked cells receive a data-marked attribute and render a small dot indicator. Selection and disabled state are not affected - override the dot via the cellTrigger slot or ui.cellTrigger using the data-[marked]: modifier.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<script lang="ts">
  import { Calendar } from 'sv5ui';
  import { today, getLocalTimeZone } from '@internationalized/date';
  import type { DateValue } from '@internationalized/date';

  let value = $state<DateValue | undefined>(undefined);

  const now = today(getLocalTimeZone());
  const holidays = [
    now.add({ days: 3 }),
    now.add({ days: 7 }),
    now.add({ days: 10 }),
    now.add({ days: 18 })
  ];

  // Predicate runs for every visible cell - keep it cheap and pure.
  const isHoliday = (date: DateValue) =>
    holidays.some((h) => h.compare(date) === 0);
</script>

<Calendar bind:value isDateHighlightable={isHoliday} />

<!-- Marked cells get a data-marked attribute, which the default theme
     renders as a small dot indicator. Override via the cellTrigger slot
     or ui.cellTrigger using the data-[marked]: modifier. -->

Multiple Months

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
<Calendar numberOfMonths={2} />

Min & Max Date

Restrict selectable dates to a range.

SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
<script lang="ts">
  import { Calendar } from 'sv5ui';
  import { today, getLocalTimeZone } from '@internationalized/date';

  const now = today(getLocalTimeZone());
</script>

<Calendar
  minValue={now}
  maxValue={now.add({ days: 14 })}
/>

Week Numbers

# SunMonTueWedThuFriSat
1 2829301234
2 567891011
3 12131415161718
4 19202122232425
5 2627282930311
6 2345678
<Calendar weekNumbers />

Inside FormField

Wrap Calendar in <FormField> - when error is set, the calendar switches to the error color and aria-describedby wires up to the FormField's description and error text.

Required
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Please pick a date

<script lang="ts">
  import { Calendar, FormField } from 'sv5ui';
  import { today, getLocalTimeZone } from '@internationalized/date';
  import type { DateValue } from '@internationalized/date';

  let value = $state<DateValue | undefined>(undefined);
  const now = today(getLocalTimeZone());
  const error = $derived(value ? '' : 'Please pick a date');
</script>

<FormField
  label="Appointment date"
  hint="Required"
  required
  help="Pick any future date"
  {error}
>
  <Calendar bind:value minValue={now} />
</FormField>

<!-- When error is set, the Calendar:
     - switches to the error color
     - aria-describedby points to FormField's description + error text
     - focus/blur/change events emit to the parent <Form> -->

In a Popover

Combine with Popover for a date picker dropdown.

<script lang="ts">
  import { Calendar, Popover, Button } from 'sv5ui';
  import type { DateValue } from '@internationalized/date';

  let value = $state<DateValue | undefined>(undefined);
  let open = $state(false);
  let display = $derived(value ? value.toString() : '');
</script>

<Popover bind:open>
  <Button
    variant="outline"
    color="surface"
    leadingIcon="lucide:calendar"
    label={display || 'Pick a date'}
  />
  {#snippet content()}
    <div class="p-3">
      <Calendar
        bind:value
        onValueChange={() => open = false}
        size="sm"
      />
    </div>
  {/snippet}
</Popover>

UI Slots

SlotDescription
rootOutermost wrapper
headerNavigation and heading
bodyMain grid area
navButtonNav buttons
navButtonIconNav button icons
gridCalendar grid table
headingMonth/year heading
headCellWeekday header cell
cellDate cell wrapper
cellTriggerClickable date button

Snippets

SnippetDescription
headingCustom month/year heading (receives value string)
dayCustom date cell content (receives day DateValue)
weekDayCustom weekday name (receives day string)

Props

PropTypeDefault
valueDateValue | DateValue[]-
type'single' | 'multiple''single'
rangebooleanfalse
colorColorType'primary'
size'xs'|'sm'|'md'|'lg'|'xl''md'
variant'solid'|'outline'|'soft'|'subtle''solid'
numberOfMonthsnumber1
minValueDateValue-
maxValueDateValue-
isDateDisabledDateMatcher-
isDateHighlightableDateMatcher-
maxDaysnumber-
fixedWeeksbooleantrue
weekNumbersbooleanfalse
weekStartsOn0-60
localestring'en'
monthControlsbooleantrue
yearControlsbooleantrue
disabledbooleanfalse
readonlybooleanfalse
idstring-
namestring-
refHTMLElement | nullnull
classstring-
uiRecord<Slot, Class>-