Skip to main content

Key Features

A tour of what the SVAR Vue Calendar widget does out of the box: the views you can switch between, how users edit and move events, what recurrence and filtering give you, and the hooks for tailoring look and behavior.

full calendar widget overview showing the toolbar on top and a populated week view below

What the Widget Gives You

The Calendar is one Vue 3 component backed by a reactive store. You hand it an array of events and it renders them in any of seven views, handles the grid math, drag interactions, and toolbar wiring. Every user action - navigating, creating, editing, deleting, filtering - flows through one event bus you can observe or intercept.

Three things to keep in mind:

  • One data shape. Every event has id, start, end, plus any custom fields you carry along (color, project id, calendar group). The same event flows through all views.
  • Views are pluggable. Built-in views are just registered classes. Configure them, add more, or write your own.
  • The store is the API. State (current date, view, filters, selection) and actions (add-event, update-event, ...) are accessed through one instance handle, exposed via ref or the init callback.

The rest of this guide groups features by what users see and what you wire up around them.

Views at a Glance

The widget ships with seven views. Only day, week, and month show up by default - list more in the views prop to expose them.

View idLayoutRangePlan
dayTime grid plus an all-day bar1 dayMIT
week7-column time grid plus an all-day bar1 weekMIT
monthCalendar matrix, weeks as rows1 monthMIT
agendaChronological list grouped by day1 monthPRO
year12 mini-months with markers on busy days1 yearPRO
resourcesResource columns over a vertical time axis1 dayPRO
timelineResource rows with a horizontal time axis1 dayPRO

Picking the active view and which ones to show

view selects the view on mount; views controls what the toolbar's view switcher offers.

<Calendar
:events="data"
view="week"
:views="['day', 'week', 'month', 'agenda', 'year']"
/>

Tweaking a built-in view

views entries can be ViewConfig objects. Use them to relabel a view or deep-merge section overrides. This week view runs from 6 AM to 10 PM and snaps drags to 30 minutes:

<Calendar
:events="data"
:views="[
{
id: 'week',
sections: {
timeGrid: {
yScale: { startHour: 6, endHour: 22, snapStep: 30 },
},
},
},
'month',
]"
/>

The merge replaces only the keys you specify; step, format, and the other section defaults stay put.

Resource scheduling

The resources and timeline views need columns (or rows) defined via xScale.items / yScale.items plus an accessor that maps each event to its column id:

<Calendar
:events="data"
view="resources"
:views="[
{
id: 'resources',
sections: {
timeGrid: {
xScale: {
items: [
{ id: 'room-a', label: 'Room A' },
{ id: 'room-b', label: 'Room B' },
],
accessor: 'roomId',
},
},
},
},
]"
/>

When a section override isn't enough - a workweek, a fortnight, a fiscal year - subclass ViewModel and call registerCalendarView.

Editing and Interaction

Out of the box, users can:

  • Click and drag on a time grid to create a new event.
  • Drag an event to move it; drag its edges to resize.
  • Click an event to select it; the Editor companion opens automatically when bound to the calendar.
  • Right-click an event for a context menu - once you wrap the calendar in <ContextMenu>.

Every interaction dispatches an action through the store. You can observe or block them by passing prop callbacks (onaddevent, onupdateevent, ondeleteevent, onselectevent) or registering handlers via api.on(...) and api.intercept(...).

Inline editing with the Editor

Editor is a separate companion component. Mount it next to <Calendar> and pass the same api:

<script setup>
import { Calendar, Editor } from "@svar-ui/vue-calendar";
import { ref } from "vue";

const api = ref(null);
</script>

<template>
<Calendar :init="a => api = a" :events="events" />
<Editor :api="api" />
</template>

It auto-saves field changes through update-event. Use placement="modal" for a dialog layout, or autoSave={false} plus a custom bottomBar for an explicit Save flow.

Read-only mode

Pass readonly to disable drag, resize, and create, and to drop the toolbar's add-event button:

<Calendar :events="events" readonly />

Right-click menu

ContextMenu wraps the calendar and adds a right-click menu. The default edit-event and delete-event items are handled internally; custom entries bubble through onclick. getMenuOptions() returns the default item list as a starting point:

<script setup>
import { Calendar, ContextMenu, getMenuOptions } from "@svar-ui/vue-calendar";
import { ref } from "vue";

const api = ref(null);

const options = [
...getMenuOptions(),
{ id: "duplicate", text: "Duplicate", icon: "wxi-empty" },
];

function onclick({ action, context }) {
if (action.id === "duplicate") {
api.value.exec("add-event", { event: { ...context, id: undefined } });
}
}
</script>

<template>
<ContextMenu :api="api" :options="options" :onclick="onclick">
<Calendar :init="a => api = a" :events="events" />
</ContextMenu>
</template>

Programmatic control

The instance API is the entry point for anything you do without user input - jumping dates, switching views, pushing events from a backend, or blocking a delete with a confirmation dialog. api.exec dispatches actions, api.intercept lets you cancel them:

<script setup>
import { ref } from "vue";

const api = ref(null);

function init(a) {
api.value = a;
a.intercept("delete-event", ({ id }) => confirm("Delete this event?"));
}

