An error shown by red border alone is hard to see for colorblind users (see color-not-alone) and simply doesn't exist for screen reader users. A properly built error tells the user which field failed and why, **in words**, programmatically ties the input to that message with `aria-describedby`, and marks the field's own state with `aria-invalid="true"`.
Wired up this way, a screen reader announces the field name and its error together when focus lands on it — 'Email, invalid, enter a valid email address.' Moving focus to the first invalid field, or announcing a summary through `aria-live`, makes the experience even better.
The demo handles the same invalid input two ways: left just turns the border red; right shows the error message inline alongside it.
When to use
When building form validation, always treat error state and error message as one pair, never state alone. aria-describedby is easy to forget to wire and unwire dynamically as the message appears and disappears.