$npx -y skills add dembrandt/dembrandt-skills --skill form-designForms have three layers of guidance: helper text below the input explains what to enter, placeholder shows the expected format, and validation confirms correctness. Real-time validation for complex inputs. Submit enables only when the form is valid. Use when designing or reviewin
| 1 | # Form Design |
| 2 | |
| 3 | Forms are where users give the product data. Every unnecessary obstacle between the user and a completed form is a failure. The design goal is to make correct input easy and incorrect input obvious — before the user submits. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## The Three Guidance Layers |
| 8 | |
| 9 | Each layer serves a distinct purpose. Do not collapse them. |
| 10 | |
| 11 | ### Layer 1 — Helper Text |
| 12 | Explains *what* to enter. Appears below the input, always visible, in small secondary text. |
| 13 | |
| 14 | ``` |
| 15 | Email address |
| 16 | [ ] |
| 17 | Use the email you signed up with. |
| 18 | ``` |
| 19 | |
| 20 | - Write in plain language from the user's perspective |
| 21 | - Keep it to one sentence — if you need more, the field is too complex or misnamed |
| 22 | - Do not repeat the label ("Enter your email" below a label that says "Email" is redundant) |
| 23 | - Helper text is not a replacement for a label — the label is still required |
| 24 | |
| 25 | ### Layer 2 — Placeholder |
| 26 | Shows the *format* or an example value. Appears inside the input, disappears on typing. |
| 27 | |
| 28 | ``` |
| 29 | [jane@example.com ] |
| 30 | ``` |
| 31 | |
| 32 | - Use a realistic example, not a description: `+358 40 123 4567` not `Enter phone number` |
| 33 | - Never use placeholder as a label — it disappears and leaves the user without context |
| 34 | - Keep it grey (`--color-text-secondary`) and lighter than actual input text |
| 35 | - Optional — not every field needs a placeholder |
| 36 | |
| 37 | ### Layer 3 — Validation |
| 38 | Confirms whether the input is correct. The most important layer. |
| 39 | |
| 40 | ``` |
| 41 | Email address |
| 42 | [jane@ ] ← invalid |
| 43 | ✗ Enter a valid email address. |
| 44 | ``` |
| 45 | |
| 46 | **Validation timing:** |
| 47 | - **On blur** (leaving the field): default for most fields — validates once the user has finished |
| 48 | - **Real-time** (on input): use when the format is complex or the error is likely — password strength, IBAN, VAT number, URL, regex-heavy fields |
| 49 | - **On submit**: catches anything missed, scrolls to the first error |
| 50 | |
| 51 | Real-time validation must be forgiving at the start — do not show an error the instant the user starts typing. Show it after a short debounce (300–500ms) or after the first character that makes the input definitively wrong. |
| 52 | |
| 53 | --- |
| 54 | |
| 55 | ## Submit Button State |
| 56 | |
| 57 | The submit button enables when the form is valid. This is one of the clearest affordance signals in form design — the user sees the goal and knows when they have reached it. |
| 58 | |
| 59 | ``` |
| 60 | [Submit] ← disabled, low contrast, cursor: not-allowed |
| 61 | (fields incomplete or invalid) |
| 62 | |
| 63 | [Submit] ← enabled, full colour, cursor: pointer |
| 64 | (all required fields valid) |
| 65 | ``` |
| 66 | |
| 67 | **Implementation:** |
| 68 | ```html |
| 69 | <button type="submit" disabled={!isFormValid}>Submit</button> |
| 70 | ``` |
| 71 | |
| 72 | For long or complex forms where real-time validation is not practical, do not disable the submit — validate on submit and scroll to errors instead. Disabled submit on a long form frustrates users who cannot tell what is missing. |
| 73 | |
| 74 | **Loading state on submit:** Replace label with spinner, disable the button. Prevent double-submission. |
| 75 | |
| 76 | --- |
| 77 | |
| 78 | ## Field Anatomy |
| 79 | |
| 80 | ``` |
| 81 | [Label] [Optional badge if optional] |
| 82 | [Input field ] |
| 83 | [Helper text — what to enter, format, constraints ] |
| 84 | [Error message — appears below helper text on validation fail ] |
| 85 | ``` |
| 86 | |
| 87 | ```html |
| 88 | <div class="field"> |
| 89 | <label for="vat">VAT number <span class="optional">Optional</span></label> |
| 90 | <input |
| 91 | id="vat" |
| 92 | type="text" |
| 93 | placeholder="FI12345678" |
| 94 | aria-describedby="vat-helper vat-error" |
| 95 | aria-invalid="true" |
| 96 | > |
| 97 | <p id="vat-helper" class="helper-text">Finnish VAT numbers start with FI followed by 8 digits.</p> |
| 98 | <p id="vat-error" class="error-text" role="alert">Enter a valid Finnish VAT number (e.g. FI12345678).</p> |
| 99 | </div> |
| 100 | ``` |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## Required vs Optional |
| 105 | |
| 106 | Mark the minority. If most field |