Case study · Personal project
The Fridge
View on GitHubA framework-free family-management PWA, built with native Web Components — shared shopping lists, tasks, and events for my own family. Equal parts practical family tool and a serious Web Components sandbox.
01 Why I built it
When I became a parent, life admin got hard. Every waking moment was preoccupied with a very demanding small human, and the usual ways of keeping on top of things fell apart. I built The Fridge so my wife and I could share the load — one place for shopping lists, tasks, and events, in sync on both our phones.
It was also an excuse. I'd wanted to try a serious project in native Web Components ever since Josh Wardle built Wordle that way, with no framework, and sold it for a small fortune. That was the nudge to see how far I could get with just the platform.
02 What I built
The Fridge is an installable PWA for couples, families, and sharehouses: shared shopping
lists, tasks, events, and photos, all collaborative and live across devices. It's built from
the platform up, around 50 native Web Components, with Vite for the build, Tailwind for
styling, and Supabase (Postgres and real-time) as the backend, deployed on Netlify. It caches
to localStorage so it opens instantly and tolerates a flaky
connection. It has a few playful touches too: type "apples" into the list and you get a burst
of 🍎 confetti.
~50 native Web Components, no framework, real-time across devices via Supabase, installable as a PWA.
03 Highlights
Clear, beautiful, mobile-first
It had to be mobile-first, since the phone is where household admin happens. I wanted it very clear but also good-looking, so most of the design time went into the typography and a simple colour palette. Clarity first, with enough warmth that it never felt like a chore app.
Native Web Components, no framework
No framework at all, just custom elements, the DOM, and a thin base class. Components
register themselves with customElements.define and manage their
own rendering. Shadow DOM shows up only where encapsulation earns it (modals), with Tailwind
doing the rest in the light DOM. I wanted to see how far the platform alone could carry a
real app.
Real-time sync with Supabase
The shopping list is the heart of the app, so it had to feel properly shared. A Supabase Postgres-changes channel pushes every insert, update, and delete to every connected device, so when one person ticks off milk at the shop it vanishes from everyone's list. Optimistic local updates make it feel instant, and the subscription keeps everyone in sync.
// Live shopping list — every device stays in sync
supabase.channel('shopping-list')
.on('postgres_changes', { event: '*', table: 'list_items' }, (payload) => {
if (payload.eventType === 'INSERT') this.onDBInsert(payload)
if (payload.eventType === 'UPDATE') this.onDBUpdate(payload)
if (payload.eventType === 'DELETE') this.onDBDelete(payload)
})
.subscribe() 04 What it looked like
From direction, to design, to the built app.
05 What I learned
Three things stuck. I learned a lot of Supabase: auth, Postgres, and real-time channels. I learned how to orchestrate state and events between Web Components without a framework holding my hand. And I learned why JS frameworks exist in the first place: the platform gets you a long way, but the biggest thing missing from Web Components is a real templating engine. By the end, I really missed JSX.