Date Range Picker

Type either endpoint or use its calendar action. An existing range commits the chosen endpoint immediately, preserving its opposite until crossed and then collapsing both to the chosen date. A new range commits on the second click, putting the earlier date first. Hover and keyboard focus preview a continuous highlight across week rows. Escape or clicking outside closes an unfinished selection. Bounds and allowed or disabled dates apply equally to typing and selection, including interior dates unless explicitly allowed. Native fields submit before JavaScript; partial or invalid text keeps the accepted pair while blocking submission. Values contain only YYYY-MM-DD dates.

Edit a date range

Type or paste either endpoint, or use its calendar action. Moving Start while it remains before End preserves End. Crossing either endpoint collapses the range to the edited date.

Su

M

T

W

Th

F

S

Code

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

<ui-date-range-picker
  value="{ start: '2026-10-10', end: '2026-10-15' }"
  start-name="from"
  end-name="to"
  start-input-id="range-start"
  end-input-id="range-end"
  locale="en-US"
></ui-date-range-picker>
<script setup>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<template>
  <UiDateRangePicker
    :value="{ start: '2026-10-10', end: '2026-10-15' }"
    start-name="from"
    end-name="to"
    start-input-id="range-start"
    end-input-id="range-end"
    locale="en-US"
  />
</template>
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    value={{"start":"2026-10-10","end":"2026-10-15"}}
    startName="from"
    endName="to"
    startInputId="range-start"
    endInputId="range-end"
    locale="en-US"
  />;
}
/** @jsxImportSource preact */
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    value={{"start":"2026-10-10","end":"2026-10-15"}}
    startName="from"
    endName="to"
    startInputId="range-start"
    endInputId="range-end"
    locale="en-US"
  />;
}
<script>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<UiDateRangePicker
  value={{"start":"2026-10-10","end":"2026-10-15"}}
  startName="from"
  endName="to"
  startInputId="range-start"
  endInputId="range-end"
  locale="en-US"
/>
import { Component } from "@angular/core";
import { UiDateRangePicker } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiDateRangePicker],
  template: `
    <span
      ui-date-range-picker
      [value]="{ start: '2026-10-10', end: '2026-10-15' }"
      [startName]="'from'"
      [endName]="'to'"
      [startInputId]="'range-start'"
      [endInputId]="'range-end'"
      [locale]="'en-US'"
    ></span>
  `,
})
export default class Example {}

Select a new range

An empty range opens a two-click selection. The earlier date becomes Start even when clicked second. Hover or keyboard focus previews the interval; The second click commits both dates together.

Su

M

T

W

Th

F

S

Code

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

<ui-date-range-picker
  start-name="from"
  end-name="to"
  start-input-id="new-range-start"
  end-input-id="new-range-end"
  today="2026-10-30"
></ui-date-range-picker>
<script setup>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<template>
  <UiDateRangePicker
    start-name="from"
    end-name="to"
    start-input-id="new-range-start"
    end-input-id="new-range-end"
    today="2026-10-30"
  />
</template>
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    startName="from"
    endName="to"
    startInputId="new-range-start"
    endInputId="new-range-end"
    today="2026-10-30"
  />;
}
/** @jsxImportSource preact */
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    startName="from"
    endName="to"
    startInputId="new-range-start"
    endInputId="new-range-end"
    today="2026-10-30"
  />;
}
<script>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<UiDateRangePicker
  startName="from"
  endName="to"
  startInputId="new-range-start"
  endInputId="new-range-end"
  today="2026-10-30"
/>
import { Component } from "@angular/core";
import { UiDateRangePicker } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiDateRangePicker],
  template: `
    <span
      ui-date-range-picker
      [startName]="'from'"
      [endName]="'to'"
      [startInputId]="'new-range-start'"
      [endInputId]="'new-range-end'"
      [today]="'2026-10-30'"
    ></span>
  `,
})
export default class Example {}

Keep unavailable dates outside the range

Bounds, a weekday blacklist and an unavailable interval restrict typing and selection. Interior dates must be available unless allow-unavailable-within-range is set explicitly.

Su

M

T

W

Th

F

S

Code

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

<ui-date-range-picker
  min="2026-10-01"
  max="2026-12-31"
  disabled-weekdays="[6, 7]"
  disabled-ranges="[{ start: '2026-11-23', end: '2026-11-27' }]"
  start-name="from"
  end-name="to"
  start-input-id="available-range-start"
  end-input-id="available-range-end"
  locale="en-US"
