Validation / Constraint props
Constraints are props
The rule is declared where the field is declared. No schema needed for the ordinary cases, and the message is generated from the rule.
A few per component
COMPONENTPROPREJECTS
PhoneInputmobileOnlyLandline numbers, by prefix
PhoneInputcountriesAnything outside the allowlist
CurrencyInputpositive / nonZeroZero and negative amounts
CurrencyInputwholeUnitsOnlyFractional currency
CardInputbrandsCards outside the accepted set, by name
CardInputnotExpiredAn expiry in the past
DurationInputmultipleOfAnything off the grid — "multiple of 15 minutes"
CronInputminIntervalSchedules that fire too often
ColorInputminContrastUnreadable colours — as a warning, not an error
IpInputmaxPrefix0.0.0.0/0 and other over-broad ranges
IpInputnoPrivateRFC 1918 addresses in a public allowlist
MentionInputmaxMentionsNotifying half the company
Every componentvalidateWhatever you say — return true or a message
The message comes from the rule
Messages are generated, not hardcoded per component, and every error element carries a data-rule attribute naming the rule that failed. Tests assert on maxPrefix rather than on a sentence, so rewording an error does not break a suite — which is what causes those tests to be deleted rather than fixed.
Where a bound is a formatted thing, the message formats it too: multipleOf="15m" produces “must be a multiple of 15 minutes”, not “must be a multiple of 900”.