SSR-safe runtime logic for handling interactivity in React server components without using
- use clientReact Zero-UI's pre-rendered data-attribute model.
The Problem: A single onClick event forces your entire component tree to become client-rendered. In Next.js, this means shipping extra JavaScript, losing SSR benefits, and adding hydration overhead, all for basic interactivity.
The Solution: This design creates the perfect bridge between static HTML and interactive UX, while maintaining:
- Server-rendered performance
- Zero JavaScript bundle overhead
- Instant visual feedback
Why sacrifice server-side rendering for a simple click handler when 300 bytes of runtime can handle all the clicks in your app?
The core runtime entrypoint that enables client-side interactivity in server components:
How it works:
- Single Global Listener - Registers one click event listener on
document - Smart Detection - Listens for clicks on elements with
data-uiattributes - Directive Parsing - Interprets
data-uidirectives in this format.
+ data-ui="global:key(val1,val2,...)" -> flips data-key on document.body
+ data-ui="scoped:key(val1,val2,...)" -> flips data-key on closest ancestor/self- Round-Robin Cycling - Cycles through values in sequence
- Instant DOM Updates - Updates DOM immediately for Tailwind responsiveness
Note: Guards against duplicate initialization using
window.__zeroflag.
Utility functions that generate valid data-ui attributes for JSX/TSX:
Global Example:
zeroSSR.onClick("theme", ["dark", "light"]);
// Returns: { 'data-ui': 'global:theme(dark,light)' }Scoped Example:
scopedZeroSSR.onClick("modal", ["open", "closed"]);
// Returns: { 'data-ui': 'scoped:modal(open,closed)' }Development Validation:
- Ensures keys are kebab-case
- Validates at least one value is provided
npm install @react-zero-ui/core@0.3.1-beta.2Run your development server to generate the required variant map:
npm run devThis creates .zero-ui/attributes.ts containing the variant map needed for runtime activation.
"use client";
import { variantKeyMap } from "path/to/.zero-ui/attributes";
import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime";
activateZeroUiRuntime(variantKeyMap);
export const InitZeroUI = () => null;import { InitZeroUI } from "path/to/InitZeroUI";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<InitZeroUI />
{children}
</body>
</html>
);
}import { zeroSSR } from "@react-zero-ui/core/experimental";
<div {...zeroSSR.onClick("theme", ["dark", "light", "spanish"])}>Click me to cycle themes!</div>;Pair with Tailwind variants:
<div class="theme-dark:bg-black theme-light:bg-white theme-spanish:bg-red-500">Interactive Server Component!</div>import { scopedZeroSSR } from "@react-zero-ui/core/experimental";
// Scopes based on matching data-* attribute (e.g. data-modal)
<div data-modal="open">
<button {...scopedZeroSSR.onClick("modal", ["open", "closed"])}>Toggle Modal</button>
</div>;- No React State - Zero re-renders involved
- Pure DOM Mutations - Works entirely via
data-*attribute changes - Server Component Compatible - Full compatibility with all server components
- Tailwind-First - Designed for conditional CSS classes
| Feature | Description |
|---|---|
activateZeroUiRuntime() |
Enables click handling on static components via data-ui |
zeroSSR / scopedZeroSSR |
Generate valid click handlers as JSX props |
| Runtime Overhead | ~300 bytes total |
| React Re-renders | Zero |
| Server Component Support | Full compatibility |
Source Code: See experimental for implementation details.
The bridge between static HTML and interactive UX
No state. No runtime overhead. Works in server components. ZERO re-renders.