Web components offer native UI primitives. However, they often introduce complex lifecycles, Shadow DOM issues, and verbose code.
SXO + Reactive Component solves this. It combines server-side rendering with vanilla JSX and a signal-based reactive system. You get declarative authoring, instant page loads, and progressive enhancement without hydration.
Server-First, Reactivity-Second
SXO inverts the traditional model:
- Server: Renders semantic HTML wrapped in custom elements (e.g.,
<product-card>). - Client: A lightweight reactive runtime (~4.8KB) binds state and behavior to the existing DOM.
There is no hydration pass. The HTML is the source of truth.
Core Concepts
Reactive Components use $-prefixed attributes to link HTML to behavior.
-
$state="key": Initializes state from text content. Updates reflect automatically. -
$bind-*="key": Two-way binds attributes (likevalueorchecked) to state. -
$on*="method": Binds events to client-side handlers. -
define(tag, setup): Defines client logic, state, effects ($effect), and events ($on).
Product Card
Lets create a product card with a quantity selector and a "Favorite" toggle.
1. Server Component (JSX)
Renders accessible HTML on the server. No client-side logic is included here.
// src/components/product-card.jsx
export function ProductCard({ title, price, image }) {
return (
<product-card class="card">
<img src={image} alt={title} />
<h3>{title}</h3>
<p>${price}</p>
<div class="controls">
<button type="button" $onclick="decrement" aria-label="Decrease">-</button>
<input type="number" $bind-value="qty" value="1" min="1" aria-label="Qty" />
<button type="button" $onclick="increment" aria-label="Increase">+</button>
</div>
<button
type="button"
$onclick="toggleFavorite"
$bind-attr="favoriteAttrs"
aria-pressed="false"
class="btn-icon"
>
♥
</button>
</product-card>
);
}
2. Client Enhancer
Attaches behavior to the server-rendered markup.
// src/components/product-card.client.js
import { define } from "@qery/reactive-component";
define("product-card", ({ $state, $on, $compute }) => {
// 1. Initialize State
$state.qty = 1;
$state.isFavorite = false;
// 2. Compute aria-pressed attribute declaratively
// and falsy values remove them
$compute("favoriteAttrs", ["isFavorite"], (isFavorite) => ({
"aria-pressed": isFavorite ? "true" : "false",
}));
// 3. Define Actions
$on.increment = () => $state.qty++;
$on.decrement = () => {
if ($state.qty > 1) $state.qty--;
};
$on.toggleFavorite = () => {
$state.isFavorite = !$state.isFavorite;
};
});
3. Page Composition
Compose the component in a server route.
// src/pages/products/index.jsx
import { ProductCard } from "../../components/product-card.jsx";
export default function ShopPage({ products }) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Shop</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<main>
${products.map(p => ProductCard(p)).join('')}
</main>
</body>
</html>
`;
}
Key Advantages
- Instant Interaction: The UI is visible immediately. No loading spinners or layout shifts.
- Simple State: Signal-based state management without complex providers.
- Zero-Config: Automatic bundling of per-route client entries.
Top comments (1)
I like the concept, but would love to see it taken further. If JS fails, it doesn’t look like there is a server-side fallback. Were this a true progressive enhancement, the increment/decrement and favoriting logic would have a fallback server path — e.g., form submission — that is overloaded with the JS-bound behavior. And any actions that are purely JS-driven — such as geolocation — should be injected via JavaScript, once you know they can run.
If anything causes JavaScript execution on the client to stop (which can and does happen), users will be shown UI that is non-functional, which is frustrating at best and absolutely leads to loss of sales, leads, etc.
If there is a critical task users must be able to do, you should have a fallback strategy for enabling them to do it no matter what.