Back to Blog
Modern workspace with laptop displaying code on a clean desk
Development
July 13, 2026
8 min read

Building a Calendar Without React Dependencies

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

Every React calendar library starts the same way: npm install react-big-calendar, then a localiser like Moment.js, then a CSS import, then a wrapper component to bridge the library's API with your state management. By the time the calendar renders, your dependency tree has grown by 16+ packages and your bundle by 50–100 KB gzipped. None of that weight is the calendar itself — it's the framework tax.

There's a simpler path: build your calendar without React dependencies entirely. A vanilla JavaScript calendar works inside React, Vue, Svelte, Angular, Astro, or a plain HTML page — with zero adapters, zero peer dependencies, and a fraction of the bundle cost.

The React dependency problem

A fresh create-react-app project installs roughly 1,400 packages before you write a single line of code. Adding a React calendar library compounds this. Here's what the popular options actually cost:

LibraryGzipped sizeDependenciesReact required
react-big-calendar~53 KB16 packages + date localiserYes (peer dep)
FullCalendar React~80 KB4–6 plugins + @fullcalendar/reactYes (adapter)
react-calendar~15 KBZeroYes (peer dep)
SimpleCalendarJS~14 KBZeroNo

The first three are locked to React. You cannot use them in a Vue app, a Svelte project, or a server-rendered page without React in the bundle. SimpleCalendarJS works everywhere because it depends on nothing but the browser's native DOM API.

Beyond bundle size, React dependencies create three recurring problems:

Version coupling. When React 19 shipped, libraries that depended on react@^18 broke. Developers filed issues, waited for maintainers to update peer dependency ranges, and dealt with --legacy-peer-deps workarounds in the meantime. A vanilla JavaScript library has no React peer dependency to break.

Transitive conflicts. GitHub issue #7120 on create-react-app documents the frustration: nearly 1,400 packages installed, many with overlapping transitive dependencies that npm resolves by installing multiple versions silently. React calendar plugins add to this sprawl.

Framework lock-in. A react-big-calendar component cannot be reused in a Vue or Svelte project. If your team maintains multiple frontend apps across frameworks — or plans to migrate in the future — React-specific components become throwaway code.

Why framework-agnostic matters

The web standards community has been converging on a clear principle: framework-agnostic components are more portable, more maintainable, and longer-lived than framework-specific ones.

A vanilla JavaScript calendar initialises against a DOM element. Every framework provides access to DOM elements:

  • React: useRef + useEffect
  • Vue: ref + onMounted
  • Svelte: bind:this + onMount
  • Angular: ViewChild + ngAfterViewInit
  • Astro: <script> tag in an island
  • Plain HTML: document.getElementById

The integration pattern is identical everywhere. You don't need a @calendar/react adapter, a @calendar/vue3 wrapper, or an @calendar/angular schematic. One package, one API, every framework.

A working calendar in 6 lines

Install the package:

npm install simple-calendar-js

Initialise it:

import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; const calendar = new SimpleCalendarJs('#calendar', { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick: (event) => console.log(event), });

That gives you a fully styled calendar with async event loading, click handlers, and built-in navigation. No plugins, no adapters, no date library required.

Using it inside React (without a React dependency)

If your app is already React-based, you don't need a React calendar library. Use useRef and useEffect to mount the vanilla calendar inside a React component:

import { useRef, useEffect } from 'react'; import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; function Calendar({ onEventClick }) { const containerRef = useRef(null); const calendarRef = useRef(null); useEffect(() => { calendarRef.current = new SimpleCalendarJs(containerRef.current, { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick, enableDragAndDrop: true, }); return () => calendarRef.current?.destroy(); }, []); return <div ref={containerRef} />; }

This pattern has two advantages over a React-specific calendar:

  1. No React re-render overhead. The calendar manages its own DOM. React doesn't re-render it on state changes, so there's no virtual DOM diffing cost for calendar interactions.
  2. No peer dependency risk. When you upgrade from React 18 to React 19 (or React 19 to 20), the calendar doesn't care. It has no opinion about your React version.

The same pattern in Vue and Svelte

Vue 3:

