017
AI ready Refreshed

Coffee Shop Template

£29 View live demo or the whole Vault, £79.20

Includes the original v1 design as a separate download.

A five-page shop and café site where every product owns a saturated colour, so the range reads as a colour field on a neutral grey ground. Visitors deal the whole shelf as a fanned stack of bags, filter the shop by flavour, dial a brew guide and pack a subscription box. Worked example: a speciality coffee roaster in Norwich.

ScrollTriggerDraggableFlip Lenis advanced
13 more details
pinningsnapdraggablekeyboard-navigationscroll-linked-colorflip-layoutclick-togglesvg-animationtimeline-controltabsscroll-revealstaggerprogressive-enhancement

Updated

About this template

A five-page shop and café site for any independent seller with a short, characterful range: coffee, tea, chocolate, natural wine, small-batch skincare. The interface itself has no brand colour. It is ink, paper and a neutral grey ground, and every saturated colour on the page belongs to a product. Each coffee carries its own colour and its own photographed bag, so the shop, the home page and the product page all take their colour from what is being sold.

Read the full template overview

The home page opens on a full-bleed photograph of the roaster's cooling tray, with this month's lot floating over it as a card you can buy in place. Below it the whole shelf is a fanned stack of nine bags: scroll, drag the top bag aside, press the arrows or use the arrow keys to deal the next one, and the full-screen field changes to that coffee's colour. The shop is a ruled grid of colour tiles with mono data strips and an add button on every bag, filtered by flavour, process and roast.

Each product page ends in a working brew guide: pick a method, slide the dose, and the water, ratio and every pour recompute while a drawn brewer fills stage by stage against a running timer. The subscription page packs a drawn box live as visitors choose a style, bag count, cadence and grind, with the price and next roast day worked out for them. The café page has a tabbed menu board, today's hours and a real street map with walking routes. A basket drawer runs on every page and keeps its contents as visitors move between them.

To make it yours, change the products in one list in the script (names, prices and colours), drop in a cut-out photograph of each of your own packs, swap the place photographs and edit the copy. The bag photographs in the demo are generated mockups.

Coffee Shop Template - website template preview

What's included

14 items
  • Five plain pages (home, shop, product with brew guide, subscription builder, café) sharing one stylesheet and one script, with a short cross-page fade where browsers support it
  • One-row phone navigation with a Menu button that traps focus and closes on Escape; links wrap to a second row without JavaScript
  • Colour-per-product system: every product sets its own colour, second colour and readable ink, and tiles, bags, the shelf field and the product band all follow it
  • The shelf: a pinned, fanned stack of bags dealt by scroll, drag, buttons or arrow keys, with the full-screen field changing to each product's colour
  • Photographed bags for all nine products: transparent cut-outs at one size and baseline, so any pack drops into the shelf, the grid and the product page (the demo set are generated mockups)
  • Shop grid in the style of a ruled spec sheet: colour tiles, mono origin strips and a price strip with an add button, filtered by flavour, process and roast with a Flip reflow and an empty state
  • Product page rendered from the address bar (coffee.html?c=...), so one page serves the whole range, with bag size and grind options
  • Brew guide for V60, AeroPress and cafetière: the dose recomputes water, ratio and every pour, and a drawn brewer fills stage by stage with start, next-stage and reset controls
  • Subscription box builder: the whole band takes the chosen style's colour; four colour-blocked styles, one to three bags, three cadences and four grinds, with per-delivery price, a monthly estimate and the next roast day
  • Basket drawer on every page: quantity steppers, per-bag grind with row merging, free-delivery bar, subscriptions as their own lines, focus trap and live announcements; contents persist between pages
  • Fly-to-basket arc from whichever bag was bought, built from two tweens of one duration on different eases
  • Café page with a tabbed menu board, today's hours highlighted, an open-now line and a MapLibre street map on OpenFreeMap tiles with drawn walking routes
  • True-colour photography and bag cut-outs bundled as optimised WebP derivatives, about 2 MB in total
  • Readable and orderable without JavaScript: the shelf becomes a row of nine colour cells and every product shows a phone ordering route

Perfect for

5 use cases
  • Coffee roasters and cafés that sell beans online and pour them in a shop
  • Tea merchants, chocolate makers and small-batch food brands with a short range
  • Natural wine, cider or spirits shops where each bottle has its own label colour
  • Skincare, candle or soap brands that sell by scent or style
  • Any subscription product where buyers pick a style rather than an exact item

How it works

5 sections

One list of products