></ui-date-range-picker>
<script setup>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<template>
  <UiDateRangePicker
    min="2026-10-01"
    max="2026-12-31"
    :disabled-weekdays="[6, 7]"
    :disabled-ranges="[{ start: '2026-11-23', end: '2026-11-27' }]"
    start-name="from"
    end-name="to"
    start-input-id="available-range-start"
    end-input-id="available-range-end"
    locale="en-US"
  />
</template>
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    min="2026-10-01"
    max="2026-12-31"
    disabledWeekdays={[6,7]}
    disabledRanges={[{"start":"2026-11-23","end":"2026-11-27"}]}
    startName="from"
    endName="to"
    startInputId="available-range-start"
    endInputId="available-range-end"
    locale="en-US"
  />;
}
/** @jsxImportSource preact */
import { UiDateRangePicker } from "@threadlabs/looma";

export default function Example() {
  return <UiDateRangePicker
    min="2026-10-01"
    max="2026-12-31"
    disabledWeekdays={[6,7]}
    disabledRanges={[{"start":"2026-11-23","end":"2026-11-27"}]}
    startName="from"
    endName="to"
    startInputId="available-range-start"
    endInputId="available-range-end"
    locale="en-US"
  />;
}
<script>
  import { UiDateRangePicker } from "@threadlabs/looma";
</script>

<UiDateRangePicker
  min="2026-10-01"
  max="2026-12-31"
  disabledWeekdays={[6,7]}
  disabledRanges={[{"start":"2026-11-23","end":"2026-11-27"}]}
  startName="from"
  endName="to"
  startInputId="available-range-start"
  endInputId="available-range-end"
  locale="en-US"