function goToToday() {
api.value.exec("navigate-time", { direction: "now" });
}
</script>

<template>
<Calendar :events="events" :init="init" />
</template>

See the API reference for the full surface.

Recurrence and Filtering

Recurring events

Set recurring and add an iCalendar rrule to events that repeat. The store expands them into occurrences and supports editing one instance or this-and-following:

<script setup>
const events = [
{
id: 1,
start: new Date(2026, 4, 4, 9, 0),
end: new Date(2026, 4, 4, 9, 30),
text: "Daily standup",
rrule: "FREQ=DAILY;INTERVAL=1",
},
];
</script>

<template>
<Calendar :events="events" recurring />
</template>

When the user edits a recurring event, onupdateevent carries mode: "single" | "following" plus the originalDate of the occurrence. Use those to persist exceptions correctly. Exceptions track back to the master event via masterEventId and originalDate; excluded dates live in exdates.

Filtering events

Filtering is view-level: the source events array isn't touched, only what gets rendered. Dispatch filter-events with a predicate and an optional tag to stack multiple filters:

api.exec("filter-events", {
filter: event => event.priority === "high",
tag: "priority",
});

// Clear by re-dispatching with no filter under the same tag
api.exec("filter-events", { filter: null, tag: "priority" });

For a turnkey calendar-group sidebar, mount CalendarPanel inside the calendar's default slot - it dispatches filter-events for you when a checkbox toggles:

<script setup>
import { Calendar, CalendarPanel } from "@svar-ui/vue-calendar";

const calendars = [
{ id: "work", label: "Work" },
{ id: "personal", label: "Personal", active: false },
];
</script>

<template>
<Calendar :events="events">
<CalendarPanel :calendars="calendars" accessor="calendarId" />
</Calendar>
</template>

Customization Hooks

The widget exposes hooks at every layer - markup, styling, toolbar layout, event content, and persistence.

Cell and event styling

cellCss paints background grid cells; eventCss paints event wrappers. Both receive a context with the active view, section, and mode, so a single callback can adapt across layouts:

<script setup>
function cellCss(ctx) {
if (ctx.date && (ctx.date.getDay() === 0 || ctx.date.getDay() === 6)) {
return "weekend";
}
return "";
}

function eventCss(ctx) {
return `priority-${ctx.event.priority ?? "normal"}`;
}
</script>

<template>
<Calendar :events="events" :cellCss="cellCss" :eventCss="eventCss" />
</template>

See the styling guide for the full set of mode and section identifiers.

Custom event content, tooltip, and event card

Three component props take Vue components: eventContent replaces the inner markup of an event, tooltip shows on hover, eventPopup shows on click. Each receives a small set of props (event, mode, sometimes close):

<Calendar
:events="events"
:eventContent="MyEventBlock"
:tooltip="MyTooltip"
:eventPopup="MyEventCard"
/>

Custom toolbar

Pass toolbar.items to replace the default layout. getToolbarItems() returns the default array as a starting point - append, remove, or swap entries:

<script setup>
import { getToolbarItems } from "@svar-ui/vue-calendar";

const toolbar = {
items: [
...getToolbarItems(),
{ id: "export", comp: "button", text: "Export" },
],
};

function onaction({ id }) {
if (id === "export") {
// ...
}
}
</script>

<template>
<Calendar :events="events" :toolbar="toolbar" :onaction="onaction" />
</template>

Pass :toolbar="null" to hide it entirely. Custom item ids fire action, which the onaction prop and api.on("action", ...) both observe.

Custom editor fields

Editor accepts an items array. Build it from getEditorItems() plus your own entries, and register a Vue component for any custom comp type via registerEditorItem:

<script setup>
import { Editor, getEditorItems, registerEditorItem } from "@svar-ui/vue-calendar";
import Comments from "@wx/vue-comments";

registerEditorItem("comments", Comments);

const items = [
...getEditorItems(),
{ comp: "comments", key: "comments", label: "Comments" },
];
</script>

<template>
<Editor :api="api" :items="items" />
</template>

Backend integration

For a one-call REST hookup, attach RestDataProvider inside init. It implements the action handlers for add-event, update-event, and delete-event, so user changes round-trip to your server without per-action wiring. Use api.setNext to attach it to the action chain:

<script setup>
import { Calendar, RestDataProvider } from "@svar-ui/vue-calendar";
import { ref, onMounted } from "vue";

const provider = new RestDataProvider("/api");
const events = ref([]);

onMounted(async () => {
events.value = await provider.getData();
});

function init(api) {
api.setNext(provider);
}
</script>

<template>
<Calendar :events="events" :init="init" />
</template>

For non-REST backends, listen to onaddevent, onupdateevent, and ondeleteevent and persist the changes yourself.

iCal import and export

parseICal turns an .ics payload into a CalendarEvent[] ready to feed into events. serializeICal does the reverse - useful for exporting api.getEvents() as a downloadable file.

import { parseICal, serializeICal } from "@svar-ui/vue-calendar";

const imported = parseICal(text);
const ics = serializeICal(api.getEvents());

Theming and localization

Wrap the calendar in Willow or WillowDark for the built-in themes. For other languages and a custom week start, use the Locale wrapper from @svar-ui/calendar-locales - see the localization guide.

Where to Go Next