Date range with presets
A recipe, not a package namespace. Ranges people pick often, such as "Last 7 days", are a single-choice ToggleGroup beside a Field.DateRangePicker, and pressing one sets the picker's value. The picker still takes any other range. There is no DateRangePresets.* export.
On this page
Press a preset, then pick a range of your own in the calendar.
Presets
Each preset is a function from today to a DateRange, so its days follow the date it is pressed on. Read today with Temporal.Now.plainDateISO() when a preset is pressed or the toggle renders, not once when the page loads. Here "Last 7 days" and "Last 30 days" end today, and "This month" runs from its first day to today.
import type { DateRange } from '@tounsoo/todae';
const PRESETS: { id: string; label: string; range: (today: Temporal.PlainDate) => DateRange }[] = [
{ id: 'last-7', label: 'Last 7 days', range: (today) => ({ start: today.subtract({ days: 6 }), end: today }) },
{ id: 'last-30', label: 'Last 30 days', range: (today) => ({ start: today.subtract({ days: 29 }), end: today }) },
{ id: 'this-month', label: 'This month', range: (today) => ({ start: today.with({ day: 1 }), end: today }) },
];
function isRangeOf(preset: (typeof PRESETS)[number], range: DateRange, today: Temporal.PlainDate) {
const { start, end } = preset.range(today);
return start.equals(range.start) && end.equals(range.end);
}
// The preset whose range is the value today, or null for a custom range. Two presets can give the
// same range on some days, such as "This month" and "Last 7 days" on the 7th, so the last one
// pressed wins while the value is still its range.
function presetOf(range: DateRange | null, pressed: string | null) {
if (!range) return null;
const today = Temporal.Now.plainDateISO();
const last = PRESETS.find((each) => each.id === pressed);
if (last && isRangeOf(last, range, today)) return last.id;
return PRESETS.find((each) => isRangeOf(each, range, today))?.id ?? null;
}
The field
The range is the source of truth. The toggle's value is worked out from it, so it shows the preset whose range the value is, including one picked day by day in the calendar, and none for any other range. The id of the last preset pressed is kept beside the range only to break a tie, and counts only while the range is still that preset's. Pressing the pressed preset again gives null, which the handler ignores, so the range stays.
import { Field, Label, Stack, ToggleGroup, type DateRange } from '@tounsoo/todae';
import { useId, useState } from 'react';
// Your app's hook for the page's language; the docs site's own is useLocale.
import { useLocale } from './useLocale';
// PRESETS and presetOf are the ones above.
export function DateRangePresets() {
const id = useId();
const locale = useLocale();
const [range, setRange] = useState<DateRange | null>(null);
const [pressed, setPressed] = useState<string | null>(null);
return (
<Stack.Root gap="condensed">
<Field.Group>
<Label.Root htmlFor={id}>Report dates</Label.Root>
<Field.Root id={id}>
<Field.DateRangePicker value={range} onValueChange={setRange} locale={locale} placeholder="Pick dates" />
</Field.Root>
</Field.Group>
<ToggleGroup.Root
aria-label="Preset ranges"
value={presetOf(range, pressed)}
onValueChange={(next) => {
// Pressing the pressed preset again gives null, which keeps its range.
const preset = PRESETS.find((each) => each.id === next);
if (!preset) return;
setRange(preset.range(Temporal.Now.plainDateISO()));
setPressed(preset.id);
}}
>
{PRESETS.map((preset) => (
<ToggleGroup.Item key={preset.id} value={preset.id}>
{preset.label}
</ToggleGroup.Item>
))}
</ToggleGroup.Root>
</Stack.Root>
);
}
With a name on the Field.Root, the form sends the range as the picker sends any range, an ISO 8601 interval such as 2026-03-06/2026-03-12, so a preset adds nothing to what the server reads.
Accessible composition
Todae handles: ToggleGroup.Root is a role="group" with one tab stop and arrow keys, and each item sets aria-pressed from the group's value. Field.DateRangePicker is named by its label and described by the range it shows, so its description follows a pressed preset. The picker's calendar, value input and required check work as on its own page.
You supply: A name for the toggle group, and labels that name each range. Presets that keep to the picker's min, max and isDateDisabled: a preset sets value directly, so the calendar's checks don't apply to it.
Watch for: Reading today once when the page loads: on a page left open past midnight, the presets would then set and press yesterday's ranges. Taking the toggle's value from the last press alone: it drifts from the range when someone picks in the calendar or types a range, so the last press only breaks a tie while the range still matches it. Showing the first preset whose range matches: on the 7th "This month" is also "Last 7 days", so pressing one would light the other.
When to use
When: A range people often set to the same spans relative to today, such as the dates of a report or a log search.
When not: One date → Field.DatePicker. A range with no common spans, such as the dates of a stay → Field.DateRangePicker on its own.