Files
code-crispies/lessons/21-html-forms-basic.json

115 lines
11 KiB
JSON

{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
"previewContainer": "preview-area",
"concept": {
"explanation": "The label-for-id connection creates an accessible relationship that assistive technologies understand. When a screen reader encounters <label for=\"name\">, it announces \"Name, edit text\" so blind users know what to enter. Clicking the label also focuses the input, giving users a larger click target (helpful on mobile and for motor disabilities). The name attribute identifies the field when submitting data to a server, while the id attribute creates the accessibility link—both serve different but essential purposes.",
"diagram": "Form Accessibility Chain\n\n┌─────────────────────────────┐\n│ <label for=\"email\"> │ ← Click target\n│ Email: │ (focuses input)\n│ </label> │\n└────────────┬────────────────┘\n │ for=\"email\"\n ↓ connects to id\n┌────────────┴────────────────┐\n│ <input id=\"email\" │ ← Accessibility link\n│ name=\"email\"> │ (server identifier)\n└─────────────────────────────┘\n\nWhat Happens:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1. User clicks label\n2. Browser finds matching id\n3. Input receives focus\n4. Screen reader announces label\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nWhy Both Attributes?\nid → Accessibility (label link)\nname → Server data (form submit)"
},
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
},
{
"type": "attribute_value",
"value": { "selector": "label", "attr": "for", "value": null },
"message": "Add a <kbd>for</kbd> attribute to your label"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "id", "value": null },
"message": "Add an <kbd>id</kbd> attribute to your input"
}
]
},
{
"id": "input-types",
"title": "Input Types",
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
"initialCode": "<form>\n \n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
"previewContainer": "preview-area",
"concept": {
"explanation": "Input types give the browser semantic understanding of what data to expect, enabling native features without JavaScript. On mobile, type=\"email\" shows a keyboard with @ and .com shortcuts, type=\"tel\" displays a numeric dialpad, and type=\"number\" shows +/- controls. The browser also provides free validation: type=\"email\" automatically checks for @ symbols and rejects invalid formats on submit. Using semantic input types is the foundation of progressive enhancement—you get better UX, accessibility, and validation for free.",
"diagram": "Input Type Benefits\n\nMobile Keyboard Optimization:\n┌────────────────────────────┐\ntype=\"text\" → QWERTY │ ABC...\ntype=\"email\" → @ .com keys │ user@domain\ntype=\"tel\" → Dialpad │ 0-9 only\ntype=\"number\" → +/- arrows │ Steppers\ntype=\"url\" → .com / www │ https://\n└────────────────────────────┘\n\nNative Validation (Free!):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nemail → Must contain @\nurl → Must start http://\nnumber → Numeric characters only\ntel → No validation (varies)\npassword → Hides characters ••••\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nAccessibility:\nScreen readers announce input type\n\"Email, edit text\" vs just \"edit text\""
},
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
"previewContainer": "preview-area",
"concept": {
"explanation": "When clicked, a submit button triggers the form's native submit event, which validates all required fields, checks input constraints (minlength, pattern, etc.), and prevents submission if validation fails—all without JavaScript. The browser handles Enter key submission automatically when focus is in any text input. Prefer <button type=\"submit\"> over <input type=\"submit\"> because buttons can contain HTML (icons, spinners during loading), while input elements can only display plain text set via the value attribute.",
"diagram": "Form Submission Flow\n\nUser Action:\n┌────────────────────────────┐\n│ <button type=\"submit\"> │ ← Click or\n│ Sign In │ Enter key\n│ </button> │ pressed\n└────────────┬───────────────┘\n │\n ↓ Triggers\n┌────────────┴───────────────┐\n│ Browser Validation: │\n│ ✓ Check required fields │\n│ ✓ Validate input types │\n│ ✓ Test constraints │\n│ ✓ Match patterns │\n└────────────┬───────────────┘\n │\n ┌──────┴──────┐\n ✓ Valid ✗ Invalid\n │ │\n ↓ ↓\n Submit form Show error\n (POST data) (block submit)\n\nButton vs Input:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n<button> → Can contain HTML\n<input> → Plain text only\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
},
"validations": [
{
"type": "element_exists",
"value": "button[type='submit'], input[type='submit']",
"message": "Add a submit button to your form"
},
{
"type": "element_text",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
}
]
}
]
}