Forms and validation states
Pico stretches form fields to full width, aligns labels and draws a focus ring. All you need is the pairing from the tutorial: a <label> for every field. Pico shows errors through the aria-invalid attribute — the same one screen readers use to learn that a field is wrong.
Fields and labels
Let us build a contact form for Anna's site:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<form>
<label for="name">Name</label>
<input type="text" id="name" name="name" placeholder="Oleg">
<label for="topic">Topic</label>
<select id="topic" name="topic">
<option>A new website</option>
<option>Improvements</option>
</select>
<label for="message">Message</label>
<textarea id="message" name="message" rows="3"></textarea>
<button type="submit">Send</button>
</form>Each field takes the full width, the label sits above it, and the select has an arrow on the right. The type="submit" button inside the form is full width too — that is how Pico styles submit buttons.
Helper text, switches, groups
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<form>
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-help">
<small id="email-help">I reply within two days.</small>
<label>
<input type="checkbox" role="switch" name="newsletter">
Get new blog posts
</label>
<fieldset role="group">
<input type="search" name="q" placeholder="Search the blog">
<input type="submit" value="Search">
</fieldset>
</form>A <small> right after a field becomes grey helper text under it. A checkbox with role="switch" looks like a toggle. <fieldset role="group"> puts the field and the button on one line.
Validation states
| Value | How the field and the <small> hint look |
|---|---|
aria-invalid="true" | Red border and icon, red hint |
aria-invalid="false" | Green border and check mark, green hint |
| no attribute | A normal field |
Your script sets the attribute after checking. The example checks the email when the field loses focus:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<label for="contact">Email for the reply</label>
<input type="email" id="contact" aria-describedby="contact-help" required>
<small id="contact-help">For example, anna@example.com</small>
<script>
const field = document.querySelector('#contact');
const help = document.querySelector('#contact-help');
field.addEventListener('blur', () => {
const ok = field.checkValidity();
field.setAttribute('aria-invalid', ok ? 'false' : 'true');
help.textContent = ok ? 'The address looks good' : 'Enter the full address';
console.log('Field is valid:', ok);
});
</script>Type "anna@" and click outside the field: the border turns red and the hint turns red with the error text. Complete the address and leave the field again — the border turns green. The checkValidity() method checks the field against its type and required attributes.
Common mistake
Setting aria-invalid="false" on every field right in the markup. Then an empty form is covered in green check marks in advance, as if everything were filled in correctly. Leave the attribute out until the field is checked, and add it only after input or a submit attempt.