Calendar - range + presets

SvCalendar as a date-range picker: selectionMode="range" with a one-click presets rail (Today, Last 7 days, This month, Year to date...), animated month navigation and mouse-wheel scrolling. Presets resolve relative to today. Same component, no extra dependency.

A live, editable Svelte 5 component example from the SvGrid gallery (Date & Time). Read the SvGrid UI component docs for the full API.

About this example

SvCalendar as a Svelte 5 date-range picker with a presets rail. selectionMode='range' plus a presets array gives one-click Today, Last 7 days, This month and Year to date ranges resolved relative to the current date, with animated month navigation, mouse-wheel scrolling, two months side by side and Monday as the first day of the week. The rail and the range band are built into the component, so there is no extra dependency and every --sg-* theme styles it.

SvCalendar as a DATE-RANGE picker with a one-click presets rail (Today, Last 7 days, This month, ...) and animated month navigation. The presets rail + range band are all built into SvCalendar - selectionMode="range" plus a presets array. Every --sg-* theme styles it automatically.

Imports, features and API used

Imports: @svgrid/grid

Frequently asked questions

How do I define a preset?

Each preset is { label, value } where value is a date, a [start, end] tuple or a function returning one, so a preset such as Last 7 days computes its dates when clicked and is always relative to today.

How do I show two months at once?

Set months={2}. The calendar lays the months side by side and the range band spans across them; wheelNavigation scrolls both together.

What does onChange deliver for a range?

An array with the start and end dates once both are picked, and a single-entry array while the user is choosing the end.

Related documentation

Related articles

Source code (258-calendar-range.svelte)

<script lang="ts">
  /**
   * SvCalendar as a DATE-RANGE picker with a one-click presets rail (Today,
   * Last 7 days, This month, ...) and animated month navigation. The presets
   * rail + range band are all built into SvCalendar - `selectionMode="range"`
   * plus a `presets` array. Every `--sg-*` theme styles it automatically.
   */
  import { SvCalendar } from '@svgrid/grid'
  import type { CalendarPreset, CalendarAnimation } from '@svgrid/grid'

  const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate())
  const addDays = (d: Date, n: number) => { const x = new Date(d); x.setDate(x.getDate() + n); return x }
  // A range value is the FULL inclusive span (so every day in it renders selected).
  const spanDays = (a: Date, b: Date) => {
    const out: Date[] = []
    for (let c = startOfDay(a); c <= b; c = addDays(c, 1)) out.push(c)
    return out
  }

  // Relative presets resolve at click time (Smart-style shortcuts).
  const presets: CalendarPreset[] = [
    { label: 'Today', value: () => { const t = startOfDay(new Date()); return [t, t] } },
    { label: 'Yesterday', value: () => { const y = addDays(startOfDay(new Date()), -1); return [y, y] } },
    { label: 'Last 7 days', value: () => [addDays(startOfDay(new Date()), -6), startOfDay(new Date())] },
    { label: 'Last 30 days', value: () => [addDays(startOfDay(new Date()), -29), startOfDay(new Date())] },
    { label: 'This month', value: () => { const n = new Date(); return [new Date(n.getFullYear(), n.getMonth(), 1), startOfDay(n)] } },
    { label: 'Last month', value: () => { const n = new Date(); return [new Date(n.getFullYear(), n.getMonth() - 1, 1), new Date(n.getFullYear(), n.getMonth(), 0)] } },
    { label: 'Year to date', value: () => [new Date(new Date().getFullYear(), 0, 1), startOfDay(new Date())] },
  ]

  let animate = $state<CalendarAnimation>('slide')
  let months = $state(2)
  let value = $state<Date[]>(spanDays(addDays(startOfDay(new Date()), -6), startOfDay(new Date())))

  const fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' })
  const start = $derived(value[0])
  const end = $derived(value[value.length - 1])
  const nights = $derived(start && end ? Math.round((+end - +start) / 86400000) : 0)
</script>

