Inputs
Date Range Field
A date-range input for a dashboard's filter bar: a field that reads "Mar 3 – 9, 2026" and opens two months side by side with a rail of shortcuts — Today, Last 7 days, This month — beside them.
import { DateRangeField } from 'omaris' Examples
Basic
A dashboard's period filter: two months and a rail of shortcuts on a desktop, one month, chips and an Apply button in a sheet on a phone. The value is { start, end }, both ends inclusive.
<script lang="ts"> import { DateRangeField, addDays, startOfDay, type DateRange } from 'omaris'; const today = startOfDay(new Date()); let period = $state<DateRange | null>({ start: addDays(today, -6), end: today });</script><DateRangeField class="w-72 max-w-full" label="Period" bind:value={period} max={today} /> Presets
presets replaces the shortcuts — built from the same date helpers, clamped to min and max when chosen. Pass [] for none, and months={1} for a narrower panel.
<script lang="ts"> import { DateRangeField, addDays, startOfDay, type DatePreset } from 'omaris'; const today = startOfDay(new Date()); const AHEAD: DatePreset[] = [ { label: 'Next 7 days', range: () => ({ start: today, end: addDays(today, 6) }) }, { label: 'Next 14 days', range: () => ({ start: today, end: addDays(today, 13) }) }, { label: 'Next 30 days', range: () => ({ start: today, end: addDays(today, 29) }) } ];</script><DateRangeField class="w-72 max-w-full" label="Booking window" presets={AHEAD} min={today} /><DateRangeField class="w-72 max-w-full" label="Any dates" presets={[]} months={1} /> States
The field's own states are a text field's: variants, sizes, invalid with its message, disabled.
Pick at most 90 days.
<script lang="ts"> import { DateRangeField } from 'omaris'; const MARCH = { start: new Date(2026, 2, 3), end: new Date(2026, 2, 9) };</script><DateRangeField class="w-64 max-w-full" variant="filled" size="sm" label="Filled" value={MARCH} /><DateRangeField class="w-64 max-w-full" label="Invalid" invalid supportingText="Pick at most 90 days."/><DateRangeField class="w-64 max-w-full" label="Disabled" disabled value={MARCH} /> Overridden
classes covers the frame and the presets; labels renames the words the field says itself. A name posts the range as an ISO interval, 2026-03-03/2026-03-09.
<script lang="ts"> import { DateRangeField } from 'omaris';</script><form class="w-56 max-w-full"> <DateRangeField label="Billing cycle" name="cycle" value={{ start: new Date(2026, 2, 3), end: new Date(2026, 2, 9) }} labels={{ presets: 'Quick ranges', apply: 'Use range' }} classes={{ field: 'bg-surface-container', preset: 'rounded-shape-sm', value: 'font-medium' }} /></form> When to use it
Use it for
- The period filter at the top of a dashboard or a report: "Mar 3 – 9, 2026", with Today, Last 7 days, Last 30 days, This month, Last month and This year one press away.
- A span in a form — a leave request, a campaign's run, a rental — where the two ends belong to one question.
- Commit-on-Apply on a phone: the sheet keeps a draft, so a thumb that lands on the wrong day changes nothing until Apply.
Not for
- One date → Date Field.
- A range picked on the page itself, with no field → Calendar with
mode="range". - A handful of fixed periods and nothing else — "7d / 30d / 90d" → Segmented Button. A calendar behind a field is overhead when nobody picks a custom range.
- A range of times within a day → two
TimePickers in Picker.
Do
- Set
max={today}on a filter over data that ends today. The presets clamp to it, so "This month" becomes month-to-date. - Replace the presets with the periods your users actually compare —
presetstakes{ label, range }, and the date helpers build the ranges. - Translate
labelsand the preset labels together when the page is not in English; the calendar translates itself. - Show the chosen period somewhere in the view it filters, not only in the field — the chart's subtitle, the export's filename.
Don't
- Keep the default presets when
minrules most of them out. A rail of greyed shortcuts reads as broken. - Expect
valueto change on the first calendar press. It changes when the range is whole — the second press on a desktop, Apply on a phone. - Put two of these side by side for "compare to". Say what is being compared and give the second one its own label.
API
DateRangeField
A date-range input for a dashboard's filter bar: a field that reads "Mar 3 – 9, 2026" and opens two months side by side with a rail of shortcuts — Today, Last 7 days, This month — beside them.
On a desktop the second press on the calendar commits the range and closes, and a shortcut commits at once. On a phone it is a bottom sheet with one month and a row of shortcut chips, and nothing changes until Apply — a thumb that lands on the wrong day costs nothing.
The value is { start, end } at local midnight, both ends inclusive, or null. With a name it posts as an ISO interval, 2026-03-03/2026-03-09.
import { DateRangeField } from 'omaris' <DateRangeField label="Period" bind:value={period} max={new Date()} /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
body- Presets and calendar together, inside the popup.
presets- The shortcut rail: a column at the start on a wide screen, a scrolling row otherwise.
preset- One shortcut.
footer- Cancel and Apply, pinned under the sheet on a phone.
root- Wrapper: label, field, supporting text.
field- The frame: border, background, height.
control- The button that opens the calendar: the whole frame, minus the clear button.
value- The formatted date, or the placeholder.
placeholder- Added to
valuewhile there is no date. icon- The calendar glyph at the end.
action- The clear button.
label- The label above the frame.
support- The helper or error line under the frame.
panel- The popover the calendar opens in, on a desktop.
sheet- The bottom sheet it opens in, on a phone.
Props
value bindableDefaults to null
DateRange | null The range, or null. Bindable.
variant Defaults to 'outline'
DateFieldVariant outline, filled or ghost — the same frames as Input.
size Defaults to 'default'
DateFieldSize sm, default or lg — the same heights as Input.
label string Label above the field.
required Defaults to false
boolean Marks the label required.
supportingText string Helper line under the field. Turns into the error text when invalid.
invalid Defaults to false
boolean Marks the field invalid and wires up aria-invalid.
placeholder Defaults to 'Select a range'
string Shown, greyed, while there is no range.
format Defaults to { dateStyle: 'medium' }
Intl.DateTimeFormatOptions Intl.DateTimeFormat options for the two ends, joined by formatRange.
presets Defaults to defaultDatePresets
DatePreset[] Shortcuts beside the calendar — Today through This year unless you pass your own, or [] for none. Each is clamped to min and max when chosen, and one that falls wholly outside them is disabled.
months Defaults to 2
1 | 2 Months side by side on a desktop. A phone always gets one.
min Date Earliest selectable day.
max Date Latest selectable day — today for a filter over data that ends now.
isDateDisabled (date: Date) => boolean Days that cannot be picked.
locale string BCP-47 tag for the field and the calendar. Defaults to <html lang>.
weekStartsOn Weekday 0 is Sunday. Defaults to the locale's first day.
clearable Defaults to true
boolean Show an × that empties the field once it has a range.
open bindableDefaults to false
boolean Whether the calendar is open. Bindable.
mobileQuery Defaults to '(max-width: 639px)'
string What counts as a phone, where the calendar opens in a bottom sheet.
labels DateRangeFieldLabels The words the field says itself — the presets group's name, Apply, Cancel.
onchange (value: DateRange | null) => void Fires when a range is committed or cleared.
class string Merged onto the root wrapper.
classes DateRangeFieldClasses Per-part Tailwind overrides — the frame's parts and the presets'.
calendarClasses CalendarClasses Per-part overrides for the calendar inside the popup.
ref bindableDefaults to null
HTMLButtonElement | null The trigger button.