@lilydesignsystem/vue-calendar
| Install | |
|---|---|
npm install @lilydesignsystem/vue-calendar |
|
| Latest Version: | 0.7.0 |
| Vue: | 3 |
| License: | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause |
| Last Updated: | Oct 25, 2026 |
| Links: | GitHub · npm |
@lilydesignsystem/vue-calendar
A headless, accessible calendar for Vue 3: week, four-week and month views, previous/next/today,
keyboard navigation, a picked day, disabled days and consumer-rendered events. Composes
@lilydesignsystem/vue-headless's CalendarTable family and the civil-date arithmetic of
@lilydesignsystem/vue-date-time-picker. Ships no CSS.
See spec/index.md and AGENTS.md.
Install
npm install @lilydesignsystem/vue-calendar @lilydesignsystem/vue-headless @lilydesignsystem/vue-date-time-picker
Quick start
<script setup lang="ts">
import { ref } from "vue";
import CalendarView from "@lilydesignsystem/vue-calendar";
const view = ref<"week" | "four-week" | "month">("month");
const anchorDate = ref("2026-01-15");
const events = [{ id: "e1", date: "2026-01-06", title: "Check-up" }];
const labels = {
monthView: "Month",
weekView: "Week",
today: "Today",
previous: (v: string) => `Previous ${v}`,
next: (v: string) => `Next ${v}`,
periodAnnouncement: (v: string, s: string, e: string) => `${v}: ${s} to ${e}`,
weekdayAbbr: (d: number) => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d],
};
</script>
<template>
<CalendarView
v-model:view="view"
v-model:anchor-date="anchorDate"
label="Appointments"
today="2026-01-08"
:events="events"
:labels="labels"
@day-click="(date) => console.log('picked', date)"
@event-click="(event) => console.log('open', event.id)"
>
<template #day="{ date, events: dayEvents, openEvent }">
<span>{{ Number(date.slice(8)) }}</span>
<button v-for="e in dayEvents" :key="e.id" type="button" @click="openEvent(e)">{{ e.title }}</button>
</template>
</CalendarView>
</template>
Keyboard
Arrows move the cursor by a day or a week; Home/End go to the row's ends; PageUp/PageDown step by the
view's length; Enter/Space pick the day; t goes to today. Disabled days stay focusable but cannot be picked.
- Typed event fields and editing (0.5.0):
eventFields+event.valuesshow typed values in the agenda;field-editedits them in place. See spec. - Export (0.5.0):
labels.exportCsv/exportTsv/exportExcelpluslabels.exportHeadersadd CSV, TSV and Excel buttons for the shown period's (or every) event.
Period announcements and limits (0.6.0)
The shown period is announced (labels.periodAnnouncement) whenever it changes, however it changed. minDate / maxDate bound navigation as well as picking: Previous / Next go aria-disabled at the limit, Today is inert out of range, keyboard moves are clamped, and labels.rangeLimit(limit, date) announces the stop. See spec §18.
Filtering and sorting (0.6.0)
filter(event)hides events it rejects in every view, combining withsearch(both must pass).eventCompare(a, b)orders the events within a day, and so in the agenda; stable.
Export: copy to clipboard (0.6.0)
- Copy to clipboard:
labels.copynames a button (data-…-export-format="copy"; for the kanban board, Gantt chart and calendar it also needslabels.exportHeaders, like the downloads) that puts exactly what a download would hold — the same events, scope and order, header row first — on the clipboard as tab-separated text (toClipboardText: no byte-order mark, formula text defused), ready to paste into a spreadsheet.copyToClipboarduses the async Clipboard API and falls back to a hidden, selected<textarea>withexecCommand("copy"); it never throws.labels.copyAnnouncement(rowCount)is announced,labels.copyFailedwhen neither path worked, and thecopyevent andcopyHandler({ text, rowCount })may returnfalseto take over (nothing is copied).
In-place edit: validation, undo and redo (0.6.0)
- Field
validate(value, values)refuses a value of the right type that is not acceptable: return the reason. The editor stays open witharia-invalid="true"(cleared as soon as the user types again) andlabels.editRefused(event, header, reason)is announced, falling back tolabels.editInvalid. Undo and redo work from anywhere in the calendar. - Undo and redo: every accepted edit is remembered (the latest 100). Ctrl/Cmd+Z puts the earlier value back and Ctrl/Cmd+Shift+Z (or Ctrl+Y) re-applies one, both through the
field-editevent with the restored value and the one it replaced — the consumer owns the data, so an undo is reported like an edit, and an undo is not itself recorded. A new edit ends the redo chain. Inside a field editor the browser's own undo is left alone; nothing happens in areadonlycomponent or without thefield-editevent.labels.undoAnnouncement(event, header, text)/labels.redoAnnouncement(event, header, text)are announced. The stacks are the pureedit-history.ts(recordEdit,undoEdit,redoEdit,historyKey).
Weekend, holiday and time-off indicators (0.6.0)
- Marked ranges (
ranges:{ start, end, kind?, label? }, already there for holidays, closures and terms) now carry theirlabelas real text: shown beside the day number (and the agenda date) when there is nodaysnippet, and read as the cell's description (aria-describedbya hidden span, several labels joined with ", "), so a screen reader hears why a day is marked and the day is never colour-only. A range without a label marks the day and adds no text. Adaysnippet still receivesrangesand renders its own. - Weekends: the weekday header cells now carry
data-weekendlike the day cells (weekendDays, never defaulted). - Shading is CSS and ships in the 57 reference themes: weekend cells are tinted; a day inside a range is tinted and striped.
Narrow screens, agenda dates, today and direction (0.6.0)
- Agenda on a narrow screen.
agendaBelow(pixels, for example640) makes a viewport narrower than that read as the agenda view; theviewprop is untouched, so a wide screen goes back to it. A view the user picks with the switcher is respected until the screen is wide again. It usesmatchMediaafter mount (a server rendersview); withoutmatchMedianothing changes. This replaces a hand-writtenmatchMediaswitch in the app. - Hide the agenda date.
agendaDate={false}hides the date beside each day's events visually (a clipped, one-pixel<time datetime>withdata-visually-hidden), so a layout that shows its own date can drop it and a screen reader still gets it. Defaulttrue. - Today holds its contrast. The reference themes mark today with a ring in the cell's own text colour (
currentColor) and a bold number, never a fill: it stays readable on a light page, a dark page and a selected cell, and says "today" without colour. The calendar table istable-layout: fixedwithmin-width: 0day cells that wrap, so a phone never clips them. - Right-to-left. The horizontal arrows are visual: in a right-to-left layout (the nearest
dir) ArrowLeft goes to the next day, and Shift+Arrow and Alt+Arrow (moving an event) follow the arrow. Up, Down, Home, End and the page keys are unchanged.isRtl/logicalKeyare exported. - Tests §8.131–§8.146, including a complete Chinese bundle that must leave no English in the DOM.
Search (0.6.0)
search (a v-model; the field renders when labels.search names it) narrows the events: every word must appear, in any order, ignoring case and diacritics. New: the calendar had no search. Only matching events show, in every view (week, four-week, month, agenda); the default text is the event title and every field's text (searchText replaces it). The agenda wraps matches in <mark class="calendar-view-match">; labels.searchAnnouncement(matches, total) is announced; Escape clears. Exports hold what the search leaves. See spec §17.
Related Packages
Lily Design System Vue 3 headless interactive data grid (sort, filter, selection...
Lily Design System Vue 3 locale picker: an icon button opening an APG listbox th...
Lily Design System Vue 3 theme picker: an icon button opening an APG listbox tha...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.7.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.6.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.5.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.3.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.2.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.1.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |