Back to Blog
Monitor displaying application code in a development environment
Framework
September 25, 2026
10 min read

How to Add a Calendar to a Ruby on Rails App

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

You're building a Rails app and you need an interactive event calendar. You search RubyGems and find two options: simple_calendar — a server-rendered HTML table with no week or day views — and fullcalendar-rails — a Sprockets wrapper around an outdated version of FullCalendar. Neither gives you a modern, lightweight calendar component that works with Rails 7+ and Hotwire. Here's how to add one without a gem wrapper.

The Rails calendar landscape

The Ruby on Rails ecosystem has a handful of calendar solutions. They fall into two camps: server-rendered gems and JavaScript library wrappers.

simple_calendar by Chris Oliver (GoRails) is the most popular Rails calendar gem — 1,700+ GitHub stars and 6.5 million+ downloads on RubyGems. It renders calendar grids server-side as plain HTML tables. You get month_calendar and week_calendar view helpers, optional Turbo Frame support, and zero JavaScript. The trade-off: no interactive day view, no client-side event click handlers, no view switching toolbar, and no async event fetching. Every navigation is a full page request.

fullcalendar-rails wraps Adam Shaw's FullCalendar as a Sprockets asset gem — 460 GitHub stars, pinned to an older FullCalendar version. It was designed for the Rails 5-era asset pipeline. In Rails 7+ apps using Propshaft, importmaps, or jsbundling-rails, a Sprockets-only gem doesn't fit the modern asset setup without workarounds.

ApproachWhat It DoesFrontend WeightInteractive Views
simple_calendar gemServer-rendered HTML grid0 KB (no JS)Month only (basic week)
fullcalendar-rails gemSprockets wrapper for FullCalendar~43 KB+ gzippedMonth, week, day
FullCalendar (direct npm)JS library, no gem wrapper~43 KB+ gzippedMonth, week, day
SimpleCalendarJSVanilla JS, no gem wrapper~14 KB gzippedMonth, week, day

Most Rails developers don't need a gem that wraps a JavaScript library. Rails 7+ gives you importmaps or esbuild/Vite for JavaScript — you can install any npm package directly and use it in your views.

The simple_calendar gem approach

The simple_calendar gem is well-suited for basic calendar grids. Here's what it looks like:

# Gemfile gem "simple_calendar", "~> 3.0"
<!-- app/views/events/index.html.erb --> <%= month_calendar(events: @events) do |date, events| %> <div class="day"> <%= date.day %> <% events.each do |event| %> <div class="event"><%= event.title %></div> <% end %> </div> <% end %>

This renders a month grid with events placed on their dates. It's simple and works. But it's a static HTML table — there's no view switching, no time-slot rendering, no event click callbacks, and navigating between months requires a page reload (or a Turbo Frame, which you have to wire up yourself). If all you need is a month grid with event dots, simple_calendar is fine. If you need an interactive event calendar with week and day views, you need JavaScript.

The vanilla JS approach (recommended)

A vanilla JavaScript calendar needs nothing from Rails except a <div> and a <script> tag. No gem, no asset pipeline wrapper, no version coupling. Here's how to add SimpleCalendarJS to a Rails app — ~14 KB gzipped, zero dependencies.

Step 1: Install the library

With importmaps (Rails 7+ default):

bin/importmap pin simple-calendar-js

With esbuild or Vite:

yarn add simple-calendar-js

Or via CDN (add to your layout):

<script src="https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.css" />

Step 2: Create the JSON API endpoint

# config/routes.rb Rails.application.routes.draw do resources :events, only: [:index] get "calendar", to: "calendar#show" end
# app/controllers/events_controller.rb class EventsController < ApplicationController def index events = Event .where(starts_at: params[:from]..params[:to]) .select(:id, :title, :starts_at, :ends_at, :color) render json: events.map { |e| { id: e.id, title: e.title, start: e.starts_at.iso8601, end: e.ends_at.iso8601, color: e.color || "#3b82f6" } } end end

