الإكمال التلقائي: التحقق من الصحة
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
The Autocomplete 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, its wording 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 { Autocomplete, type OptionValue } from "slotsmith/autocomplete";
import { countries, type Country } from "../shared/options";
export default function Validation() {
const [country, setCountry] = useState<OptionValue | 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 && country === null;
function submit(event: FormEvent) {
event.preventDefault();
setSubmitted(true);
setSent(country !== null);
// `id` lands on the combobox, so focus can go straight back to the field that failed.
if (country === 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 }}>
Shipping country
</span>
<Autocomplete<Country>
id={fieldId}
options={countries}
getOptionValue={(entry) => entry.code}
getOptionLabel={(entry) => entry.name}
value={country}
onChange={(next: OptionValue | null) => {
setCountry(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)" }}>
Pick a country to continue.
</p>
)}
</div>
<div>
<button type="submit" style={{ background: "transparent" }}>
Continue
</button>
</div>
<p role="status" style={{ margin: 0 }}>
{sent ? `Sent: ${country}` : ""}
</p>
</form>
);
}export type Country = { code: string; name: string };
/** Sorted by name, as a picker shows them. */
export const countries: Country[] = [
{ code: "ar", name: "Argentina" },
{ code: "au", name: "Australia" },
{ code: "br", name: "Brazil" },
{ code: "ca", name: "Canada" },
{ code: "eg", name: "Egypt" },
{ code: "fr", name: "France" },
{ code: "de", name: "Germany" },
{ code: "in", name: "India" },
{ code: "jp", name: "Japan" },
{ code: "jo", name: "Jordan" },
{ code: "ke", name: "Kenya" },
{ code: "mx", name: "Mexico" },
{ code: "ma", name: "Morocco" },
{ code: "nl", name: "Netherlands" },
{ code: "no", name: "Norway" },
{ code: "pt", name: "Portugal" },
{ code: "sa", name: "Saudi Arabia" },
{ code: "za", name: "South Africa" },
{ code: "es", name: "Spain" },
{ code: "se", name: "Sweden" },
{ code: "tr", name: "Türkiye" },
{ code: "ae", name: "United Arab Emirates" },
{ code: "gb", name: "United Kingdom" },
{ code: "us", name: "United States" },
];
/** `id` and `label` are the names the autocomplete reads by default, so topics need no getters. */
export type Topic = { id: string; label: string; archived?: boolean };
export const topics: Topic[] = [
{ id: "accessibility", label: "Accessibility" },
{ id: "animation", label: "Animation" },
{ id: "caching", label: "Caching" },
{ id: "design-systems", label: "Design systems" },
{ id: "forms", label: "Forms" },
{ id: "i18n", label: "Internationalisation" },
{ id: "performance", label: "Performance" },
{ id: "rtl", label: "Right-to-left" },
{ id: "server-components", label: "Server components" },
{ id: "state", label: "State" },
{ id: "testing", label: "Testing" },
{ id: "typescript", label: "TypeScript" },
{ id: "legacy", label: "Legacy browsers", archived: true },
];
/** A country with the region it is listed under; a `disabled` one stays in the list but cannot be picked. */
export type RegionCountry = Country & { region: string; disabled?: boolean };
/** A short list grouped by region. Groups show in the order of their first option, so the regions come in this order. */
export const countriesByRegion: RegionCountry[] = [
{ code: "de", name: "Germany", region: "Europe" },
{ code: "fr", name: "France", region: "Europe" },
{ code: "es", name: "Spain", region: "Europe" },
{ code: "ge", name: "Georgia", region: "Europe" },
{ code: "dz", name: "Algeria", region: "Africa" },
{ code: "eg", name: "Egypt", region: "Africa" },
{ code: "ng", name: "Nigeria", region: "Africa" },
{ code: "ne", name: "Niger", region: "Africa", disabled: true },
{ code: "jp", name: "Japan", region: "Asia" },
{ code: "sg", name: "Singapore", region: "Asia" },
{ code: "sa", name: "Saudi Arabia", region: "Asia" },
{ code: "sy", name: "Syria", region: "Asia" },
];Press Continue with nothing picked: the field turns red, the message appears and focus returns to the picker. Pick a country and the error clears.
What invalid does
invalid (default false) sets:
aria-invalid="true"on the combobox: the trigger, or the input withsearchIn="trigger". Not on the popup’s search box, whose text is only a filter, not the value.data-invalidon the root and on the trigger, so a skin or your CSS can style the error state.- In the default stylesheet, the trigger’s border and focus ring in the
--ss-dangertoken (#dc2626light,#f87171dark). Set--ss-danger, or--sac-dangerfor the autocomplete alone; see Theming.
The component does not validate and does not render a message. When to check, and what to say, is up to your form code or form library.
Tying the message to the field
Give the message an id and point the combobox at it. The naming and validation props you pass to Autocomplete go to the combobox, not to the wrapper.
aria-describedbyis read by every major screen reader, after the field’s name. The demo uses it, and sets it only while the message shows.aria-errormessageis meant for exactly this, but screen reader support is uneven. If you use it, keeparia-describedbyas well.aria-required="true"tells screen readers the field must be filled.
The id you pass also lands on the combobox, so after a failed submit document.getElementById(id).focus() moves focus straight to the field, as the demo does.
The label and the message
The demo colours its label and message with var(--ss-danger, #dc2626), so they match the trigger’s border in both themes. Colour is not the only signal: the message text says what is wrong, and aria-invalid tells assistive technology.
The ready-made adapters show each library’s own error style on the trigger: shadcn/ui’s destructive border and ring, MUI’s error colour, Chakra UI’s _invalid style, Ant Design’s status="error" and Radix Themes’ red accent. The date picker takes the same prop; see its validation guide.