<div class="wrap">
  <header>
    <h2>Date-range picker</h2>
    <p>
      <code>selectionMode="range"</code> with a <strong>presets rail</strong> and animated navigation.
      Click two days to draw a range, or pick a shortcut. This is the same
      <code>SvCalendar</code> the grid uses - no extra component.
    </p>
  </header>

  <div class="controls">
    <label>Animation
      <select bind:value={animate}>
        <option value="slide">slide</option><option value="fade">fade</option><option value="none">none</option>
      </select>
    </label>
    <label>Panels
      <select bind:value={months}>
        <option value={1}>1</option><option value={2}>2</option>
      </select>
    </label>
  </div>

  <div class="stage">
    <SvCalendar
      value={value}
      selectionMode="range"
      {months}
      {animate}
      {presets}
      wheelNavigation
      firstDayOfWeek={1}
      onChange={(dates) => (value = dates)}
    />

    <aside class="readout">
      <h3>Selected range</h3>
      {#if start && end}
        <p class="big">{fmt.format(start)}</p>
        <p class="arrow">&darr;</p>
        <p class="big">{fmt.format(end)}</p>
        <p class="nights"><strong>{nights}</strong> night{nights === 1 ? '' : 's'} &middot; {nights + 1} days</p>
      {:else}
        <p class="empty">Pick a start and end day</p>
      {/if}
      <p class="hint">Shortcuts on the left resolve relative to today. Scroll the grid to change months.</p>
    </aside>
  </div>
</div>

<style>
  .wrap { padding: 20px; max-width: 940px; display: flex; flex-direction: column; gap: 16px; }
  header h2 { margin: 0 0 4px; font-size: 20px; font-weight: 700; }
  header p { margin: 0; color: var(--sg-muted, #64748b); font-size: 13.5px; line-height: 1.5; max-width: 660px; }
  code { background: var(--sg-row-hover-bg, #eef2ff); padding: 1px 5px; border-radius: 5px; font-size: 12px; }
  .controls { display: flex; flex-wrap: wrap; gap: 14px; align-items: end; }
  .controls label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; font-weight: 600; color: var(--sg-muted, #64748b); }
  .controls select { padding: 5px 8px; border: 1px solid var(--sg-border, #cbd5e1); border-radius: 7px; background: var(--sg-input-bg, #fff); color: inherit; font: inherit; font-size: 13px; }
  .stage { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
  .readout { flex: 1; min-width: 200px; }
  .readout h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
  .readout .big { margin: 0; font-size: 17px; font-weight: 650; }
  .readout .arrow { margin: 2px 0; color: var(--sg-muted, #94a3b8); }
  .readout .nights { margin: 10px 0 0; font-size: 14px; }
  .readout .empty { color: var(--sg-muted, #94a3b8); font-style: italic; }
  .readout .hint { margin-top: 14px; font-size: 12px; color: var(--sg-muted, #94a3b8); line-height: 1.5; }
</style>

View this example on GitHub

More Date & Time examples

  • Calendar - SvCalendar: a themeable month/year/decade calendar with single / range / week / multi selection, min-max, restricted + important dates and week numbers. The same component SvGrid mounts to edit a date cell - and usable standalone in any SvGrid app.
  • Time picker - SvTimePicker: an analog clock-dial picker with 12/24-hour, minute snapping and hour to minute auto-switch. Drag the hand or click a number. The SvGrid time cell editor, usable standalone in any SvGrid app.
  • Date-time picker - SvDateTimePicker: a formatted input plus a portalled dropdown with DATE / TIME tabs (calendar + clock). Type a masked value or pick it; invalid input reverts. min/max, nullable, 12/24-hour, spin buttons. The SvGrid datetime cell editor, standalone in any SvGrid app.
  • Date-range input - SvDateRangeInput: a compact start/end field that opens a two-month range calendar with one-click presets (Today, Last 7 days, ...). Composes the same headless range engine as SvCalendar, and carries the shared editor contract - label, hint, error validation, dir (RTL) and localizable messages.
  • Calendar - date of birth - SvCalendar as a date-of-birth picker: single selection with max clamped to today so future dates are blocked, opening on the decade (year) grid for fewer clicks to a birth year, with a live age readout. Same component SvGrid mounts to edit a date cell.