howdy/ui/calendar

Calendars and date pickers: one date, a range, or several dates, over one month or a few side by side.

calendar.new("due", year: 2026, month: 9)
|> calendar.selected(Some(calendar.Date(2026, 9, 24)))
|> calendar.today(calendar.Date(2026, 9, 24))
|> calendar.disabled(fn(date) { calendar.weekday(date) >= 6 })
|> calendar.navigation(
  previous: [event.on_click(PreviousMonth)],
  next: [event.on_click(NextMonth)],
)
|> calendar.name("due")
|> calendar.view

Clicking a day selects it; the arrow keys move a day or a week, Home and End go to the ends of the week, and Page Up and Page Down press the previous and next month buttons. The choice is kept in the hidden input called name, so it submits with a form and a live view hears it with live.on_value:

months shows several months side by side, and locale names the months, weekdays and buttons in another language. Changing month is the server’s job: navigation takes the attributes of the previous and next buttons, such as click handlers in a live view. On a page without a live view, a native <input type="date"> usually serves better for a single date.

picker puts the calendar in a popover behind a button that shows the choice.

Types

A calendar under construction.

pub opaque type Calendar(msg)

A day in the proleptic Gregorian calendar. Months and days count from 1.

pub type Date {
  Date(year: Int, month: Int, day: Int)
}

Constructors

  • Date(year: Int, month: Int, day: Int)

The words a calendar uses. weekday_names and weekday_abbreviations run from Monday; heading writes a month and year, given the year and the month, as at the top of the calendar; date_label writes a date out in full; day_number writes the number on each day; first_day is the day weeks start on, from 1 for Monday to 7 for Sunday.

english is the default. howdy/ui/words holds one of these for its locale, with names and formats from CLDR.

pub type Locale {
  Locale(
    weekday_names: List(String),
    weekday_abbreviations: List(String),
    heading: fn(Int, Int) -> String,
    date_label: fn(Date) -> String,
    day_number: fn(Int) -> String,
    first_day: Int,
    previous_month: String,
    next_month: String,
    choose_date: String,
  )
}

Constructors

  • Locale(
      weekday_names: List(String),
      weekday_abbreviations: List(String),
      heading: fn(Int, Int) -> String,
      date_label: fn(Date) -> String,
      day_number: fn(Int) -> String,
      first_day: Int,
      previous_month: String,
      next_month: String,
      choose_date: String,
    )

What is chosen.

pub type Selection {
  Single(option.Option(Date))
  Range(from: option.Option(Date), to: option.Option(Date))
  Multiple(List(Date))
}

Constructors

Values

pub fn add_days(date: Date, days: Int) -> Date
pub fn add_months(date: Date, months: Int) -> Date

The same day in the month months later, or the last day of that month when it is shorter.

pub fn caption_class() -> css.Class
pub fn classes() -> List(css.Class)

Every class this module uses, for howdy/ui/export.

pub fn compare(a: Date, b: Date) -> order.Order
pub fn dates_from_value(value: String) -> List(Date)

Read several dates as their hidden input sends them. Anything that is not a date is left out.

pub fn day_class() -> css.Class
pub fn days_in_month(year: Int, month: Int) -> Int
pub fn disabled(
  calendar: Calendar(msg),
  disabled: fn(Date) -> Bool,
) -> Calendar(msg)

Days that cannot be chosen, such as those in the past.

pub fn english() -> Locale
pub fn first_day(
  calendar: Calendar(msg),
  day: Int,
) -> Calendar(msg)

Start weeks on this day, from 1 for Monday to 7 for Sunday, whatever the locale’s custom.

pub fn from_iso(value: String) -> Result(Date, Nil)

Read YYYY-MM-DD, as a hidden input or <input type="date"> sends it.

pub fn grid_class() -> css.Class
pub fn heading_class() -> css.Class
pub fn is_leap_year(year: Int) -> Bool
pub fn locale(
  calendar: Calendar(msg),
  locale: Locale,
) -> Calendar(msg)

Name the months, weekdays and buttons in another language, and start weeks on the locale’s first day unless first_day says otherwise.

pub fn long_date(date: Date) -> String

“24 September 2026”.

pub fn months(
  calendar: Calendar(msg),
  count: Int,
) -> Calendar(msg)

Show count months side by side, starting with the calendar’s month.

pub fn months_class() -> css.Class
pub fn multiple(
  calendar: Calendar(msg),
  dates: List(Date),
) -> Calendar(msg)

Choose any number of dates; these ones so far.

pub fn name(
  calendar: Calendar(msg),
  name: String,
) -> Calendar(msg)

Keep the choice in a hidden input with this name.

pub fn nav_button_class() -> css.Class
pub fn nav_class() -> css.Class
pub fn navigation(
  calendar: Calendar(msg),
  previous previous: List(attribute.Attribute(msg)),
  next next: List(attribute.Attribute(msg)),
) -> Calendar(msg)

Show previous and next month buttons with these attributes.

pub fn new(
  id: String,
  year year: Int,
  month month: Int,
) -> Calendar(msg)

A calendar showing month of year. id must be unique in the document.

pub fn picker(
  calendar: Calendar(msg),
  placeholder placeholder: String,
) -> element.Element(msg)

A button showing the choice, or placeholder, that opens the calendar in a popover. Choosing one date, or the end of a range, closes it. Give the calendar a name so the choice is kept.

pub fn picker_class() -> css.Class
pub fn popover_class() -> css.Class
pub fn range(
  calendar: Calendar(msg),
  from from: option.Option(Date),
  to to: option.Option(Date),
) -> Calendar(msg)

Choose a range; this one, as far as it is chosen.

pub fn range_from_value(
  value: String,
) -> Result(#(Date, option.Option(Date)), Nil)

Read a range as its hidden input sends it: 2026-09-10/2026-09-14, or 2026-09-10/ while only the start is chosen.

pub fn root_class() -> css.Class
pub fn selected(
  calendar: Calendar(msg),
  date: option.Option(Date),
) -> Calendar(msg)

Choose one date; this one, if there is one.

pub fn selection_to_value(selection: Selection) -> String

The hidden input’s value for a selection.

pub fn sunday_first(calendar: Calendar(msg)) -> Calendar(msg)

Start weeks on Sunday, whatever the locale’s custom.

pub fn to_iso(date: Date) -> String

YYYY-MM-DD.

pub fn today(
  calendar: Calendar(msg),
  date: Date,
) -> Calendar(msg)

Today, which is marked. The server’s today may not be the user’s, so pass the date in the user’s time zone when you know it.

pub fn trigger_class() -> css.Class
pub fn view(calendar: Calendar(msg)) -> element.Element(msg)

The calendar on its own.

pub fn weekday(date: Date) -> Int

The day of the week, from 1 for Monday to 7 for Sunday.

pub fn weekday_class() -> css.Class
✨ Search Document