Date picker
An outline button is the field. It opens a native popover that holds the Calendar month grid.
Choosing a day writes an English date
(15 August 2026) into the field and calls hidePopover().
Escape and light-dismiss are native popover — there is no
second overlay.
Paste the markup first. Copy src/components/ui/Place.svelte and call on_toggle so the panel sits next to the
field (see Popover).
Native Date builds the cells; there is no date
library. There is no wrapper. Tiny $state on
this page is enough. Inert — no booking backend.
Preview
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
Markup
<button class="button button-outline date-picker" type="button" popovertarget="when" aria-haspopup="dialog" aria-expanded="false" data-placeholder="">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M8 2v4"></path>
<path d="M16 2v4"></path>
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
<path d="M3 10h18"></path>
</svg>
Pick a date
</button>
<div id="when" popover class="popover date-picker" data-side="bottom" data-align="start">
<div class="calendar">
<div class="calendar-caption">
<button class="button button-outline button-icon button-sm calendar-prev" type="button" aria-label="Previous month"></button>
<div class="calendar-heading">August 2026</div>
<button class="button button-outline button-icon button-sm calendar-next" type="button" aria-label="Next month"></button>
</div>
<div class="calendar-grid">
<div class="calendar-weekday">Su</div>
<button class="calendar-day" type="button">15</button>
</div>
</div>
</div>Imported
<script>
import { on_toggle } from './ui/Place.svelte';
const MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
const WEEKDAYS = [ 'Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa' ];
const now = new Date();
let view = $state({ year: now.getFullYear(), month: now.getMonth() });
let selected = $state(null);
function month_label(year, month) {
return MONTHS[month] + ' ' + year;
}
function month_days(year, month) {
const weekday = new Date(year, month, 1).getDay();
const start = new Date(year, month, 1 - weekday);
const days = [];
for (let i = 0; i < 42; i++) {
const date = new Date(start.getFullYear(), start.getMonth(), start.getDate() + i);
days.push({ year: date.getFullYear(), month: date.getMonth(), day: date.getDate(), outside: date.getMonth() !== month });
}
return days;
}
function is_today(cell) {
const today = new Date();
return cell.year === today.getFullYear() && cell.month === today.getMonth() && cell.day === today.getDate();
}
function is_selected(cell) {
return selected && selected.year === cell.year && selected.month === cell.month && selected.day === cell.day;
}
function shift(delta) {
const next = new Date(view.year, view.month + delta, 1);
view = { year: next.getFullYear(), month: next.getMonth() };
}
function field_label(value) {
if (!value) return 'Pick a date';
return value.day + ' ' + MONTHS[value.month] + ' ' + value.year;
}
function pick(cell) {
selected = { year: cell.year, month: cell.month, day: cell.day };
if (cell.outside) view = { year: cell.year, month: cell.month };
document.getElementById('when')?.hidePopover();
}
</script>
<button class="button button-outline date-picker" type="button" popovertarget="when" aria-haspopup="dialog" aria-expanded="false" data-placeholder={selected ? undefined : ''}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M8 2v4"></path>
<path d="M16 2v4"></path>
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
<path d="M3 10h18"></path>
</svg>
{field_label(selected)}
</button>
<div id="when" popover class="popover date-picker" data-side="bottom" data-align="start" ontoggle={on_toggle}>
<div class="calendar">
<div class="calendar-caption">
<button class="button button-outline button-icon button-sm calendar-prev" type="button" aria-label="Previous month" onclick={() => shift(-1)}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="m15 18-6-6 6-6"></path>
</svg>
</button>
<div class="calendar-heading" aria-live="polite">{month_label(view.year, view.month)}</div>
<button class="button button-outline button-icon button-sm calendar-next" type="button" aria-label="Next month" onclick={() => shift(1)}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<div class="calendar-grid">
{#each WEEKDAYS as day}
<div class="calendar-weekday">{day}</div>
{/each}
{#each month_days(view.year, view.month) as cell}
<button class="calendar-day" type="button" data-outside={cell.outside ? '' : undefined} data-today={is_today(cell) ? '' : undefined} data-active={is_selected(cell) ? '' : undefined} onclick={() => pick(cell)}>{cell.day}</button>
{/each}
</div>
</div>
</div>