Calendar

Selection values and events always contain only a YYYY-MM-DD civil date, with no time or timezone. Temporal calendar navigation with one day-grid tab stop, locale-aware Gregorian labels, direct month/year selection and typed year navigation. Availability combines inclusive bounds, allowed dates/ranges and disabled dates/ranges/ISO weekdays. Navigation leaves selection unchanged; input and change report a changed selection, and select reports every accepted day activation. Compose with Date Field for typed date entry and native form submission. The server-safe model producer is prepared through HTML Next's existing module binding hooks. Day choices use centered circular ghost hover/selection paint; unavailable dates retain transparent surfaces and muted text. Week order follows locale information, falling back to Sunday first. week-starts-on overrides it, including Sunday (7), Saturday (6), or Monday (1). Sunday and Saturday columns have a faint theme-derived surface. Month/year controls provide the visible heading.

Choose a date

Open the month or year heading to jump directly. Arrow keys move through available dates; Page Up/Down changes month, and Shift changes year.

Su

M

T

W

Th

F

S

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-calendar/ui-calendar.html"
>

<ui-calendar
  value="2026-10-30"
  today="2026-10-30"
  locale="en-US"
  label="Choose a date"
></ui-calendar>
<script setup>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<template>
  <UiCalendar
    value="2026-10-30"
    today="2026-10-30"
    locale="en-US"
    label="Choose a date"
  />
</template>
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    value="2026-10-30"
    today="2026-10-30"
    locale="en-US"
    label="Choose a date"
  />;
}
/** @jsxImportSource preact */
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    value="2026-10-30"
    today="2026-10-30"
    locale="en-US"
    label="Choose a date"
  />;
}
<script>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<UiCalendar
  value="2026-10-30"
  today="2026-10-30"
  locale="en-US"
  label="Choose a date"
/>
import { Component } from "@angular/core";
import { UiCalendar } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCalendar],
  template: `
    <section
      ui-calendar
      [value]="'2026-10-30'"
      [today]="'2026-10-30'"
      [locale]="'en-US'"
      [label]="'Choose a date'"
    ></section>
  `,
})
export default class Example {}

Available weekdays

Weekends, a blocked interval and dates outside the inclusive bounds remain visible and unavailable. The same rules validate typed fields.

Su

M

T

W

Th

F

S

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-calendar/ui-calendar.html"
>

<ui-calendar
  month="2026-10"
  min="2026-10-05"
  max="2026-11-20"
  disabled-weekdays="[6,7]"
  disabled-ranges="[{&quot;start&quot;:&quot;2026-10-12&quot;,&quot;end&quot;:&quot;2026-10-16&quot;}]"
  locale="en-US"
></ui-calendar>
<script setup>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<template>
  <UiCalendar
    month="2026-10"
    min="2026-10-05"
    max="2026-11-20"
    :disabled-weekdays="[6, 7]"
    :disabled-ranges="[{ start: '2026-10-12', end: '2026-10-16' }]"
    locale="en-US"
  />
</template>
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    month="2026-10"
    min="2026-10-05"
    max="2026-11-20"
    disabledWeekdays={[6,7]}
    disabledRanges={[{"start":"2026-10-12","end":"2026-10-16"}]}
    locale="en-US"
  />;
}
/** @jsxImportSource preact */
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    month="2026-10"
    min="2026-10-05"
    max="2026-11-20"
    disabledWeekdays={[6,7]}
    disabledRanges={[{"start":"2026-10-12","end":"2026-10-16"}]}
    locale="en-US"
  />;
}
<script>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<UiCalendar
  month="2026-10"
  min="2026-10-05"
  max="2026-11-20"
  disabledWeekdays={[6,7]}
  disabledRanges={[{"start":"2026-10-12","end":"2026-10-16"}]}
  locale="en-US"
/>
import { Component } from "@angular/core";
import { UiCalendar } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCalendar],
  template: `
    <section
      ui-calendar
      [month]="'2026-10'"
      [min]="'2026-10-05'"
      [max]="'2026-11-20'"
      [disabledWeekdays]="[6, 7]"
      [disabledRanges]="[{ start: '2026-10-12', end: '2026-10-16' }]"
      [locale]="'en-US'"
    ></section>
  `,
})
export default class Example {}

