Skip to main content
On this pageFunctions

Ui/Calendar

Functions

dropToDays

functionsource
/**
 * Returns the calendar to Days mode regardless of current depth. Useful for
 * standalone (non-popovered) consumers that want to wire their own back-out
 * gesture. Popovered consumers like `Ui.DatePicker` don't need this. Escape
 * closes the popover, and the calendar resets to Days on next open.
 * 
 * Reconciles `maybeFocusedDate` to a date inside the visible (`viewYear`,
 * `viewMonth`). Months/Years navigation can leave the cursor on a date
 * outside the days grid (paged-away year, etc.), which would otherwise
 * cause `aria-activedescendant` to point at a non-rendered cell and the
 * next ArrowLeft to jump to the cursor's stale year.
 */
(model: Calendar.Model): Calendar.Model

init

functionsource
/**
 * Creates an initial calendar model. The view month defaults to the month
 * of the initial selected date, or today if no date is pre-selected.
 */
(config: InitConfig): Calendar.Model

selectDate

functionsource
/**
 * Programmatically selects a date on the calendar, committing it as the
 * chosen value and moving the cursor onto it. Use this in controlled-mode
 * handlers (when the view's `onSelectedDate` callback is provided) to write
 * the selection back to the calendar's internal state.
 * 
 * Equivalent to dispatching `ClickedDay({ date })` through `update`.
 */
(
  model: Calendar.Model,
  date: {
    day: number
    month: number
    year: number
  }
): UpdateReturn

update

functionsource

Types

CalendarAttributes

typesource
/**
 * Discriminated union of attribute groups and derived data the calendar
 * component provides to the consumer's `toView` callback. The variant
 * matches `model.viewMode`. Pattern-match on `_tag` with `M.tagsExhaustive`
 * to render each mode.
 */
type CalendarAttributes = DaysModeAttributes | MonthsModeAttributes | YearsModeAttributes

ColumnHeader

typesource
/** A column header for the day grid's first row (day-of-week labels). */
type ColumnHeader = Readonly<{
  attributes: ReadonlyArray<ChildAttribute>
  name: string
}>

DayCell

typesource
/** Information about a single day cell in the rendered calendar grid. */
type DayCell = Readonly<{
  buttonAttributes: ReadonlyArray<ChildAttribute>
  cellAttributes: ReadonlyArray<ChildAttribute>
  date: CalendarDate
  isDisabled: boolean
  isFocused: boolean
  isInViewMonth: boolean
  isSelected: boolean
  isToday: boolean
  label: string
}>

DaysModeAttributes

typesource
/** Attributes provided to the consumer when rendering the day grid. */
type DaysModeAttributes = Readonly<{
  _tag: "Days"
  columnHeaders: ReadonlyArray<ColumnHeader>
  grid: ReadonlyArray<ChildAttribute>
  headerRow: ReadonlyArray<ChildAttribute>
  heading: Readonly<{
    id: string
    text: string
  }>
  headingButton: ReadonlyArray<ChildAttribute>
  nextMonthButton: ReadonlyArray<ChildAttribute>
  previousMonthButton: ReadonlyArray<ChildAttribute>
  root: ReadonlyArray<ChildAttribute>
  weeks: ReadonlyArray<Week>
}>

InitConfig

typesource
/** Configuration for creating a calendar model with `init`. */
type InitConfig = Readonly<{
  disabledDates: ReadonlyArray<CalendarDate>
  disabledDaysOfWeek: ReadonlyArray<Calendar.DayOfWeek>
  id: string
  initialSelectedDate: CalendarDate
  locale: Calendar.LocaleConfig
  maxDate: CalendarDate
  minDate: CalendarDate
  today: CalendarDate
}>

MonthCell

typesource
/**
 * Information about a single month cell in the rendered months grid.
 * `label` is the locale-aware full month name (e.g. "September"); `shortLabel`
 * is the locale-aware abbreviation (e.g. "Sep"). Render whichever fits the
 * cell. Never substring `label` to abbreviate, since that's not safe across
 * locales.
 */
type MonthCell = Readonly<{
  buttonAttributes: ReadonlyArray<ChildAttribute>
  cellAttributes: ReadonlyArray<ChildAttribute>
  isCurrentMonth: boolean
  isDisabled: boolean
  isFocused: boolean
  isSelected: boolean
  label: string
  month: number
  shortLabel: string
}>

MonthsModeAttributes

typesource
/**
 * Attributes provided to the consumer when rendering the months grid. The
 * 12 cells are pre-built in calendar (locale-ordered). The consumer arranges
 * them in whatever grid layout they prefer (3×4 is the typical choice).
 */
