Date range picker
Choose a start and end date on a calendar.
date-range-picker.tsx
"use client";
import { useState, type SyntheticEvent } from "react";
import { isWeekend, parseDate, type DateValue } from "@internationalized/date";
import { LuCalendarCheck } from "react-icons/lu";
import { Alert, Button, CalendarMonth, Select, SelectOption } from "@nexera-ui/react";
type Range = { start: DateValue; end: DateValue } | null;
const BALANCE = 14;
const FIRST_BOOKABLE = parseDate("2026-10-08");
const LAST_BOOKABLE = parseDate("2027-03-31");
const formatDay = new Intl.DateTimeFormat("en-GB", {
weekday: "short",
day: "numeric",
month: "short",
timeZone: "UTC",
});
function workingDays(range: NonNullable<Range>) {
let count = 0;
for (let day = range.start; day.compare(range.end) <= 0; day = day.add({ days: 1 })) {
if (!isWeekend(day, "en-GB")) count += 1;
}
return count;
}
/** Date range picker: choose the first and last day of a leave request on a calendar, with the working days counted. */
export default function DateRangePicker() {
const [range, setRange] = useState<Range>({
start: parseDate("2026-10-19"),
end: parseDate("2026-10-23"),
});
const [sent, setSent] = useState(false);
const days = range ? workingDays(range) : 0;
const overBalance = days > BALANCE;
const onSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
if (range && !overBalance) setSent(true); // replace with your leave request
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<form
onSubmit={onSubmit}
className="mx-auto grid max-w-4xl gap-6 rounded-xl border border-default bg-surface p-5 shadow-sm @3xl:grid-cols-[minmax(0,1fr)_minmax(0,18rem)] @3xl:p-8"
>
<div className="grid content-start gap-4">
<header className="grid gap-1">
<h2 className="text-heading-h3 text-primary">Request time off</h2>
<p className="text-body-default text-secondary">
Pick your first and last day away. Weekends are not counted.
</p>
</header>
<CalendarMonth
selectionMode="range"
aria-label="Leave dates"
value={range}
onChange={(value) => {
setRange(value);
setSent(false);
}}
minValue={FIRST_BOOKABLE}
maxValue={LAST_BOOKABLE}
isInvalid={overBalance}
errorMessage={`You have ${BALANCE} days left. Choose a shorter range.`}
/>
</div>
<aside className="grid content-start gap-4 rounded-lg bg-sunken p-4">
<h3 className="text-heading-h4 text-primary">Summary</h3>
<Select label="Leave type" name="type" defaultValue="annual">
<SelectOption id="annual" label="Annual leave" />
<SelectOption id="unpaid" label="Unpaid leave" />
<SelectOption id="study" label="Study leave" />
</Select>
<dl className="grid gap-3">
<div className="grid gap-0.5">
<dt className="text-body-small text-secondary">From</dt>
<dd className="text-body-strong text-primary">
{range ? formatDay.format(range.start.toDate("UTC")) : "Not chosen"}
</dd>
</div>
<div className="grid gap-0.5">
<dt className="text-body-small text-secondary">To</dt>
<dd className="text-body-strong text-primary">
{range ? formatDay.format(range.end.toDate("UTC")) : "Not chosen"}
</dd>
</div>
<div className="grid gap-0.5">
<dt className="text-body-small text-secondary">Working days</dt>
<dd className="text-body-strong text-primary">
{days} of {BALANCE} left
</dd>
</div>
</dl>
{sent ? (
<Alert tone="success" title="Request sent to Ahmed Raza" politeness="polite" />
) : null}
<div className="flex flex-wrap gap-2">
<Button
type="submit"
size="md"
leadingIcon={<LuCalendarCheck />}
isDisabled={!range || overBalance}
>
Send request
</Button>
<Button
variant="ghost"
size="md"
onPress={() => {
setRange(null);
}}
isDisabled={!range}
>
Clear dates
</Button>
</div>
</aside>
</form>
</div>
);
}
More forms and data entry blocks
- Contact formName, email, topic and message with validation.
- Add a personCreate a record in a modal.
- Multi-step formLong forms split into steps you can go back to.
- Address and phoneCountry, address lookup and phone with dialling code.
- File uploadDrag files in and watch each one upload.
- Search with suggestionsType ahead and pick from matching results.
- Terms and consentScrollable terms with a required checkbox.
- Choose an optionPick one option from a short list in a dialog.
- Pick a colourBrand or label colour from swatches or a picker.