Back to Blog
A group of people having a meeting in the office, collaborating around a whiteboard with project plans
Tutorial
July 9, 2026
11 min read

How to Build a Team Availability Calendar in JavaScript

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

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.

FeaturePersonal CalendarTeam Availability Calendar
Data sourceOne user's eventsN users' busy/free data
Primary view"What am I doing?""When is everyone free?"
Conflict detectionNot neededCore requirement
Time zone handlingOptionalEssential (remote teams)
Visual outputEvent listHeatmap / 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:

LibraryBundle Size (gzip)Resource/Team ViewsPricing
FullCalendar Scheduler~100 KB+Timeline, resource columnsPremium 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 KBMonth/week/day viewsFree 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 tutorialO(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.DateTimeFormat API — 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

Image Credits

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.