type MonthsModeAttributes = Readonly<{
  _tag: "Months"
  cells: ReadonlyArray<MonthCell>
  grid: ReadonlyArray<ChildAttribute>
  heading: Readonly<{
    id: string
    text: string
  }>
  headingButton: ReadonlyArray<ChildAttribute>
  root: ReadonlyArray<ChildAttribute>
}>

ViewInputs

typesource
/**
 * Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
 * 
 *  The Calendar dispatches its own `ClickedDay` message on date commit
 *  and emits a `SelectedDate` OutMessage. Consumers handle date
 *  selection by pattern-matching the OutMessage in their
 *  `GotCalendarMessage` handler.
 */
type ViewInputs = Readonly<{
  daysHeadingButtonLabel: string
  monthsHeadingButtonLabel: string
  nextMonthLabel: string
  nextYearsPageLabel: string
  previousMonthLabel: string
  previousYearsPageLabel: string
  toView: (attributes: CalendarAttributes) => Html
}>

Week

typesource
/**
 * A single week row in the day grid, carrying its own row attributes (role,
 * aria-rowindex) alongside its 7 day cells.
 */
type Week = Readonly<{
  attributes: ReadonlyArray<ChildAttribute>
  cells: ReadonlyArray<DayCell>
}>

YearCell

typesource
/** Information about a single year cell in the rendered years grid. */
type YearCell = Readonly<{
  buttonAttributes: ReadonlyArray<ChildAttribute>
  cellAttributes: ReadonlyArray<ChildAttribute>
  isCurrentYear: boolean
  isDisabled: boolean
  isFocused: boolean
  isSelected: boolean
  label: string
  year: number
}>

YearsModeAttributes

typesource
/**
 * Attributes provided to the consumer when rendering the years grid. The
 * 12 cells span one paged window; prev/next buttons page by 12 years.
 */
type YearsModeAttributes = Readonly<{
  _tag: "Years"
  cells: ReadonlyArray<YearCell>
  grid: ReadonlyArray<ChildAttribute>
  heading: Readonly<{
    id: string
    text: string
  }>
  nextPageButton: ReadonlyArray<ChildAttribute>
  previousPageButton: ReadonlyArray<ChildAttribute>
  root: ReadonlyArray<ChildAttribute>
}>

Constants

BlurredGrid

constsource
/** Sent when the grid container loses DOM focus. */
const BlurredGrid: CallableTaggedStruct<"BlurredGrid", {}>

ChangedViewMonth

constsource
/**
 * Emitted when the visible month changes due to navigation. Consumers of an
 * inline calendar may use this to load month-scoped data (holidays, events).
 * A click that commits a date in a different month emits `SelectedDate`, not
 * `ChangedViewMonth`. The parent infers the month change from the date.
 */
const ChangedViewMonth: CallableTaggedStruct<"ChangedViewMonth", {
  month: Int
  year: Int
}>

ClickedDay

constsource
/** Sent when the user clicks a day cell in the grid. */
const ClickedDay: CallableTaggedStruct<"ClickedDay", {
  date: Struct<{
    day: Int
    month: Int
    year: Int
  }>
}>

ClickedHeading

constsource
/**
 * Sent when the user clicks the calendar heading. Zooms out one mode
 * level: Days → Months, Months → Years. Terminal in Years mode.
 */
const ClickedHeading: CallableTaggedStruct<"ClickedHeading", {}>

ClickedNextMonthButton

constsource
/**
 * Sent when the user clicks the next-month navigation button in Days
 * mode. (The Years mode prev/next-page buttons dispatch `PagedYears`.)
 */
const ClickedNextMonthButton: CallableTaggedStruct<"ClickedNextMonthButton", {}>

ClickedPreviousMonthButton

constsource
/**
 * Sent when the user clicks the previous-month navigation button in Days
 * mode. (The Years mode prev/next-page buttons dispatch `PagedYears`.)
 */
const ClickedPreviousMonthButton: CallableTaggedStruct<"ClickedPreviousMonthButton", {}>

CompletedFocusGrid

constsource
/** Sent when a FocusGrid command completes. */
const CompletedFocusGrid: CallableTaggedStruct<"CompletedFocusGrid", {}>

FocusGrid

constsource
/**
 * Focuses the calendar grid container. Parent components like DatePicker
 * dispatch this after opening to hand focus to the grid's keyboard layer.
 */
const FocusGrid: CommandDefinitionWithArgs<"FocusGrid", {
  id: String
}, Effect<{
  _tag: "CompletedFocusGrid"
}, never, never>>