This returns only the events within the visible date range — not the entire table. No jbuilder template, no serializer gem. A plain render json: is all you need.

Step 3: Add the calendar to your view

<!-- app/views/calendar/show.html.erb --> <h1>Event Calendar</h1> <div id="calendar" data-events-url="<%= events_path(format: :json) %>"></div> <script> document.addEventListener("DOMContentLoaded", function () { var container = document.getElementById("calendar"); var calendar = new SimpleCalendarJs(container, { defaultView: "month", locale: "en-US", enabledViews: ["month", "week", "day"], fetchEvents: async function (start, end) { var url = container.dataset.eventsUrl + "?from=" + start.toISOString() + "&to=" + end.toISOString(); var response = await fetch(url); return response.json(); }, onEventClick: function (event) { window.location.href = "/events/" + event.id + "/edit"; }, onSlotClick: function (date) { window.location.href = "/events/new?date=" + date.toISOString(); }, }); }); </script>

That's a complete, working Rails event calendar. The data-events-url attribute passes the API URL from ERB to JavaScript cleanly. fetchEvents fires with the visible date range on every navigation, and onSlotClick routes to your new event form with the date pre-filled.

What this gives you

  • Month, week, and day views with a built-in toolbar for switching
  • Async event fetching — only loads events within the visible date range
  • Click handlers for existing events and empty time slots — wire to your Rails routes for CRUD
  • 34+ locales built in — pass locale: "pt-BR" or locale: "ja-JP" and the calendar renders in that language
  • No gem dependency — the JavaScript library handles all rendering, your Rails backend just serves JSON

Integrating with Stimulus (Hotwire)

If your Rails app uses Hotwire (the default since Rails 7), wrapping the calendar in a Stimulus controller gives you proper lifecycle management — the calendar initialises when the controller connects and cleans up on disconnect, which matters for Turbo Drive navigations.

// app/javascript/controllers/calendar_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static values = { eventsUrl: String, locale: { type: String, default: "en-US" }, } connect() { this.calendar = new SimpleCalendarJs(this.element, { defaultView: "month", locale: this.localeValue, enabledViews: ["month", "week", "day"], fetchEvents: async (start, end) => { const url = `${this.eventsUrlValue}?from=${start.toISOString()}&to=${end.toISOString()}`; const response = await fetch(url); return response.json(); }, onEventClick: (event) => { Turbo.visit(`/events/${event.id}/edit`); }, onSlotClick: (date) => { Turbo.visit(`/events/new?date=${date.toISOString()}`); }, }); } disconnect() { this.calendar = null; } }
<!-- app/views/calendar/show.html.erb --> <h1>Event Calendar</h1> <div data-controller="calendar" data-calendar-events-url-value="<%= events_path(format: :json) %>" data-calendar-locale-value="en-US"> </div>

Stimulus values pass configuration from ERB to JavaScript through data-* attributes — the Rails-idiomatic way to bridge server and client. When users navigate away and back with Turbo Drive, the controller reconnects and the calendar re-initialises automatically.

Theming the calendar to match your Rails app

SimpleCalendarJS uses CSS custom properties. Override them in your stylesheet:

