Appearance
@carrot/design-vue-calendar
Vue 3 components and composables for calendar views. Wraps @carrot/design-components-calendar CSS with behaviour, navigation, event positioning, drag/resize, recurring event support via @carrot/temporal, and popover for event details.
Zero CSS in this package. All visual styling comes from @carrot/design-components-calendar.
Installation
bash
npm install @carrot/design-vue-calendarRequires the component-level CSS to be imported separately:
css
@import "@carrot/design-components-calendar";Quick Start
vue
<script setup lang="ts">
import { ref } from "vue";
import { CarrotCalendar, type CalendarEventData } from "@carrot/design-vue-calendar";
const currentDate = ref("2026-03-22");
const events = ref<CalendarEventData[]>([
{ id: "1", title: "Standup", start: "2026-03-22T09:00Z", end: "2026-03-22T09:30Z", variant: "accent" },
]);
</script>
<template>
<CarrotCalendar v-model="currentDate" :events="events" editable />
</template>Components
CarrotCalendar (Root)
Props: modelValue · view · events · weekStart · slotInterval · showWeekNumbers · showAllDay · compact · timezone · editable · workingHoursStart · workingHoursEnd · workingHoursMode · weekDays · formatDate · formatTime Events: update:modelValue · update:view · event:click · event:dblclick · event:drop · event:resize · slot:click · date:click · more:click · series:edit · event:delete Slots: header Provides: CalendarContext via CALENDAR_INJECTION_KEY
CarrotCalendarHeader
Props: title · showToday · showViewSwitcher · availableViews Slots: nav-prev · nav-next · nav-today · title · view-switcher · actions
CarrotCalendarEvent
Props: event (required) · variant · size · selected · dragging · resizing · continued · continues Events: click · dblclick · dragstart Slots: default · time
CarrotCalendarMonthEvent
Props: event (required) · variant · continued · continues Events: click · dblclick Slots: default
CarrotCalendarEventPopover
Props: event · open · anchor · isRecurring Events: update:open · close · edit:this · edit:future · edit:all · delete:this · delete:future · delete:all Slots: header · default · actions
CarrotCalendarDayView / CarrotCalendarWeekView
Internal view components. Inject CalendarContext. Emit: event:click · event:dblclick · event:drop · event:resize · slot:click
CarrotCalendarMonthView
Internal view component. Emits: event:click · event:dblclick · date:click · more:click
CarrotCalendarYearView
Internal view component. Emits: date:click
CarrotCalendarNowIndicator
Rendered automatically in day/week views for today's column.
Composables
useCalendarNavigation(options?)
Date navigation state machine. Returns currentDate, view, rangeStart, rangeEnd, visibleDays, title, next(), prev(), today(), goTo(), setView().
Options: initialDate · view · weekStart · weekDays
useCalendarEvents(options)
Event filtering and overlap layout. Returns eventsForDate(), allDayEventsForDate(), timedEventsForDate(), positionEvents().
Options: events · slotHeight · slotInterval
useCalendarGrid(options?)
Time slot generation and coordinate conversion. In "hide" mode, slots outside working hours are excluded entirely. Returns timeSlots, hourLabels, slotHeight, dayHeight, visibleStartMinutes, minutesToPixels(), pixelsToMinutes(), groupIntoWeeks(), groupIntoMonths().
Options: slotInterval · workingHoursStart · workingHoursEnd · workingHoursMode
useCalendarDrag(options)
Pointer-based drag with snap. Returns state, beginDrag(), isDragging().
Options: minutesToPixels · pixelsToMinutes · snapMinutes · onDrop · onResize
useNowIndicator(options?)
Real-time clock. Returns nowMinutes, todayDate, isToday().
Options: interval · enabled
CalendarContext (Provide/Inject)
Provided by CarrotCalendar via CALENDAR_INJECTION_KEY.
State: view · currentDate · rangeStart · rangeEnd · visibleDays · slotInterval · slotHeight · weekStart · timezone · workingHoursStart · workingHoursEnd · workingHoursMode · visibleStartMinutes · editable · events · selectedEventId Navigation: next() · prev() · today() · goTo() · setView() Events: selectEvent() · eventsForDate() · allDayEventsForDate() · timedEventsForDate() Conversion: minutesToPixels() · pixelsToMinutes() · formatDate() · formatTime()
CalendarEventData
ts
interface CalendarEventData {
id: string;
title: string;
description?: string;
start: string;
end: string;
allDay?: boolean;
variant?: CalendarEventVariant;
color?: string;
colorFg?: string;
icon?: string;
location?: string;
editable?: boolean;
recurrence?: RecurringEvent;
seriesId?: string;
instanceIndex?: number;
isOverride?: boolean;
meta?: Record<string, unknown>;
}Dependencies
| Package | Purpose |
|---|---|
@carrot/design-components-calendar | CSS styles |
@carrot/temporal | RecurringEvent type |
@carrot/core | Shared utilities |
vue ^3.5.18 | Peer dependency |
Build
bash
npm run buildUsage Guide
Included in the umbrella. If you use
@carrot/design-vueand@carrot/design-components, you already have calendar — no extra install or import needed.
Setup
ts
// Recommended — from the umbrella (nothing extra to install)
import {
CarrotCalendar,
CarrotCalendarEvent,
CarrotCalendarEventPopover,
type CalendarEventData,
} from "@carrot/design-vue";
// Or from the individual package for explicit tree-shaking
import { CarrotCalendar, type CalendarEventData } from "@carrot/design-vue-calendar";CSS is included in the @carrot/design-components umbrella. If you use individual CSS packages instead:
ts
import "@carrot/design-components-calendar";CarrotCalendar — Practical Examples
Basic Month Calendar
vue
<script setup lang="ts">
import { ref } from "vue";
import { CarrotCalendar, type CalendarEventData } from "@carrot/design-vue";
const currentDate = ref("2026-03-22");
const events = ref<CalendarEventData[]>([
{ id: "1", title: "Team standup", start: "2026-03-22T09:00Z", end: "2026-03-22T09:30Z", variant: "accent" },
{ id: "2", title: "Sprint review", start: "2026-03-22", end: "2026-03-22", allDay: true, variant: "success" },
{ id: "3", title: "Holiday", start: "2026-03-25", end: "2026-03-27", allDay: true, color: "#8b5cf6" },
]);
</script>
<template>
<CarrotCalendar
v-model="currentDate"
view="month"
:events="events"
week-start="monday"
/>
</template>Work Week View (5 days, dimmed non-working hours)
vue
<template>
<CarrotCalendar
v-model="currentDate"
view="week"
:events="events"
:week-days="5"
slot-interval="30"
editable
:working-hours-start="8"
:working-hours-end="18"
working-hours-mode="dim"
/>
</template>Hidden non-working hours
Set working-hours-mode="hide" to remove non-working hours entirely. The grid only renders the working range (e.g. 08:00–18:00):
vue
<template>
<CarrotCalendar
v-model="currentDate"
view="week"
:events="events"
:working-hours-start="8"
:working-hours-end="22"
working-hours-mode="hide"
/>
</template>Day View with Click-to-Create
vue
<script setup lang="ts">
import { ref } from "vue";
import { CarrotCalendar, type CalendarEventData } from "@carrot/design-vue";
const currentDate = ref("2026-03-22");
const events = ref<CalendarEventData[]>([]);
function onSlotClick(date: string, time: string) {
events.value.push({
id: `new-${Date.now()}`,
title: "New event",
start: `${date}T${time}Z`,
end: `${date}T${time.replace(/:\d{2}$/, ":30")}Z`,
variant: "accent",
editable: true,
});
}
</script>
<template>
<CarrotCalendar
v-model="currentDate"
view="day"
:events="events"
slot-interval="30"
editable
@slot:click="onSlotClick"
/>
</template>Year Overview with Drill-Down
vue
<script setup lang="ts">
import { ref } from "vue";
import { CarrotCalendar, type CalendarView } from "@carrot/design-vue";
const currentDate = ref("2026-03-22");
const view = ref<CalendarView>("year");
function onDateClick(date: string) {
currentDate.value = date;
view.value = "day";
}
</script>
<template>
<CarrotCalendar
v-model="currentDate"
v-model:view="view"
:events="events"
@date:click="onDateClick"
/>
</template>Custom Event Rendering
vue
<template>
<CarrotCalendar v-model="currentDate" :events="events">
<!-- Custom header -->
<template #header="{ title, nav }">
<div style="display: flex; align-items: center; padding: 1rem;">
<button @click="nav.prev"><</button>
<h2 style="margin: 0 1rem;">{{ title }}</h2>
<button @click="nav.next">></button>
</div>
</template>
</CarrotCalendar>
</template>Event Popover with Recurring Event Support
vue
<script setup lang="ts">
import { ref } from "vue";
import {
CarrotCalendar,
CarrotCalendarEventPopover,
type CalendarEventData,
} from "@carrot/design-vue";
import { addException, splitEvent } from "@carrot/temporal";
const popoverEvent = ref<CalendarEventData | null>(null);
const popoverAnchor = ref<HTMLElement | null>(null);
const popoverOpen = ref(false);
function onEventClick(event: CalendarEventData, domEvent: MouseEvent) {
popoverEvent.value = event;
popoverAnchor.value = domEvent.target as HTMLElement;
popoverOpen.value = true;
}
function onEditThis() {
// Edit single instance — add override via @carrot/temporal
console.log("Edit this instance:", popoverEvent.value?.id);
popoverOpen.value = false;
}
function onEditFuture() {
// Split series at this date — use splitEvent() from @carrot/temporal
console.log("Edit this and future:", popoverEvent.value?.id);
popoverOpen.value = false;
}
</script>
<template>
<CarrotCalendar
v-model="currentDate"
:events="events"
@event:click="onEventClick"
/>
<CarrotCalendarEventPopover
v-model:open="popoverOpen"
:event="popoverEvent"
:anchor="popoverAnchor"
:is-recurring="!!popoverEvent?.recurrence"
@edit:this="onEditThis"
@edit:future="onEditFuture"
/>
</template>Drag & Drop Events
vue
<script setup lang="ts">
function onEventDrop(event: CalendarEventData, newStart: string, newEnd: string) {
// Update the event with the new times
const idx = events.value.findIndex((e) => e.id === event.id);
if (idx !== -1) {
events.value[idx] = { ...events.value[idx], start: newStart, end: newEnd };
}
}
function onEventResize(event: CalendarEventData, newStart: string, newEnd: string) {
const idx = events.value.findIndex((e) => e.id === event.id);
if (idx !== -1) {
events.value[idx] = { ...events.value[idx], start: newStart, end: newEnd };
}
}
</script>
<template>
<CarrotCalendar
v-model="currentDate"
view="week"
:events="events"
editable
@event:drop="onEventDrop"
@event:resize="onEventResize"
/>
</template>Using Composables Directly
For custom calendar layouts, use the composables without the CarrotCalendar wrapper:
vue
<script setup lang="ts">
import {
useCalendarNavigation,
useCalendarGrid,
useCalendarEvents,
useNowIndicator,
} from "@carrot/design-vue"; // or "@carrot/design-vue-calendar"
const nav = useCalendarNavigation({ view: "week", weekStart: "monday" });
const grid = useCalendarGrid({ slotInterval: "30" });
const { isToday } = useNowIndicator();
// Build your own layout using nav.visibleDays, grid.timeSlots, etc.
</script>Common Patterns
"+N more" popover
vue
<script setup lang="ts">
function onMoreClick(date: string, events: CalendarEventData[], domEvent: MouseEvent) {
// Show a dropdown/modal with all events for this date
console.log(`${events.length} events on ${date}`);
}
</script>Colour-coding by category
ts
const categoryColors: Record<string, string> = {
meeting: "#3b82f6",
deadline: "#ef4444",
personal: "#8b5cf6",
travel: "#f59e0b",
};
const events: CalendarEventData[] = tasks.map((task) => ({
id: task.id,
title: task.name,
start: task.start,
end: task.end,
color: categoryColors[task.category],
}));