125 lines
14 KiB
JSON
125 lines
14 KiB
JSON
{
|
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
|
"id": "html-forms-validation",
|
|
"title": "HTML Validation",
|
|
"description": "Learn HTML5 built-in form validation attributes",
|
|
"mode": "html",
|
|
"difficulty": "intermediate",
|
|
"lessons": [
|
|
{
|
|
"id": "required-fields",
|
|
"title": "Required Fields",
|
|
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
|
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
|
"previewHTML": "",
|
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
"sandboxCSS": "",
|
|
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
|
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
|
"previewContainer": "preview-area",
|
|
"concept": {
|
|
"explanation": "The required attribute activates the browser's Constraint Validation API, which checks field values before allowing form submission. When a user tries to submit with empty required fields, the browser automatically focuses the first invalid field, displays a localized error message (\"Please fill out this field\" in English), and blocks the submit event—no JavaScript needed. The :invalid CSS pseudo-class lets you style invalid fields (like red borders), and screen readers announce required fields as \"Email, required, edit text\" so all users know which fields are mandatory.",
|
|
"diagram": "Native Validation Flow\n\nBefore Submit:\n┌────────────────────────────┐\n│ <input required> │ ← Browser monitors\n│ [empty] │ validity state\n└────────────────────────────┘\n :invalid pseudo-class\n\nOn Submit Click:\n┌────────────────────────────┐\n│ Browser checks: │\n│ ✓ Is field filled? │\n│ ✗ Empty → INVALID │\n└────────────┬───────────────┘\n │\n ↓ Blocks submit\n┌────────────┴───────────────┐\n│ [!] Please fill out this │ ← Localized\n│ field │ browser message\n└────────────────────────────┘\n Focus moved here\n\nCSS States Available:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n:valid → Green border\n:invalid → Red border\n:required → Asterisk icon\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
},
|
|
"validations": [
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
|
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
|
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "input-constraints",
|
|
"title": "Constraints",
|
|
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
|
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
|
"previewHTML": "",
|
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
"sandboxCSS": "",
|
|
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
"previewContainer": "preview-area",
|
|
"concept": {
|
|
"explanation": "Constraint attributes define validation rules that the browser enforces automatically. The minlength attribute triggers :invalid state and blocks submission if the value is shorter than 8 characters, while maxlength physically prevents typing beyond 20 characters (a hard limit, not just validation). The pattern attribute accepts regex for complex rules like \"uppercase + lowercase + number\" without any JavaScript validation code. Placeholder text disappears when typing starts, so never use it instead of a label—use aria-describedby to link visible hint text for screen reader users.",
|
|
"diagram": "Constraint Validation Rules\n\nAttribute Behaviors:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nminlength=\"8\" Validates on submit\n Can type less, can't submit\n Error: \"Too short (min 8)\"\n\nmaxlength=\"20\" Prevents typing\n Keyboard blocked at char 20\n No error (can't violate)\n\npattern=\"...\" Regex validation\n Example: \"[A-Z][a-z]+\"\n Error: \"Match format\"\n\nmin=\"1\" max=\"5\" Number range\n For type=\"number\"\n Error: \"Out of range\"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nAccessibility Pattern:\n┌────────────────────────────┐\n│ <label for=\"pw\">Password │ ← Visible label\n│ <input id=\"pw\" │\n│ aria-describedby=\"hint\">│ ← Links to hint\n│ <small id=\"hint\"> │ ← Visible hint\n│ Must be 8-20 chars │ (not placeholder)\n└────────────────────────────┘"
|
|
},
|
|
"validations": [
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "complete-registration",
|
|
"title": "Full Form",
|
|
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
|
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
|
"previewHTML": "",
|
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
"sandboxCSS": "",
|
|
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
"previewContainer": "preview-area",
|
|
"concept": {
|
|
"explanation": "This form demonstrates layered validation and accessibility best practices working together. Semantic input types (email, password) provide baseline validation and appropriate mobile keyboards, required attributes enforce mandatory fields, minlength adds password security rules, and the visual asterisks (*) in labels give sighted users a hint—but screen readers rely on the required attribute to announce \"Email, required, edit text\". Checkboxes can be required too, forcing users to agree to terms before submission. All validation happens natively in the browser before any server-side processing, saving bandwidth and providing instant feedback.",
|
|
"diagram": "Complete Form Validation Layers\n\n┌─────────────────────────────┐\n│ <input type=\"email\" │ Layer 1: Type Validation\n│ required │ Layer 2: Required\n│ minlength=\"8\"> │ Layer 3: Constraints\n└─────────────────────────────┘\n\nValidation Execution Order:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1. Check required (not empty?)\n2. Check type (valid email?)\n3. Check constraints (minlength?)\n4. Check pattern (regex match?)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nALL must pass to submit ✓\n\nAccessibility Checklist:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✓ <label for=\"id\"> linked\n✓ Semantic input types\n✓ required attribute (not just *)\n✓ Visible error hints\n✓ Focus outline (keyboard nav)\n✓ Checkbox labeled correctly\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBenefits:\n• No JavaScript needed\n• Localized error messages\n• Mobile keyboard optimization\n• Screen reader compatible\n• Instant client-side feedback"
|
|
},
|
|
"validations": [
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
"message": "Make the full name field <kbd>required</kbd>"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
"message": "Make the email field <kbd>required</kbd>"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
"message": "Make the password field <kbd>required</kbd>"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
|
},
|
|
{
|
|
"type": "attribute_value",
|
|
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
"message": "Make the terms checkbox <kbd>required</kbd>"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|