Show a refused profile field under the input it belongs to

The onboarding baseline step flashed a toast naming one refused field
and walked on to the done screen, stamping the account as set up over
a value the server never stored. The value the person typed was gone
with the step.

A refused field now writes its reason into that field's own error
slot, marks the control invalid, and holds the step until the person
corrects it. Every refused field is named, not only the first, and the
reason comes from the validator code rather than the validator's own
prose, in all seven shipped languages.
This commit is contained in:
Marc-André Bombeck committed 2026-09-09 23:21:21 +02:00
1 parent ae20ee3d9d
commit 36cbf48286
16 files changed
+438 -26

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
import type { ReactNode } from "react";
/**
* The error slot that belongs to one input.
*
* Every form that writes the profile shows a refused field the same
* way, or the same rejection reads differently depending on where the
* person happened to be standing. This is that one way: a destructive
* sentence directly under the control, carrying an id the control
* points `aria-describedby` at, so the reason is announced with the
* field rather than only painted next to it.
*
* `role="alert"` is deliberate. The sentence appears in response to a
* save the person just asked for, and a person who has already moved
* focus past the field would otherwise never learn the value did not
* land.
*/
export function FieldError({
id,
message,
}: {
/** Conventionally `<control id>-error`. */
id: string;
message?: ReactNode;
}) {
if (!message) return null;
return (
<p id={id} role="alert" className="text-destructive text-sm">
{message}
</p>
);
}