Every product fact (name, origin, notes, price for each size, colours and bag photograph) lives in one list at the top of the script. The pages also carry those facts in their markup so they read without JavaScript, and on load the script writes the list's values over them, so a price edited in one place is right everywhere.

The shelf

The fanned stack is pinned to the viewport and scroll is the single source of which bag is on top. The arrow buttons, the drag and the arrow keys all scroll the page to that bag's stop, so the stack, the field colour and the scrollbar can never disagree. With reduced motion or without JavaScript the same markup lays out as a row of nine colour cells, each with its own link.

Basket across pages

The basket is one Map keyed by product, bag size and grind, kept in session storage and rebuilt on each page load, so plain page navigation keeps its contents. Changing a line's grind to match another line merges the two. The basket works with GSAP blocked; only the fly-to-basket arc needs it.

Brew guide and box builder

The brew guide derives every pour from the dose as a running target, so the pours always add up to the water total, and the drawing's timeline is rebuilt from the same plan whenever the dose or method changes. The box builder prices each delivery from the chosen style, applies the subscription discount and works out the next roast day from the order cut-off.

Fallbacks

Reveals are hidden only under a class set before first paint and removed if the animation library fails to load. Reduced motion keeps every feature but swaps tweens for instant changes, and the map keeps an OpenStreetMap link until it has loaded.

Plugins ScrollTrigger, Draggable, Flip
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo (5 pages, lowest score shown), 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.

Questions about this template

Does this only suit a coffee roaster?

No. The design is a grey ground with one saturated colour per product, which suits a tea merchant, chocolate maker, natural wine shop or a skincare brand selling by scent just as well. Rename the products and their notes, choose a colour and photograph a pack for each, replace the roastery and café photographs, and the brew guide can be removed or rewritten as a serving guide.

Does the basket take payment or save orders?

No. It is a complete front end: lines, quantities, grind options, subscriptions, a subtotal and a free-delivery bar, kept in the visitor's browser tab while they move between pages. Nothing is sent anywhere and the Checkout button shows a notice. Connect it to Stripe Checkout, Shopify's Buy Button or Snipcart; each line already carries a product id, size and grind for that hand-off.

Do the newsletter and basket forms send anything?

No. The footer's roast-day letter form collects an email address and shows a thank-you notice; the subscription builder and the basket collect a style, bag count, cadence, grind, coffees, sizes and quantities, and keep them only in the visitor's browser tab. Nothing is posted. Point the newsletter at Mailchimp, Buttondown or ConvertKit, and the basket and subscriptions at Stripe Checkout, Stripe Billing, Shopify or Snipcart.

How do I add a tenth product or change a colour?

Add an entry to the product list at the top of the script with its price, colour, second colour and ink, save a cut-out of the pack as bag-<sku>-400.webp and bag-<sku>-800.webp, then copy one tile in the shop markup and change its data attributes, colours and image paths. The shelf on the home page deals however many bags it contains, so add one there too if you want it in the stack.

Are the bag photographs real, and how do I use my own?

The nine bags in the demo are generated mockups of packaging that does not exist. Replace them with your own: photograph each pack front-on on a plain light-grey ground, cut it out to a transparent image on the same 5:7 canvas, with the pack at the same height and baseline, and save it under the product's name. The repository's cut-out script does the keying, scaling and both sizes for you.

Can I change the brew guide's recipes?

Yes. Each method is a short entry with its ratio, grind and a list of stages giving a time, a label and the running water target. Change the ratio and the water and pours follow; add a stage and the schedule, timer and drawing pick it up. Remove a method by deleting its radio button and its entry.

Is the subscription builder connected to anything?

It works out the price, a monthly estimate and the next roast day, and adds the subscription to the shared basket as its own line, but it does not create a recurring order. Recurring billing belongs to your payment provider, for example Stripe Billing or a Shopify subscriptions app. The style prices, discount and roast days are set in the markup and near the top of the script.

Paid template

Purchase to unlock the code.

Buying Coffee Shop Template opens 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.

£29 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £79.20, one payment.

Documentation

Quick Start

  1. Unzip and open index.html in a browser, or serve the folder with any static server (npx serve .).
  2. Every page is a plain HTML file: index.html (Home), shop.html (all coffee), coffee.html (one coffee and its brew guide), subscribe.html (box builder) and cafe.html (menu, hours, map).
  3. All pages share assets/style.css and assets/script.js. There is no build step.
  4. Deploy the folder as it is to any static host.

