Tutorial: Build Your First BiModal Site
A hands-on guide to building a product page that works for humans and AI agents
In this tutorial, you'll build a complete product page from scratch using BiModal Design principles. We'll start with semantic HTML, add accessibility features, include structured data, and progressively enhance with CSS and JavaScript. By the end, you'll have a page that works perfectly for both human visitors and AI agents.
What We're Building
A product page for "Premium Wireless Headphones" with:
For Humans
- ✓ Beautiful, responsive design
- ✓ Interactive image gallery
- ✓ Add to cart functionality
- ✓ Customer reviews
- ✓ Full keyboard navigation
- ✓ Screen reader accessible
For Agents
- ✓ Semantic HTML structure
- ✓ Schema.org Product markup
- ✓ Clear pricing and availability
- ✓ Extractable specifications
- ✓ Purchase action metadata
- ✓ Works without JavaScript
Prerequisites
- • Basic HTML, CSS, and JavaScript knowledge
- • A text editor (VS Code, Sublime, etc.)
- • A modern web browser
- • 30-45 minutes of time
HTML Foundation
Let's start with the HTML structure. This is the most important step—everything builds on this foundation.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Premium Wireless Headphones - AudioTech</title>
<meta name="description" content="Premium noise-canceling wireless headphones with 30-hour battery life">
</head>
<body>
<!-- Skip to main content link -->
<a href="#main" class="skip-link">Skip to main content</a>
<!-- Header with navigation -->
<header>
<nav aria-label="Main navigation">
<a href="/">
<img src="/logo.svg" alt="AudioTech Home" width="120" height="40">
</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products" aria-current="page">Products</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<!-- Breadcrumb navigation -->
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/products/headphones">Headphones</a></li>
<li aria-current="page">Premium Wireless Headphones</li>
</ol>
</nav>
<!-- Main content -->
<main id="main">
<article>
<!-- Product heading -->
<h1>Premium Wireless Headphones</h1>
<!-- Product images -->
<section aria-label="Product images">
<img
src="/headphones-front.jpg"
alt="Premium wireless headphones, front view showing cushioned ear cups"
width="600"
height="600"
>
<img
src="/headphones-side.jpg"
alt="Side view showing adjustable headband"
width="600"
height="600"
>
</section>
<!-- Product details -->
<section aria-labelledby="details-heading">
<h2 id="details-heading">Product Details</h2>
<p>
Experience studio-quality audio with our Premium Wireless Headphones.
Featuring active noise cancellation, 30-hour battery life, and
premium comfort for all-day wear.
</p>
<!-- Pricing -->
<div>
<p>
<strong>Price:</strong>
<span>$299.99</span>
</p>
<p>
<strong>Availability:</strong>
<span>In Stock</span>
</p>
</div>
<!-- Specifications -->
<h3>Specifications</h3>
<dl>
<dt>Battery Life</dt>
<dd>30 hours</dd>
<dt>Connectivity</dt>
<dd>Bluetooth 5.0</dd>
<dt>Weight</dt>
<dd>250g</dd>
<dt>Noise Cancellation</dt>
<dd>Active (ANC)</dd>
</dl>
<!-- Add to cart form -->
<form action="/cart/add" method="POST">
<input type="hidden" name="product-id" value="headphones-001">
<label for="quantity">Quantity</label>
<input
type="number"
id="quantity"
name="quantity"
min="1"
max="10"
value="1"
required
>
<button type="submit">Add to Cart</button>
</form>
</section>
<!-- Customer reviews -->
<section aria-labelledby="reviews-heading">
<h2 id="reviews-heading">Customer Reviews</h2>
<p>
<strong>Average Rating:</strong> 4.5 out of 5
(based on 142 reviews)
</p>
<article>
<h3>Excellent sound quality</h3>
<p>
<strong>Rating:</strong> 5 out of 5
</p>
<p>
<strong>By:</strong> Sarah J.
</p>
<p>
<strong>Date:</strong> <time datetime="2024-12-15">December 15, 2024</time>
</p>
<p>
The noise cancellation is incredible and the battery lasts
forever. Best headphones I've ever owned!
</p>
</article>
<article>
<h3>Very comfortable</h3>
<p>
<strong>Rating:</strong> 4 out of 5
</p>
<p>
<strong>By:</strong> Mike T.
</p>
<p>
<strong>Date:</strong> <time datetime="2024-12-10">December 10, 2024</time>
</p>
<p>
I wear these all day for work. Super comfortable and great sound.
</p>
</article>
</section>
</article>
</main>
<!-- Footer -->
<footer>
<p>© 2025 AudioTech. All rights reserved.</p>
<nav aria-label="Footer navigation">
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
</ul>
</nav>
</footer>
</body>
</html>✓ What We Accomplished
- • Semantic HTML structure (header, nav, main, article, footer)
- • Proper heading hierarchy (h1 → h2 → h3)
- • Skip link for keyboard users
- • Breadcrumb navigation
- • Accessible form with labels
- • Descriptive alt text for images
Add ARIA Attributes
Now let's enhance our HTML with ARIA attributes to provide additional context for assistive technologies and agents.
<!-- Navigation with current page indicator -->
<nav aria-label="Main navigation">
<a href="/">
<img src="/logo.svg" alt="AudioTech Home" width="120" height="40">
</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products" aria-current="page">Products</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- Breadcrumb with proper structure -->
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/products/headphones">Headphones</a></li>
<li aria-current="page">Premium Wireless Headphones</li>
</ol>
</nav>
<!-- Form with enhanced accessibility -->
<form action="/cart/add" method="POST" aria-label="Add to cart">
<input type="hidden" name="product-id" value="headphones-001">
<label for="quantity">Quantity</label>
<input
type="number"
id="quantity"
name="quantity"
min="1"
max="10"
value="1"
required
aria-required="true"
aria-describedby="quantity-hint"
>
<p id="quantity-hint">Choose between 1 and 10</p>
<button type="submit" aria-label="Add Premium Wireless Headphones to cart">
Add to Cart
</button>
</form>
<!-- Reviews with better structure -->
<section aria-labelledby="reviews-heading">
<h2 id="reviews-heading">Customer Reviews</h2>
<div aria-label="Average rating">
<p>
<strong>Average Rating:</strong>
<span aria-label="4.5 out of 5 stars">4.5 out of 5</span>
(based on 142 reviews)
</p>
</div>
<article aria-labelledby="review-1-title">
<h3 id="review-1-title">Excellent sound quality</h3>
<p aria-label="5 out of 5 stars">
<strong>Rating:</strong> 5 out of 5
</p>
<p>
<strong>By:</strong> Sarah J.
</p>
<p>
<strong>Date:</strong> <time datetime="2024-12-15">December 15, 2024</time>
</p>
<p>
The noise cancellation is incredible and the battery lasts
forever. Best headphones I've ever owned!
</p>
</article>
</section>✓ What We Accomplished
- • Added aria-label for navigation regions
- • Used aria-current for active pages
- • Enhanced form with aria-required and aria-describedby
- • Improved screen reader announcements for ratings
Add Structured Data
Add Schema.org JSON-LD to make your product data explicitly machine-readable.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Premium Wireless Headphones",
"description": "Premium noise-canceling wireless headphones with 30-hour battery life",
"image": [
"https://example.com/headphones-front.jpg",
"https://example.com/headphones-side.jpg"
],
"brand": {
"@type": "Brand",
"name": "AudioTech"
},
"sku": "headphones-001",
"offers": {
"@type": "Offer",
"price": "299.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"url": "https://example.com/products/headphones",
"seller": {
"@type": "Organization",
"name": "AudioTech"
}
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": "142",
"bestRating": "5",
"worstRating": "1"
},
"review": [
{
"@type": "Review",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"author": {
"@type": "Person",
"name": "Sarah J."
},
"datePublished": "2024-12-15",
"reviewBody": "The noise cancellation is incredible and the battery lasts forever. Best headphones I've ever owned!",
"name": "Excellent sound quality"
},
{
"@type": "Review",
"reviewRating": {
"@type": "Rating",
"ratingValue": "4",
"bestRating": "5"
},
"author": {
"@type": "Person",
"name": "Mike T."
},
"datePublished": "2024-12-10",
"reviewBody": "I wear these all day for work. Super comfortable and great sound.",
"name": "Very comfortable"
}
],
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "Battery Life",
"value": "30 hours"
},
{
"@type": "PropertyValue",
"name": "Connectivity",
"value": "Bluetooth 5.0"
},
{
"@type": "PropertyValue",
"name": "Weight",
"value": "250g"
},
{
"@type": "PropertyValue",
"name": "Noise Cancellation",
"value": "Active (ANC)"
}
]
}
</script>💡 Test Your Structured Data
Validate your Schema.org markup with Google's Rich Results Test:
Test with Google →✓ What We Accomplished
- • Product details in machine-readable format
- • Price and availability explicitly stated
- • Reviews structured with ratings
- • Specifications as PropertyValue objects
- • Agents can now extract all product data
Add Agent Attributes
Add custom data attributes to help agents understand actions and extract data easily.
<!-- Product article with entity type -->
<article
data-entity-type="product"
data-product-id="headphones-001"
>
<h1 data-field="name">Premium Wireless Headphones</h1>
<!-- Pricing with data attributes -->
<div data-entity-type="pricing">
<p>
<strong>Price:</strong>
<span
data-field="price"
data-amount="299.99"
data-currency="USD"
>
$299.99
</span>
</p>
<p>
<strong>Availability:</strong>
<span
data-field="availability"
data-status="in-stock"
>
In Stock
</span>
</p>
</div>
<!-- Specifications with data fields -->
<h3>Specifications</h3>
<dl data-entity-type="specifications">
<dt>Battery Life</dt>
<dd data-field="battery-life" data-value="30" data-unit="hours">
30 hours
</dd>
<dt>Connectivity</dt>
<dd data-field="connectivity" data-value="Bluetooth 5.0">
Bluetooth 5.0
</dd>
<dt>Weight</dt>
<dd data-field="weight" data-value="250" data-unit="g">
250g
</dd>
<dt>Noise Cancellation</dt>
<dd data-field="noise-cancellation" data-value="Active">
Active (ANC)
</dd>
</dl>
<!-- Add to cart form with action intent -->
<form
action="/cart/add"
method="POST"
data-action="add-to-cart"
data-intent="transact"
data-requires-auth="false"
>
<input type="hidden" name="product-id" value="headphones-001">
<label for="quantity">Quantity</label>
<input
type="number"
id="quantity"
name="quantity"
min="1"
max="10"
value="1"
required
aria-required="true"
data-field="quantity"
>
<button
type="submit"
data-action="add-to-cart"
data-product-id="headphones-001"
>
Add to Cart
</button>
</form>
<!-- Reviews with rating data -->
<section aria-labelledby="reviews-heading">
<h2 id="reviews-heading">Customer Reviews</h2>
<div
data-entity-type="aggregate-rating"
data-rating-value="4.5"
data-review-count="142"
>
<p>
<strong>Average Rating:</strong>
<span data-field="rating">4.5 out of 5</span>
(based on <span data-field="review-count">142</span> reviews)
</p>
</div>
<article
data-entity-type="review"
data-rating="5"
>
<h3 data-field="review-title">Excellent sound quality</h3>
<p>
<strong>Rating:</strong>
<span data-field="rating" data-value="5">5 out of 5</span>
</p>
<p>
<strong>By:</strong>
<span data-field="author">Sarah J.</span>
</p>
<p>
<strong>Date:</strong>
<time datetime="2024-12-15" data-field="date">
December 15, 2024
</time>
</p>
<p data-field="review-body">
The noise cancellation is incredible and the battery lasts
forever. Best headphones I've ever owned!
</p>
</article>
</section>
</article>✓ What We Accomplished
- • Product entity clearly identified
- • All key data points have data-field attributes
- • Form action and intent explicitly marked
- • Specifications are easily extractable
- • Agents can programmatically access all data
Add CSS Styling
Now let's make it beautiful with CSS. Remember: the site already works; CSS only enhances the presentation.
<style>
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
color: #333;
background: #fff;
}
/* Skip link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
/* Header and navigation */
header {
background: #fff;
border-bottom: 1px solid #e5e7eb;
padding: 1rem 2rem;
}
nav[aria-label="Main navigation"] {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
}
nav ul {
display: flex;
gap: 2rem;
list-style: none;
}
nav a {
text-decoration: none;
color: #374151;
font-weight: 500;
transition: color 0.2s;
}
nav a:hover,
nav a:focus {
color: #10b981;
}
nav a[aria-current="page"] {
color: #10b981;
font-weight: 600;
}
/* Breadcrumb */
nav[aria-label="Breadcrumb"] {
max-width: 1200px;
margin: 1rem auto;
padding: 0 2rem;
}
nav[aria-label="Breadcrumb"] ol {
display: flex;
gap: 0.5rem;
list-style: none;
flex-wrap: wrap;
}
nav[aria-label="Breadcrumb"] li:not(:last-child)::after {
content: "›";
margin-left: 0.5rem;
color: #9ca3af;
}
nav[aria-label="Breadcrumb"] a {
color: #6b7280;
text-decoration: none;
}
nav[aria-label="Breadcrumb"] a:hover {
color: #10b981;
}
/* Main content */
main {
max-width: 1200px;
margin: 2rem auto;
padding: 0 2rem;
}
article {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
/* Product images */
section[aria-label="Product images"] {
display: flex;
flex-direction: column;
gap: 1rem;
}
section[aria-label="Product images"] img {
width: 100%;
height: auto;
border-radius: 8px;
border: 1px solid #e5e7eb;
}
/* Product details */
h1 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 1rem;
color: #111827;
}
h2 {
font-size: 1.5rem;
font-weight: 600;
margin: 2rem 0 1rem;
color: #111827;
}
h3 {
font-size: 1.25rem;
font-weight: 600;
margin: 1.5rem 0 0.75rem;
color: #111827;
}
p {
margin-bottom: 1rem;
color: #4b5563;
}
/* Pricing */
[data-entity-type="pricing"] {
background: #f9fafb;
padding: 1.5rem;
border-radius: 8px;
margin: 1rem 0;
}
[data-entity-type="pricing"] p {
margin: 0.5rem 0;
font-size: 1.125rem;
}
[data-field="price"] {
color: #10b981;
font-size: 2rem;
font-weight: 700;
}
[data-status="in-stock"] {
color: #10b981;
font-weight: 600;
}
/* Specifications */
dl {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.75rem 1.5rem;
margin: 1rem 0;
}
dt {
font-weight: 600;
color: #374151;
}
dd {
color: #6b7280;
}
/* Form */
form {
margin: 2rem 0;
padding: 1.5rem;
background: #fff;
border: 2px solid #e5e7eb;
border-radius: 8px;
}
label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: #374151;
}
input[type="number"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 1rem;
margin-bottom: 1rem;
}
input:focus {
outline: 2px solid #10b981;
outline-offset: 2px;
}
button {
width: 100%;
padding: 1rem;
background: #10b981;
color: white;
border: none;
border-radius: 4px;
font-size: 1.125rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
button:hover {
background: #059669;
}
button:focus {
outline: 2px solid #10b981;
outline-offset: 2px;
}
/* Reviews */
article[data-entity-type="review"] {
display: block;
background: #f9fafb;
padding: 1.5rem;
border-radius: 8px;
margin: 1rem 0;
border: 1px solid #e5e7eb;
}
/* Footer */
footer {
background: #111827;
color: #fff;
padding: 2rem;
margin-top: 4rem;
text-align: center;
}
footer nav ul {
display: flex;
gap: 2rem;
justify-content: center;
list-style: none;
margin-top: 1rem;
}
footer a {
color: #9ca3af;
text-decoration: none;
}
footer a:hover {
color: #fff;
}
/* Responsive */
@media (max-width: 768px) {
article {
grid-template-columns: 1fr;
}
nav ul {
flex-direction: column;
gap: 1rem;
}
}
</style>✓ What We Accomplished
- • Beautiful, modern design
- • Responsive layout for all screen sizes
- • Clear visual hierarchy
- • Visible focus indicators
- • Site still works if CSS fails to load
Add JavaScript Interactivity
Finally, let's add JavaScript to enhance the user experience. The site already works—this just makes it better.
<script>
// Image gallery enhancement
const images = document.querySelectorAll('[aria-label="Product images"] img');
let currentImage = 0;
images.forEach((img, index) => {
img.style.cursor = 'pointer';
img.addEventListener('click', () => {
// Could open a lightbox or zoom
console.log('Image clicked:', index);
});
});
// Add to cart form enhancement
const form = document.querySelector('form[data-action="add-to-cart"]');
const button = form.querySelector('button');
const quantityInput = form.querySelector('#quantity');
// Real-time quantity validation
quantityInput.addEventListener('input', () => {
const value = parseInt(quantityInput.value);
const min = parseInt(quantityInput.min);
const max = parseInt(quantityInput.max);
if (value < min || value > max || isNaN(value)) {
quantityInput.setAttribute('aria-invalid', 'true');
} else {
quantityInput.setAttribute('aria-invalid', 'false');
}
});
// AJAX form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Show loading state
const originalText = button.textContent;
button.textContent = 'Adding...';
button.disabled = true;
try {
const formData = new FormData(form);
const response = await fetch(form.action, {
method: 'POST',
body: formData
});
if (response.ok) {
// Show success message
button.textContent = '✓ Added to Cart!';
button.style.background = '#10b981';
// Reset after 2 seconds
setTimeout(() => {
button.textContent = originalText;
button.disabled = false;
}, 2000);
// Could also update cart count in header
console.log('Added to cart successfully');
} else {
throw new Error('Failed to add to cart');
}
} catch (error) {
console.error('Error:', error);
// Fallback to standard form submission
button.textContent = originalText;
button.disabled = false;
form.submit();
}
});
// Smooth scroll for skip link
document.querySelector('.skip-link').addEventListener('click', (e) => {
e.preventDefault();
document.querySelector('#main').scrollIntoView({
behavior: 'smooth'
});
document.querySelector('#main').focus();
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Press 'C' to focus on cart button
if (e.key === 'c' && !e.ctrlKey && !e.metaKey) {
const target = e.target;
if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA') {
button.focus();
e.preventDefault();
}
}
});
</script>✓ What We Accomplished
- • AJAX form submission (faster, no page reload)
- • Real-time quantity validation
- • Smooth scrolling for skip link
- • Keyboard shortcuts
- • Fallback to standard form if JS fails
- • Site still works without JavaScript
Test Your Page
Now let's verify everything works for both humans and agents.
✓ Accessibility Testing
- Keyboard Navigation: Unplug your mouse and navigate using only Tab, Enter, and Space
- Screen Reader: Turn on VoiceOver (Mac) or NVDA (Windows) and navigate the page
- Color Contrast: Use the WAVE tool to check contrast ratios
- Focus Indicators: Verify all interactive elements show focus
✓ Agent Testing
- The Curl Test: Run
curl https://yoursite.comand verify all content is visible - Disable JavaScript: Turn off JS in DevTools and verify the form still works
- Schema Validator: Test with Google Rich Results Test
- Data Extraction: Use console to test
document.querySelector('[data-field="price"]')
✓ Performance Testing
- Lighthouse: Run in Chrome DevTools (aim for 90+ in all categories)
- Page Load: Verify content visible in under 2 seconds
- Mobile: Test on actual mobile devices
✓ Automated Testing
- axe DevTools: Browser extension for accessibility issues
- HTML Validator: Check for valid HTML at validator.w3.org
- Schema Validator: Verify JSON-LD at validator.schema.org
🎉 Congratulations!
You've built a complete BiModal product page that works for both humans and AI agents!
What You've Accomplished:
- ✓ Semantic HTML structure
- ✓ Full keyboard accessibility
- ✓ Screen reader compatible
- ✓ WCAG AA compliant
- ✓ Schema.org structured data
- ✓ Agent-friendly markup
- ✓ Progressive enhancement
- ✓ Works without JavaScript
- ✓ Responsive design
- ✓ Production-ready code
Next Steps
Expand Your Knowledge
Dive deeper into BiModal Design principles and advanced techniques.
Read the Guide →See More Examples
Explore complete code examples for different types of sites and components.
View Examples →