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
January
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
31
|
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
|
Code
<%= render Hakumi::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
January
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
31
|
1
|
2
|
3
|
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
|
11
|
12
|
13
|
14
|
15
|
16
|
17
|
|
18
|
19
|
20
|
21
|
22
|
23
|
24
This is warning event. This is usual event. |
|
25
|
26
|
27
|
28
|
29
This is warning event. This is usual event. This is error event. |
30
|
31
|
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 Hakumi::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
January
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
31
|
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
|
Code
<%= render Hakumi::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
January
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
31
|
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
|
Code
<%= render Hakumi::Calendar::Component.new(
value: Date.today,
id: "selectable-calendar"
) %>
<div style="margin-top: 16px;">
<%= render Hakumi::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
January
January
February
March
April
May
June
July
August
September
October
November
December
| Su | Mo | Tu | We | Th | Fr | Sa | |
|---|---|---|---|---|---|---|---|
|
1
|
28
|
29
|
30
|
31
|
1
|
2
|
3
|
|
2
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
|
3
|
11
|
12
|
13
|
14
|
15
|
16
|
17
|
|
4
|
18
|
19
|
20
|
21
|
22
|
23
|
24
|
|
5
|
25
|
26
|
27
|
28
|
29
|
30
|
31
|
Code
<%= render Hakumi::Calendar::Component.new(show_week: true) %>
Customize Header
Customize Calendar header content using the header_content slot.
Custom Header
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
|
28
|
29
|
30
|
31
|
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
|
Code
<%= render Hakumi::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 Hakumi::Button::Component.new(
type: :text,
size: :small,
id: "cal-prev-year",
icon: :double_left
) %>
<%= render Hakumi::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 Hakumi::Button::Component.new(
type: :text,
size: :small,
id: "cal-next-month",
icon: :right
) %>
<%= render Hakumi::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 |
Date.today |
The current selected date |
default_value |
Date or String |
- |
The initial date when first rendered |
mode |
:month or :year |
: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 |
±50 years |
Years to show in year selector. 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. |
Calendar Slots
| Prop | Type | Default | Description |
|---|---|---|---|
header_content |
Slot |
- |
Custom content for the calendar header |
JavaScript API (element.hakumiCalendar)
| Prop | Type | Default | Description |
|---|---|---|---|
getValue() |
Function |
- |
Returns the current Date object |
setValue(date) |
Function |
- |
Sets the calendar to the specified date |
getMode() |
Function |
- |
Returns current mode ('month' or 'year') |
setMode(mode) |
Function |
- |
Sets the display mode |
goToToday() |
Function |
- |
Navigates to current date |
nextMonth() |
Function |
- |
Navigates to next month |
prevMonth() |
Function |
- |
Navigates to previous month |
nextYear() |
Function |
- |
Navigates to next year |
prevYear() |
Function |
- |
Navigates to previous year |
Events
| Prop | Type | Default | Description |
|---|---|---|---|
hakumi--calendar:select |
Event |
- |
Fired when a date/month is selected. Detail: { date, source } |
hakumi--calendar:change |
Event |
- |
Fired when year/month changes. Detail: { year, month, date } |
hakumi--calendar:panel_change |
Event |
- |
Fired when mode changes. Detail: { mode, year, month } |