How to Build a Team Availability Calendar in JavaScript
By SimpleCalendarJS Team
Every SaaS dashboard, internal tool, and team app eventually needs the same feature: a calendar that shows who is available and when. Not just one person's schedule — the combined availability of an entire team, colour-coded so a manager or coordinator can spot free windows at a glance. Search for "team availability calendar JavaScript" and you'll find commercial schedulers quoting $849–$2,499+ per developer, full platforms like Cal.com that want to own your scheduling flow, and When2Meet clones that haven't changed since 2008. You don't need any of that. Here's how to build one from scratch.
What makes a team availability calendar different
A standard event calendar shows one person's events on a timeline. A team availability calendar inverts the problem: it aggregates multiple people's schedules and highlights when overlap exists — or more importantly, when it doesn't.
| Feature | Personal Calendar | Team Availability Calendar |
|---|---|---|
| Data source | One user's events | N users' busy/free data |
| Primary view | "What am I doing?" | "When is everyone free?" |
| Conflict detection | Not needed | Core requirement |
| Time zone handling | Optional | Essential (remote teams) |
| Visual output | Event list | Heatmap / overlay grid |
This distinction drives every architectural decision. You need a way to fetch busy times for multiple people, merge them, and render the result as something more useful than a wall of overlapping event blocks.
The commercial landscape
If you've researched scheduler libraries with resource views, you've seen the pricing:
| Library | Bundle Size (gzip) | Resource/Team Views | Pricing |
|---|---|---|---|
| FullCalendar Scheduler | ~100 KB+ | Timeline, resource columns | Premium license (annual) |
| Bryntum Calendar | ~200 KB+ | Built-in resource scheduling | $2,499+/developer |
| Syncfusion Scheduler | ~150 KB+ | Timeline with 5 variations | $995+/developer |
| DayPilot Pro | ~80 KB+ | Scheduler, timeline, Gantt | $849+ (1 dev) |
| DHTMLX Scheduler | ~100 KB+ | Timeline, units, map views | $599+ (PRO) |
| DayPilot Lite | ~60 KB+ | Calendar + scheduler (limited) | Free (Apache 2.0) |
| SimpleCalendarJS | ~18 KB | Month/week/day views | Free personal / license req. commercial |
Bryntum and Syncfusion bundle resource-row views where each team member gets their own lane. That's powerful — but $995–$2,499 per developer powerful. DayPilot Lite is a capable free option (~11K weekly npm downloads for the JS package), but its scheduler view is limited compared to the Pro edition, and the bundle is still 3–4x larger than SimpleCalendarJS.
The lightweight path: use SimpleCalendarJS for the calendar UI and build the availability overlay yourself. The calendar renders month, week, and day views at ~18 KB gzipped. You layer team availability on top using colour-coded events and a small merge algorithm. Total custom code: under 150 lines.
Building the availability calendar
Step 1: Set up the calendar
npm install simple-calendar-js
import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/dist/simple-calendar-js.min.css'; const calendar = new SimpleCalendarJs('#team-calendar', { defaultView: 'week', locale: 'en-US', enabledViews: ['week', 'day'], fetchEvents: async (start, end) => { // Load availability overlay events (built in Step 3) const overlay = await fetchTeamAvailability(start, end); return overlay; }, onSlotClick: (date) => { // User clicked a free slot — open meeting creation form openScheduleForm({ selectedDate: date }); }, onEventClick: (event) => { // Show who is busy in this slot showSlotDetails(event); }, });
This gives you a navigable week/day calendar. The real work happens in fetchTeamAvailability.
Step 2: Fetch busy times for every team member
Each team member's busy/free data comes from your API. The endpoint returns time ranges where a person is unavailable — meetings, focus blocks, PTO, whatever your app tracks:
async function fetchBusyTimes(memberIds, start, end) { const results = await Promise.all( memberIds.map(async (id) => { const res = await fetch( `/api/users/${id}/busy?from=${start.toISOString()}&to=${end.toISOString()}` ); const data = await res.json(); return { memberId: id, name: data.name, busy: data.busyTimes }; }) ); return results; }
Step 3: Build the availability heatmap
This is the core algorithm. Take every team member's busy times, divide the visible time range into slots, and count how many people are busy in each slot. The count determines the colour:
function buildAvailabilityOverlay(teamBusyData, start, end) { const SLOT_MINUTES = 30; const slotMs = SLOT_MINUTES * 60 * 1000; const events = []; // Convert all busy times to timestamp ranges const allBusy = teamBusyData.flatMap((member) => member.busy.map((b) => ({ start: new Date(b.start).getTime(), end: new Date(b.end).getTime(), name: member.name, })) ); const teamSize = teamBusyData.length; // Walk through each slot in the visible range let cursor = start.getTime(); while (cursor < end.getTime()) { const slotStart = cursor; const slotEnd = cursor + slotMs; // Count how many people are busy in this slot const busyNames = allBusy .filter((b) => b.start < slotEnd && b.end > slotStart) .map((b) => b.name); const busyCount = new Set(busyNames).size; if (busyCount > 0) { // Colour based on how many people are unavailable let color; if (busyCount === teamSize) { color = '#ef444460'; // Red: nobody free } else if (busyCount >= teamSize / 2) { color = '#f59e0b50'; // Amber: half the team busy } else { color = '#10b98130'; // Green-tinted: mostly free } events.push({ id: `avail-${slotStart}`, title: `${teamSize - busyCount}/${teamSize} free`, start: new Date(slotStart), end: new Date(slotEnd), color, }); } cursor = slotEnd; } return events; }
Now wire it into the calendar:
async function fetchTeamAvailability(start, end) { const teamIds = getSelectedTeamMemberIds(); // From your UI const busyData = await fetchBusyTimes(teamIds, start, end); return buildAvailabilityOverlay(busyData, start, end); }
The result: every 30-minute slot on the calendar shows a coloured overlay — green when most of the team is free, amber when half are busy, red when nobody is available. The label shows "4/6 free" so coordinators can make decisions instantly.
Step 4: Handle time zones
Remote teams span time zones. Store all times in UTC on the server and convert on the client:
function toLocalTime(utcDate, timeZone) { return new Intl.DateTimeFormat('en-US', { timeZone, hour: '2-digit', minute: '2-digit', hour12: false, }).format(new Date(utcDate)); } // Show each member's local time alongside their availability function renderMemberTimezone(member) { const localNow = toLocalTime(new Date(), member.timeZone); return `${member.name} (${localNow} local)`; }
The Intl.DateTimeFormat API is built into every modern browser — no library needed. For server-side rendering or edge cases, Luxon (~20 KB) is the lightest option with full IANA time zone support.
Step 5: Add a "find free slot" feature
Let users ask: "When is everyone free for 60 minutes this week?" This reuses the merge-intervals algorithm from the heatmap, but in reverse — find gaps where zero people are busy:
function findFreeSlots(teamBusyData, start, end, durationMinutes) { const durationMs = durationMinutes * 60 * 1000; // Flatten and merge all busy intervals const allBusy = teamBusyData .flatMap((m) => m.busy) .map((b) => ({ start: new Date(b.start).getTime(), end: new Date(b.end).getTime(), })) .sort((a, b) => a.start - b.start); const merged = []; for (const interval of allBusy) { const last = merged[merged.length - 1]; if (last && interval.start <= last.end) { last.end = Math.max(last.end, interval.end); } else { merged.push({ ...interval }); } } // Scan gaps for windows that fit the requested duration const freeSlots = []; let cursor = start.getTime(); for (const busy of merged) { if (busy.start > cursor && busy.start - cursor >= durationMs) { freeSlots.push({ start: new Date(cursor), end: new Date(cursor + durationMs), }); } cursor = Math.max(cursor, busy.end); } if (end.getTime() - cursor >= durationMs) { freeSlots.push({ start: new Date(cursor), end: new Date(cursor + durationMs), }); } return freeSlots; }
This is the same merge intervals algorithm used in the meeting scheduler tutorial — O(n log n) time, handles all edge cases including adjacent and fully overlapping blocks.
Theming the availability overlay
SimpleCalendarJS uses CSS custom properties, so you can match the availability colours to your app's design system:
.uc-calendar { --uc-primary: #3b82f6; --uc-bg: #ffffff; --uc-border: #e5e7eb; --uc-text: #111827; } /* Dark mode */ @media (prefers-color-scheme: dark) { .uc-calendar { --uc-primary: #60a5fa; --uc-bg: #1f2937; --uc-border: #374151; --uc-text: #f9fafb; } }
The overlay colours are set per-event in the buildAvailabilityOverlay function, so you can adjust the green/amber/red values to match your palette without touching the calendar's theme.
When a commercial scheduler is the right choice
If your team availability calendar needs to support resource rows (each person as a dedicated lane with their own events), drag-and-drop shift assignment, or Gantt-style timeline views, a commercial scheduler earns its price:
- Bryntum Calendar — best-in-class drag-and-drop, resource grouping, and conflict resolution. Worth it for project management tools with 50+ resources
- DayPilot Pro — timeline views with resource rows at a lower price point ($849 vs. $2,499). Good fit for booking and resource planning apps
- FullCalendar Scheduler — the most widely adopted option (~500K weekly npm downloads for the core package). Premium resource views require a separate license
If your use case is simpler — show team availability as a heatmap overlay, find free slots, let coordinators pick a time — a ~18 KB calendar plus ~100 lines of JavaScript gets you there without the enterprise price tag or bundle weight.
Summary
- A team availability calendar aggregates multiple people's schedules into a single heatmap view — fundamentally different from a personal event calendar or a booking system
- Commercial schedulers with built-in resource views (Bryntum, Syncfusion, DayPilot Pro) cost $849–$2,499+ per developer and add 80–200 KB+ to your bundle
- SimpleCalendarJS provides the calendar UI at ~18 KB gzipped — layer availability overlays on top using colour-coded events from
fetchEvents - The heatmap algorithm divides visible time into slots, counts busy team members per slot, and assigns green/amber/red based on conflict density
- Handle time zones with the built-in
Intl.DateTimeFormatAPI — store UTC, display local, no extra dependencies - Use the merge intervals algorithm to find free windows where the entire team is available —
O(n log n)time, same pattern used in meeting schedulers
Sources & Further Reading
Research & References
- Top JavaScript Scheduler Libraries in 2026 — jqwidgets.com
- Compare the Best React Scheduler Components for 2025–2026 — DHTMLX
- FullCalendar Resource Data — fullcalendar.io
- FullCalendar Vertical Resource View — fullcalendar.io
- DayPilot Lite for JavaScript — Open Source (Apache 2.0)
- DayPilot Pro Pricing — javascript.daypilot.org
- @daypilot/daypilot-lite-javascript — npm
- Schedule-X — Modern JavaScript Event Calendar
- Cal.com — Open Source Scheduling Platform
- 9 Best LettuceMeet Alternatives in 2026 — usecarly.com
- Intl.DateTimeFormat — MDN Web Docs
Image Credits
- Cover: A Group of People Having a Meeting in the Office — Pexels
All images free to use under the Pexels License.
Frequently Asked Questions
What is a team availability calendar?
A team availability calendar is a shared view that shows when each team member is free or busy. Unlike a booking system (one customer, one provider), it aggregates multiple people's schedules so you can find overlapping free windows for meetings, shifts, or project assignments.
Can I build a team availability calendar without React or Vue?
Yes. The calendar UI is the hardest part, and framework-agnostic libraries like SimpleCalendarJS (~18 KB, zero dependencies) handle that. The availability logic — fetching busy times, computing overlaps, rendering heatmaps — is plain JavaScript that works in any environment.
How do I handle time zones in a team availability calendar?
Store all times in UTC on the server. Convert to each user's local time zone on the client using the Intl.DateTimeFormat API or a library like Luxon. Display times in the viewer's time zone by default, with an option to toggle to a teammate's zone for context.
What is the best JavaScript library for a team availability calendar?
It depends on your budget. FullCalendar's Scheduler addon (~100 KB+ gzipped, premium license) has built-in resource views. DayPilot Pro ($849+) offers timeline views with resource rows. SimpleCalendarJS (~18 KB gzipped, free for personal use) provides the calendar UI, and you build the availability layer yourself for full control.
How do I show availability as a heatmap on a calendar?
Fetch each team member's busy times, then count how many people are busy in each time slot. Render slots with zero conflicts in green, partial conflicts in amber, and full conflicts in red. SimpleCalendarJS's fetchEvents callback lets you return colour-coded overlay events that act as a visual heatmap.
Do I need a commercial scheduler for team availability?
Not necessarily. Commercial schedulers like Bryntum ($2,499+/dev), Syncfusion ($995+/dev), and DayPilot Pro ($849+) bundle resource views and conflict detection out of the box. But if your availability logic is straightforward — show free/busy per person, find overlap — a lightweight calendar library plus ~100 lines of custom JavaScript covers it at a fraction of the cost and bundle size.
Add a calendar to your app today
Free for personal projects. $49/year or $199 lifetime per commercial project.