Using It With Your Own Design

  • Products and colour: the product list is RANGE near the top of assets/script.js. Each entry has a sku, name, origin and tasting notes, a price and price1kg in pence, and three colours: c (the product colour), c2 (the colour of the giant origin word on the shelf) and ink (the text colour that reads on c, #141414 or #FFFFFF). Keep text on c at 4.5:1 or better.
  • Bag photographs: each product has assets/img/bag-<sku>-400.webp and -800.webp, a transparent cut-out of its pouch. The bags in the demo are generated mockups, not real packaging. See Bag photographs below for swapping in your own.
  • Ground, paper and ink: the token block at the top of assets/style.css. The interface deliberately has no brand colour of its own; the products supply it.
  • Type: Schibsted Grotesk (display and body) and Martian Mono (data strips, timer, labels), self-hosted in assets/fonts/ (see Fonts below). Swap the files, the @font-face rules at the top of assets/style.css and --font-display, --font-body and --font-mono.
  • Photographs: the bag cut-outs (above), the hero (roaster's cooling tray), the roasting floor, cupping, the lot of the month, the café room and bar, a pour-over and the counter. See Images below.

Pages and Configuration

Page Sections Hands-on piece
Home Drum-photo hero with the lot of the month, the shelf, lot block, six-tile range, roastery, subscription styles, café band The shelf (deal the bags), add in place
Shop Title, filter chips, ruled grid of nine tiles, delivery strip Flavour / process / roast filter
Coffee Product band, the lot and roast log, brew guide, also on the shelf Size and grind, brew guide
Subscribe Title, four-step builder, drawn box with summary, questions Box builder
Café Room photo, menu board, hours, notes, map Menu tabs, walking routes

Every feature looks for its own markup and does nothing when it is missing, so you can delete a section, or a page and its nav link, without touching the script.

Navigation

The same <header class="nav"> is on every page; mark the current page's link with aria-current="page". On the home page nav--over sets it in white over the photograph until the hero has scrolled away. On phones the bar is one row with a Menu button that opens the four links: it traps Tab, closes on Escape, a link or a click outside, and returns focus to the button. Without JavaScript the links simply wrap onto a second row.

The home hero's headline and lot card arrive on a CSS animation ([data-hero-rise] with a --i stagger index), so they are on screen within a few frames of first paint whether or not the script has loaded.

Product markup

The pages carry product facts in their markup too, so they read without JavaScript. On load the script writes the RANGE price over every [data-price] inside a [data-product][data-sku] tile and every [data-price-of="<sku>"], so a price changed in RANGE is right everywhere. When you change a colour, change the inline --c, --c2 and --on on that product's markup as well; those are what a visitor without JavaScript sees.

A tile is an <article class="tile" data-product data-sku="..." data-flavour="..." data-process="..." data-roast="..." data-buy> with a bag photograph (img.bag[data-bag], with srcset for both sizes), a name, notes, a .strip__meta line and a .strip__buy line holding the [data-add="<sku>"] button. data-buy marks the region the fly-to-basket arc starts from.

The shelf (Home)

[data-deal] holds one [data-deal-item data-sku="..."] per bag, in order. The section pins and deals the whole shelf in about one and a half screens of scroll (PIN_SCREENS in the script), snapping to each bag; the arrow buttons, a drag of the top bag past about a fifth of the screen, and the Left and Right arrow keys move the page to the next stop. The full-screen field, the giant origin word and the basket count badge take that coffee's colours from RANGE. The add button always adds the bag on top. Add or remove items freely; the pin length follows the count.

With reduced motion, or without JavaScript, the same markup is a horizontal row of colour cells, each with the bag, its name and a link.

Shop filter

Each chip group is a [data-chip-group="flavour|process|roast"] of [data-chip="value"] buttons, matched against the tile's data-flavour, data-process and data-roast. Chips in one group combine with OR, groups combine with AND. Add a value by adding a chip and setting the attribute on the tiles. A combination that matches nothing shows the empty state with a clear button.

Product page

coffee.html?c=<sku> renders that coffee from RANGE: name, origin, notes, the spec table, the roast log, the bag photograph, the band colour, the bag-size prices and the page title. Without a c, or with an unknown one, it shows the coffee written in the markup. Bag size (250 g, 1 kg) and grind are part of the basket line.

Brew guide

METHODS in assets/script.js holds one entry per method: ratio (water grams per gram of coffee), ratioLabel, grind, and stages(dose, water) returning stages of { t, label, to, pour }, where t is the start time in seconds and to is the running water target in grams. Pours are running targets, so they always add up to the water total. The AeroPress entry caps the chamber at 220 g and adds the rest as bypass water. The drawing has one [data-brewer="<method>"] group per method in the SVG; its liquid levels are rectangles marked data-level. The default method follows the coffee's roast (filter, omni, espresso). window.crowfootBrewPlan(method, dose) returns the plan if you want the numbers elsewhere.

Subscription builder

Styles are radios with data-price (pence per bag before the discount) and their colour as an inline --c / --on. The whole builder band takes the chosen style's colour (--style-c, --style-on), with the style's first word as a giant mark behind it. Each style radio also carries data-bags="<sku> <sku> ...", the coffees whose bag photographs are packed into the drawn box in that order (bag one is the first coffee, bag two the second, wrapping round if the list is shorter; one to three bags, centred), and each style card holds a small fan of the same coffees (.style__bags, decorative, alt=""): change the img paths to match your range. SUB_DISCOUNT (0.9) and PER_MONTH set the discount and the monthly estimate. The next roast day comes from nextRoastDay(): Tuesday and Thursday, orders by noon the day before. Change the days and cut-off there. Add subscription to basket adds one line to the shared basket; it does not set up recurring billing.

Basket

One Map keyed by product, size and grind (subscriptions by their options), saved to sessionStorage under crowfoot-basket so it follows the visitor between pages and clears when the tab closes. FREE_FROM sets the free-delivery threshold; subscriptions always post free. The drawer traps focus, closes on Escape, returns focus to the button, stops Lenis while open and carries data-lenis-prevent on its list. Checkout is not connected: the button shows a notice. Each line carries sku, size, grind and qty for a hand-off to Stripe Checkout, Shopify or Snipcart. The drawer markup sits in a <template data-drawer-tpl> at the foot of each page and is stamped in by the script, so without JavaScript there is no basket on the page at all (the button stays hidden too), and every product shows a phone ordering link.

Café: hours and map

HOURS in the script holds opening hours per day (decimal hours, 7.5 is 7:30) for the open-now line and today's row; the hours table and the home band print them as text, so update those too. The map is MapLibre GL on OpenFreeMap tiles; the script loads MapLibre (the two URLs in MAPLIBRE_JS and MAPLIBRE_CSS) only when the map nears the screen, so the page paints without it. CAFE is the pin as [lng, lat]; ROUTES are walking routes as [lng, lat] arrays from the OpenStreetMap foot router (https://routing.openstreetmap.de/routed-foot/route/v1/foot/<from>;<to>?overview=full&geometries=geojson), and the minutes in the route buttons are that router's estimates. Replace all three to move the café. Until the map loads, the box holds a link to the place on openstreetmap.org.

Bag photographs

All bag files share one 5:7 canvas (1000 × 1400 at full size) with the pouch filling 90% of the height and its base 2% above the foot, so any bag lines up with the others in the shelf stack, the grid and the product page. To use your own packaging:

  1. Photograph each pack front-on, evenly lit, on a plain light-grey ground.
  2. Cut it out to a transparent PNG or WebP, trimmed to the pack, then place it on a 1000 × 1400 transparent canvas, scaled to 1260 px tall and centred with its base at y = 1372.
  3. Export bag-<sku>-800.webp (800 wide) and bag-<sku>-400.webp (400 wide) into assets/img/.

If you work from this repository's tooling, assets/img-src/build-bags.mjs does all of this: put NN-<sku>.jpg shots in assets/img-src/bags/ and run bun effects/coffee-roaster-template/assets/img-src/build-bags.mjs. It keys each shot against its grey ground, fills the label, removes the floor shadow and grey edge fringe, and normalises size and baseline. If a bag file is missing the page keeps the bag's space and shows its alt text.

Palette

Token Value Role
--ground #DCDDD9 Page ground
--paper #F3F3F0 Strips, cards, drawer
--ink #141414 Text, rules, buttons, footer
--ink-soft #4A4B48 Secondary text (6.4:1 on ground)

Product colours in the example range: violet #6A3FD9, pink #F25CA2, sky #6EC3F2, cobalt #2348E0, mint #3CC4A0, leaf #3FA34D, mustard #F2B425, plum #6B2F5E, citron #E6E13A. Each pairs with the ink recorded in RANGE; measured contrast is listed in the comment at the top of assets/style.css.

Images

The place photographs are bundled in assets/img/ as optimised WebP derivatives (about 1 MB in total), cropped and resized from the originals listed in assets/img-manifest.json. All are from Pexels and used under the Pexels licence:

File Shows Source
hero-drum.webp Beans turning in a roaster's cooling tray https://www.pexels.com/photo/22718754/
roaster.webp A roaster at the controls of a drum roaster https://www.pexels.com/photo/7175956/
tray-overhead.webp Cooling tray from above https://www.pexels.com/photo/31953357/
cupping.webp Cupping table from above https://www.pexels.com/photo/20643643/
lot-cherries.webp Ripe coffee cherries in cupped hands https://www.pexels.com/photo/13819623/
cafe-room.webp Café interior with customers https://www.pexels.com/photo/6829469/
cafe-bar.webp Long café bar with baristas https://www.pexels.com/photo/32416016/
pour.webp Pour-over at the bar https://www.pexels.com/photo/7125769/
serve.webp Barista handing over a bag https://www.pexels.com/photo/6684768/

The nine bag-*.webp files are generated mockups (one consistent set of matte pouches on a grey sweep), cut out by assets/img-src/build-bags.mjs; replace them with photographs of your own packs.

Some photographs come in more than one file so phones download less. hero-drum-phone and cafe-room-phone (.avif and .webp) are the centre square of the full hero photograph at full resolution: narrow portrait screens only ever show that square of the cover-fitted frame, so each page's <picture> serves it to them and the head preloads it. Re-cut both squares if you replace a hero photograph. roaster-700, lot-cherries-700, cupping-700 and tray-overhead-700 are 700-pixel-wide copies that the srcset picks on phones; a replacement needs both widths.

To use your own, replace a file with one of the same name and similar proportions, and update its alt text and caption. Keep the hero photograph darker towards the bottom, where the white headline sits on a scrim. If you work from this repository's tooling, edit the manifest and regenerate with bun scripts/build-template-assets.ts coffee-roaster-template --force.

Accessibility

  • Native buttons, radios, range input, selects and links throughout; visible focus outlines on every surface, including the coloured ones.
  • The shelf is operable by buttons and arrow keys as well as drag; the add button and the panel text always name the bag on top.
  • The basket drawer is a modal dialog with a focus trap, Escape to close and focus returned to the trigger; every basket change is announced in a polite live region.
  • Menu tabs follow the tabs pattern (arrow keys, Home, End). Brew stages are announced as they start.
  • Bag photographs carry alt text naming origin, lot and "250 g bag" (decorative repeats have empty alt); the giant origin word, the colour stripe and map markers are hidden from assistive technology.
  • Reduced motion: no pin, no arcs, no drops; the shelf becomes a row, the brew drawing jumps between stages while the timer still runs, and the cross-page fade is off.
  • Without JavaScript every page is a complete document; the basket, filters and brew controls are absent rather than broken, and products show a phone ordering route.

Dependencies

  • GSAP with ScrollTrigger, Draggable (home) and Flip (shop). Built against 3.15.0; works with 3.12+.
  • Lenis for optional smooth scrolling. Built against 1.3.17. Turn it off with ?smooth=off in the URL or data-smooth="off" on <html>.
  • MapLibre GL 4.7.1 on the café page, loaded by the script when the map is needed, with OpenFreeMap tiles (OpenStreetMap data, no API key). Keep the OpenStreetMap attribution.
  • Schibsted Grotesk and Martian Mono, self-hosted in assets/fonts/.

Fonts

Schibsted Grotesk and Martian Mono are self-hosted in assets/fonts/, so no page makes a render-blocking third-party request. The files are the Latin subsets Google Fonts serves (both are variable fonts), declared by the @font-face rules at the top of assets/style.css with the same weights, widths and unicode-range Google uses. Each page preloads both, since both set the first screen. Licence: SIL Open Font License 1.1, full text in assets/fonts/OFL.txt (Schibsted Grotesk: Copyright 2023 The Schibsted-Grotesk Project Authors, https://github.com/schibsted/schibsted-grotesk; Martian Mono: Copyright 2021 The Martian Mono Project Authors, https://github.com/evilmartians/mono).

To swap a face, put your .woff2 in assets/fonts/, point its @font-face rules and the token at it, and change the preloads in each page's <head>. To use Google Fonts instead, delete the @font-face rules and the preloads and add the Google Fonts <link> to every page.

Teardown

Each page is a plain document, so features run once on load and need no teardown between pages. The page's GSAP work lives in one gsap.context() (exposed as window.gsapContext) and one gsap.matchMedia(), reverted on unload; switching reduced motion on or off at runtime tears down the shelf, the pin and Lenis and rebuilds the right version. The map removes itself on unload. A host page's own window.lenis is never created over or destroyed.

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