Label, helper text and error message only exist inside TextField. Any other
control that needs to explain a validation failure has no way to do it, and no
similar code to copy — you have to rebuild the field wrapper by hand.
This looks like the extraction fitting the apps' current usage: TextField was
the only control the apps validated with a message, so the field concerns were
built into it rather than beside it.
Where things stand
| Component |
invalid styling |
helper text |
error message |
| TextField |
yes (input recipe) |
yes |
yes (RAC FieldError) |
| Input (bare) |
yes |
– |
– |
| NativeSelect |
yes (input recipe) |
– |
– |
| NumberField |
yes (renders input() inside) |
– |
– |
| Select / ComboBox |
yes, once #38 lands |
– |
– |
| Checkbox |
no |
– |
– |
| Radio / RadioGroup |
no |
– |
– |
| Switch |
no |
– |
– |
| Slider |
n/a — RAC has no validation |
– |
– |
errorMessage and helperText appear only in TextField.recipe.ts and
TextField.tsx. Nothing else has the slots or the props.
Two consequences worth separating:
- No control other than TextField can carry an error message. A Select can
go red, but nothing can say why. Colour as the only signal fails WCAG 1.4.1
and 3.3.1. An app cannot work around it: children go to the listbox, and
RAC's FieldError has to be rendered inside the RAC field component to be
wired up with aria-describedby.
- Checkbox, Radio/RadioGroup and Switch have no invalid styling at all. RAC
sets data-invalid on all three; none of our recipes read it. A required
checkbox in a submitted form changes nothing visually.
Options
A — per-component errorMessage / helperText props, mirroring TextField.
Small and predictable, and each component keeps control of where the message
sits. But it is the same four slots re-implemented per component (Select,
NumberField, NativeSelect, Checkbox, Radio, Switch), and every new control pays
the cost again.
B — a Field wrapper owning label, required indicator, helper text and error
message, with TextField reduced to Field + Input. One implementation, and
it matches what Chakra's FormControl did, so app code ports across. The catch
is that RAC's FieldError and the aria-describedby wiring live inside each
RAC field component, so the wrapper cannot render the message from outside —
each control still has to render a slot the wrapper fills, probably via context.
B is the better shape if we expect more than one or two more controls to need
messages. A is the cheaper way to unblock a specific app.
Either way, the missing invalid styling on Checkbox/Radio/Switch is independent
and can be fixed first: those recipes just need to read data-invalid.
Context
#38 fixes the related case where the invalid rule on the Select trigger could
never match, because RAC puts data-invalid on the field root rather than the
control. Worth reading that PR's comments before starting either option here —
the same "which element does RAC mark" question comes up for every control.
Drafted by Claude in a Claude Code session, from a survey of the library's
recipes and components. The findings were checked against the source in the
session; the wording has not had a separate review.
Label, helper text and error message only exist inside
TextField. Any othercontrol that needs to explain a validation failure has no way to do it, and no
similar code to copy — you have to rebuild the field wrapper by hand.
This looks like the extraction fitting the apps' current usage: TextField was
the only control the apps validated with a message, so the field concerns were
built into it rather than beside it.
Where things stand
FieldError)input()inside)errorMessageandhelperTextappear only inTextField.recipe.tsandTextField.tsx. Nothing else has the slots or the props.Two consequences worth separating:
go red, but nothing can say why. Colour as the only signal fails WCAG 1.4.1
and 3.3.1. An app cannot work around it:
childrengo to the listbox, andRAC's
FieldErrorhas to be rendered inside the RAC field component to bewired up with
aria-describedby.sets
data-invalidon all three; none of our recipes read it. A requiredcheckbox in a submitted form changes nothing visually.
Options
A — per-component
errorMessage/helperTextprops, mirroring TextField.Small and predictable, and each component keeps control of where the message
sits. But it is the same four slots re-implemented per component (Select,
NumberField, NativeSelect, Checkbox, Radio, Switch), and every new control pays
the cost again.
B — a
Fieldwrapper owning label, required indicator, helper text and errormessage, with TextField reduced to
Field+Input. One implementation, andit matches what Chakra's
FormControldid, so app code ports across. The catchis that RAC's
FieldErrorand thearia-describedbywiring live inside eachRAC field component, so the wrapper cannot render the message from outside —
each control still has to render a slot the wrapper fills, probably via context.
B is the better shape if we expect more than one or two more controls to need
messages. A is the cheaper way to unblock a specific app.
Either way, the missing invalid styling on Checkbox/Radio/Switch is independent
and can be fixed first: those recipes just need to read
data-invalid.Context
#38 fixes the related case where the invalid rule on the Select trigger could
never match, because RAC puts
data-invalidon the field root rather than thecontrol. Worth reading that PR's comments before starting either option here —
the same "which element does RAC mark" question comes up for every control.
Drafted by Claude in a Claude Code session, from a survey of the library's
recipes and components. The findings were checked against the source in the
session; the wording has not had a separate review.