When Ungarnished came to Studio Smith-Cordell, the brief was straightforward on paper: build a headless Shopify store to sell Angelos Bafas’s cocktail book, some merchandise, and future publications. Angelos — known to his following as Mr Ungarnished — has built a reputation as one of London’s most respected bartenders, and the studio wanted a store that matched the quality of everything else they do. Nothing about the store should feel like an afterthought bolted onto a beautiful site.
What started as “build a fast Shopify front end” turned into a genuinely interesting engineering problem, mostly centred on one component: the cart. We’ve used enough e-commerce sites to know exactly where they tend to fall apart, and we wanted to fix as many of those problems as we could within this project.
The core problem: most carts live on a server, and it shows.
The standard pattern for e-commerce carts is to treat the server as the single source of truth. You click a button, a request goes out, you wait, and eventually the UI updates to reflect what actually happened. Most of the time this is fine. But the seams show constantly: a laggy click-to-quantity-change, a spinner that appears for a fraction of a second too long, a cart that silently fails to update and leaves you wondering if you actually added anything.
None of that is really Shopify’s fault — it’s a consequence of treating the network as the thing your UI waits on, rather than something your UI works alongside. So for Ungarnished’s store, we flipped the model.
Local-first state, synced in the background.
The site is built using SvelteKit and hosted on Cloudflare’s edge network, so the shell of the site is about as fast as it can be. But speed at the network layer doesn’t help if the cart itself still waits on a round trip to Shopify for every interaction.
Instead, the cart’s state lives in three places, in this order of precedence:
- Application state — the actual source of truth while you’re on the page, held in a class in a
.svelte.tsfile. Using Svelte 5’s runes inside the class means every property is reactive via$state(), so updating a value on the class ripples through the whole UI immediately — no separate store wiring, no manual subscriptions. - Browser local storage — a persistent copy, so refreshing the page or closing the tab doesn’t lose your cart.
- Shopify’s servers — synced via their API, which is what ultimately processes the order.
The class itself only handles cart state — quantities, line items, totals, and the logic for what should happen when. All the actual Shopify request functions live in a separate file. That separation kept state and side effects from tangling together, and meant the request layer could evolve without touching any of the reactive logic sitting on top of it.
When you add something to the cart, the UI updates immediately from application state. The write to local storage and the sync call to Shopify both happen in the background, without the user needing to wait for either. This is what’s usually called an optimistic update: assume the action will succeed, show the result straight away, and only correct course if it turns out you were wrong.
That correction matters, because sometimes you are wrong. A product might sell out in the moments between a page loading and someone clicking “add to cart”. Someone might bump a line item’s quantity above what’s actually in stock. In both cases, the optimistic change rolls back once the sync with Shopify completes and reveals the real state of inventory — the user sees an accurate cart again, with a moment’s warning rather than silence or a confusing checkout error later.
The same principle applies when someone closes the tab entirely and comes back later. On return, the cart loads instantly from local storage — so there’s no blank flash or spinner while it figures out what you had — and then quietly validates that cart session against Shopify’s servers in the background. If the session has expired, or stock has changed in the meantime, the local copy gets corrected. You get the speed of a cached cart without the risk of it going stale and silently showing you something that’s no longer true.
Debouncing the quantity buttons.
One detail I’ll happily call out as a personal pet peeve: quantity steppers in carts that can’t keep up with you. You click “+” a few times quickly, and either it visibly lags a step behind, or — worse — a race condition between overlapping requests leaves the quantity wrong entirely.
The fix is a short debounce on the sync to Shopify. Every click updates the local application state instantly, so the number on screen changes exactly as fast as you click it. The actual API call to update the quantity on Shopify’s side waits for a brief pause in activity before firing, batching up rapid clicks into a single request rather than firing one for every click. The result is a stepper that feels instantaneous and never falls out of sync with itself, no matter how fast you click.
A cart that knows what free delivery costs.
Ungarnished ships to many countries, but in the UK, free delivery kicks in above a certain spend. Rather than leaving that as a line of text buried in the cart or, worse, a surprise at checkout, the cart shows a sliding progress indicator: how much further you need to spend to unlock free UK delivery, updating live as you add or remove items. It’s a small nudge, but it’s an honest one — it tells you something useful about your own order rather than trying to manipulate you into spending more, and it only appears for users actually browsing from the UK, where it’s relevant.
Fixing the multi-tab problem.
Here’s a scenario anyone who shops online has lived through: you’ve got a product open in one tab and a comparable one in another, so you can weigh them up side by side. You add one to the cart. You flick back to the other tab, and it has no idea anything happened — no updated total, no indication the cart changed at all, possibly not even after a refresh depending on how the site’s caching behaves.
Because the cart’s canonical state during a session lives in local storage, this turned out to be solvable with the browser’s own storage event. Every open tab attaches a listener — using Svelte’s on function from svelte/events rather than the raw DOM API, which keeps it consistent with the rest of the codebase and gets cleaned up automatically. When one tab writes a change to local storage, every other tab picks it up straight away and updates its own view of the cart to match — no server request needed to reconcile it. Add something in one tab, and the running total updates in every other tab you’ve got open on the same site, in real time.
The cart drawer component, and the accessibility details that don’t get skipped.
The cart itself is a drawer that slides in from the right-hand side of the screen. On larger screens it’s a fixed-width panel — capped at a max-width of around 480px, user font size dependent. On mobile, it expands to fill the entire viewport instead, since a narrow drawer on a small screen is just fiddly to use and reads as an afterthought. It opens automatically when you add an item, both as confirmation that the action worked and as a gentle nudge toward checking out — but it’s never a trap. You can close it by clicking anywhere outside it, by pressing the top-right X, by hitting Escape, or by clearing every item from the cart.
On mobile, closing the cart needed its own thought. A swipe-back gesture on a phone’s browser normally means "go back a page," which is the last thing you want to trigger when someone’s just trying to dismiss a drawer. The cart’s open and closed state is tied into the page’s history stack: opening the cart pushes a new history entry, and closing it pops that entry back off. That means a swipe-back gesture closes the cart rather than navigating away from the page entirely, matching what people actually expect from the gesture without any of the side effects.
Every interactive element in the cart has proper aria labelling, so it holds up under a screen reader as well as it does visually. And for anyone with prefers-reduced-motion set at the OS level, the drawer’s animations are switched off entirely rather than just toned down — the cart still opens and closes exactly the same, just without the sliding motion for people who’ve asked not to see it.
Why bother with any of this?
Realistically, none of this was strictly necessary to sell some cocktail books and merch. A conventional Shopify cart would have worked. But “would have worked” is a low bar, and it’s rarely the reason people remember a piece of digital work. The gap between a store that functions and one that feels considered is almost always in details like these — the ones a customer might never consciously notice, but that add up to a site that feels quick, honest, and looked-after rather than merely adequate.
That’s the standard we try to hold headless commerce builds to, and it’s exactly the kind of project — a premium brand, backed by someone genuinely respected in their field, that cares as much about craft as we do — that makes this kind of engineering worth doing properly.