FocusedGrid

constsource
/** Sent when the grid container receives DOM focus. */
const FocusedGrid: CallableTaggedStruct<"FocusedGrid", {}>

Message

constsource
/** Union of all messages the calendar component can produce. */
const Message: Union<readonly [
  CallableTaggedStruct<"ClickedDay", {
    date: Struct<{
      day: Int
      month: Int
      year: Int
    }>
  }>,
  CallableTaggedStruct<"PressedKeyOnGrid", {
    isShift: Boolean
    key: String
  }>,
  CallableTaggedStruct<"ClickedPreviousMonthButton", {}>,
  CallableTaggedStruct<"ClickedNextMonthButton", {}>,
  CallableTaggedStruct<"ClickedHeading", {}>,
  CallableTaggedStruct<"SelectedMonth", {
    month: Int
  }>,
  CallableTaggedStruct<"SelectedYear", {
    year: Int
  }>,
  CallableTaggedStruct<"PagedYears", {
    direction: Literals<readonly [1, -1]>
  }>,
  CallableTaggedStruct<"FocusedGrid", {}>,
  CallableTaggedStruct<"BlurredGrid", {}>,
  CallableTaggedStruct<"RefreshedToday", {
    today: Struct<{
      day: Int
      month: Int
      year: Int
    }>
  }>,
  CallableTaggedStruct<"CompletedFocusGrid", {}>
]>

Model

constsource
/**
 * Schema for the calendar component's state. Tracks the visible month/year,
 * the keyboard-focused and user-selected dates, the active view mode, and
 * the configuration that governs navigation (locale, min/max, disabled
 * days).
 */
const Model: Struct<{
  disabledDates: $Array<Struct<{
    day: Int
    month: Int
    year: Int
  }>>
  disabledDaysOfWeek: $Array<Literals<readonly ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]>>
  id: String
  isGridFocused: Boolean
  locale: Struct<{
    dayNames: Tuple<readonly [String, String, String, String, String, String, String]>
    firstDayOfWeek: Literals<readonly ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]>
    monthNames: Tuple<readonly [String, String, String, String, String, String, String, String, String, String, String, String]>
    shortDayNames: Tuple<readonly [String, String, String, String, String, String, String]>
    shortMonthNames: Tuple<readonly [String, String, String, String, String, String, String, String, String, String, String, String]>
  }>
  maybeFocusedDate: Option<Struct<{
    day: Int
    month: Int
    year: Int
  }>>
  maybeMaxDate: Option<Struct<{
    day: Int
    month: Int
    year: Int
  }>>
  maybeMinDate: Option<Struct<{
    day: Int
    month: Int
    year: Int
  }>>
  maybeSelectedDate: Option<Struct<{
    day: Int
    month: Int
    year: Int
  }>>
  today: Struct<{
    day: Int
    month: Int
    year: Int
  }>
  viewMode: Literals<readonly ["Days", "Months", "Years"]>
  viewMonth: Int
  viewYear: Int
}>

OutMessage

constsource
/** Union of the calendar's OutMessages. */
const OutMessage: Union<readonly [
  CallableTaggedStruct<"ChangedViewMonth", {
    month: Int
    year: Int
  }>,
  CallableTaggedStruct<"SelectedDate", {
    date: Struct<{
      day: Int
      month: Int
      year: Int
    }>
  }>
]>

PagedYears

constsource
/**
 * Sent when the user pages the years grid forward or backward by one
 * window. Direction is `1` for next, `-1` for previous.
 */
const PagedYears: CallableTaggedStruct<"PagedYears", {
  direction: Literals<readonly [1, -1]>
}>

PressedKeyOnGrid

constsource
/**
 * Sent when the user presses a key on the grid container. The update maps
 * the key to a navigation or selection action.
 */
const PressedKeyOnGrid: CallableTaggedStruct<"PressedKeyOnGrid", {
  isShift: Boolean
  key: String
}>

RefreshedToday

constsource
/** Sent when a long-lived session's "today" reference should be refreshed. */
const RefreshedToday: CallableTaggedStruct<"RefreshedToday", {
  today: Struct<{
    day: Int
    month: Int
    year: Int
  }>
}>

SelectedDate

constsource
/**
 * Emitted when the user commits a date selection via click or keyboard. The
 * calendar's internal state already reflects the new selection by the time
 * this fires; consumers react by lifting the date into their domain state
 * (closing a popover, advancing a form step, etc.).
 */
