Component
Month calendar
<DomMonthCalendar>A large scrolling month calendar that keeps weekday grids aligned and lets each day render custom UI.
Playground
Try every prop live
Month calendar playground
Edit the range and display props to preview a stacked month grid.
June 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
8Mon
9Tue
10Wed
11Thu
12Fri
13Sat
14Sun
15Mon
16Tue
17Wed
18Thu
19Fri
20Sat
21Sun
22Mon
23Tue
24Wed
25Thu
26Fri
27Sat
28Sun
29Mon
30Tue
1Wed
2Thu
3Fri
4Sat
5Sun
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
29Mon
30Tue
1Wed
2Thu
3Fri
4Sat
5Sun
6Mon
7Tue
8Wed
9Thu
10Fri
11Sat
12Sun
13Mon
14Tue
15Wed
16Thu
17Fri
18Sat
19Sun
20Mon
21Tue
22Wed
23Thu
24Fri
25Sat
26Sun
27Mon
28Tue
29Wed
30Thu
31Fri
1Sat
2Sun
Playground.vuevue
<script setup>
import { reactive } from 'vue';
import { DomMonthCalendar } from '@getdom/studio/vue';
const data = reactive({
"startDate": "2026-06-11",
"initialMonth": null,
"initialYear": null,
"months": 2,
"locale": "en-GB",
"weekStartsOn": 1,
"weekdayFormat": "short",
"showAdjacentDays": true,
"fixedWeeks": false,
"mutedBefore": "2026-06-11",
"mutedAfter": "",
"min": "",
"max": "",
"clickable": false,
"clickableAdjacentDays": false,
"disabledDate": null,
"dayHeaders": true,
"dayClass": "",
"dayStyle": ""
});
</script>
<template>
<DomMonthCalendar
v-bind="data"
/>
</template>Demo
Coparent calendar
Use dayClass and dayStyle to colour full days, then listen for day-click to change future assignments.
June 2026
Steve / Linda care schedule
Mon
Tue
Wed
Thu
Fri
Sat
Sun
July 2026
Steve / Linda care schedule
Mon
Tue
Wed
Thu
Fri
Sat
Sun
August 2026
Steve / Linda care schedule
Mon
Tue
Wed
Thu
Fri
Sat
Sun
<script setup>
import { ref } from 'vue';
import { DomMonthCalendar } from '../../../lib/vue';
const today = '2026-06-11';
const overrides = ref({});
const handovers = {
'2026-06-12': 'Handover at drama class',
'2026-06-19': 'School pickup at 3:20',
'2026-06-26': 'Linda takes kit bag',
'2026-07-03': 'Steve collects from camp',
'2026-07-10': 'Handover after swimming',
};
function parseDate(value) {
const [year, month, day] = value.split('-').map(Number);
return new Date(year, month - 1, day);
}
function daysBetween(a, b) {
const start = parseDate(a);
const end = parseDate(b);
return Math.round((end - start) / 86400000);
}
function baseParent(value) {
return Math.floor(daysBetween('2026-06-01', value) / 7) % 2 === 0 ? 'Steve' : 'Linda';
}
function parentFor(value) {
return overrides.value[value] || baseParent(value);
}
function toggleParent({ cell }) {
if (!cell.currentMonth || cell.value < today) return;
const current = parentFor(cell.value);
overrides.value = {
...overrides.value,
[cell.value]: current === 'Steve' ? 'Linda' : 'Steve',
};
}
function disabledDate(cell) {
return !cell.currentMonth || cell.value < today;
}
function dayStyle(cell) {
if (!cell.currentMonth) return '';
const parent = parentFor(cell.value);
const past = cell.value < today;
if (parent === 'Steve') {
return { backgroundColor: past ? '#fff1f2' : '#fee2e2' };
}
return { backgroundColor: past ? '#f0fdf4' : '#dcfce7' };
}
function dayClass(cell) {
if (!cell.currentMonth) return '';
return parentFor(cell.value) === 'Steve' ? 'text-red-950' : 'text-emerald-950';
}
</script>
<template>
<div class="w-[76rem] max-w-full" data-testid="coparent-example">
<DomMonthCalendar
start-date="2026-06-11"
:months="3"
:min="today"
:muted-before="today"
clickable
:disabled-date="disabledDate"
:day-style="dayStyle"
:day-class="dayClass"
@day-click="toggleParent"
>
<template #month-header>
<span>Steve / Linda care schedule</span>
</template>
<template #default="{ cell }">
<div v-if="cell.currentMonth" class="flex h-[calc(100%-1.75rem)] flex-col justify-between gap-3">
<div class="text-sm font-bold">
{{ parentFor(cell.value) }}
</div>
<div v-if="handovers[cell.value]" class="rounded-lg border border-current/15 bg-white/70 px-2.5 py-2 text-xs font-semibold leading-4 shadow-xs">
<div class="uppercase tracking-wide opacity-70">Handover</div>
<div class="mt-1">{{ handovers[cell.value] }}</div>
</div>
</div>
</template>
</DomMonthCalendar>
</div>
</template>
Demo
Content planner
Render article planning cards through the default day slot while the calendar handles month layout and muted dates.
June 2026
Articles are generated and published at 7AM-9AM UTC.
Mon
Tue
Wed
Thu
Fri
Sat
Sun
8Mon
9Tue
10Wed
11Thu
Guide: Explainer
controlled components
Vol: 170Diff: 14
Visit Article12Fri
Guide: How-to
tailwind css themes
Vol: 390Diff: 24
View Article13Sat
List: Examples
examples of a sidebar
Vol: 590Diff: 22
14Sun
List: Resources
tailwind css components
Vol: 390Diff: 45
15Mon
Guide: How-to
component testing
Vol: 480Diff: 26
16Tue
Guide: Explainer
border color in css
Vol: 590Diff: 16
17Wed
Guide: How-to
switch button css
Vol: 590Diff: 10
18Thu
Guide: Explainer
code splitting
Vol: 210Diff: 30
19Fri
Guide: Explainer
component composition
Vol: 50Diff: 18
20Sat
Guide: How-to
react loading bar
Vol: 590Diff: 17
21Sun
Guide: Explainer
vue component library
Vol: 320Diff: 30
22Mon
Guide: How-to
accessibility audit
Vol: 150Diff: 26
23Tue
Guide: How-to
js progress bar
Vol: 590Diff: 29
24Wed
Guide: Explainer
performance optimization
Vol: 320Diff: 33
25Thu
Guide: Explainer
css variables
Vol: 2,900Diff: 0
26Fri
List: Examples
component examples
Vol: 390Diff: 39
27Sat
Guide: How-to
screen reader testing
Vol: 260Diff: 18
28Sun
Guide: Explainer
high contrast mode
Vol: 920Diff: 24
29Mon
Guide: How-to
accessible dropdown menu
Vol: 70Diff: 15
30Tue
List: Resources
design system tools
Vol: 210Diff: 24
1Wed
2Thu
3Fri
4Sat
5Sun
July 2026
Articles are generated and published at 7AM-9AM UTC.
Mon
Tue
Wed
Thu
Fri
Sat
Sun
29Mon
30Tue
1Wed
Guide: Explainer
menu component
Vol: 70Diff: 38
2Thu
List: Resources
accessibility testing tools
Vol: 50Diff: 40
3Fri
Guide: Explainer
color contrast accessibility
Vol: 390Diff: 52
4Sat
Guide: Explainer
command palette
Vol: 760Diff: 24
5Sun
Guide: Explainer
drawer component
Vol: 140Diff: 30
6Mon
7Tue
8Wed
9Thu
10Fri
11Sat
12Sun
13Mon
14Tue
15Wed
16Thu
17Fri
18Sat
19Sun
20Mon
21Tue
22Wed
23Thu
24Fri
25Sat
26Sun
27Mon
28Tue
29Wed
30Thu
31Fri
1Sat
2Sun
<script setup>
import { computed } from 'vue';
import { DomMonthCalendar } from '../../../lib/vue';
const startDate = '2026-06-11';
const articles = [
{ date: '2026-06-11', type: 'Guide: Explainer', title: 'controlled components', volume: '170', difficulty: 14, cta: 'Visit Article' },
{ date: '2026-06-12', type: 'Guide: How-to', title: 'tailwind css themes', volume: '390', difficulty: 24, cta: 'View Article' },
{ date: '2026-06-13', type: 'List: Examples', title: 'examples of a sidebar', volume: '590', difficulty: 22 },
{ date: '2026-06-14', type: 'List: Resources', title: 'tailwind css components', volume: '390', difficulty: 45 },
{ date: '2026-06-15', type: 'Guide: How-to', title: 'component testing', volume: '480', difficulty: 26 },
{ date: '2026-06-16', type: 'Guide: Explainer', title: 'border color in css', volume: '590', difficulty: 16 },
{ date: '2026-06-17', type: 'Guide: How-to', title: 'switch button css', volume: '590', difficulty: 10 },
{ date: '2026-06-18', type: 'Guide: Explainer', title: 'code splitting', volume: '210', difficulty: 30 },
{ date: '2026-06-19', type: 'Guide: Explainer', title: 'component composition', volume: '50', difficulty: 18 },
{ date: '2026-06-20', type: 'Guide: How-to', title: 'react loading bar', volume: '590', difficulty: 17 },
{ date: '2026-06-21', type: 'Guide: Explainer', title: 'vue component library', volume: '320', difficulty: 30 },
{ date: '2026-06-22', type: 'Guide: How-to', title: 'accessibility audit', volume: '150', difficulty: 26 },
{ date: '2026-06-23', type: 'Guide: How-to', title: 'js progress bar', volume: '590', difficulty: 29 },
{ date: '2026-06-24', type: 'Guide: Explainer', title: 'performance optimization', volume: '320', difficulty: 33 },
{ date: '2026-06-25', type: 'Guide: Explainer', title: 'css variables', volume: '2,900', difficulty: 0 },
{ date: '2026-06-26', type: 'List: Examples', title: 'component examples', volume: '390', difficulty: 39 },
{ date: '2026-06-27', type: 'Guide: How-to', title: 'screen reader testing', volume: '260', difficulty: 18 },
{ date: '2026-06-28', type: 'Guide: Explainer', title: 'high contrast mode', volume: '920', difficulty: 24 },
{ date: '2026-06-29', type: 'Guide: How-to', title: 'accessible dropdown menu', volume: '70', difficulty: 15 },
{ date: '2026-06-30', type: 'List: Resources', title: 'design system tools', volume: '210', difficulty: 24 },
{ date: '2026-07-01', type: 'Guide: Explainer', title: 'menu component', volume: '70', difficulty: 38 },
{ date: '2026-07-02', type: 'List: Resources', title: 'accessibility testing tools', volume: '50', difficulty: 40 },
{ date: '2026-07-03', type: 'Guide: Explainer', title: 'color contrast accessibility', volume: '390', difficulty: 52 },
{ date: '2026-07-04', type: 'Guide: Explainer', title: 'command palette', volume: '760', difficulty: 24 },
{ date: '2026-07-05', type: 'Guide: Explainer', title: 'drawer component', volume: '140', difficulty: 30 },
];
const articleByDate = computed(() => Object.fromEntries(articles.map((article) => [article.date, article])));
function articleFor(value) {
return articleByDate.value[value];
}
function badgeClass(article) {
return article.type.startsWith('List:')
? 'border-amber-300 bg-amber-50 text-amber-800'
: 'border-sky-300 bg-sky-50 text-sky-800';
}
</script>
<template>
<div class="w-[78rem] max-w-full" data-testid="content-planner-example">
<DomMonthCalendar
:start-date="startDate"
:months="2"
:muted-before="startDate"
>
<template #month-header>
<span>Articles are generated and published at 7AM-9AM UTC.</span>
</template>
<template #default="{ cell }">
<article
v-if="cell.currentMonth && articleFor(cell.value)"
class="flex min-h-[7.4rem] flex-col rounded-lg border border-violet-200 bg-violet-50/35 p-3 text-sm shadow-xs"
>
<div
class="mb-2 w-fit rounded-full border px-1.5 py-0.5 text-[11px] font-bold leading-none"
:class="badgeClass(articleFor(cell.value))"
>
{{ articleFor(cell.value).type }}
</div>
<h3 class="text-sm font-bold leading-5 text-fg">
{{ articleFor(cell.value).title }}
</h3>
<div class="mt-auto flex items-center justify-between border-t border-violet-100 pt-2 text-xs text-muted-fg">
<span>Vol: <strong class="text-fg">{{ articleFor(cell.value).volume }}</strong></span>
<span>Diff: <strong class="text-fg">{{ articleFor(cell.value).difficulty }}</strong></span>
</div>
<a
v-if="articleFor(cell.value).cta"
href="#"
class="mt-2 inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 text-xs font-bold text-primary-fg transition hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
@click.prevent
>
{{ articleFor(cell.value).cta }}
</a>
</article>
</template>
</DomMonthCalendar>
</div>
</template>
Reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
locale | string | string | 'en-GB' | Locale used for month, weekday, and date labels. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | number | 1 | First day of week. 0 is Sunday, 1 is Monday. |
weekdayFormat | 'narrow' | 'short' | 'long' | string | 'short' | Weekday label length. |
showAdjacentDays | boolean | boolean | true | Show days from the previous and next months so the grid keeps its weekday alignment. |
fixedWeeks | boolean | boolean | false | Render six weeks per month even when the month needs fewer rows. |
mutedBefore | string | string | '' | Dates before this YYYY-MM-DD value render muted. |
mutedAfter | string | string | '' | Dates after this YYYY-MM-DD value render muted. |
dayHeaders | boolean | boolean | true | Render weekday headers. |
dayClass | string | array | object | function | string | Array<unknown> | Record<string, boolean> | ((cell: | '' | Extra classes, or a function that receives a day cell and returns classes for the day cell. |
dayStyle | string | object | function | string | Record<string, string | number> | ((cell: | '' | Extra styles, or a function that receives a day cell and returns styles for the day cell. |
Interaction
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
min | string | string | '' | Minimum clickable date as YYYY-MM-DD. |
max | string | string | '' | Maximum clickable date as YYYY-MM-DD. |
clickable | boolean | boolean | false | Render days as keyboard-reachable buttons and emit day-click. |
clickableAdjacentDays | boolean | boolean | false | Allow day-click on visible days outside the displayed month. |
disabledDate | function | (cell: | — | Function that receives a day cell and returns true when the day should not be clickable. |
Range
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
startDate | string | string | '' | First date in the rendered range as YYYY-MM-DD. The first month starts on the week containing this date. |
initialMonth | number | number | — | Initial month, 1-12. Used when startDate is not set. |
initialYear | number | number | — | Initial year. Used when startDate is not set. |
months | number | number | 3 | Number of consecutive months to render. |
Auto-generated from Month calendar.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @day-click | ({ | Fired when a clickable day is activated. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #(default) | { | Custom content inside each visible calendar day. |
| #month-header | { month } | Custom content rendered opposite the month title. |
Keyboard
- TabMove through clickable days when clickable is enabled.
- Enter / SpaceActivate the focused day.