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
- Real-Time Grids - Live WebSocket Updates in SvGrid - How to wire a high-frequency WebSocket feed into SvGrid without frame drops - covering stable row identity, RAF batching, and flash feedback done right.
- We Asked Claude Code for a Svelte Table. It Wrote Its Own Every Time, Until the Project Said Otherwise - Ten fresh Claude Code sessions, one prompt, a bare SvelteKit app. With no hint it hand-wrote a DataTable component, even for 10,000 rows. One paragraph in AGENTS.md changed that.
- Building a Real-Time Trading Grid in Svelte - How to wire a WebSocket tick feed into SvGrid without dropping frames - one array replacement per animation frame, stable row identity, up/down flashes, and which of the grid's two update paths to use for what.
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">↓</p>
<p class="big">{fmt.format(end)}</p>
<p class="nights"><strong>{nights}</strong> night{nights === 1 ? '' : 's'} · {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>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.