|
1 | | -# React Zero-UI (Beta) |
| 1 | +# React Zero‑UI (Beta) |
2 | 2 |
|
3 | | -**Instant UI state updates. ZERO React re-renders. ZERO runtime overhead.** Update the UI instantly, manage global UI state from anywhere. No prop drilling. get started with one command in your existing React app. `npx create-zero-ui` |
| 3 | + **Instant UI state updates. ZERO React re‑renders. <1 KB runtime.** |
4 | 4 |
|
5 | | -[](https://www.npmjs.com/package/@austinserb/react-zero-ui) |
6 | | -[](https://opensource.org/licenses/MIT) |
7 | | - |
| 5 | + Pre‑render your UI once, flip a `data-*` attribute to update — that's it. |
| 6 | + |
| 7 | +< a href= "https://www.npmjs.com/package/@austinserb/react-zero-ui" target= "_blank" rel= "noopener noreferrer">< img src= "https://badgen.net/bundlephobia/min/@austinserb/[email protected]" alt= "npm version" /> |
| 8 | +</a> |
8 | 9 |
|
9 | | -## Why Zero-UI? |
| 10 | +<a href="https://www.npmjs.com/package/@austinserb/react-zero-ui" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/npm/v/@austinserb/react-zero-ui" alt="npm version" /></a> <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>  |
10 | 11 |
|
11 | | -Traditional React state triggers RE-renders for every UI change. Switching themes? That's every component RE-rendering. Opening a menu? Same story. |
| 12 | +--- |
| 13 | + |
| 14 | +## 🚀 Live Demo |
12 | 15 |
|
13 | | -**Enter "PRE-rendering"** |
14 | | -Zero-UI bypasses React entirely for pure UI state. Instead of re-renders, it: |
| 16 | +| Example | Link | What it shows | Link to Code | |
| 17 | +| --------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------- |------------------------------------------------------------------------------ | |
| 18 | +| Interactive menu with render tracker | <a href="https://react-zero-ui.vercel.app/" target="_blank" rel="noopener noreferrer"><strong>Main Demo↗</strong></a> | Compare Zero‑UI vs. React side‑by‑side while toggling a menu. | <a href="https://github.com/Austin1serb/React-Zero-UI/tree/main/examples/demo" target="_blank" rel="noopener noreferrer">Github</a> | |
| 19 | +| React benchmark (10 000 nested nodes) | <a href="https://react-zero-ui.vercel.app/react" target="_blank" rel="noopener noreferrer"><strong>React 10k↗</strong></a> | How long the traditional React render path takes. | <a href="https://github.com/Austin1serb/React-Zero-UI/tree/main/examples/demo/src/app/react" target="_blank" rel="noopener noreferrer">Github</a> | |
| 20 | +| Zero‑UI benchmark (10 000 nested nodes) | <a href="https://react-zero-ui.vercel.app/zero-ui" target="_blank" rel="noopener noreferrer"><strong style="text-align: nowrap;">Zero‑UI 10k↗</strong></a> | Identical DOM, but powered by Zero‑UI's `data-*` switch. | <a href="https://github.com/Austin1serb/React-Zero-UI/tree/main/examples/demo/src/app/zero-ui" target="_blank" rel="noopener noreferrer">Github</a> | |
| 21 | + |
| 22 | +--- |
15 | 23 |
|
16 | | -- Pre-renders CSS styles and keeps them in the DOM |
17 | | -- For state changes it flips a `data-*` attribute key. |
18 | | -- Thats it. |
| 24 | +## 🧐 Why Zero‑UI? |
19 | 25 |
|
20 | | -**Result:** UI updates that are 10-50x faster. |
| 26 | +Every `setState` in React triggers the full VDOM → Diff → Reconciliation → Paint pipeline. For *pure UI state* (themes, menus, toggles) that work is wasted. |
21 | 27 |
|
22 | | -### Performance Comparison |
| 28 | +**Zero‑UI introduces "*PRE‑rendering*":** |
23 | 29 |
|
24 | | -Apple M1 |
| 30 | +1. Tailwind variants for every state are **generated at build‑time**. |
| 31 | +2. The app **pre‑renders once**. |
| 32 | +3. Runtime state changes only **flip a `data-*` attribute on `<body>`**. |
25 | 33 |
|
26 | | -| Nodes | React State | Zero-UI | Improvement | |
27 | | -| ------ | ----------- | ------- | ----------- | |
28 | | -| 1,000 | ~50ms | ~5ms | 10x faster | |
29 | | -| 5,000 | ~180ms | ~15ms | 12x faster | |
30 | | -| 10,000 | ~400ms | ~20ms | 20x faster | |
| 34 | +Result → **5-10× faster visual updates** with **ZERO additional bundle cost**. |
31 | 35 |
|
32 | | -## Quick Start |
| 36 | +### 📊 Micro‑benchmarks (Apple M1) |
33 | 37 |
|
34 | | -**Prerequisites:** Tailwind v4 must be initialized |
| 38 | +| Nodes updated | React state | Zero‑UI | Speed‑up | |
| 39 | +| ------------- | ----------- | ------- | -------- | |
| 40 | +| 10,000 | \~50 ms | \~5 ms | **10×** | |
| 41 | +| 25,000 | \~180 ms | \~15 ms | **12×** | |
| 42 | +| 50,000 | \~300 ms | \~20 ms | **15×** | |
| 43 | + |
| 44 | +Re‑run these numbers yourself via the links above. |
| 45 | + |
| 46 | +--- |
35 | 47 |
|
36 | | -CLI script - in your existing Next or Vite App's root: |
| 48 | +## ⚡️ Quick Start |
| 49 | + |
| 50 | +> **Prerequisite:** Tailwind CSS v4 must already be initialized in your project. |
37 | 51 |
|
38 | 52 | ```bash |
| 53 | +# Inside an existing *Next.js (App Router)* or *Vite* repo |
39 | 54 | npx create-zero-ui |
40 | 55 | ``` |
41 | 56 |
|
42 | | -## Manual Installation |
| 57 | +That's it — the CLI patch‑installs the required Babel & PostCSS plugins and updates `configs` for you. |
| 58 | + |
| 59 | +### Manual Install |
43 | 60 |
|
44 | 61 | ```bash |
45 | 62 | npm install @austinserb/react-zero-ui |
46 | 63 | ``` |
47 | 64 |
|
48 | | -### Setup |
| 65 | +Then follow **Setup →** for your bundler. |
| 66 | + |
| 67 | +--- |
49 | 68 |
|
50 | | -**Prerequisites:** Tailwind v4 must be initialized. [tailwind set up ]("tailwind.com") |
| 69 | +## 🔧 Setup |
51 | 70 |
|
52 | | -#### Vite |
| 71 | +### Vite |
53 | 72 |
|
54 | 73 | ```js |
55 | 74 | // vite.config.* |
56 | 75 | import { zeroUIPlugin } from '@austinserb/react-zero-ui/vite'; |
57 | 76 |
|
58 | 77 | export default { |
59 | | - //*REMOVE TAILWIND PLUGIN* Zero-UI extends tailwinds plug-in |
60 | | - plugins: [zeroUIPlugin()], |
| 78 | + // ❗️Remove the default `tailwindcss()` plugin — Zero‑UI extends it internally |
| 79 | + plugins: [zeroUIPlugin()], |
61 | 80 | }; |
62 | 81 | ``` |
63 | 82 |
|
64 | | -#### Next.js |
| 83 | +### Next.js (App Router) |
65 | 84 |
|
66 | | -#### 1. Spread bodyAttributes on `<body>` in Layout |
| 85 | +1. **Spread `bodyAttributes` on `<body>`** in your root layout. |
67 | 86 |
|
68 | | -```jsx |
69 | | -import { bodyAttributes } from '@zero-ui/attributes'; |
70 | | -//or |
71 | | -import { bodyAttributes } from '../.zero-ui/attributes'; |
72 | | - |
73 | | -export default function RootLayout({ children }) { |
74 | | - return ( |
75 | | - <html> |
76 | | - <body {...bodyAttributes}>{children}</body> |
77 | | - </html> |
78 | | - ); |
79 | | -} |
80 | | -``` |
| 87 | + ```tsx |
| 88 | + // app/layout.tsx |
| 89 | + import { bodyAttributes } from '@austinserb/react-zero-ui/attributes'; |
| 90 | + // or: import { bodyAttributes } from '../.zero-ui/attributes'; |
81 | 91 |
|
82 | | -#### 2. Add PostCSS Plugin |
| 92 | + export default function RootLayout({ children }) { |
| 93 | + return ( |
| 94 | + <html lang="en"> |
| 95 | + <body {...bodyAttributes}>{children}</body> |
| 96 | + </html> |
| 97 | + ); |
| 98 | + } |
| 99 | + ``` |
83 | 100 |
|
84 | | -```js |
85 | | -// postcss.config.js |
86 | | -module.exports = { |
87 | | - plugins: { |
88 | | - ['@austinserb/react-zero-ui/postcss'] |
89 | | - //*tailwindcss MUST come AFTER Zero-UI |
90 | | - ['@tailwindcss'] |
91 | | - } |
92 | | -} |
93 | | -``` |
| 101 | +2. **Add the PostCSS plugin (must come *before* Tailwind).** |
94 | 102 |
|
95 | | -## Usage |
| 103 | + ```js |
| 104 | + // postcss.config.js |
| 105 | + module.exports = { |
| 106 | + plugins: { |
| 107 | + '@austinserb/react-zero-ui/postcss': {}, |
| 108 | + tailwindcss: {}, |
| 109 | + }, |
| 110 | + }; |
| 111 | + ``` |
96 | 112 |
|
97 | | -**Basic Theme Switching** |
| 113 | +--- |
| 114 | + |
| 115 | +## 🏄♂️ Usage |
| 116 | + |
| 117 | +### Theme toggle example |
98 | 118 |
|
99 | 119 | ```tsx |
100 | 120 | import { useUI } from '@austinserb/react-zero-ui'; |
101 | 121 |
|
102 | | -function ThemeToggle() { |
103 | | - const [, setTheme] = useUI<"light | dark">('theme', 'light'); |
| 122 | +export function ThemeToggle() { |
| 123 | + const [, setTheme] = useUI<'light' | 'dark'>('theme', 'light'); |
104 | 124 |
|
105 | | - <button onClick={() => setTheme('dark')}> |
106 | | - Switch Theme |
107 | | - </button> |
| 125 | + return ( |
| 126 | + <button onClick={() => setTheme('dark')}>Switch to dark</button> |
| 127 | + ); |
| 128 | +} |
108 | 129 | ``` |
109 | 130 |
|
110 | | -**Consume the state in any component with tailwind!** |
| 131 | +Consume the state anywhere with Tailwind variants: |
111 | 132 |
|
112 | 133 | ```jsx |
113 | | -className = 'theme-light:bg-white theme-dark:bg-black'; |
| 134 | +<div className="theme-light:bg-white theme-dark:bg-black" /> |
114 | 135 | ``` |
115 | 136 |
|
116 | | -**Mutate the state in any component!** |
| 137 | +--- |
117 | 138 |
|
118 | | -```jsx |
| 139 | +## 🛠 API |
119 | 140 |
|
120 | | -function UnrelatedPage() |
121 | | - const [, setTheme] = useUI('theme', 'light'); |
| 141 | +### `useUI(key, defaultValue)` |
122 | 142 |
|
123 | | - <button onClick={setTheme("dark")}> |
124 | | - </button> |
| 143 | +```ts |
| 144 | +const [staleValue, setValue] = useUI<'open' | 'closed'>( |
| 145 | + 'sidebar', |
| 146 | + 'closed', |
| 147 | +); |
125 | 148 | ``` |
126 | 149 |
|
127 | | -**Use with complex Tailwind Variants** |
| 150 | +* `key` → becomes `data-{key}` on `<body>`. |
| 151 | +* `defaultValue` → optional, prevents FOUC. |
| 152 | +* **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). |
| 153 | + |
| 154 | +### Tailwind variants |
128 | 155 |
|
129 | 156 | ```jsx |
130 | | -clasName = 'md:theme-dark:bg-black md:peer-checked:theme-light:hidden'; |
| 157 | +<div className="sidebar-open:translate-x-0 sidebar-closed:-translate-x-full" /> |
131 | 158 | ``` |
132 | 159 |
|
133 | | -## How It Works |
134 | | -
|
135 | | -1. **State Store**: The `useUI` hook writes to `data-*` attributes on the `<body>` tag instead of React state |
136 | | -
|
137 | | - ```html |
138 | | - <body |
139 | | - data-theme="dark" |
140 | | - data-accent="blue" |
141 | | - data-sidebar="open"></body> |
142 | | - ``` |
143 | | -
|
144 | | -2. **Babel Transform**: Automatically detects all `useUI` variants in your code during build |
145 | | -
|
146 | | -3. **PostCSS Plugin**: Generates Tailwind variant classes for every detected state |
147 | | -
|
148 | | - ```css |
149 | | - .theme-dark\:bg-gray-900 { |
150 | | - background-color: rgb(17 24 39); |
151 | | - } |
152 | | - ``` |
153 | | -
|
154 | | -4. **Instant Updates**: When you call a setter, only the data attribute changes. The browser doesn't have to create a VDOM, and compare it to the current HTML tree (Diffing), and then determine if an update is needed (Reconciliation), and then apply that update (Re-render) by injecting into the html tree. Then the browser has to compile the new injected Html and Css, then compute style, THEN paint the pixels. |
155 | | -
|
156 | | -## API |
| 160 | +Any `data-{key}="{value}"` pair becomes a variant: `{key}-{value}:`. |
157 | 161 |
|
158 | | -### `useUI(key: string, SSRValue: string)` |
159 | | -
|
160 | | -Returns a tuple similar to `useState`, but the first value is intentionally stale. |
| 162 | +--- |
161 | 163 |
|
162 | | -```jsx |
163 | | -const [staleValue, setValue] = useUI('theme', 'light'); |
164 | | -``` |
| 164 | +## 🧬 How it works |
165 | 165 |
|
166 | | -- `key`: The UI state key (becomes `data-{key}` on body) |
167 | | -- `defaultValue`: Initial value if not set |
168 | | -- Returns: `[staleValue, setValue]` |
| 166 | +1. **`useUI`** → writes to `data-*` attributes on `<body>`. |
| 167 | +2. **Babel plugin** → scans code, finds every `key/value`, injects them into **PostCSS**. |
| 168 | +3. **PostCSS plugin** → generates static Tailwind classes **at build‑time**. |
| 169 | +4. **Runtime** → changing state only touches the attribute — no VDOM, no reconciliation, no re‑paint. |
169 | 170 |
|
170 | | -**note:** normaly used as const `const [,setValue]=useUI()` to denote that the value is stale and will not update |
| 171 | +--- |
171 | 172 |
|
172 | | -### Tailwind Variants |
| 173 | +## ✅ Features |
173 | 174 |
|
174 | | -Use the pattern `{key}-{value}:` as a Tailwind variant: |
| 175 | +* **Zero React re‑renders** for UI‑only state. |
| 176 | +* **Global setters** — call from any component or util. |
| 177 | +* **Tiny**: < 1 KB gzipped runtime. |
| 178 | +* **TypeScript‑first**. |
| 179 | +* **SSR‑friendly** (Next.js & Vite SSR). |
| 180 | +* **Framework‑agnostic CSS** — generated classes work in plain HTML / Vue / Svelte as well. |
175 | 181 |
|
176 | | -```jsx |
177 | | -<div className="theme-light:bg-white theme-dark:bg-black" /> |
178 | | -<div className="accent-red:text-red-500 accent-blue:text-blue-500" /> |
179 | | -<div className="sidebar-open:translate-x-0 sidebar-closed:-translate-x-full" /> |
180 | | -``` |
| 182 | +--- |
181 | 183 |
|
182 | | -## Features |
| 184 | +## 🏗 Best Practices |
183 | 185 |
|
184 | | -- ✅ **Zero React re-renders** for UI state changes |
185 | | -- ✅ **No Context providers** needed |
186 | | -- ✅ **Works globally** - call setters from anywhere |
187 | | -- ✅ **TypeScript support** out of the box |
188 | | -- ✅ **SSR compatible** with Next.js |
189 | | -- ✅ **Tiny bundle size** (954bytes) |
190 | | -- ✅ **Framework agnostic CSS** - the generated CSS works everywhere |
| 186 | +1. **UI state only** → themes, layout toggles, feature flags. |
| 187 | +2. **Business logic stays in React** → fetching, data mutation, etc. |
| 188 | +3. **Kebab‑case keys** → e.g. `sidebar-open`. |
| 189 | +4. **Provide defaults** to avoid Flash‑Of‑Unstyled‑Content. |
191 | 190 |
|
192 | | -## Best Practices |
| 191 | +--- |
193 | 192 |
|
194 | | -1. **Use for UI-only state**: Themes, sidebar states, UI flags |
195 | | -2. **Not for business logic**: Keep using React state for data that affects logic |
196 | | -3. **Consistent naming**: Prefer kebab-case for keys (`sidebar-state`, not `sidebarState`) |
197 | | -4. **Default values**: Always provide default value for |
198 | | - `useUI('key', 'value')` to avoid FOUC. |
| 193 | +## 🤝 Contributing |
199 | 194 |
|
200 | | -## Contributing |
| 195 | +PRs & issues welcome! Please read the [Contributing Guide](CONTRIBUTING.md). |
201 | 196 |
|
202 | | -We love contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. |
| 197 | +--- |
203 | 198 |
|
204 | | -## License |
| 199 | +## 📜 License |
205 | 200 |
|
206 | | -MIT © Austin Serb |
| 201 | +[MIT](LICENSE) © Austin Serb |
207 | 202 |
|
208 | 203 | --- |
209 | 204 |
|
210 | | -Built with ❤️ for the React community. If Zero-UI helps your app feel snappier, consider [starring the repo](https://github.com/austinserb/zero-ui)! |
| 205 | +Built with ❤️ for the React community. If Zero‑UI makes your app feel snappier, please ⭐️ the repo! |
0 commit comments