Basic
A basic calendar component with Year/Month switch.
2026
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
July
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
|
26
|
27
|
28
|
29
|
30
|
31
|
1
|
Code
<%= render HakumiComponents::Calendar::Component.new %>
Notice Calendar
This component can be rendered by using date_cell_render with the data you need.
2026
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
July
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
12
This is warning event. This is usual event. |
13
|
14
|
15
|
16
|
17
This is warning event. This is usual event. This is error event. |
18
|
|
19
|
20
|
21
|
22
This is warning event. This is very long usual event...... |
23
|
24
|
25
|
|
26
|
27
This is error event 1. This is error event 2. This is error event 3. This is error event 4. |
28
|
29
|
30
|
31
|
1
|
Code
<%
events = {
Date.today => [
{ id: 1, type: :warning, content: "This is warning event." },
{ id: 2, type: :success, content: "This is usual event." }
],
Date.today + 5 => [
{ id: 3, type: :warning, content: "This is warning event." },
{ id: 4, type: :success, content: "This is usual event." },
{ id: 5, type: :error, content: "This is error event." }
],
Date.today + 10 => [
{ id: 6, type: :warning, content: "This is warning event." },
{ id: 7, type: :success, content: "This is very long usual event......" }
],
Date.today + 15 => [
{ id: 8, type: :error, content: "This is error event 1." },
{ id: 9, type: :error, content: "This is error event 2." },
{ id: 10, type: :error, content: "This is error event 3." },
{ id: 11, type: :error, content: "This is error event 4." }
]
}
%>
<%= render HakumiComponents::Calendar::Component.new(events: events, id: "notice-calendar") %>
<script>
(() => {
const calendarEl = document.getElementById("notice-calendar")
if (!calendarEl) return
let wired = false
const wire = () => {
if (wired) return true
const api = calendarEl.hakumiComponent?.api
if (!api) return false
calendarEl.addEventListener("hakumi--calendar:select", (e) => {
const date = e.detail.date
const dateStr = date.toISOString().split("T")[0]
const events = api.getEventsForDate(dateStr)
console.log("📅 Selected date:", dateStr)
console.log("📋 Events for this date:", events)
if (events.length > 0) {
console.table(events)
}
})
console.log("🗓️ Calendar wired! All events:", api.getEvents())
wired = true
return true
}
if (wire()) return
const interval = setInterval(() => { if (wire()) clearInterval(interval) }, 50)
setTimeout(() => clearInterval(interval), 5000)
})()
</script>
Card
Nested inside a container element for rendering in limited space.
2026
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
July
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
|
26
|
27
|
28
|
29
|
30
|
31
|
1
|
Code
<%= render HakumiComponents::Calendar::Component.new(fullscreen: false) %>
Selectable Calendar
A basic calendar component with Year/Month switch. Listen to the select event to get the selected date.
2026
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
July
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
|
26
|
27
|
28
|
29
|
30
|
31
|
1
|
Code
<%= render HakumiComponents::Calendar::Component.new(
value: Date.today,
id: "selectable-calendar"
) %>
<div style="margin-top: 16px;">
<%= render HakumiComponents::Alert::Component.new(
type: :info,
message: "Selected date will be shown here",
id: "selected-date-alert"
) %>
</div>
<script>
(() => {
const calendarEl = document.getElementById("selectable-calendar")
const alertEl = document.getElementById("selected-date-alert")
if (!calendarEl || !alertEl) return
let wired = false
const wire = () => {
if (wired) return true
const calendarApi = calendarEl.hakumiComponent?.api
const alertApi = alertEl.hakumiComponent?.api
if (!calendarApi || !alertApi) return false
calendarEl.addEventListener("hakumi--calendar:select", (e) => {
const date = e.detail.date
const formatted = date.toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
})
alertApi.setMessage(`You selected: ${formatted}`)
})
wired = true
return true
}
if (wire()) return
const interval = setInterval(() => { if (wire()) clearInterval(interval) }, 50)
setTimeout(() => clearInterval(interval), 5000)
})()
</script>
Show Week
Show week number in fullscreen calendar by setting show_week prop to true.
2026
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
July
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa | |
|---|---|---|---|---|---|---|---|
|
27
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
28
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
29
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
|
30
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
|
31
|
26
|
27
|
28
|
29
|
30
|
31
|
1
|
Code
<%= render HakumiComponents::Calendar::Component.new(show_week: true) %>
Customize Header
Customize Calendar header content using the with_header_content slot.
Custom Header
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
1
|
2
|
3
|
4
|
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
|
19
|
20
|
21
|
22
|
23
|
24
|
25
|
|
26
|
27
|
28
|
29
|
30
|
31
|
1
|
Code
<%= render HakumiComponents::Calendar::Component.new(
fullscreen: false,
id: "custom-header-calendar"
) do |calendar|
calendar.with_header_content do %>
<div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
<div style="display: flex; align-items: center; gap: 8px;">
<%= render HakumiComponents::Button::Component.new(
type: :text,
size: :small,
id: "cal-prev-year",
icon: :double_left
) %>
<%= render HakumiComponents::Button::Component.new(
type: :text,
size: :small,
id: "cal-prev-month",
icon: :left
) %>
</div>
<div id="cal-header-title" style="font-weight: 500;">
Custom Header
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<%= render HakumiComponents::Button::Component.new(
type: :text,
size: :small,
id: "cal-next-month",
icon: :right
) %>
<%= render HakumiComponents::Button::Component.new(
type: :text,
size: :small,
id: "cal-next-year",
icon: :double_right
) %>
</div>
</div>
<% end %>
<% end %>
<script>
(() => {
const calendarEl = document.getElementById("custom-header-calendar")
if (!calendarEl) return
let wired = false
const wire = () => {
if (wired) return true
const api = calendarEl.hakumiComponent?.api
if (!api) return false
const getTitleEl = () => document.getElementById("cal-header-title")
const updateTitle = () => {
const date = api.getValue()
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
const title = getTitleEl()
if (title) {
title.textContent = `${monthNames[date.getMonth()]} ${date.getFullYear()}`
}
}
let detachControls = () => {}
const attachControls = () => {
detachControls()
const removers = []
const bindControl = (id, handler) => {
const el = document.getElementById(id)
if (!el) return
const wrapped = (event) => {
event.preventDefault()
handler()
}
el.addEventListener("click", wrapped)
removers.push(() => el.removeEventListener("click", wrapped))
}
bindControl("cal-prev-year", () => api.prevYear())
bindControl("cal-prev-month", () => api.prevMonth())
bindControl("cal-next-month", () => api.nextMonth())
bindControl("cal-next-year", () => api.nextYear())
detachControls = () => removers.forEach((off) => off())
}
calendarEl.addEventListener("hakumi--calendar:change", () => {
updateTitle()
attachControls()
})
attachControls()
updateTitle()
wired = true
return true
}
if (wire()) return
const interval = setInterval(() => { if (wire()) clearInterval(interval) }, 50)
setTimeout(() => clearInterval(interval), 5000)
})()
</script>
Calendar API
| Prop | Type | Default | Description |
|---|---|---|---|
value |
Date or String |
nil |
The current selected date (defaults to Date.today for display if not provided) |
default_value |
Date or String |
nil |
The initial date when first rendered |
mode |
Symbol |
:month |
The display mode of the calendar |
fullscreen |
Boolean |
true |
Whether to display in fullscreen mode |
show_week |
Boolean |
false |
Whether to show week number column |
locale |
:en or :es |
:en |
The locale for day/month names |
valid_range |
[Date, Date] |
- |
Range of valid selectable dates |
year_range |
Range or Array |
nil |
Years to show in year selector (defaults to ±10 years from current). E.g., 2020..2030 or [2020, 2021, 2022] |
events |
Hash |
{} |
Events to display on dates. Format: { Date => [{ type: :success/:warning/:error, content: 'text |
date_cell_render |
Proc |
- |
Custom render function for date cells. Receives date as argument. Overrides events prop. |
month_cell_render |
Proc |
- |
Custom render function for month cells. Receives month number as argument. |
header_render |
Proc or false |
- |
Custom header render function, or false to hide header |
disabled_date |
Proc |
- |
Function to determine if a date is disabled. Receives date as argument. |
static |
Boolean |
auto |
Prevent client-side re-rendering. Auto-enabled when using date_cell_render/month_cell_render. |
JavaScript API
Access via element.hakumiComponent.api or HakumiComponents.get(element.id).
| Prop | Type | Default | Description |
|---|---|---|---|
getValue() |
Method |
- |
Return a Date for the first day of the current display month. |
getSelectedDate() |
Method |
- |
Return the selected date, or null when no date is selected. |
setValue(date) |
Method |
- |
Set the selected Date. Pass { navigate: true } as the second argument to update the displayed month/year. |
clearSelection() |
Method |
- |
Clear the selected date and refresh the calendar body. |
getMode() |
Method |
- |
Return the current display mode. |
setMode(mode) |
Method |
- |
Set the display mode to month or year. |
goToToday() |
Method |
- |
Navigate the display to the current month. |
nextMonth() |
Method |
- |
Navigate the display to the next month. |
prevMonth() |
Method |
- |
Navigate the display to the previous month. |
nextYear() |
Method |
- |
Navigate the display to the next year. |
prevYear() |
Method |
- |
Navigate the display to the previous year. |
nextDecade() |
Method |
- |
Navigate the display forward ten years. |
prevDecade() |
Method |
- |
Navigate the display back ten years. |
refresh() |
Method |
- |
Re-render the current calendar body. |
getEvents() |
Method |
- |
Return a shallow copy of the calendar event map. |
setEvents(events) |
Method |
- |
Replace the calendar event map and refresh dynamic calendars. |
addEvent(date, event) |
Method |
- |
Add an event object to a date. |
updateEvent(id, updates) |
Method |
- |
Update the event with the matching id. |
removeEvent(date, indexOrId) |
Method |
- |
Remove all events for a date, or remove an event by index for that date. |
removeEventById(id) |
Method |
- |
Remove the event with the matching id. |
getEventById(id) |
Method |
- |
Return the matching event with its date, or null when missing. |
getEventsForDate(date) |
Method |
- |
Return the event array for a date. |
clearEvents() |
Method |
- |
Remove all calendar events and refresh dynamic calendars. |
setRange(startDate, endDate, hoveredDate) |
Method |
- |
Set the highlighted range and optional hovered date. |
clearRange() |
Method |
- |
Clear the highlighted range state. |
getRange() |
Method |
- |
Return the current range date keys. |
setHoveredDate(date) |
Method |
- |
Set or clear the currently hovered range date. |