html★ is built on progressive enhancement. This guide describes the full three-layer architecture for building resilient web applications.
The Three Layers
Layer 3: Custom Elements ← Rich interactivity (accordions, carousels)Layer 2: html★ ← AJAX navigation, partial updates, transitionsLayer 1: HTML ← Server-rendered, works everywhere
Each layer enhances the one below it. Remove any layer and the page still works — it just loses that layer's enhancements.
Layer 1: HTML (Server-Rendered)
The foundation. A server renders complete HTML pages. Every link navigates, every form submits, every piece of content is readable — with zero JavaScript.
<!-- Server-rendered HTML — works everywhere --><nav> <a href="/products?category=shoes">Shoes</a> <a href="/products?category=boots">Boots</a></nav> <section id="product-list"> <div class="product-card"> <h3>Running Shoe</h3> <p>$49.00</p> <form action="/cart/add" method="post"> <input type="hidden" name="product_id" value="123"> <button>Add to Cart</button> </form> </div></section>
Without JavaScript, this page works. Links navigate to full pages. Forms submit with page reloads. Content is visible and indexable.
Layer 2: html★ (AJAX Enhancement)
Add html★ attributes and the same HTML gets AJAX behavior:
<nav data-target="#product-list" data-push> <a href="/products?category=shoes">Shoes</a> <a href="/products?category=boots">Boots</a></nav> <section id="product-list"> <div class="product-card"> <h3>Running Shoe</h3> <p>$49.00</p> <form action="/cart/add" method="post" data-target="#cart-status" data-swap="inner"> <input type="hidden" name="product_id" value="123"> <button>Add to Cart</button> </form> </div></section><div id="cart-status"></div>
Now:
- Category links fetch fragments and swap into
#product-listwithout page reload - The add-to-cart form submits via AJAX, updating
#cart-status - URLs update via
data-push, back button works - View Transitions animate the content swap
If html★ fails to load, the page falls back to Layer 1 behavior.
Layer 3: Custom Elements (Rich Interactivity)
For components that need more than AJAX — client-side state, animations, complex interactions — use Custom Elements:
<product-card> <h3>Running Shoe</h3> <p>$49.00</p> <form action="/cart/add" method="post" data-target="#cart-status" data-swap="inner"> <input type="hidden" name="product_id" value="123"> <quantity-selector name="quantity" value="1"></quantity-selector> <button>Add to Cart</button> </form></product-card>
<p>Without JavaScript, <code><quantity-selector></code> renders its light DOM content (or nothing, and the form still works with a default quantity). With JavaScript, it upgrades to a rich quantity picker.</p><h2>The Combined Model</h2><code-block language="text">Request → Server renders HTML (Layer 1) ↓Browser renders page ↓html★ loads → Enhances links and forms (Layer 2) ↓Custom Elements upgrade → Rich interactivity (Layer 3)
All three layers are independently valuable and gracefully degrade:
| Scenario | What Works |
|---|---|
| No JavaScript | Layer 1: Full page navigation, form submission |
| html★ loaded, CE not | Layers 1-2: AJAX nav, partial updates, transitions |
| Everything loaded | All layers: Full interactivity |
| html★ fails, CE loads | Layers 1+3: Full pages with rich components |
Example: This Documentation Site
This site demonstrates all three layers:
Layer 1 (HTML): Astro renders complete HTML pages on the server. With JavaScript disabled, every page is readable and every link navigates.
Layer 2 (html★): Navigation uses
data-target="#content"anddata-select="#content"for AJAX page transitions with View Transitions animations.Layer 3 (Custom Elements):
<code-block>provides syntax highlighting,<dropdown-wc>handles header navigation menus,<search-wc>powers the search dialog.
Best Practices
Build Layer 1 first. Make sure the page works without any JavaScript. This is your baseline.
Add html★ second. Put
data-targeton navigation containers,data-pushfor URL management. The page now has AJAX without any custom JavaScript.Add Custom Elements last. Only for components that genuinely need client-side logic — not for things html★ already handles.
Keep layers independent. Custom Elements shouldn't depend on html★, and html★ doesn't depend on Custom Elements. Each layer should enhance the page on its own.
Test each layer in isolation. Disable JavaScript to test Layer 1. Remove Custom Element scripts to test Layers 1+2. This ensures graceful degradation actually works.