Highlight a range

Calendar displays inclusive range endpoints; Date Range Picker coordinates independently editable endpoints.

Su

M

T

W

Th

F

S

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-calendar/ui-calendar.html"
>

<ui-calendar
  month="2026-10"
  start="2026-10-12"
  end="2026-10-17"
  locale="en-US"
></ui-calendar>
<script setup>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<template>
  <UiCalendar
    month="2026-10"
    start="2026-10-12"
    end="2026-10-17"
    locale="en-US"
  />
</template>
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    month="2026-10"
    start="2026-10-12"
    end="2026-10-17"
    locale="en-US"
  />;
}
/** @jsxImportSource preact */
import { UiCalendar } from "@threadlabs/looma";

export default function Example() {
  return <UiCalendar
    month="2026-10"
    start="2026-10-12"
    end="2026-10-17"
    locale="en-US"
  />;
}
<script>
  import { UiCalendar } from "@threadlabs/looma";
</script>

<UiCalendar
  month="2026-10"
  start="2026-10-12"
  end="2026-10-17"
  locale="en-US"
/>
import { Component } from "@angular/core";
import { UiCalendar } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCalendar],
  template: `
    <section
      ui-calendar
      [month]="'2026-10'"
      [start]="'2026-10-12'"
      [end]="'2026-10-17'"
      [locale]="'en-US'"
    ></section>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: section

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
allowed-datesAllow-listed dates; omission is unrestricted, an explicit empty list allows none.allowedDateslist(string)——
allowed-rangesInclusive allowed intervals, unioned with allowed-dates.allowedRangeslist(object)
  • [].start: string (required)
  • [].end: string (required)
——
disabledPrevents selection and navigation.disabledbooleanfalse—
disabled-datesUnavailable dates, subtracted after bounds and allowed rules.disabledDateslist(string)[]—
disabled-rangesInclusive unavailable intervals with YYYY-MM-DD start and end dates, subtracted after bounds and allowed rules.disabledRangeslist(object)
  • [].start: string (required)
  • [].end: string (required)
[]—
disabled-weekdaysUnavailable ISO weekdays: Monday 1 through Sunday 7.disabledWeekdayslist(integer)[]—
empty-labelMessage when the displayed month has no selectable date.emptyLabelstringNo available dates this month.—
endOptional range highlight's inclusive YYYY-MM-DD end. The caller owns range coordination.endstring—
labelAccessible name of the calendar group.labelstringCalendar—
localeIntl Gregorian labels, digits and default week order.localestringen-US—
maxInclusive latest selectable YYYY-MM-DD date.maxstring—
minInclusive earliest selectable YYYY-MM-DD date.minstring—
modelCalendar data supplied by the component module's server-safe producer. Hosts prepare this input before SSR; caller-supplied fixtures override production.modelobject
  • rangeBands: list(object) (required)
  • rangeBands[].start: integer (required)
  • rangeBands[].end: integer (required)
  • month: string (required)
  • title: string (required)
  • monthLabel: string (required)
  • yearLabel: string (required)
  • weekStartsOn: integer (required)
  • weekendColumns: list(integer) (required)
  • weekdays: list(object) (required)
  • weekdays[].day: integer (required)
  • weekdays[].label: string (required)
  • weekdays[].fullLabel: string (required)
  • rows: list(list(object)) (required)
  • rows[][].value: string (required)
  • rows[][].day: integer (required)
  • rows[][].outside: boolean (required)
  • rows[][].label: string (required)
  • rows[][].disabled: boolean (required)
  • rows[][].selected: boolean (required)
  • rows[][].today: boolean (required)
  • rows[][].rangeStart: boolean (required)
  • rows[][].rangeEnd: boolean (required)
  • rows[][].inRange: boolean (required)
  • rows[][].tabIndex: integer (required)
  • focusDate: string (required)
  • empty: boolean (required)
  • previousMonth: string (required)
  • nextMonth: string (required)
  • months: list(object) (required)
  • months[].value: string (required)
  • months[].label: string (required)
  • months[].fullLabel: string (required)
  • months[].current: boolean (required)
  • months[].outsideBounds: boolean (required)
  • months[].disabled: boolean (required)
  • months[].tabIndex: integer (required)
  • years: list(object) (required)
  • years[].value: integer (required)
  • years[].label: string (required)
  • years[].current: boolean (required)
  • years[].outsideBounds: boolean (required)
  • years[].disabled: boolean (required)
  • years[].tabIndex: integer (required)
  • yearPageStart: integer (required)
  • yearPageLabel: string (required)
  • monthRows: list(list(object)) (required)
  • monthRows[][].value: string (required)
  • monthRows[][].label: string (required)
  • monthRows[][].fullLabel: string (required)
  • monthRows[][].current: boolean (required)
  • monthRows[][].outsideBounds: boolean (required)
  • monthRows[][].disabled: boolean (required)
  • monthRows[][].tabIndex: integer (required)
  • yearRows: list(list(object)) (required)
  • yearRows[][].value: integer (required)
  • yearRows[][].label: string (required)
  • yearRows[][].current: boolean (required)
  • yearRows[][].outsideBounds: boolean (required)
  • yearRows[][].disabled: boolean (required)
  • yearRows[][].tabIndex: integer (required)
——
monthInitial visible YYYY-MM, replaced on an external change. Omission follows value, today, then the current UTC month.monthstring—
month-labelAccessible name of the month view toggle.monthLabelstringChoose month—
next-labelAccessible name of the next month, year or year-page action.nextLabelstringNext—
previous-labelAccessible name of the previous month, year or year-page action.previousLabelstringPrevious—
readonlyAllows navigation while preventing selection.readonlybooleanfalse—
startOptional range highlight's inclusive YYYY-MM-DD start.startstring—
todayOptional explicit YYYY-MM-DD date to mark as today; omission adds no today marker.todaystring—
valueInitial selected YYYY-MM-DD civil date, replaced on an external change. Selection events always export just the date, without a time or timezone. Navigation does not select.valuestring—
week-starts-onWeek order override using ISO weekdays: Sunday 7, Saturday 6, or Monday 1. Omission follows the locale's week information, with Sunday as the fallback when unavailable.weekStartsOninteger——
year-error-labelDirect year entry error.yearErrorLabelstringEnter a year from 1 to 9999.—
year-labelAccessible name of the year view toggle and direct year input.yearLabelstringChoose year—

Framework props

NameTypeDefaultAllowed values
allowedDatesreadonly (string)[]——
allowedRangesreadonly ({ readonly start: string; readonly end: string })[]
  • [].start: string (required)
  • [].end: string (required)
——
disabledbooleanfalse—
disabledDatesreadonly (string)[][]—
disabledRangesreadonly ({ readonly start: string; readonly end: string })[]
  • [].start: string (required)
  • [].end: string (required)
[]—
disabledWeekdaysreadonly (number)[][]—
emptyLabelstringNo available dates this month.—
endstring—
labelstringCalendar—
localestringen-US—
maxstring—
minstring—
model{ readonly rangeBands: readonly ({ readonly start: number; readonly end: number })[]; readonly month: string; readonly title: string; readonly monthLabel: string; readonly yearLabel: string; readonly weekStartsOn: number; readonly weekendColumns: readonly (number)[]; readonly weekdays: readonly ({ readonly day: number; readonly label: string; readonly fullLabel: string })[]; readonly rows: readonly (readonly ({ readonly value: string; readonly day: number; readonly outside: boolean; readonly label: string; readonly disabled: boolean; readonly selected: boolean; readonly today: boolean; readonly rangeStart: boolean; readonly rangeEnd: boolean; readonly inRange: boolean; readonly tabIndex: number })[])[]; readonly focusDate: string; readonly empty: boolean; readonly previousMonth: string; readonly nextMonth: string; readonly months: readonly ({ readonly value: string; readonly label: string; readonly fullLabel: string; readonly current: boolean; readonly outsideBounds: boolean; readonly disabled: boolean; readonly tabIndex: number })[]; readonly years: readonly ({ readonly value: number; readonly label: string; readonly current: boolean; readonly outsideBounds: boolean; readonly disabled: boolean; readonly tabIndex: number })[]; readonly yearPageStart: number; readonly yearPageLabel: string; readonly monthRows: readonly (readonly ({ readonly value: string; readonly label: string; readonly fullLabel: string; readonly current: boolean; readonly outsideBounds: boolean; readonly disabled: boolean; readonly tabIndex: number })[])[]; readonly yearRows: readonly (readonly ({ readonly value: number; readonly label: string; readonly current: boolean; readonly outsideBounds: boolean; readonly disabled: boolean; readonly tabIndex: number })[])[] }
  • rangeBands: list(object) (required)
  • rangeBands[].start: integer (required)
  • rangeBands[].end: integer (required)
  • month: string (required)
  • title: string (required)
  • monthLabel: string (required)
  • yearLabel: string (required)
  • weekStartsOn: integer (required)
  • weekendColumns: list(integer) (required)
  • weekdays: list(object) (required)
  • weekdays[].day: integer (required)
  • weekdays[].label: string (required)
  • weekdays[].fullLabel: string (required)
  • rows: list(list(object)) (required)
  • rows[][].value: string (required)
  • rows[][].day: integer (required)
  • rows[][].outside: boolean (required)
  • rows[][].label: string (required)
  • rows[][].disabled: boolean (required)
  • rows[][].selected: boolean (required)
  • rows[][].today: boolean (required)
  • rows[][].rangeStart: boolean (required)
  • rows[][].rangeEnd: boolean (required)
  • rows[][].inRange: boolean (required)
  • rows[][].tabIndex: integer (required)
  • focusDate: string (required)
  • empty: boolean (required)
  • previousMonth: string (required)
  • nextMonth: string (required)
  • months: list(object) (required)
  • months[].value: string (required)
  • months[].label: string (required)
  • months[].fullLabel: string (required)
  • months[].current: boolean (required)
  • months[].outsideBounds: boolean (required)
  • months[].disabled: boolean (required)
  • months[].tabIndex: integer (required)
  • years: list(object) (required)
  • years[].value: integer (required)
  • years[].label: string (required)
  • years[].current: boolean (required)
  • years[].outsideBounds: boolean (required)
  • years[].disabled: boolean (required)
  • years[].tabIndex: integer (required)
  • yearPageStart: integer (required)
  • yearPageLabel: string (required)
  • monthRows: list(list(object)) (required)
  • monthRows[][].value: string (required)
  • monthRows[][].label: string (required)
  • monthRows[][].fullLabel: string (required)
  • monthRows[][].current: boolean (required)
  • monthRows[][].outsideBounds: boolean (required)
  • monthRows[][].disabled: boolean (required)
  • monthRows[][].tabIndex: integer (required)
  • yearRows: list(list(object)) (required)
  • yearRows[][].value: integer (required)
  • yearRows[][].label: string (required)
  • yearRows[][].current: boolean (required)
  • yearRows[][].outsideBounds: boolean (required)
  • yearRows[][].disabled: boolean (required)
  • yearRows[][].tabIndex: integer (required)
——
monthstring—
monthLabelstringChoose month—
nextLabelstringNext—
previousLabelstringPrevious—
readonlybooleanfalse—
startstring—
todaystring—
valuestring—
weekStartsOnnumber——
yearErrorLabelstringEnter a year from 1 to 9999.—
yearLabelstringChoose year—

Events

NameDetailDescription
changeobject
  • value: string (required)
  • trigger: keyword (required)
Emitted when a toggleable control changes checked state.
inputobject
  • value: string (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.
month-changeobject
  • month: string (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.
selectobject
  • value: string (required)
  • trigger: keyword (required)
Emitted when a selectable option becomes active.

Slots

No slots.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

No component tokens.

Shared tokens

No shared tokens.