Date of birth input
A date of birth as day, month and year segments, each typed or chosen from a grid.
On this page
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"A crew member's date of birth, required: press a segment for its grid (days the month lacks are disabled, months " +
"by name, years back 120), or type the digits and watch the focus move on; paste 02/03/1984, or try 31 February " +
"or a day after 11 October 2026 to see the field refuse it.";
export default function Showcase(): ReactElement {
const [born, setBorn]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
return (
<div style={{ display: "grid", justifyItems: "start", gap: "var(--space-sm)" }}>
<DateOfBirthInput
label="Date of birth"
required
today="2026-10-11"
value={born}
onValueChange={setBorn}
hint="As on the passport."
/>
<output style={{ fontSize: 12, color: "var(--text-dim)" }}>
The page receives: {born === "" ? "no date yet" : born}
</output>
</div>
);
}
A date of birth input is one outlined field with three segments, DD, MM and YYYY, in the order every one of the
crew's languages writes a date. Each segment is a button with a chevron, named by its part and value
(Month: March). Pressing it opens a panel under the field, titled with the part, holding a grid of the choices it
can take: the days in six columns, with the days the chosen month and year lack disabled; the months by name; the
years from the current one back 120 years, or back to min. Arrow keys move by a cell or a row, Home and End go to
the ends, Enter or Space chooses and moves to the next segment, and Escape closes the panel. The chevron points up
while its panel is open.
Digits typed into a focused segment fill it and move on: a day of 4 or more and a month of 2 or more need no second
digit. Two digits make a year already, the latest ending in them that minAge allows: 93 is 1993, and 20 is 2020, or
1920 with minAge 18; typing on to four digits makes any year. Letters on the month type its name, and in an open
grid, typing a name or a number jumps to the matching choice. Backspace clears a segment, and on an empty one the one before it. A pasted date, in ISO 8601 or as day,
month and year with any separator, fills all three.
The page receives the date in ISO 8601, or "" until it is whole and valid. A day the calendar does not have, such
as 31 February, and a date after the current day are refused as soon as the date is whole; a missing part is named
once the crew leave the field.
Age bounds
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
import { useState, type Dispatch, type SetStateAction } from "react";
export const description: string =
"A crew member at least 16 and at most 65: the bounds take no date outside the ages, and say the age, not the date.";
export default function Age(): ReactElement {
const [value, setValue]: [string, Dispatch<SetStateAction<string>>] = useState<string>("1995-06-14");
return (
<DateOfBirthInput
label="Date of birth"
required
today="2026-10-11"
minAge={16}
maxAge={65}
value={value}
onValueChange={setValue}
/>
);
}
minAge and maxAge bound the date by the crew member's age rather than by dates on a calendar: minAge 16 takes
no birth date later than 16 years before the current day, maxAge 65 none earlier than 65 years. The year grid runs within
the bounds, and the message says the age — "at least 16", "at most 65" — not a date to count from.
Examples
Empty
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string =
"Three empty segments, DD, MM and YYYY, in the order every one of the crew's languages writes a date.";
export default function Empty(): ReactElement {
return <DateOfBirthInput label="Date of birth" today="2026-10-11" />;
}
Filled
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string = "A held date: each segment is named with its value, the month by name.";
export default function Filled(): ReactElement {
return <DateOfBirthInput label="Date of birth" today="2026-10-11" defaultValue="1979-05-14" />;
}
Written forms it reads
Code
import { Button } from "@bosunkit/components/button";
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import { useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";
export const description: string =
"Press a form to paste it into the day segment as the clipboard would: ISO 8601, or day, month and year with any " +
"separator.";
const FORMS: readonly string[] = ["1984-03-02", "02/03/1984", "2.3.1984", "2 3 1984"];
export default function WrittenForms(): ReactElement {
const [born, setBorn]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
const field: RefObject<HTMLDivElement | null> = useRef<HTMLDivElement | null>(null);
function paste(text: string): void {
const data: DataTransfer = new DataTransfer();
data.setData("text/plain", text);
field.current
?.querySelector("button")
?.dispatchEvent(new ClipboardEvent("paste", { clipboardData: data, bubbles: true }));
}
return (
<div style={{ width: "100%", maxWidth: 360, display: "grid", gap: "var(--space-sm)" }}>
<DateOfBirthInput ref={field} label="Date of birth" today="2026-10-11" value={born} onValueChange={setBorn} />
<div style={{ display: "flex", flexWrap: "wrap", gap: "var(--space-xs)" }}>
{FORMS.map((form: string): ReactElement => (
<Button
key={form}
size="sm"
variant="ghost"
onClick={(): void => {
setBorn("");
paste(form);
}}
>
{form}
</Button>
))}
</div>
</div>
);
}
Invalid
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string =
"A whole date the field cannot take is refused at once: here a birth date after the current day. Type 31 02 1984 " +
"to see a day the month lacks refused with the month's length.";
export default function Invalid(): ReactElement {
return <DateOfBirthInput label="Date of birth" today="2026-10-11" defaultValue="2027-01-05" />;
}
Read-only
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string = "A read-only date opens no grid and takes no digits.";
export default function ReadOnly(): ReactElement {
return <DateOfBirthInput label="Skipper's date of birth" today="2026-10-11" defaultValue="1979-05-14" readOnly />;
}
Disabled
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string = "A disabled date of birth.";
export default function Disabled(): ReactElement {
return <DateOfBirthInput label="Date of birth" today="2026-10-11" defaultValue="1988-11-02" disabled />;
}
Inside a form field
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import { FormField } from "@bosunkit/components/form-field";
import type { ReactElement } from "react";
export const description: string = "Inside a form field, the group named by `aria-label`.";
export default function InFormField(): ReactElement {
return (
<FormField label="Guest's date of birth">
<DateOfBirthInput aria-label="Guest's date of birth" today="2026-10-11" />
</FormField>
);
}
Paste
The paste button, at the end of the field, fills all three segments. It reads:
- ISO 8601, with or without a time after it;
- day, month and year in figures, with the day first when the figures use dots (14.07.2002);
- figures with slashes, hyphens or spaces when their order is plain: a first number past 12 is the day, and a second past 12 makes it a month-first date (07/14/2002);
- a date with its month in words in any of the six languages, in either order, with ordinals and three-letter abbreviations (14 July 2002, 14. Juli 2002, 1er mars 2002, July 14, 2002).
Two different numbers of 12 or less, such as 07/05/2002, could be either date, and the field asks for the day first with the month in words. The paste button shows only when the clipboard holds something the field can take, named by what it would paste (Paste 14 July 2002). To know that, the field reads the clipboard when it takes focus and when the window comes back, and only where the browser lets the page read it without asking: the clipboard-read permission is granted. Where the browser would ask first, or does not know the permission (Firefox, Safari), the field reads nothing and shows no button, so no prompt appears on focus; Ctrl+V and a long press on a phone paste through the same reader. A read-only or disabled field shows no paste button.
- 14 July 2002
- 14. Juli 2002
- 14 de julio de 2002
- 07/14/2002
- 14.07.2002
- 07/05/2002
Code
import { DateOfBirthInput } from "@bosunkit/components/date-of-birth-input";
import type { ReactElement } from "react";
export const description: string =
"Copy one of these lines, then focus the field: where the browser lets the page read the clipboard, a paste button named by the date appears; Ctrl+V on a segment works everywhere. Months in words in any of the six languages and figures in a plain order fill the date; the last line, which could be 7 May or 5 July, is refused with a request for the day first.";
const SAMPLES: readonly string[] = [
"14 July 2002",
"14. Juli 2002",
"14 de julio de 2002",
"07/14/2002",
"14.07.2002",
"07/05/2002",
];
export default function Paste(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: 520, display: "grid", gap: "var(--space-md)" }}>
<DateOfBirthInput label="Date of birth" today="2026-10-11" />
<ul style={{ margin: 0, paddingLeft: "var(--space-lg)", fontFamily: "var(--font-mono)", fontSize: 12 }}>
{SAMPLES.map((sample: string): ReactElement => (
<li key={sample} style={{ userSelect: "all", overflowWrap: "anywhere" }}>
{sample}
</li>
))}
</ul>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | undefined | – | The field's name on the outline, translated by the caller; omit it inside a FormField. |
| value | string | undefined | – | The date as ISO 8601 (1984-03-02), "" while the field does not hold a whole, valid one. |
| defaultValue | string | undefined | – | The date it starts with when the caller does not hold value. |
| onValueChange | ((value: string) => void) | undefined | – | Called with the ISO date once day, month and year make a valid one, and with "" when they no longer do. |
| minAge | number | undefined | – | The youngest the person may be, in whole years: no birth date later than that many years before today. |
| maxAge | number | undefined | – | The oldest the person may be, in whole years: no birth date earlier than that many years before today. |
| today | string | undefined | – | Today's date (ISO 8601): no birth date after it is taken. The browser's local date when unset. |
| min | string | undefined | – | The earliest date taken (ISO 8601); 120 years before today when unset. |
| hint | string | undefined | – | Text under the field, translated by the caller. |
| error | string | undefined | – | The caller's own error, shown in place of the field's. |
| required | boolean | undefined | – | Whether the field must hold a date. |
| disabled | boolean | undefined | – | Whether the field takes no input. |
| readOnly | boolean | undefined | – | Whether the field shows its date without letting the crew change it. |
| id | string | undefined | – | The day segment's id, for a Label's htmlFor. |
| name | string | undefined | – | The form field name; the form receives the ISO date. |
| aria-label | string | undefined | – | Names the field when there is no label. |
| className | string | undefined | – | Classes added to the field's root. |
| testId | string | undefined | – | data-testid on the field. |
| ref | Ref<HTMLDivElement> | undefined | – | The field's root element. |
| motion | MotionProp | undefined | – | false opens the choice panels at once and keeps the error line still; true also moves the error line. |
Related
- Date picker: a date chosen from a month calendar.
- Crew picker: a crew member's details, date of birth included.
- Outlined field: the box every maritime input draws in.