Shop
A furniture catalogue with an editorial opening, product photography and an itemised shopping bag. Filter the collection, add a few pieces, then open the bag to remove one or start again. Changes stay in this example and reset when you reload.
Point at any part of the page to see what it is made of.
<style>
.example-stack { display:grid; grid-template-columns:minmax(0,1fr); gap:var(--ds-unit); min-width:0; align-content:start; }
.example-tight { display:grid; grid-template-columns:minmax(0,1fr); gap:calc(var(--ds-unit) / 2); min-width:0; align-content:start; }
.example-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ds-unit); }
.example-two { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:calc(2 * var(--ds-unit)); align-items:start; }
.example-actions { display:flex; flex-wrap:wrap; align-items:flex-end; gap:calc(var(--ds-unit) / 2); }
.example-between { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-end; gap:var(--ds-unit); }
.example-grow { flex:1; min-width:0; }
.example-section { padding-block:var(--ds-unit); }
.example-between > ds-input { width:calc(10 * var(--ds-unit)); max-width:100%; }
.example-overflow { overflow-x:auto; }
.example-rule { border-top:1px solid var(--ds-border-color); padding-top:var(--ds-unit); }
.example-caption { display:flex; justify-content:space-between; gap:var(--ds-unit); }
.example-caption > * { min-width:0; }
.example-nav { display:flex; justify-content:space-between; align-items:flex-end; gap:var(--ds-unit); }
.example-brand { flex:none; }
.example-footer { padding-top:var(--ds-unit); }
.example-stack [hidden], .example-grid [hidden], .example-two [hidden], .example-tight [hidden] { display:none!important; }
.example-status:empty { display:none; }
.example-title { --ds-sf:calc(2 * var(--sf, 1)); --ds-display-text-size:40px; --ds-typeface-regular:var(--typeface-compressed); }
.example-copy { max-width:calc(24 * var(--ds-unit)); }
.example-shop-hero { display:grid; grid-template-columns:2fr 3fr; gap:var(--ds-unit); align-items:stretch; }
.example-shop-intro { display:flex; flex-direction:column; justify-content:space-between; gap:calc(2 * var(--ds-unit)); padding-block:var(--ds-unit); }
.example-shop-hero ds-image { align-self:start; }
.example-story { display:grid; grid-template-columns:1fr 2fr; gap:calc(2 * var(--ds-unit)); padding-block:calc(2 * var(--ds-unit)); }
.example-product { display:grid; gap:calc(var(--ds-unit) / 2); min-width:0; }
.example-bag { padding-block:var(--ds-unit); border-block:1px solid var(--ds-border-color); }
.example-workspace { --ds-page-nav:calc(8 * var(--ds-unit)); --ds-page-gap:calc(2 * var(--ds-unit)); }
.example-workspace-nav { display:grid; gap:calc(2 * var(--ds-unit)); }
.example-week { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:calc(var(--ds-unit) / 2); }
.example-day { display:grid; gap:calc(var(--ds-unit) / 2); min-height:calc(4 * var(--ds-unit)); padding-block:calc(var(--ds-unit) / 2); border-top:1px solid var(--ds-border-color); }
.example-day[aria-current] { border-top-color:var(--ds-text-color); }
.example-work { display:grid; grid-template-columns:minmax(0,3fr) minmax(0,2fr); gap:calc(2 * var(--ds-unit)); }
.example-brief { display:grid; gap:calc(var(--ds-unit) / 2); }
.example-contact-sheet { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:calc(var(--ds-unit) / 2); }
.example-contact-sheet > :first-child { grid-column:1 / -1; }
.example-item { box-sizing:border-box; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:calc(var(--ds-unit) / 2); min-height:var(--ds-unit); align-items:end; border-bottom:1px solid var(--ds-border-color); }
.example-item label { display:flex; align-items:flex-end; gap:calc(var(--ds-unit) / 2); min-width:0; }
.example-item label ds-text { flex:1; }
.example-item-meta { display:flex; align-items:flex-end; gap:calc(var(--ds-unit) / 2); }
#balance-stat { --ds-stat-size:40px; --ds-stat-lines:3; --ds-stat-typeface:var(--typeface-compressed); }
.example-stats { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ds-unit); }
.example-cash { display:grid; grid-template-columns:minmax(0,3fr) minmax(0,1fr); gap:calc(2 * var(--ds-unit)); }
.example-goal { display:grid; grid-template-columns:calc(6 * var(--ds-unit)) minmax(0,1fr); gap:var(--ds-unit); align-items:center; }
.example-audio-hero { display:grid; grid-template-columns:1fr 1fr; gap:var(--ds-unit); }
.example-audio-copy { display:flex; flex-direction:column; justify-content:space-between; gap:var(--ds-unit); padding:var(--ds-unit); background:var(--one-pink-pastel); }
.example-audio-copy .example-title { --ds-typeface-regular:var(--typeface-regular); }
.example-audio-strip { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ds-unit); padding-block:var(--ds-unit); }
.example-audio-detail { display:grid; grid-template-columns:1fr 1fr; gap:calc(2 * var(--ds-unit)); align-items:center; }
.example-package { display:grid; grid-template-columns:1fr 1fr; gap:calc(2 * var(--ds-unit)); padding-block:calc(2 * var(--ds-unit)); }
.example-specs { display:grid; gap:0; }
.example-specs > div { display:flex; justify-content:space-between; gap:var(--ds-unit); border-bottom:1px solid var(--ds-border-color); padding-block:calc(var(--ds-unit) / 2); }
@media(max-width:900px) { .example-title { --ds-sf:var(--sf, 1); } .example-work { grid-template-columns:1fr; } .example-brief { grid-template-columns:1fr 1fr; gap:var(--ds-unit); } .example-cash { grid-template-columns:minmax(0,1fr); } }
@media(max-width:600px) {
.example-title { --ds-sf:var(--sf, 1); --ds-display-text-size:30px; }
.example-shop-hero,.example-audio-hero,.example-audio-detail,.example-package,.example-two,.example-story,.example-brief { grid-template-columns:1fr; gap:var(--ds-unit); }
.example-shop-intro { padding-block:0; gap:var(--ds-unit); }
.example-shop-hero { --hero-ratio:4 / 3; }
.example-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--ds-unit) calc(var(--ds-unit) / 2); }
.example-grid > :first-child { grid-column:1 / -1; }
.example-stats { grid-template-columns:repeat(2,minmax(0,1fr)); }
.example-stats > :first-child { grid-column:1 / -1; }
.example-audio-strip { grid-template-columns:1fr; }
.example-week { gap:calc(var(--ds-unit) / 2); }
.example-day { min-height:calc(3 * var(--ds-unit)); }
.example-day ds-text[size=small] { display:none; }
.example-nav .example-nav-secondary { display:none; }
.example-goal { grid-template-columns:calc(4 * var(--ds-unit)) minmax(0,1fr); }
.example-item { grid-template-columns:1fr; }
.example-item-meta { padding-inline-start:calc(1.5 * var(--ds-unit)); }
.example-package { padding-block:var(--ds-unit); }
}
</style><ds-page layout="column" measure="52" data-ds-surface>
<div slot="header" class="example-nav"><ds-text literal strong size="display" class="example-brand">Room</ds-text><div class="example-actions"><ds-button href="#collection">Collection</ds-button><ds-button href="#story" class="example-nav-secondary">About Room</ds-button><ds-button id="bag" secondary>Bag · 0</ds-button></div></div>
<div class="example-stack">
<section class="example-shop-hero">
<div class="example-shop-intro"><div class="example-tight"><ds-text literal size="small" tone="muted" wrap>Furniture, objects & everyday rituals</ds-text><ds-text literal size="display" class="example-title" wrap role="heading" aria-level="1">A room is never really finished.</ds-text></div><div class="example-stack example-copy"><div data-ds-prose><p>A chair pulled up. A cup set down. A few things that make a place your own.</p></div><div><ds-button href="#collection" primary>Find your next favourite <ds-icon type="down" aria-hidden="true"></ds-icon></ds-button></div><ds-text literal size="small" tone="muted">The autumn collection</ds-text></div></div>
<ds-image src="/examples/images/room.jpg" alt="A cane chair in a room filled with afternoon light" ratio="var(--hero-ratio, 6 / 5)" loading="eager" position="50% 75%"></ds-image>
</section>
<div class="example-caption"><ds-text literal size="small" tone="muted">Good things. Kept for years.</ds-text><ds-text literal size="small" tone="muted">Made for living</ds-text></div>
<section id="collection" class="example-stack example-section">
<div class="example-between"><ds-text literal size="display" strong wrap role="heading" aria-level="2">The everyday collection</ds-text><div class="example-actions" role="group" aria-label="Filter collection"><ds-button control="host" data-category="all" active aria-pressed="true">All</ds-button><ds-button control="host" data-category="furniture" aria-pressed="false">Furniture</ds-button><ds-button control="host" data-category="objects" aria-pressed="false">Objects</ds-button></div></div>
<div class="example-grid">
<article class="example-product" data-product="furniture"><ds-image src="/examples/images/chair.jpg" alt="Everyday chair, a wooden chair with a slatted back" ratio="1" ></ds-image><div class="example-caption"><ds-text literal strong wrap>Everyday chair</ds-text><ds-text literal >€180</ds-text></div><ds-text literal tone="muted" size="small" wrap>Wood, with a little character.</ds-text><div><ds-button secondary data-add="Everyday chair" data-price="180"><ds-icon type="plus" aria-hidden="true"></ds-icon> Add to bag</ds-button></div></article>
<article class="example-product" data-product="objects"><ds-image src="/examples/images/cup.jpg" alt="A speckled ceramic cup" ratio="1" ></ds-image><div class="example-caption"><ds-text literal strong wrap>Morning cup</ds-text><ds-text literal >€28</ds-text></div><ds-text literal tone="muted" size="small" wrap>For the first ten quiet minutes.</ds-text><div><ds-button secondary data-add="Morning cup" data-price="28"><ds-icon type="plus" aria-hidden="true"></ds-icon> Add to bag</ds-button></div></article>
<article class="example-product" data-product="objects"><ds-image src="/examples/images/forest.webp" alt="A sunlit trail through a green forest" ratio="1" ></ds-image><div class="example-caption"><ds-text literal strong wrap>Forest print</ds-text><ds-text literal >€45</ds-text></div><ds-text literal tone="muted" size="small" wrap>A window into somewhere else.</ds-text><div><ds-button secondary data-add="Forest print" data-price="45"><ds-icon type="plus" aria-hidden="true"></ds-icon> Add to bag</ds-button></div></article>
</div>
<ds-text id="shop-status" class="example-status" literal role="status" wrap></ds-text>
<section id="bag-summary" class="example-stack example-bag" hidden aria-label="Your bag"><div class="example-between"><ds-text literal size="display" strong wrap role="heading" aria-level="2">Your bag</ds-text><ds-button id="clear-bag">Empty bag</ds-button></div><ds-list id="bag-items"></ds-list><ds-text id="bag-content" literal wrap></ds-text></section>
</section>
<section id="story" class="example-story example-rule"><div><ds-text literal size="display" strong wrap role="heading" aria-level="2">Less, but more lived in.</ds-text></div><div class="example-stack"><div data-ds-prose><p>We look for pieces with a useful shape and a reason to stay. Nothing too precious to touch. Nothing that needs a special occasion.</p><p>Put your feet up. Leave the book open. Let the room become yours.</p></div><div class="example-actions"><ds-tag>Useful by nature</ds-tag><ds-tag>Better with time</ds-tag></div></div></section>
</div><div slot="footer" class="example-footer"><ds-text literal size="small" tone="muted" wrap>Fictional demo. Your changes stay here.</ds-text></div></ds-page> const items = new Map();
const bag = document.querySelector('#bag');
const summary = document.querySelector('#bag-summary');
function renderBag() {
let count=0,total=0; const list=document.querySelector('#bag-items'); list.replaceChildren();
for(const [name,item] of items){
count+=item.quantity; total+=item.quantity*item.price;
const row=document.createElement('ds-row'); row.setAttribute('fill','');
const words=document.createElement('ds-text'); words.setAttribute('literal',''); words.setAttribute('wrap',''); words.textContent=name+' × '+item.quantity;
const remove=document.createElement('ds-button'); remove.textContent='Remove'; remove.setAttribute('label','Remove '+name);
remove.addEventListener('click',()=>{if(--item.quantity===0)items.delete(name);renderBag();document.querySelector('#shop-status').textContent=name+' removed.';});
row.append(words,remove);list.append(row);
}
bag.textContent='Bag · '+count;
document.querySelector('#bag-content').textContent=count?'Subtotal €'+total+'. No payment is taken in this demo.':'Your bag is empty. Find something in the collection.';
}
document.querySelectorAll('[data-add]').forEach(button=>button.addEventListener('click',()=>{const name=button.dataset.add;const item=items.get(name)||{quantity:0,price:Number(button.dataset.price)};item.quantity++;items.set(name,item);renderBag();document.querySelector('#shop-status').textContent=name+' added to your bag.';}));
bag.addEventListener('click',()=>{renderBag();summary.hidden=!summary.hidden;if(!summary.hidden)summary.scrollIntoView({block:'nearest'});});
document.querySelector('#clear-bag').addEventListener('click',()=>{items.clear();renderBag();document.querySelector('#shop-status').textContent='Bag emptied.';});
document.querySelectorAll('[data-category]').forEach(button=>button.addEventListener('click',()=>{
document.querySelectorAll('[data-category]').forEach(item=>{item.toggleAttribute('active',item===button);item.setAttribute('aria-pressed',String(item===button));});
document.querySelectorAll('[data-product]').forEach(item=>{item.hidden=button.dataset.category!=='all'&&item.dataset.product!==button.dataset.category;});
})); The page uses the current DS1 components and tokens. The example only supplies content, layout composition and local interactions, so shared component updates carry through. Use Code to copy the example, Phone to check the narrow layout, or Show layers to inspect the pieces.