{ "$schema": "../schemas/code-crispies-module-schema.json", "id": "html-data-attributes", "title": "Data Attrs", "description": "Store custom data on HTML elements with data-* attributes", "mode": "html", "difficulty": "beginner", "lessons": [ { "id": "data-attributes-intro", "title": "Adding Data Attributes", "description": "Custom data-* attributes let you store extra information on any HTML element. The attribute name starts with data- followed by your custom name.

Examples: data-id, data-category, data-price", "task": "Create two product cards using <article> elements. Each should have:
1. A data-category attribute (e.g., 'electronics' or 'clothing')
2. A data-price attribute with a number
3. An <h2> with the product name", "previewHTML": "", "previewBaseCSS": "body { font-family: system-ui; padding: 20px; display: grid; gap: 15px; } article { padding: 20px; border-radius: 10px; background: #f5f5f5; border-left: 4px solid #ccc; } article[data-category='electronics'] { border-left-color: #2196f3; background: #e3f2fd; } article[data-category='clothing'] { border-left-color: #e91e63; background: #fce4ec; } h2 { margin: 0 0 10px 0; } article::after { content: '€' attr(data-price); display: block; font-weight: bold; color: #4caf50; margin-top: 10px; }", "sandboxCSS": "", "initialCode": "", "solution": "
\n

Laptop

\n

A powerful laptop for work and play.

\n
\n\n
\n

T-Shirt

\n

A comfortable cotton t-shirt.

\n
", "previewContainer": "preview-area", "concept": { "explanation": "Data attributes provide a standards-compliant way to embed custom metadata directly in HTML without inventing non-standard attributes or abusing existing ones like class or id. The browser ignores data-* attributes for rendering but preserves them in the DOM, making them accessible to JavaScript and CSS. Unlike storing data in JavaScript variables or hidden divs, data attributes keep information with the element it describes, improving maintainability. JavaScript can read them via element.dataset (data-category becomes dataset.category), and CSS can select or display them using attribute selectors and attr(). This pattern separates presentation (CSS classes) from data (data attributes), following the principle of separation of concerns.", "diagram": "Data Attribute Access\n\nHTML:\n
\n\nJavaScript Access:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nconst el = document.querySelector('article');\nel.dataset.category → \"electronics\"\nel.dataset.price → \"299\"\nel.dataset.inStock → \"true\"\n\n(Hyphens become camelCase)\ndata-in-stock → dataset.inStock\n\nCSS Access:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n/* Select by attribute */\narticle[data-category=\"electronics\"] {\n border-color: blue;\n}\n\n/* Display value */\narticle::after {\n content: \"€\" attr(data-price);\n}\n\nvs Other Approaches:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✗ class=\"electronics price-299\"\n → Mixes presentation & data\n✗
\n → Abuses id attribute\n✓ data-category=\"electronics\"\n → Semantic & maintainable" }, "validations": [ { "type": "element_count", "value": { "selector": "article[data-category]", "min": 2 }, "message": "Create at least 2 articles with data-category attributes" }, { "type": "element_count", "value": { "selector": "article[data-price]", "min": 2 }, "message": "Add data-price attribute to your articles" }, { "type": "element_count", "value": { "selector": "article h2", "min": 2 }, "message": "Add an <h2> inside each article" } ] }, { "id": "data-attributes-css", "title": "Styling with Data Attributes", "description": "CSS can select elements by their data attributes using [data-*] selectors. You can even match specific values!

The preview CSS uses [data-status='active'] to style active items differently.", "task": "Create a task list with 3 <li> items. Give each a data-status attribute:
1. One with data-status=\"completed\"
2. One with data-status=\"active\"
3. One with data-status=\"pending\"", "previewHTML": "", "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } ul { list-style: none; padding: 0; } li { padding: 15px; margin: 8px 0; border-radius: 8px; background: #f5f5f5; } li[data-status='completed'] { background: #c8e6c9; text-decoration: line-through; color: #388e3c; } li[data-status='active'] { background: #fff3e0; border-left: 4px solid #ff9800; font-weight: 600; } li[data-status='pending'] { background: #e3f2fd; color: #1976d2; }", "sandboxCSS": "", "initialCode": "", "solution": "", "previewContainer": "preview-area", "concept": { "explanation": "CSS attribute selectors enable state-based styling without adding/removing classes via JavaScript—you just change the attribute value and CSS reactivity handles the rest. The selector [data-status='active'] has the same specificity as a class (0,0,1,0), making it equally powerful but more semantic for data-driven states. This pattern shines in component libraries and SPAs where state changes frequently: updating one attribute triggers CSS transitions and visual changes automatically. Unlike classes that describe presentation (\"button-blue\"), data attributes describe meaning (\"status=active\"), and CSS translates meaning to presentation, keeping your HTML semantic and your styling decoupled from implementation details.", "diagram": "CSS Attribute Selectors\n\nAttribute Selector Syntax:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n[data-status] → Has attribute\n[data-status=\"active\"] → Exact match\n[data-status^=\"act\"] → Starts with\n[data-status$=\"ed\"] → Ends with\n[data-status*=\"iv\"] → Contains\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nState-Based Styling:\n\n
  • \n ↓ CSS applies\nli[data-status=\"pending\"] {\n background: lightblue;\n}\n\nJS changes state:\nel.dataset.status = \"active\"\n ↓ CSS automatically updates\nli[data-status=\"active\"] {\n background: orange;\n}\n\nvs Class Approach:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ndata-status=\"active\"\n✓ Semantic (describes state)\n✓ One attribute to update\n✓ Clear data meaning\n\nclass=\"task active pending\"\n✗ Presentational\n✗ Multiple classes to manage\n✗ Unclear which is data\n\nSpecificity:\n[data-status=\"active\"] = .active\nBoth have 0,0,1,0 specificity" }, "validations": [ { "type": "element_exists", "value": "li[data-status='completed']", "message": "Add an item with data-status=\"completed\"" }, { "type": "element_exists", "value": "li[data-status='active']", "message": "Add an item with data-status=\"active\"" }, { "type": "element_exists", "value": "li[data-status='pending']", "message": "Add an item with data-status=\"pending\"" } ] } ] }