Senren UI
Components / Calendar

Calendar

Stable Stimulus: senren--calendar

date display and selection

April 2026

Sun
Mon
Tue
Wed
Thu
Fri
Sat

Usage example #

Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.

app/views/.../calendar_example.html.erb
<div class="flex justify-center">
  <%= render Senren::CalendarComponent.new(date: Date.new(2026, 4, 1), selected: Date.new(2026, 4, 27), name: "due_on") %>
</div>

Install this component #

Copy the official component into your app

This component requires Stimulus. Keep --client so the controller is copied with the ViewComponent.

Terminal
bin/rails senren:add calendar --client

Create a custom component with the same conventions

Use this when you need an app-specific component that follows Senren's ViewComponent and Stimulus structure. --client is required for this behavior.

Terminal
bin/rails generate senren:component calendar --client

Dependencies are resolved by senren:add: button.

At a glance #

Category Forms
Class name Senren::CalendarComponent
Stimulus senren--calendar
Variants default
Depends on button
Pairs with date_picker

Source #

app/components/senren/calendar_component.html.erb
<div <%= tag.attributes(**root_attrs("w-72 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-3 text-[hsl(var(--senren-card-foreground))]", data: { controller: "senren--calendar" })) %>>
  <% if name.present? %>
    <input type="hidden" name="<%= name %>" value="<%= selected&.iso8601 %>" data-senren--calendar-target="value">
  <% end %>
  <div class="mb-3 flex items-center justify-between">
    <p class="font-display text-sm font-semibold tracking-tight"><%= title %></p>
  </div>
  <div class="grid grid-cols-7 gap-1 text-center text-xs text-[hsl(var(--senren-muted-foreground))]">
    <% weekday_labels.each do |day| %>
      <div class="py-1"><%= day %></div>
    <% end %>
  </div>
  <div role="grid" class="mt-1 grid grid-cols-7 gap-1">
    <% calendar_days.each do |day| %>
      <% outside = day.month != date.month %>
      <button type="button" role="gridcell" data-senren--calendar-target="day" data-date="<%= day.iso8601 %>" data-action="click->senren--calendar#select" class="h-9 cursor-pointer rounded-md text-sm transition-colors <%= selected?(day) ? "bg-[hsl(var(--senren-primary))] text-[hsl(var(--senren-primary-foreground))]" : "hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]" %> <%= outside ? "text-[hsl(var(--senren-muted-foreground)/0.45)]" : "text-[hsl(var(--senren-foreground))]" %>">
        <%= day.day %>
      </button>
    <% end %>
  </div>
</div>
app/components/senren/calendar_component.rb
module Senren
  class CalendarComponent < BaseComponent
    VARIANTS = { default: '' }.freeze
    SIZES = { md: '' }.freeze

    def initialize(date: Date.current, selected: nil, name: nil, class_name: nil, **html)
      super(variant: :default, size: :md, class_name: class_name, **html)
      @date = date.to_date
      @selected = selected&.to_date
      @name = name
    end

    attr_reader :date, :selected, :name

    def month_start = date.beginning_of_month
    def month_end = date.end_of_month
    def title = date.strftime('%B %Y')
    def weekday_labels = Date::ABBR_DAYNAMES

    def calendar_days
      start_day = month_start.beginning_of_week(:sunday)
      end_day = month_end.end_of_week(:sunday)
      (start_day..end_day).to_a
    end

    def selected?(day)
      selected && day == selected
    end
  end
end

AI agent rules #

Use for

  • +date display and selection

Avoid

  • -time-only selection

Accessibility #

  • grid with row/columnheader; arrow keys to navigate days.