Fylgja Form Field
A form field groups a label, a control, optional hint text, and an error message.
Validation comes from the browser itself: attributes like required, type="email",
minlength, and pattern decide when the value is invalid. Fylgja base turns the
border red, and the field-error-message class from form extend reveals the
message, all without JavaScript.
Preview
Type something that is not an email address, then move focus away.
<div class="field">
<label for="field-email">Email</label>
<input
type="email"
id="field-email"
name="email"
autocomplete="email"
required
aria-errormessage="field-email-error"
/>
<small id="field-email-error" class="field-error-message block">
Enter an email address, like me@example.com.
</small>
</div>Usage
- Wrap the label, control, hint, and error in an element with the
fieldclass. The error is scoped to that wrapper, so it can sit anywhere inside it. - Add the native validation attributes the value needs. The browser does the checking.
- Give the error message the
field-error-messageclass. It stays hidden until the control matches:user-invalidor hasaria-invalid="true". - Point
aria-errormessageat the error andaria-describedbyat the hint.
The message uses visibility, so its space stays reserved while hidden and the
layout does not jump when it appears.
Anatomy
.field: the wrapper that ties the error message to its control.<label>: names the control, linked withforandid.- Control: any
<input>,<select>, or<textarea>with validation attributes. - Hint (optional): a
<small>below the control with guidance that is always visible. Use it sparingly, for passwords and other inputs with rules the label cannot convey. .field-error-message: the message shown once the control is invalid.
Accessibility
- Every control needs a visible
<label>. A placeholder is not a label, since it disappears as soon as the user types. - Link the hint with
aria-describedbyso it is read along with the label. Do not add the error toaria-describedbyas well, since screen readers would then read the hidden message every time. aria-errormessagelinks the error to the control. Screen reader support for it is still uneven, but on submit the browser also announces its own validation message and moves focus to the first invalid field.- Write the error as a fix, such as “Enter an email address”, not just “Invalid”.
- Do not rely on the red border alone. The message text carries the meaning for users who cannot tell the colors apart.
Variants
Several rules
Combine required, minlength, maxlength, and pattern on one control. Since the
message cannot tell which rule failed, describe all of them in it.
<div class="field">
<label for="field-username">Username</label>
<input
type="text"
id="field-username"
name="username"
autocomplete="username"
required
minlength="3"
maxlength="16"
pattern="[a-z0-9_]+"
aria-describedby="field-username-hint"
aria-errormessage="field-username-error"
/>
<small id="field-username-hint" class="block">3 to 16 characters, lowercase letters, numbers or underscores.</small>
<small id="field-username-error" class="field-error-message block">
Use 3 to 16 lowercase letters, numbers or underscores.
</small>
</div>Server error
Some errors only the server knows about. Render the control with
aria-invalid="true" and the message is visible on page load.
<div class="field">
<label for="field-coupon">Coupon code</label>
<input
type="text"
id="field-coupon"
name="coupon"
value="SUMMER20"
aria-invalid="true"
aria-errormessage="field-coupon-error"
/>
<small id="field-coupon-error" class="field-error-message block">
This coupon has expired.
</small>
</div>Checkbox
A required checkbox works the same way. Wrap it in its label and keep the error
inside the .field wrapper.
<div class="field">
<label>
<input type="checkbox" name="terms" required aria-errormessage="field-terms-error" />
I agree to the terms of service
</label>
<small id="field-terms-error" class="field-error-message block">
You need to accept the terms to continue.
</small>
</div>Examples
Sign-up form
Submit the empty form and every invalid field shows its error at once. The browser
focuses the first one. Use flow on the form to space the fields.
<form action="#" class="flow">
<div class="field">
<label for="signup-name">Name</label>
<input type="text" id="signup-name" name="name" autocomplete="name" required aria-errormessage="signup-name-error" />
<small id="signup-name-error" class="field-error-message block">Enter your name.</small>
</div>
<div class="field">
<label for="signup-email">Email</label>
<input type="email" id="signup-email" name="email" autocomplete="email" required aria-errormessage="signup-email-error" />
<small id="signup-email-error" class="field-error-message block">Enter an email address, like me@example.com.</small>
</div>
<div class="field">
<label for="signup-password">Password</label>
<input
type="password"
id="signup-password"
name="password"
autocomplete="new-password"
required
minlength="8"
aria-describedby="signup-password-hint"
aria-errormessage="signup-password-error"
/>
<small id="signup-password-hint" class="block">At least 8 characters.</small>
<small id="signup-password-error" class="field-error-message block">Use at least 8 characters.</small>
</div>
<button>Create account</button>
</form>Related: Select, Search Box, Input Switch.
FAQ
Why does the error not show while I am still typing?
The error follows the :user-invalid pseudo-class, which only matches after the user has changed the field and moved on, or tried to submit the form. An empty required field is invalid from the start, and :user-invalid keeps that from being shown before the user has done anything.
Can I show a different message for each validation rule?
Not with CSS alone. The field-error-message class only knows the field is invalid, not why. Write one message that covers every rule, or read input.validity in a script and swap the message text.
How do I show an error from the server?
Render the field with aria-invalid="true" and the message shows straight away. Remove the attribute with a script once the user edits the value, or the error stays visible.
Can I hide the browser's own validation bubble?
Adding novalidate to the form removes the bubble, but also stops the browser from blocking an invalid submit, so you then need a script to do that. Keep the native behavior unless you have a reason to replace it.