Step 01

Semantic HTML foundation

This step uses custom elements as semantic HTML markers before any JavaScript exists. Pages written this way work with just HTML and CSS, following progressive enhancement principles — combine that with hover effects, invokers (commandfor), dialogs, popovers, and view transitions, and you can get surprisingly far before writing a script. But starting here also means the move to registered web components, or to a static site generator, is seamless rather than a rewrite.

Custom Elements as Semantic Markers

Custom elements can be used to add semantic meaning to your HTML structure even without JavaScript. This approach replaces "div soup" with meaningful, descriptive element names.

❌ Old Way: Div Soup

<div class="card"> <div class="card-title">Title</div> <div class="card-price">$29.99</div> </div>

✅ New Way: Semantic Custom Elements

<product-card> <product-title>Title</product-title> <price-display>$29.99</price-display> </product-card>
💡 Tip: Custom element names must contain a hyphen (kebab-case) to distinguish them from standard HTML elements.

Demo 1: Product Grid Component

A responsive grid displaying product cards with semantic custom elements. Notice how the element names clearly describe what they represent.

Custom Elements Used

  • <product-grid> - Container for product cards
  • <product-card> - Individual product wrapper
  • <card-image> - Product image container
  • <card-content> - Content wrapper
  • <product-title> - Product name
  • <product-description> - Product details
  • <price-display> - Price information
  • <product-rating> - Star rating display
  • <card-actions> - Action buttons container

HTML Structure

<product-card role="gridcell" data-price="29.99" data-category="electronics"> <card-image> <img src="https://placehold.co/200x150" alt="Smartphone"> </card-image> <card-content> <product-title>Smartphone</product-title> <product-description> The latest model features advanced technology. </product-description> <price-display currency="USD">$29.99</price-display> <product-rating stars="4.5">★★★★★</product-rating> </card-content> <card-actions> <add-to-cart-button role="button" tabindex="0"> Add to Cart </add-to-cart-button> <wishlist-button role="button" tabindex="0"> ♡ Save </wishlist-button> </card-actions> </product-card>

Live Demo

CSS Styling

/* Notice with the custom elements and nesting just how simple our CSS gets! Literal values here, the same ones the demo file uses — see the note under "CSS Architecture Tips" below for why. */ product-card { display: block; background: #ffffff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); overflow: hidden; transition: transform 0.2s ease, box-shadow 0.2s ease; &:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } card-content { display: block; padding: 1.5rem; } product-title { display: block; font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; color: #1f2937; } }

Demo 2: User Profile Component

Display component showing user information with profile stats. Demonstrates how custom elements can create a clear information hierarchy.

Custom Elements Used

  • <user-profile> - Profile container
  • <profile-avatar> - User image
  • <profile-info> - User information wrapper
  • <user-name> - Display name
  • <user-role> - Job title/role
  • <user-email> - Email address
  • <profile-stats> - Statistics container
  • <stat-item> - Individual stat

HTML Structure

<user-profile role="article" tabindex="0"> <profile-avatar> <img src="https://placehold.co/80x80" alt="Jane Smith"> </profile-avatar> <profile-info> <user-name>Jane Smith</user-name> <user-role>Senior Developer</user-role> <user-email> <a href="mailto:jane@example.com">jane@example.com</a> </user-email> </profile-info> <profile-stats> <stat-item> <stat-label>Projects</stat-label> <stat-value>24</stat-value> </stat-item> <stat-item> <stat-label>Contributions</stat-label> <stat-value>1.2k</stat-value> </stat-item> </profile-stats> </user-profile>

Live Demo

CSS Architecture Tips

To use custom elements well consider adopting modern CSS features including:

  • Tokens (CSS variables) for consistent spacing, colors, and typography
  • CSS Layers for organized cascade control
  • CSS Nesting for encapsulation
  • Relative values for fluidity
  • Container queries for responsive behavior

Self-Contained Demo Styles

/* Each demo on this page is a single, self-contained file — no shared token file, no @import chain. Literal values stand in for what a larger app would keep in CSS custom properties: */ product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; }
â„šī¸ Note: The tutorial this series is adapted from shares CSS layers, design tokens, and common components across every demo through one shared.css import. These pages keep each demo file fully self-contained instead — literal values, no imports — so any single demo can be copied out on its own and still work.

CSS Layers

Layers control cascade specificity:

@layer reset, base, layout, components, utilities; @layer base { body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; } } @layer components { product-card { /* Component styles */ } }

Key Takeaways

⭐ Important Principles

  • Progressive Enhancement: HTML structure works without JavaScript
  • Semantic Naming: Element names describe their purpose
  • Accessibility First: Proper ARIA labels and keyboard navigation
  • Modern CSS: Utilize layers and nesting for organized cascade control and simplicity
  • Design Tokens: Consistent spacing, colors, and typography

Don't Abuse Custom Elements

Use standard HTML elements when they exist:

  • ✅ Use <header>, <footer>, <nav>, button, etc.
  • ❌ Don't create <section-header> or <section-footer> or my-button

Next Steps

In Step 2: Custom elements, we'll add JavaScript to enhance these elements:

  • Register elements with customElements.define()
  • Create proper classes extending HTMLElement
  • Split setup between the constructor and connectedCallback
  • See what "upgrade" means when the definition arrives after the markup
  • Compare autonomous elements with customized built-ins