Date picker: Validation
The date picker takes invalid for a value that failed your check. It marks the combobox with aria-invalid="true" and draws the trigger’s border in the danger colour. The error message and the label’s colour are your app’s markup, tied to the combobox with aria-describedby.
import { useId, useState, type FormEvent } from "react";
import { DatePicker, type ISODate } from "slotsmith/date-picker";
export default function Validation() {
const [start, setStart] = useState<ISODate | null>(null);
const [submitted, setSubmitted] = useState(false);
const [sent, setSent] = useState(false);
const fieldId = useId();
const labelId = useId();
const errorId = useId();
// Check once the form has been submitted, then again on every change.
const invalid = submitted && start === null;
function submit(event: FormEvent) {
event.preventDefault();
setSubmitted(true);
setSent(start !== null);
// `id` lands on the combobox, so focus can go straight back to the field that failed.
if (start === null) document.getElementById(fieldId)?.focus();
}
return (
<form onSubmit={submit} noValidate style={{ display: "grid", gap: "1rem", maxWidth: 360 }}>
<div style={{ display: "grid", gap: "0.375rem" }}>
{/* The label and the message are the app's markup: the component only marks itself invalid. */}
<span id={labelId} style={{ fontWeight: 500, color: invalid ? "var(--ss-danger, #dc2626)" : undefined }}>
Start date
</span>
<DatePicker
id={fieldId}
value={start}
onChange={(next) => {
setStart(next);
setSent(false);
}}
// aria-invalid on the combobox, data-invalid on the root and trigger.
invalid={invalid}
aria-labelledby={labelId}
aria-required="true"
aria-describedby={invalid ? errorId : undefined}
/>
{invalid && (
<p id={errorId} style={{ margin: 0, color: "var(--ss-danger, #dc2626)" }}>
The start date is required.
</p>
)}
</div>
<div>
<button type="submit" style={{ background: "transparent" }}>
Continue
</button>
</div>
<p role="status" style={{ margin: 0 }}>
{sent ? `Sent: ${start}` : ""}
</p>
</form>
);
}Press Continue with no date picked: the field turns red, the message appears and focus returns to the picker. Pick a day and the error clears.
What invalid does
invalid (default false) sets:
aria-invalid="true"on the trigger, the element withrole="combobox".data-invalidon the root and on the trigger, for your own styles.- In the default stylesheet, the trigger’s border and focus ring in the danger colour, read from
--sdp-danger, then the shared--ss-danger(#dc2626light,#f87171dark). See Theming.
In a plain HTML form, give the picker a name: its root holds a hidden input of that name per picked day, written as valueFormat says, so the form submits the value rather than the text in the field. A range submits its start, then its end.
The picker does not validate and does not render a message. A rule such as “the end must be after the start” belongs in your form code; the picker shows the result.
Tying the message to the field
The naming and validation props you pass to DatePicker go to the combobox. Give the message an id and pass it as aria-describedby, which every major screen reader reads after the field’s name. aria-errormessage is meant for this too, but support is uneven, so keep aria-describedby beside it. Add aria-required="true" for a required date.
The id you pass also lands on the combobox, so after a failed submit document.getElementById(id).focus() moves focus to the field, as the demo does.
The demo colours its label and message with var(--ss-danger, #dc2626), to match the border in both themes; the message text carries the meaning, so colour is not the only signal. The ready-made adapters show each library’s own error style on the trigger. The autocomplete’s validation guide covers the same pattern for a picker.