section-044

Shoppable Lookbook

Vault only: not sold separatelyGet the Vault

An editorial room-set lookbook whose product cards grow out of markers on each photograph, hand over between pieces without closing, and add to a bag without losing the visitor's place.

advanced
6 more details
clip-pathstate-transitionkeyboard-navigationprogressive-enhancementcontainer-queriesresponsive-section

This section reads better at your own screen size than in the frame below:

Released

About this section

Shoppable Lookbook answers the visitor's question about an editorial photograph: what is in it, and where do I get it? Each room set carries numbered markers anchored to the products in the picture. Selecting one grows a product card out of the marker itself, with a detail crop, price and actions. Moving to another marker hands the same card shape across without closing first, so comparing pieces in a room feels continuous. Adding a piece updates a bag that stays in view beside the rooms.

Read the full section overview

Hollin is a fictional interiors brand with four room sets from one autumn shoot: a dining table, a reading corner, a bedroom and a hallway. Every product is ordinary semantic markup with its marker position as percentages on data attributes, so the section re-points to a fashion lookbook, a showroom or a gallery shop by editing HTML. Without JavaScript every piece is listed as a readable card beneath its photograph.

Shoppable Lookbook - website section preview

What's included

10 items
  • Product cards that grow out of their marker and hand over between markers without closing
  • Rapid hopping between markers settles on the last one chosen
  • Markers stay anchored under responsive object-fit crops and follow each photo's object-position
  • Card placement avoids covering other markers; narrow stages dock the card below the photo and push the next room down
  • A quiet per-room shopping list in sync with the open card, which also reaches pieces cropped out of view
  • Three room layouts by modifier class: default, wide panorama and side caption
  • A bag with running total, removal and a polite live announcement
  • Full keyboard operation: logical order, arrow keys between markers, Escape with focus return
  • Four generated editorial room photographs from one house at different times of day, with matching product detail crops
  • Readable product list fallback for no JavaScript or missing GSAP; owned teardown and remount

Perfect for

4 use cases
  • Interiors and furniture room sets
  • Fashion lookbooks with shoppable outfits
  • Showroom or hotel room 'shop the space' pages
  • Gallery or museum shop installation photographs

How it works

1 section

Each room is a photograph with an ordered list of products. The script reads each product's data-x and data-y percentages, maps them through the image's object-fit cover crop and object-position, and places a numbered marker there. Selecting a marker grows a rounded clip-path shell from the marker into the card's rectangle, then fades the card's content in; selecting another marker morphs the same shell to the new card while the previous content fades out. Every change starts from the shell's current geometry, so interrupted or rapid selections settle on the last one. The add button updates a bag list and total held beside the rooms.

Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this website section's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Vault only

Get the Vault to use this section.

The Vault includes Shoppable Lookbook, the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate. Sections are available only in the Vault for now while the collection is small.

Documentation

Quick start

Open index.html through a local HTTP server for the full preview. There is no build step. assets/style.css and assets/script.js are the editable sources; assets/script.min.js is a compact copy. Photos are in assets/img/. The section uses GSAP core 3.15.0 and no plugins or smooth-scroll library.

To add it to a page, copy the complete <section class="shop-look" data-shop-look> element (between the "Extract" comments), including the small inline <script> that is its first child. Load the stylesheet before it and GSAP plus the script after it. Bring the Schibsted Grotesk font link, or set your own font (see below).

Code snippet omitted: it ships with the download.

The inline script adds shop-look--js so the section never flashes its no-JavaScript product list before the markers appear. If the main script or GSAP fails to load, the class is removed (immediately when GSAP is missing, after four seconds if the script never runs) and the readable list returns.

The preview <main>, the body background style and the page <title> are demo furniture, not part of the section.

Make it yours

Text and links

Edit the brand, heading, introduction and sample disclosure at the top of .shop-look__rail. Each room is an <article data-shop-look-room> with a caption (.shop-look__room-number, .shop-look__room-title, .shop-look__room-copy) followed by its stage. The room links in .shop-look__nav point at each room's id; keep them in step when you add, remove or rename rooms. The bag's "Review bag" link (.shop-look__bag-link) is an example destination.

Products

Each product is one <li data-shop-look-item data-x="…" data-y="…"> inside the room's <ol class="shop-look__items">, holding an ordinary <article class="shop-look__card">:

  • data-x / data-y: the product's position in the source photograph, as percentages from its left and top edges (0 to 100). Measure them on the original image file, not on a cropped screenshot.
  • .shop-look__card-media img: the card photo (4:5 portrait, shown about 136px wide, so supply at least 272 x 340) and its alt text.
  • .shop-look__card-name, .shop-look__card-detail: name and one line of material or size.
  • .shop-look__card-price with data-price="185": the visible price text and the plain number used for the bag total. Set data-currency (ISO code) and data-locale on the section root to change how totals are formatted.
  • The data-shop-look-add button and the "View details" link (your product URL).

Markers are numbered, announced and tabbed in markup order, so list products in reading order (left to right across the photo). The script derives everything else from these items: the markers, the room's shopping list (number, name and price), the close buttons and the bag lines. Add, remove or reorder <li> elements freely; there is no fixed count. A room with a single product still works. Give each card heading a unique id per page (the card's aria-labelledby points at it); if two instances share ids the script assigns unique ones while mounted.

Photographs, crops and anchoring

Each room photo is an ordinary <img data-shop-look-photo> with src, srcset, sizes, intrinsic width/height and alt text. To replace one, drop in your file, update those attributes, then re-measure each product's data-x/data-y on the new image. No animation code changes.