.uc-calendar { --cal-primary: #cc0000; --cal-primary-dark: #990000; --cal-today-bg: #fff0f0; --cal-font-size: 14px; } @media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #1a1a2e; --cal-text: #e0e0e0; --cal-border: #2a2a3e; --cal-today-bg: #2a2a3e; } }

Four CSS variables and the calendar matches your app's design. No fighting with gem-provided stylesheets or Sprockets overrides.

Bundle size: what you're shipping to the browser

Rails 7+ apps use Propshaft for static assets and either importmaps (zero build step) or esbuild/Vite (with bundling). In both cases, every kilobyte of JavaScript you add affects page load time. Rails was designed to be server-first — bloated frontend libraries work against that philosophy.

SetupGzipped SizeGem RequiredjQuery Required
simple_calendar0 KB (server-rendered)YesNo
fullcalendar-rails gem~43 KB+Yes (Sprockets)Legacy versions: Yes
FullCalendar (npm direct)~43 KB+NoNo
SimpleCalendarJS~14 KBNoNo

SimpleCalendarJS delivers month, week, and day views at ~14 KB gzipped — roughly one-third the size of FullCalendar's core bundle. No gem wrapper, no Sprockets dependency, no framework coupling.

When to use simple_calendar instead

The simple_calendar gem is the right choice when:

  • You need a basic month grid with events rendered server-side — no JavaScript required
  • Your calendar is read-only — users view events but don't interact with them beyond clicking links
  • You want Turbo Frame compatibility out of the box for month navigation without full page reloads
  • You prefer zero JavaScript and are willing to trade interactive features for simplicity
  • Your app targets users on very low-powered devices where even 14 KB of JS matters

With 1,700+ stars and 6.5 million+ gem downloads, simple_calendar is battle-tested for basic calendar grids. But if you need interactive week and day views, client-side event click handlers, or async date-range fetching, you need a JavaScript calendar component.

Summary

  • simple_calendar (1,700+ stars) renders server-side HTML calendar grids — great for basic month views, but no interactive week/day views or client-side event handling
  • fullcalendar-rails wraps an older FullCalendar version as a Sprockets asset — outdated for Rails 7+ apps using Propshaft or importmaps
  • You don't need a gem wrapper — Rails 7+ handles JavaScript via importmaps or esbuild/Vite, so any npm package works directly
  • SimpleCalendarJS delivers month, week, and day views in ~14 KB gzipped — install via importmaps, npm, or CDN, initialise in a Stimulus controller or inline script, and point fetchEvents at your JSON endpoint
  • The Stimulus controller pattern gives you proper Hotwire lifecycle management — the calendar re-initialises automatically on Turbo Drive navigations

Sources & Further Reading

Research & References

Image Credits

All images free to use under the Pexels License.

Frequently Asked Questions

What is the best calendar library for Ruby on Rails?

It depends on what you need. The simple_calendar gem (1,700+ GitHub stars, 6.5M+ downloads) renders server-side HTML calendar grids — great for basic month views but no interactive week/day views or client-side event handling. For a full interactive event calendar with month, week, and day views, SimpleCalendarJS adds ~14 KB gzipped with zero dependencies and works in any Rails view with a script tag and a div.

Is the fullcalendar-rails gem still maintained?

The fullcalendar-rails gem (bokmann/fullcalendar-rails, 460 stars) wraps an older version of FullCalendar as a Sprockets asset. It hasn't kept pace with FullCalendar 6, and the Sprockets asset pipeline approach is outdated in Rails 7+ apps using Propshaft or importmaps. You're better off installing FullCalendar directly via npm or using a lighter alternative.

Do I need a gem to add a calendar to Rails?

No. A vanilla JavaScript calendar library works in any Rails ERB or Slim template — just include the JS and CSS files via importmaps, a CDN, or your JavaScript bundler (esbuild/Vite). No Gemfile entry, no asset pipeline wrapper, no version coupling between the gem and the upstream library.

How do I pass Rails data to a JavaScript calendar?

Use a JSON API endpoint. Create a standard Rails controller action that returns events as JSON with jbuilder or a simple render json:. The calendar's fetchEvents callback hits that endpoint with the visible date range, so you only load events the user can see.

Can I use SimpleCalendarJS with Hotwire and Stimulus?

Yes. Wrap the calendar initialisation in a Stimulus controller — connect() creates the calendar instance and disconnect() cleans it up. Use data attributes to pass configuration from your ERB template. Turbo Drive works out of the box because Stimulus controllers re-connect on Turbo navigations.

Is SimpleCalendarJS free to use in a Rails project?

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

Add a calendar to your app today

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