<script setup> import { ref, onMounted, onUnmounted } from 'vue'; import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; const container = ref(null); let calendar; onMounted(() => { calendar = new SimpleCalendarJs(container.value, { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick: (event) => console.log(event), }); }); onUnmounted(() => calendar?.destroy()); </script> <template> <div ref="container" /> </template>

Svelte:

<script> import { onMount, onDestroy } from 'svelte'; import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/simple-calendar-js.css'; let container; let calendar; onMount(() => { calendar = new SimpleCalendarJs(container, { fetchEvents: async (start, end) => { const res = await fetch(`/api/events?start=${start.toISOString()}&end=${end.toISOString()}`); return res.json(); }, onEventClick: (event) => console.log(event), }); }); onDestroy(() => calendar?.destroy()); </script> <div bind:this={container} />

The API is identical in every case. The only difference is how each framework gives you access to a DOM element.

What you get at 14 KB with zero dependencies

FeatureIncluded
Month, week, day viewsYes
Async event fetchingYes
Drag-and-dropYes
Event resizingYes
Click and slot handlersYes
Dark mode (CSS variables)Yes
34+ localesYes
Keyboard navigationYes
Framework adapters neededNone
DependenciesZero
PricingFree (personal) / $49/yr or $199 lifetime (commercial)

Compare that with the typical React calendar stack: react (44 KB) + react-big-calendar (53 KB) + date-fns or Moment.js (12–70 KB) = 109–167 KB gzipped before your application code. That's 8–12× the weight of SimpleCalendarJS for the same core features.

Summary

  • React calendar libraries ship framework tax — peer dependencies on React/ReactDOM, adapter packages, and date localiser libraries that inflate your bundle by 50–167 KB gzipped.
  • Version coupling between calendar libraries and React creates upgrade friction and transitive dependency conflicts.
  • A vanilla JavaScript calendar works in every framework — React, Vue, Svelte, Angular, Astro, or plain HTML — using the same API and the same npm package.
  • SimpleCalendarJS delivers full calendar features at ~14 KB gzipped with zero dependencies, no framework lock-in, and no peer dependency risk.
  • Using a vanilla calendar inside React is straightforward — mount it via useRef + useEffect and let it manage its own DOM, avoiding virtual DOM overhead entirely.

Sources & Further Reading

Research & References

Image Credits

All images free to use under the Pexels License.

Frequently Asked Questions

Can I use a vanilla JavaScript calendar inside a React app?

Yes. A vanilla JavaScript calendar like SimpleCalendarJS works in any environment — including React. You initialise it against a DOM element inside a useEffect hook and it renders independently of React's virtual DOM. No adapter package, no JSX wrapper, no peer dependency on a specific React version.

Why do React calendar libraries need so many dependencies?

React calendar libraries typically depend on React and ReactDOM as peer dependencies (44+ KB gzipped combined), often require a date utility library like Moment.js or date-fns for localisation, and may pull in additional packages for drag-and-drop or styling. Each dependency adds to your bundle size and creates potential version conflicts during upgrades.

What is a framework-agnostic JavaScript library?

A framework-agnostic library is built on native browser APIs (vanilla JavaScript and the DOM) with no dependency on React, Vue, Angular, or any other framework. It works identically in any environment — a React component, a Vue SFC, a Svelte script block, an Astro island, or a plain HTML page — without framework-specific wrappers or adapters.

How much bundle size does React itself add to a calendar component?

React (react + react-dom) adds approximately 44 KB gzipped to your bundle. A React-specific calendar like react-big-calendar then adds another 53 KB gzipped on top, plus a required date localiser library. The total can exceed 100 KB gzipped before you write a single line of application code. A vanilla JavaScript calendar avoids this entirely.

Is SimpleCalendarJS free to use?

SimpleCalendarJS is free for personal and open-source projects. Commercial projects require a paid license — $49/year or $199 lifetime per project.

What features does a dependency-free calendar support?

SimpleCalendarJS ships month, week, and day views, async event fetching, click and slot handlers, drag-and-drop, event resizing, 34+ built-in locales, dark mode via CSS variables, and full keyboard navigation — all at ~14 KB gzipped with zero dependencies.

Add a calendar to your app today

Free for personal projects. $49/year or $199 lifetime per commercial project.