Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/examples
pnpm-lock.yaml
packages/core/__tests__/fixtures/vite/vite.config.ts
packages/core/__tests__/fixtures/next/tsconfig.json
Expand Down
75 changes: 65 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,74 @@
<!-- purpose: AI and codegen agents should use this to generate correct Zero-UI examples -->

# Guidelines for AI Agents in this Repo

This repository contains **React Zero-UI**, a library for global UI state without React re-renders.
Use these tips when working with the codebase or generating examples.

## How React Zero-UI works

1. `useUI()` writes to `document.body.dataset` using keys you specify (e.g. `theme` β†’ `data-theme`).
2. Build tooling scans for all keys and values, generating CSS variants for each.
3. When a setter is called, the corresponding body attribute changes instantly with no React re-render.
1. `useUI()` writes to `document.body.dataset` using keys you specify.

```tsx
const [staleValue, setValue] = useUI<'open' | 'closed'>('sidebar', 'closed');
```

- `key` β†’ becomes `data-{key}` on `<body>` (e.g., `sidebar` β†’ `data-sidebar="closed"`).
- `defaultValue` β†’ used for SSR to avoid FOUC.
- The first value is **always stale** β€” do NOT rely on it for reactive updates.

2. Consumption is done strictly with tailwind variant classNames:

```html
<div className="sidebar-open:translate-x-0 sidebar-closed:-translate-x-full" />
```

3. Build-time tooling scans all `useUI()` keys and values, then generates matching Tailwind variants.

4. At runtime, calling the setter updates the `data-*` attribute on `<body>` immediately. No VDOM. No re-renders.

---

## Best Practices for AI Agents

- βœ… Use `useUI()` **only for UI state**: themes, layout flags, open/closed toggles, etc.
- βœ… Prefer **kebab-case keys**: e.g. `sidebar-open`, `theme-dark`.
- βœ… Always provide a `defaultValue`: prevents FOUC and enables SSR.
- βœ… Do **NOT** use the first value from `useUI()` for logic β€” it DOES NOT UPDATE.
- βœ… You can call setters **from anywhere** in the app β€” no prop drilling or context needed.
- βœ… Tailwind classes must use `key-value:` pattern:

- `theme-dark:bg-black`
- `accent-blue:text-blue-500`
- `sidebar-open:translate-x-0`

---

## Example: Toggle Theme

```tsx
// Set state
const [, setTheme] = useUI<'light' | 'dark'>('theme', 'light');
<button onClick={() => setTheme('dark')}>Switch to dark</button>;
```

```html
<!-- React component with Tailwind variant -->
<div className="theme-light:bg-white theme-dark:bg-black" />
```

---

## What NOT to do

- ❌ Don't use `useUI()` for business logic or data fetching
- ❌ Don't rely on the first tuple value for reactivity
- ❌ Don't use camelCase keys (will break variant generation)

---

## Summary

## Best practices
**React Zero-UI is a ZERO re-render UI state engine with global state baked in.** It replaces traditional VDOM cycles with `data-*` attribute flips and compile-time CSS. No React context. No prop drilling. No runtime cost.

- Only use `useUI` for UI-only state (themes, flags, etc.).
- Prefer kebab-case keys (`sidebar-open`) so generated variants are predictable.
- Always pass a default value to `useUI(key, defaultValue)` to avoid flashes during SSR.
- The first value is ALWAYS STALE, do not use it if you need reactivity.
- Mutate the state anywhere in the app: `const [, setTheme] = useUI('theme', 'light');` then call `setTheme('dark')`.
- Compose Tailwind classes anywhere with the pattern `key-value:` like `theme-dark:bg-black`.
Think of it as writing atomic Tailwind variants for every UI state β€” but flipping them dynamically at runtime without re-rendering anything.
27 changes: 0 additions & 27 deletions examples/demo/.prettierignore

This file was deleted.

16 changes: 0 additions & 16 deletions examples/demo/.prettierrc.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/demo/.zero-ui/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const bodyAttributes = {
"data-active": "zero",
"data-menu-open": "false",
"data-mobile-menu": "closed",
"data-scrolled": "down",
"data-scrolled": "up",
"data-theme": "light",
"data-theme-test": "light"
};
2 changes: 1 addition & 1 deletion examples/demo/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
/* config options here */
/* config options here */
};

export default nextConfig;
70 changes: 34 additions & 36 deletions examples/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
{
"name": "react-zero",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"type-check": "tsc --noEmit",
"clean": "rm -rf .next"
},
"dependencies": {
"@austinserb/react-zero-ui": "^1.0.19",
"@vercel/analytics": "^1.5.0",
"clsx": "^2.1.1",
"motion": "^12.16.0",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8.5.5",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.12",
"tailwindcss": "^4.1.10",
"typescript": "^5"
}
}
"name": "react-zero",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"type-check": "tsc --noEmit",
"clean": "rm -rf .next"
},
"dependencies": {
"@austinserb/react-zero-ui": "^1.0.21",
"@vercel/analytics": "^1.5.0",
"clsx": "^2.1.1",
"motion": "^12.16.0",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8.5.5",
"tailwindcss": "^4.1.10",
"typescript": "^5"
}
}
4 changes: 1 addition & 3 deletions examples/demo/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// postcss.config.mjs

const config = {
plugins: ['@austinserb/react-zero-ui/postcss', '@tailwindcss/postcss'],
};
const config = { plugins: ['@austinserb/react-zero-ui/postcss', '@tailwindcss/postcss'] };

export default config;
Loading