const SelectedDate: CallableTaggedStruct<"SelectedDate", {
  date: Struct<{
    day: Int
    month: Int
    year: Int
  }>
}>

SelectedMonth

constsource
/**
 * Sent when the user picks a month from the months grid. Jumps the view
 * to that month and returns the calendar to Days mode.
 */
const SelectedMonth: CallableTaggedStruct<"SelectedMonth", {
  month: Int
}>

SelectedYear

constsource
/**
 * Sent when the user picks a year from the years grid. Jumps the view to
 * that year and transitions the calendar to Months mode for further drilling.
 */
const SelectedYear: CallableTaggedStruct<"SelectedYear", {
  year: Int
}>

ViewMode

constsource
/**
 * Which grid the calendar is currently displaying. `Days` is the standard
 * 6×7 day grid; `Months` is a 3×4 month-name grid for fast month jumps;
 * `Years` is a 3×4 year grid paged in 12-year windows for fast year jumps.
 */
const ViewMode: Literals<readonly ["Days", "Months", "Years"]>

reflectDisabledDates

constsource
/**
 * Reflects the list of individually-disabled dates onto the model. Pass an
 * empty array to clear. Does NOT reconcile the current selection.
 */
const reflectDisabledDates: Reflect<Model, ReadonlyArray<CalendarDate>>

reflectDisabledDaysOfWeek

constsource
/**
 * Reflects the days of the week that are disabled (e.g. weekends) onto the
 * model. Pass an empty array to clear. Does NOT reconcile the current
 * selection.
 */
const reflectDisabledDaysOfWeek: Reflect<Model, ReadonlyArray<Calendar.DayOfWeek>>

reflectMaxDate

constsource
/**
 * Reflects the maximum selectable date onto the model. Pass `Option.none()`
 * to remove the maximum. Does NOT reconcile the current selection.
 */
const reflectMaxDate: Reflect<Model, Option.Option<CalendarDate>>

reflectMinDate

constsource
/**
 * Reflects the minimum selectable date onto the model. Pass `Option.none()`
 * to remove the minimum. Use this when the minimum derives from other Model
 * state (e.g. a start date field whose current selection constrains an end
 * date picker).
 * 
 * Does NOT reconcile the current selection. If a previously-selected date
 * is now below the new minimum, it remains selected. Callers should clear or
 * reassign the selection explicitly if their domain requires it.
 */
const reflectMinDate: Reflect<Model, Option.Option<CalendarDate>>

reflectSelectedDate

constsource
/**
 * Reflects an externally-sourced selected date onto the model without
 *  emitting an OutMessage. When a date is given, sets the selection and
 *  moves the view to the date's month so it stays visible, mirroring
 *  `selectDate`'s state change minus the `SelectedDate` announcement. Pass
 *  `Option.none()` to clear the selection (the view is left where it is).
 *  Use this to mirror external truth (a URL parameter, a saved draft) onto
 *  the calendar. Contrast with `selectDate`, a user or programmatic
 *  *choice* that emits `SelectedDate`. Returns the model directly because
 *  it produces no commands and no OutMessage.
 */
const reflectSelectedDate: Reflect<Model, Option.Option<CalendarDate>>

view

constsource
/**
 * Renders an accessible calendar. Publishes mode-specific ARIA attribute
 *  bundles + derived cell data, then delegates layout to the consumer's
 *  `toView` callback. The variant of `CalendarAttributes` passed to
 *  `toView` matches `model.viewMode`.
 */
const view: SubmodelView<Calendar.Model, {
  _tag: "ClickedDay"
  date: {
    day: number
    month: number
    year: number
  }
} | {
  _tag: "PressedKeyOnGrid"
  isShift: boolean
  key: string
} | {
  _tag: "ClickedPreviousMonthButton"
} | {
  _tag: "ClickedNextMonthButton"
} | {
  _tag: "ClickedHeading"
} | {
  _tag: "SelectedMonth"
  month: number
} | {
  _tag: "SelectedYear"
  year: number
} | {
  _tag: "PagedYears"
  direction: -1 | 1
} | {
  _tag: "FocusedGrid"
} | {
  _tag: "BlurredGrid"
} | {
  _tag: "RefreshedToday"
  today: {
    day: number
    month: number
    year: number
  }
} | {
  _tag: "CompletedFocusGrid"
}, Readonly<{
  daysHeadingButtonLabel: string
  monthsHeadingButtonLabel: string
  nextMonthLabel: string
  nextYearsPageLabel: string
  previousMonthLabel: string
  previousYearsPageLabel: string
  toView: (attributes: CalendarAttributes) => Html
}>>

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson