← All projects

Learning project · Frontend R&D

Blog Bits

A small-scale CMS I built to sharpen my frontend — and to test a hunch: that a Shadow-DOM component library plus targeted React islands can replace React where it isn't earning its weight.

Web Components Shadow DOM React (islands) HTMX

Overview

A CMS as a frontend proving ground

Blog Bits started as a way to refine my frontend skills on something real: a small content-management system. But the goal underneath was architectural — to see how far you can get with plain Web Components and the Shadow DOM before reaching for a full framework.

The result is a lightweight UI library that handles the static-and-structural work, with islands of micro React apps dropped in only where genuine interactivity earns it — for example, an embeddable chat widget that can live inside an HTMX page or another Shadow-DOM-based site.

Role Solo — build & research
Theme Islands architecture
Goal Less JS, same UX

Architecture

Server-driven, hydrated only where needed

The CMS renders server-driven HTML enhanced with HTMX. Reusable pieces are Shadow-DOM components with fully scoped styles. Only the genuinely interactive pockets — a chat widget, a rich editor — are hydrated as small React islands, so the JavaScript you ship maps to the interactivity you actually use.

CMS core Content, authoring, server-rendered pages backend
Shadow-DOM UI library Encapsulated, reusable components Web Components
HTMX layer Server-driven interactivity, no SPA HTMX
↓  hydrate selectively  ↓
React islands Chat widget, rich editor — only where needed React

Reach for React last, not first — the library and HTMX carry most of the UI, islands cover the rest.

static HTML  ·  Shadow-DOM components  ·  0 kB JS
💬 chat widget
React island
✎ rich editor
React island
static HTML  ·  server-rendered  ·  0 kB JS

Only the genuinely interactive pockets (the accented boxes) become React "islands" — the rest of the page ships zero framework code.

Sample UI experience

Content editor

blogbits.local/admin/posts
Blog Bits
Posts
Pages
Media
Components
Settings
Posts New post
TitleStatus
Shadow DOM, revisited Published
Islands over frameworks Draft
Embedding a chat widget Draft
B I ↳
Editor toolbar · React island
💬
Chat widget · embeddable
</>
Shadow component

A representative view — a mostly server-rendered admin where only the toolbar and chat are React, each isolated in its own island.

Engineering challenges

What was hard, and what I learned

01

Where to draw the hydration boundary

Problem

Islands architecture only pays off if the seam between server-rendered and interactive is drawn well — too coarse and you ship a framework for static content; too fine and the page fragments into dozens of tiny runtimes.

Approach

Make interactivity the unit of decision: a region becomes an island only when it owns real client state, and everything else stays server-rendered.

02

Island state vs. server-driven swaps

Problem

HTMX replaces regions of the DOM from the server, while an island holds its own in-memory state. Reconciling two sources of truth over the same page is the core logical problem.

Approach

Give islands stable identity and a defined lifecycle tied to HTMX's swap events, so ownership of each region is unambiguous at any moment.

03

A stable component contract

Problem

A reusable library lives or dies by its API. Components need a predictable contract — inputs, events, composition — that doesn't leak their internals to consumers.

Approach

Treat each Web Component as a black box with a declared attribute/event interface, so pages compose against a contract rather than an implementation.

04

Keeping React off the critical path

Problem

The whole premise fails if React ends up in the base bundle — then every page pays for a framework most of them don't use.

Approach

Isolate React into per-island bundles loaded only when that island mounts, keeping the default page's dependency graph free of it entirely.