Calendar
CoolAdmin's calendar page uses FullCalendar 7.0.2 with four views (Month / Week / Day / List) and a self-updating event generator — every page load spreads ~20 events across the current month so the demo never goes stale.
Last updated August 3, 2026
The calendar page (calendar.html) is a working FullCalendar 7.0.2 instance with four built-in views (Month, Week, Day, List), event color-coding by tag, and a deadline list pane next to the grid.
The clever bit: events aren’t a static array. The page generates events relative to today on every load, so whether you open the demo in August 2026 or March 2030, the month always looks populated with past, present, and upcoming events.
Upgrading from CoolAdmin 3.3 or earlier? v3.4 moved from FullCalendar 6 to 7. The asset paths, the theming approach, and the per-event color keys all changed — see What changed in FullCalendar 7 at the bottom.
Where the code lives
FullCalendar loads on the calendar page only — not on every page in the template. The calendar’s init is an inline <script> at the bottom of calendar.html (generated from src/pug/partials/content/calendar.scripts.html).
v7 ships its CSS as real files, where v6 injected styles from JavaScript. That means two scripts and three stylesheets, and the order matters:
<!-- calendar.html — only this page loads FullCalendar -->
<!-- CSS order: skeleton → theme → palette (palette holds the color tokens) -->
<link rel="stylesheet" href="vendor/fullcalendar-7.0.2/skeleton.css">
<link rel="stylesheet" href="vendor/fullcalendar-7.0.2/themes/classic/theme.css">
<link rel="stylesheet" href="vendor/fullcalendar-7.0.2/themes/classic/palette.css">
<!-- …page markup… -->
<!-- Core bundle first — the theme self-registers into FullCalendar.Shared.globalPlugins -->
<script src="vendor/fullcalendar-7.0.2/fullcalendar.global.js"></script>
<script src="vendor/fullcalendar-7.0.2/themes/classic/theme.global.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
if (!calendarEl) return;
const events = buildEvents(new Date());
renderDeadlineList(events);
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
height: 'auto',
events: events,
eventDisplay: 'block',
displayEventTime: true,
dayMaxEvents: 3,
moreLinkText: 'more',
navLinks: true,
nowIndicator: true,
eventTimeFormat: { hour: 'numeric', minute: '2-digit', meridiem: 'short' },
// Stable styling hooks — see "Theming" below
dayHeaderInnerClass: 'ca-fc-day-header',
dayCellTopInnerClass: 'ca-fc-day-number',
eventClick: (info) => { /* …show a toast or open a detail modal… */ }
});
calendar.render();
window.calendarInstance = calendar;
});
</script>
The instance is parked on window.calendarInstance so other scripts can reach into it for calendar.addEvent(), calendar.refetchEvents(), etc.
No temporal-polyfill is required. FullCalendar 7 lists it as a peer dependency, but the global bundle carries its own Temporal shim and only uses the native globalThis.Temporal when the browser provides it. The peer dependency applies to bundler-based ESM consumers.
Theming
This is the biggest practical change in v7. FullCalendar now generates hashed internal class names (.fc-classic-dl6, .fc-classic-1Wx) that change between releases, so there is no stable class API to override. Selectors like .fc-daygrid-event or .fc-button-primary — which worked in v6 — match nothing in v7.
Instead, colors are driven by the theme’s public custom properties. CoolAdmin rebinds them to its own --m-* design tokens, which means the calendar automatically follows the theme switcher presets:
body.app {
/* Toolbar buttons — flat, surface-colored, accent when active */
--fc-classic-button: var(--m-surface);
--fc-classic-button-border: var(--m-border);
--fc-classic-button-foreground: var(--m-text);
--fc-classic-button-strong: var(--m-accent);
--fc-classic-button-strong-border: var(--m-accent);
/* Primary / events */
--fc-classic-primary: var(--m-accent);
--fc-classic-primary-foreground: #fff;
/* Calendar content */
--fc-classic-today: var(--m-accent-soft);
--fc-classic-highlight: rgba(var(--m-accent-rgb), 0.12);
/* Neutrals — align the grid with card surfaces */
--fc-classic-background: var(--m-surface);
--fc-classic-foreground: var(--m-text);
--fc-classic-border: var(--m-border);
}
The full list of properties lives in vendor/fullcalendar-7.0.2/themes/classic/palette.css. That file also ships a [data-color-scheme=dark] block, so the calendar gets dark-mode tokens for free.
For anything that isn’t a color — typography, spacing — use the public class-name options rather than guessing at hashed selectors. CoolAdmin uses two:
dayHeaderInnerClass: 'ca-fc-day-header', // uppercase SUN MON TUE headers
dayCellTopInnerClass: 'ca-fc-day-number', // date number sizing
body.app .ca-fc-day-header {
color: var(--m-text-faint);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
Because these class names are yours, they survive FullCalendar patch upgrades. Most render hooks have a matching *Class option — eventClass, dayCellClass, slotLabelClass, and so on.
The self-updating event generator
A buildEvents(today) function takes the current date and returns an array of ~20 events spread across that month — past, present, and future:
function buildEvents(today) {
const y = today.getFullYear();
const m = today.getMonth();
// Helpers
const ymd = (d) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
const ymdt = (d, h, mm) => ymd(d) + 'T' + `${String(h).padStart(2,'0')}:${String(mm||0).padStart(2,'0')}:00`;
const offset = (n) => { const d = new Date(today); d.setDate(today.getDate() + n); return d; };
const nthDow = (n, dow) => { /* nth occurrence of weekday `dow` in this month */ };
const lastDow = (dow) => { /* last occurrence of weekday `dow` in this month */ };
return [
// …weekly recurring items (weekday standups, every Friday coffee)
// …date-relative anchors (offset(-5) for "five days ago", offset(+7) for "next week")
// …calendar-anchored events (nthDow(1, 1) for "first Monday of the month")
];
}
Event color coding
Events are tagged by type, and each tag maps to a background/foreground pair:
const TAG_COLORS = {
meeting: { bg: '#eaf0fc', fg: '#4272d7' }, /* brand blue */
task: { bg: '#e0f3f1', fg: '#0d8780' }, /* teal */
presentation: { bg: '#fff1e6', fg: '#d45f0a' }, /* warm orange */
deadline: { bg: '#fef2f2', fg: '#dc2626' }, /* red */
personal: { bg: '#fce7f3', fg: '#be185d' }, /* magenta */
};
Those get applied per event. v7 replaced v6’s backgroundColor / borderColor / textColor trio with color (fill) and contrastColor (text):
const e = (title, start, end, allDay, tag) => ({
title,
start,
end,
allDay,
color: TAG_COLORS[tag].bg, // was backgroundColor + borderColor
contrastColor: TAG_COLORS[tag].fg, // was textColor
extendedProps: { type: tag },
});
extendedProps carries per-event metadata that the click handler and the deadline list pane read for icons and labels. To add a tag, append to TAG_COLORS and pass the new key to e().
The deadline list pane
The right-side panel shows upcoming events in a sorted list, built by renderDeadlineList(events):
- Filters events to those starting today or later
- Sorts by start time
- Takes the first 6
- Renders each as a card with date, time, and title
This is a separate static render — it doesn’t update when the user navigates to a different month. For that, hook FullCalendar’s datesSet callback and re-render from calendar.getEvents().
Switching views
The header toolbar exposes four views:
| Button | View key | What it shows |
|---|---|---|
| Month | dayGridMonth |
6-week grid (the default) |
| Week | timeGridWeek |
Hour-granular columns for the week |
| Day | timeGridDay |
Hour-granular column for one day |
| List | listWeek |
Flat list of the week’s events |
Change the initial view by editing initialView:
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'timeGridWeek', // ← was 'dayGridMonth'
// …
});
Adding a real event source
The demo uses inline-generated events. To wire it to a backend, swap the events option to a function or URL:
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: async (info, successCallback, failureCallback) => {
try {
const r = await fetch(`/api/events?start=${info.startStr}&end=${info.endStr}`);
successCallback(await r.json());
} catch (e) {
failureCallback(e);
}
}
});
FullCalendar calls the function every time the visible date range changes. Your API returns events in the same shape: { title, start, end?, allDay?, color?, contrastColor?, extendedProps?: {} }.
See the FullCalendar event sources docs for the full schema.
What changed in FullCalendar 7
If you customized the calendar on CoolAdmin 3.3 or earlier, these are the breaking changes:
| Area | v6 | v7 |
|---|---|---|
| Assets | one JS file, CSS injected by JS | 2 scripts + 3 stylesheets |
| Theming | override .fc-daygrid-event, .fc-button-primary, … |
--fc-classic-* custom properties |
| Class names | stable and semantic | hashed, change between releases |
| Event fill | backgroundColor + borderColor |
color |
| Event text | textColor |
contrastColor |
| Resize handling | windowResize: () => calendar.updateSize() |
removed — handled internally |
| Custom classes | CSS selectors | dayHeaderInnerClass and friends |
Any v6 .fc-* CSS you wrote will silently stop applying rather than erroring — if your calendar looks unstyled after upgrading, that’s why.
Why FullCalendar and not a custom calendar
FullCalendar gets four things almost for free:
- All four views (Month / Week / Day / List) wired and styled
- Navigation toolbar with prev/next/today and a view switcher
- Click + drag interactions for resizing events and moving them between days
- Accessibility — keyboard navigation, ARIA labels, focus management
Writing those from scratch is a multi-week project.
The cost is ~825 KB of FullCalendar assets, loaded only on the calendar page. That’s a one-page tax, not a template-wide cost — and if you don’t use the calendar, deleting vendor/fullcalendar-7.0.2/ reclaims all of it.
See also
- Charts — the other major library integration
- Architecture — page-specific vendor scripts pattern
- Theming — the
--m-*token system the calendar hooks into - Interactive components — inbox, kanban, data table