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
No available dates this month.
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
No available dates this month.
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
No available dates this month.
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
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
allow-unavailable-within-range | Allows crossing unavailable interior dates. Both endpoints must still be available. | allowUnavailableWithinRange | boolean | false | — |
allowed-dates | Allowed ISO dates, unioned with allowed-ranges. Omission is unrestricted; an explicit empty allow set permits nothing. | allowedDates | list(string) | — | — |
allowed-ranges | Inclusive allowed date intervals, unioned with allowed-dates. | allowedRanges | list(object)
| — | — |
disabled | Prevents editing and omits both endpoints from submission. | disabled | boolean | false | — |
disabled-dates | Unavailable ISO dates, subtracted after bounds and allowed rules. | disabledDates | list(string) | [] | — |
disabled-ranges | Inclusive unavailable date intervals. | disabledRanges | list(object)
| [] | — |
disabled-weekdays | Unavailable ISO weekdays: Monday 1 through Sunday 7. | disabledWeekdays | list(integer) | [] | — |
display-options | Intl date display options while fields are unfocused. Focus restores numeric entry; canonical endpoints remain YYYY-MM-DD. | displayOptions | object
| — | — |
end-input-id | ID for the end field and its visible label, including before JavaScript. | endInputId | string | | — |
end-label | Visible label of the end field. | endLabel | string | End date | — |
end-name | Native form name for the end date. | endName | string | | — |
form | Optional ID of a form outside this component's ancestor tree. | form | string | | — |
invalid | External invalid appearance supplied by the application. | invalid | boolean | false | — |
label | Accessible name of the range group. | label | string | Date range | — |
locale | Intl Gregorian editing order, digits, labels and default week order. ISO date paste is always accepted. | locale | string | en-US | — |
max | Inclusive latest YYYY-MM-DD endpoint. | max | string | | — |
min | Inclusive earliest YYYY-MM-DD endpoint. | min | string | | — |
model | Server-prepared endpoint availability, owned by the component module's producer. | model | object
| — | — |
month-label | Accessible name of the month navigation toggle. | monthLabel | string | Choose month | — |
next-label | Accessible name of the next-page action. | nextLabel | string | Next | — |
picker-label | Accessible name of the picker dialog and actions. | pickerLabel | string | Choose date range | — |
previous-label | Accessible name of the previous-page action. | previousLabel | string | Previous | — |
readonly | Retains selection and submission while preventing edits. | readonly | boolean | false | — |
required | Requires both endpoints. A partially entered optional range also requires its missing endpoint. | required | boolean | false | — |
size | Shared input and action size. | size | keyword | md | sm, md, lg |
start-input-id | ID for the start field and its visible label, including before JavaScript. | startInputId | string | | — |
start-label | Visible label of the start field. | startLabel | string | Start date | — |
start-name | Native form name for the start date. | startName | string | | — |
today | Optional explicit YYYY-MM-DD date to mark as today. | today | string | | — |
value | Initial 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. | value | object
| { start: '', end: '' } | — |
week-starts-on | ISO week order override. Omission follows the locale, falling back to Sunday (7). | weekStartsOn | integer | — | — |
year-label | Accessible name of year navigation and direct year entry. | yearLabel | string | Choose year | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
allowedDates | readonly (string)[] | — | — |
allowedRanges | readonly ({ readonly start: string; readonly end: string })[]
| — | — |
allowUnavailableWithinRange | boolean | false | — |
disabled | boolean | false | — |
disabledDates | readonly (string)[] | [] | — |
disabledRanges | readonly ({ readonly start: string; readonly end: string })[]
| [] | — |
disabledWeekdays | readonly (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 }
| — | — |
endInputId | string | | — |
endLabel | string | End date | — |
endName | string | | — |
form | string | | — |
invalid | boolean | false | — |
label | string | Date range | — |
locale | string | en-US | — |
max | string | | — |
min | string | | — |
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 }
| — | — |
monthLabel | string | Choose month | — |
nextLabel | string | Next | — |
pickerLabel | string | Choose date range | — |
previousLabel | string | Previous | — |
readonly | boolean | false | — |
required | boolean | false | — |
size | "sm" | "md" | "lg" | md | sm, md, lg |
startInputId | string | | — |
startLabel | string | Start date | — |
startName | string | | — |
today | string | | — |
value | { readonly start: string; readonly end: string }
| { start: '', end: '' } | — |
weekStartsOn | number | — | — |
yearLabel | string | Choose year | — |
Events
| Name | Detail | Description |
|---|---|---|
change | object
| Emitted when a toggleable control changes checked state. |
close | object
| Emitted when the component transitions to a closed state. |
input | object
| Emitted with the declared detail payload. |
open | object
| Emitted when the component transitions to an open state. |
query-change | object
| Emitted with the declared detail payload. |
validation-change | object
| 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.