The frame is cropped with object-fit: cover:

  • --shop-look-photo-ratio (default 3 / 2) sets the frame on wide layouts; --shop-look-photo-ratio-narrow (default 1 / 1) on narrow ones. Set either on the section root or on one room's stage, e.g. style="--shop-look-photo-ratio: 4 / 5".
  • object-position on the <img> (inline style or CSS, media queries allowed) chooses the focal point when the frame crops the photo. The demo sets it per room, e.g. style="object-position: 62% 50%" keeps the bedside table in a square crop.

How markers stay anchored: the script reads the image's natural size, the frame's size and the computed object-position, works out where the cover-scaled photo sits in the frame, and places each marker at offset + data-x% x scaled width (and the same for y). The marker therefore stays on the product at every width and crop, and recomputes on resize, image load and font load. A product the current crop cuts out of view loses its marker; it remains in the room's shopping list, whose entry becomes keyboard focusable for that item and opens its card from the nearest edge of the frame. If an important product falls out of a crop, move object-position towards it rather than moving the coordinates.

The card photos are detail crops taken from the room photographs, so they always show the same object. They are cut at no more than the photo's native resolution, so they stay sharp on 2x screens at the card's size. You can use separate packshots instead: 4:5, or they are cropped to fit.

Room layouts

Every room uses the same markup; a modifier class on the room <article> changes its rhythm. Copy any room block to add a room, then pick a modifier:

  • No modifier: caption and shopping list above a 3:2 photograph across the full column.
  • shop-look__room--wide: a 2:1 panoramic frame with the caption and list below it. Set the vertical focal point with object-position (the demo uses 55% 40%).
  • shop-look__room--side: the caption and list in a column beside a 4:3 frame, aligned to the photo's foot. Below 960px of section width it stacks like the default. For a portrait source, set --shop-look-photo-ratio: 4 / 5 (or 2 / 3) on that article.

Each modifier only sets layout and the frame ratio variables, so markers, cards and anchoring work the same in all three. Mix them freely; the demo alternates default, wide, side, default.

Brand variables

All on .shop-look at the top of the CSS: --shop-look-ground, --shop-look-ink, --shop-look-muted, --shop-look-rule, --shop-look-accent (hover, focus ring, selected names), --shop-look-card, --shop-look-card-ink, --shop-look-card-muted (card surface and text; keep these readable together when the page ground goes dark), --shop-look-dot and --shop-look-dot-ink (markers), --shop-look-font, --shop-look-width, --shop-look-gutter, --shop-look-rail, --shop-look-room-gap, --shop-look-radius, --shop-look-card-width, --shop-look-card-media (the card photo column) and --shop-look-sticky-top (set this to your fixed header height so the sticky rail and room bar clear it).

A dark rebrand example: --shop-look-ground: #1f1c19; --shop-look-ink: #f4efe7; --shop-look-muted: #b9aea2; --shop-look-rule: rgba(244,239,231,.2); --shop-look-accent: #e39a6f; leaves the cards light with their own ink.

Behaviour

  • Open: a marker click, tap, Enter or Space grows the card out of the marker. The marker stays visible at the card's corner. The card picks the side that covers the fewest other markers, so the next piece stays reachable.
  • Handover: selecting another marker in the same room morphs the open card shape to the new piece while the old content fades out. Selecting a marker in another room closes the first. Any number of quick selections settles on the last one.
  • Narrow stages (under 560px wide) dock the card below the photograph, overlapping its edge, so no marker is hidden. The room grows by the card's height while it is open, pushing the next room down rather than covering it, and a card that opens below the fold is scrolled into view.
  • Close: the marker again, the card's close button, Escape (focus returns to the marker) or a click outside the room.
  • Bag: "Add to bag" toggles to "Remove from bag"; the rail (or the sticky bar on narrow layouts) shows lines, total and remove buttons. Changes are announced through a polite live region. Unless reduced motion is on, a copy of the detail crop flies to the bag.

Optional API on each root: root.shopLook.open(index), root.shopLook.close(), root.shopLook.getState() (active item index and bag item indexes, counted across the whole section in markup order) and root.shopLook.revert(). revert() removes its markers, shells, shopping lists, listeners, observers and tweens and restores the readable product list; use it before removing the section in client-side navigation. ShopLook.mount(root) mounts it again (mounting twice replaces the previous instance).

Accessibility and integration

  • Markers are native buttons named with number, product and price, with aria-expanded and aria-controls. The card follows its marker in the DOM, so Tab moves from a marker into its card's actions and then on to the next marker. Arrow keys move between a room's markers and, while a card is open, hand it over.
  • Closed cards are removed from layout and inert. Reduced motion keeps every behaviour but changes state instantly; changing the preference while mounted is respected.
  • Without JavaScript, or with GSAP missing, every product is shown as a readable card under its photograph and the bag is hidden.
  • The section heading is an h2, rooms are h3 and products h4; change the tags to suit your outline, the classes carry the styles.
  • The rail is position: sticky inside the section on wide layouts, and the room/bag bar is sticky on narrow ones. Neither touches the page's scroll behaviour. Layout responds to the section's own width through a named container query, so it also works in a narrow column. Two instances work independently on one page.

Dependencies and credits

  • GSAP 3.15.0 core (no plugins).
  • Schibsted Grotesk from Google Fonts (SIL Open Font License).
  • Photography: AI-generated concept images of fictional products and rooms, not photographs of real products, made for this section. Replace them with your own photography before publishing.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.