/>
import { Component } from "@angular/core";
import { UiDateRangePicker } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiDateRangePicker],
  template: `
    <span
      ui-date-range-picker
      [min]="'2026-10-01'"
      [max]="'2026-12-31'"
      [disabledWeekdays]="[6, 7]"
      [disabledRanges]="[{ start: '2026-11-23', end: '2026-11-27' }]"
      [startName]="'from'"
      [endName]="'to'"
      [startInputId]="'available-range-start'"
      [endInputId]="'available-range-end'"
      [locale]="'en-US'"
    ></span>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: span

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
allow-unavailable-within-rangeAllows crossing unavailable interior dates. Both endpoints must still be available.allowUnavailableWithinRangebooleanfalse—
allowed-datesAllowed ISO dates, unioned with allowed-ranges. Omission is unrestricted; an explicit empty allow set permits nothing.allowedDateslist(string)——
allowed-rangesInclusive allowed date intervals, unioned with allowed-dates.allowedRangeslist(object)
  • [].start: string (required)
  • [].end: string (required)
——
disabledPrevents editing and omits both endpoints from submission.disabledbooleanfalse—
disabled-datesUnavailable ISO dates, subtracted after bounds and allowed rules.disabledDateslist(string)[]—
disabled-rangesInclusive unavailable date intervals.disabledRangeslist(object)
  • [].start: string (required)
  • [].end: string (required)
[]—
disabled-weekdaysUnavailable ISO weekdays: Monday 1 through Sunday 7.disabledWeekdayslist(integer)[]—
display-optionsIntl date display options while fields are unfocused. Focus restores numeric entry; canonical endpoints remain YYYY-MM-DD.displayOptionsobject
  • dateStyle: keyword
  • weekday: keyword
  • year: keyword
  • month: keyword
  • day: keyword
  • era: keyword
  • numberingSystem: string
  • localeMatcher: string
  • formatMatcher: string
——
end-input-idID for the end field and its visible label, including before JavaScript.endInputIdstring—
end-labelVisible label of the end field.endLabelstringEnd date—
end-nameNative form name for the end date.endNamestring—
formOptional ID of a form outside this component's ancestor tree.formstring—
invalidExternal invalid appearance supplied by the application.invalidbooleanfalse—
labelAccessible name of the range group.labelstringDate range—
localeIntl Gregorian editing order, digits, labels and default week order. ISO date paste is always accepted.localestringen-US—
maxInclusive latest YYYY-MM-DD endpoint.maxstring—
minInclusive earliest YYYY-MM-DD endpoint.minstring—
modelServer-prepared endpoint availability, owned by the component module's producer.modelobject
  • startRanges: list(object) (required)
  • startRanges[].start: string (required)
  • startRanges[].end: string (required)
  • endRanges: list(object) (required)
  • endRanges[].start: string (required)
  • endRanges[].end: string (required)
  • calendarRanges: list(object) (required)
  • calendarRanges[].start: string (required)
  • calendarRanges[].end: string (required)
  • weekStartsOn: integer (required)
——
month-labelAccessible name of the month navigation toggle.monthLabelstringChoose month—
next-labelAccessible name of the next-page action.nextLabelstringNext—
picker-labelAccessible name of the picker dialog and actions.pickerLabelstringChoose date range—
previous-labelAccessible name of the previous-page action.previousLabelstringPrevious—
readonlyRetains selection and submission while preventing edits.readonlybooleanfalse—
requiredRequires both endpoints. A partially entered optional range also requires its missing endpoint.requiredbooleanfalse—
sizeShared input and action size.sizekeywordmdsm, md, lg
start-input-idID for the start field and its visible label, including before JavaScript.startInputIdstring—
start-labelVisible label of the start field.startLabelstringStart date—
start-nameNative form name for the start date.startNamestring—
todayOptional explicit YYYY-MM-DD date to mark as today.todaystring—
valueInitial inclusive date range, replaced on external changes. Each endpoint is YYYY-MM-DD, without a time or timezone. A complete accepted edit emits both endpoints together. Reset restores this external value.valueobject
  • start: string (required)
  • end: string (required)
{ start: '', end: '' }—
week-starts-onISO week order override. Omission follows the locale, falling back to Sunday (7).weekStartsOninteger——
year-labelAccessible name of year navigation and direct year entry.yearLabelstringChoose year—

Framework props

NameTypeDefaultAllowed values
allowedDatesreadonly (string)[]——
allowedRangesreadonly ({ readonly start: string; readonly end: string })[]
  • [].start: string (required)
  • [].end: string (required)
——
allowUnavailableWithinRangebooleanfalse—
disabledbooleanfalse—
disabledDatesreadonly (string)[][]—
disabledRangesreadonly ({ readonly start: string; readonly end: string })[]
  • [].start: string (required)
  • [].end: string (required)
[]—
disabledWeekdaysreadonly (number)[][]—
displayOptions{ readonly dateStyle?: "full" | "long" | "medium" | "short" | null; readonly weekday?: "long" | "short" | "narrow" | null; readonly year?: "numeric" | "2-digit" | null; readonly month?: "numeric" | "2-digit" | "long" | "short" | "narrow" | null; readonly day?: "numeric" | "2-digit" | null; readonly era?: "long" | "short" | "narrow" | null; readonly numberingSystem?: string | null; readonly localeMatcher?: "lookup" | "best fit" | null; readonly formatMatcher?: "basic" | "best fit" | null }
  • dateStyle: keyword
  • weekday: keyword
  • year: keyword
  • month: keyword
  • day: keyword
  • era: keyword
  • numberingSystem: string
  • localeMatcher: string
  • formatMatcher: string
——
endInputIdstring—
endLabelstringEnd date—
endNamestring—
formstring—
invalidbooleanfalse—
labelstringDate range—
localestringen-US—
maxstring—
minstring—
model{ readonly startRanges: readonly ({ readonly start: string; readonly end: string })[]; readonly endRanges: readonly ({ readonly start: string; readonly end: string })[]; readonly calendarRanges: readonly ({ readonly start: string; readonly end: string })[]; readonly weekStartsOn: number }
  • startRanges: list(object) (required)
  • startRanges[].start: string (required)
  • startRanges[].end: string (required)
  • endRanges: list(object) (required)
  • endRanges[].start: string (required)
  • endRanges[].end: string (required)
  • calendarRanges: list(object) (required)
  • calendarRanges[].start: string (required)
  • calendarRanges[].end: string (required)
  • weekStartsOn: integer (required)
——
monthLabelstringChoose month—
nextLabelstringNext—
pickerLabelstringChoose date range—
previousLabelstringPrevious—
readonlybooleanfalse—
requiredbooleanfalse—
size"sm" | "md" | "lg"mdsm, md, lg
startInputIdstring—
startLabelstringStart date—
startNamestring—
todaystring—
value{ readonly start: string; readonly end: string }
  • start: string (required)
  • end: string (required)
{ start: '', end: '' }—
weekStartsOnnumber——
yearLabelstringChoose year—

Events

NameDetailDescription
changeobject
  • value: object (required)
  • value.start: string (required)
  • value.end: string (required)
  • trigger: keyword (required)
Emitted when a toggleable control changes checked state.
closeobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to a closed state.
inputobject
  • value: object (required)
  • value.start: string (required)
  • value.end: string (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.
openobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to an open state.
query-changeobject
  • endpoint: keyword (required)
  • query: string (required)
Emitted with the declared detail payload.
validation-changeobject
  • invalid: boolean (required)
  • message: string (required)
  • endpoint: keyword
Emitted with the declared detail payload.

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.