@kbach/ui 0.1.0-beta.0 → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/KBACH.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Kbach is a Tailwind-like utility CSS framework for React (web) and React Native. Classes are written as `className` strings and resolved to inline styles at render time. On web, stateful and structural CSS rules are injected into a `<style>` tag so they work with the browser cascade. On native, only inline-compatible styles are applied.
4
4
 
5
- One package, `@kbach/ui`, covers both platforms — React web, React Native, and Expo (Expo Go, Expo web, and native builds). The React Native/Expo pieces (a native-aware `ThemeProvider`, the Babel preset, Metro/Babel config helpers) live at the `@kbach/ui/native` and `@kbach/ui/babel` subpaths; everything else is the same import regardless of platform.
5
+ One package, `@kbach/ui`, covers both platforms — React web, React Native, and Expo (Expo Go, Expo web, and native builds). `ThemeProvider` auto-detects native at render time, so it's the same import (`@kbach/ui`) on every platform. The Babel preset and Metro/Babel config helpers (Node-only build tooling) live at the `@kbach/ui/native` and `@kbach/ui/babel` subpaths.
6
6
 
7
7
  `@kbach/native` is deprecated and no longer maintained — its last published npm version stays available as a compatibility shim re-exporting `@kbach/ui`, but new setups should install `@kbach/ui` directly (see below).
8
8
 
@@ -71,10 +71,10 @@ Or the one-liner helper: `const { createKbachConfig } = require('@kbach/ui/nativ
71
71
 
72
72
  ### Wrap app
73
73
  ```jsx
74
- import { ThemeProvider } from '@kbach/ui/native';
74
+ import { ThemeProvider } from '@kbach/ui';
75
75
  <ThemeProvider defaultMode="system"><AppContent /></ThemeProvider>
76
76
  ```
77
- This is a native-aware `ThemeProvider` that wraps the base one reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Don't import the plain `ThemeProvider` from `@kbach/ui` on native; it doesn't have the automatic RN wiring.
77
+ `ThemeProvider` auto-detects React Native at render time and reads `useColorScheme()`/`useWindowDimensions()` automatically no extra props, no separate `/native` import needed.
78
78
 
79
79
  After changing babel.config.js: `npx expo start --clear`
80
80
 
@@ -88,8 +88,7 @@ In a browser (Expo Web, Metro web), `@kbach/ui` switches to the same CSS-class s
88
88
  - Recommended: use the Vite plugin same as the web Static CSS setup above — `import { kbach } from '@kbach/ui/vite'` — and import `kbach.css` in your entry file, for zero runtime cost on the web target too
89
89
  - Not using the Vite plugin (the common case for Expo/Metro web, no Vite build step)? Render `<KbachReset />` once near your root — e.g. Expo Router's root `app/_layout.tsx`, inside `<ThemeProvider>`:
90
90
  ```jsx
91
- import { KbachReset } from '@kbach/ui';
92
- import { ThemeProvider } from '@kbach/ui/native';
91
+ import { KbachReset, ThemeProvider } from '@kbach/ui';
93
92
  <ThemeProvider defaultMode="system"><KbachReset /><Slot /></ThemeProvider>
94
93
  ```
95
94
 
package/README.md CHANGED
@@ -1,28 +1,21 @@
1
1
  # @kbach/ui
2
2
 
3
- Tailwind-like utility classes for React (web). Write `className` strings a custom JSX runtime resolves them at render time. An optional Vite plugin outputs a static `kbach.css` for zero runtime cost.
3
+ Tailwind-like utility classes for React web, React Native, and Expo. Write `className` strings once; a custom JSX runtime resolves them at render time on every platform. An optional Vite plugin outputs a static `kbach.css` for zero runtime cost on web.
4
4
 
5
5
  ```jsx
6
6
  <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
7
7
  <div className="bg-blue-7 hover:bg-blue-8 dark:bg-indigo-6 rounded-lg px-6 py-3" />
8
- <div className="group">
9
- <span className="opacity-0 group-hover:opacity-100 transition" />
10
- </div>
11
- ```
12
-
13
- ## Install
14
-
15
- ```
16
- npm install @kbach/ui
17
8
  ```
18
9
 
19
10
  [npm package](https://www.npmjs.com/package/@kbach/ui)
20
11
 
21
12
  ## Setup
22
13
 
23
- One step is always required, then pick **one** of the two setups below — they're independent, don't mix them.
14
+ ```
15
+ npm install @kbach/ui
16
+ ```
24
17
 
25
- ### Step 1 — JSX runtime (always required)
18
+ ### JSX runtime (always required)
26
19
 
27
20
  **tsconfig.json:**
28
21
 
@@ -36,12 +29,12 @@ That's the only setting needed — Vite, Next.js, and React Router all read it.
36
29
 
37
30
  | Framework | Use |
38
31
  |---|---|
39
- | Vite, React Router library mode, CRA, other Vite-based | **[Static CSS setup](#static-css-setup)** (recommended) — zero runtime cost, catches typos at build time. [Runtime setup](#runtime-setup) is there if you'd rather skip the plugin for now. |
40
- | React Router, framework mode | **[Static CSS setup](#static-css-setup)** — and skip `@vitejs/plugin-react`, see note in that section |
41
- | Next.js | **[Next.js setup](#nextjs-setup)** — Runtime setup, plus one App Router-specific detail |
42
- | React Native, Expo | **[React Native / Expo setup](#react-native--expo-setup)** — different setup entirely (Babel preset, not the JSX runtime step above) |
32
+ | Vite, React Router library mode, CRA, other Vite-based | **[Static CSS](#static-css)** (recommended) — zero runtime cost, catches typos at build time |
33
+ | React Router, framework mode | **[Static CSS](#static-css)** — and skip `@vitejs/plugin-react`, see note below |
34
+ | Next.js | **[Next.js](#nextjs)** — Runtime setup, plus one App Router-specific detail |
35
+ | React Native, Expo | **[React Native / Expo](#react-native--expo)** — different setup entirely (Babel preset, not the JSX runtime step above) |
43
36
 
44
- ## Static CSS setup
37
+ ### Static CSS
45
38
 
46
39
  Vite only, and the recommended setup for any Vite-based app — a build-time plugin writes real CSS into a file you import at build time, so nothing is generated client-side and there's zero runtime cost. Three pieces, all required:
47
40
 
@@ -70,7 +63,7 @@ import './kbach.css';
70
63
 
71
64
  This import is what actually switches the app over to Static CSS — the plugin alone only generates the file; without importing it, runtime injection stays active and you get both at once.
72
65
 
73
- **3. Wrap your app — no `<KbachReset />` here, `kbach.css` already includes the reset:**
66
+ **3. Wrap your app — no `<KbachReset />` here, `kbach.css` already includes the base reset:**
74
67
 
75
68
  ```jsx
76
69
  import { ThemeProvider } from '@kbach/ui';
@@ -80,9 +73,9 @@ export default function Root() {
80
73
  }
81
74
  ```
82
75
 
83
- Done. The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection, so there's no double-styling between this and Runtime setup below. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
76
+ Done. The plugin scans your source at build time and writes CSS between the markers, and warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize — usually a typo.
84
77
 
85
- Using a custom `kbach.config.js`? It needs to be wired in **twice** here — once to `kbach()` above (step 1) so the generated CSS reflects it, and once to `ThemeProvider` (step 3) so dark mode/`useColors()`/animations do too. See [Wiring the config in](#wiring-the-config-in--required-for-both-setups) — easy to only do one and have the other silently fall back to defaults.
78
+ Using a custom `kbach.config.js`? See [Wiring a custom config in](#wiring-a-custom-config-in) below it needs to be passed in twice for Static CSS specifically.
86
79
 
87
80
  **React Router framework mode:** don't add `@vitejs/plugin-react` — `reactRouter()` already provides JSX handling, and both together crash the page (`Identifier 'RefreshRuntime' has already been declared`).
88
81
 
@@ -97,9 +90,9 @@ export default defineConfig({ plugins: [kbach(), reactRouter()] });
97
90
 
98
91
  (React Router library mode — `createBrowserRouter`, no SSR — has no such conflict; set it up like any Vite + React app.)
99
92
 
100
- ## Runtime setup
93
+ ### Runtime
101
94
 
102
- Client-side CSS injection — works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …), no build plugin. Next.js always uses this (see [Next.js setup](#nextjs-setup) below for the one extra detail), or use it on Vite if you'd rather not wire up the plugin yet. This is the whole setup:
95
+ Client-side CSS injection — works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …), no build plugin. Next.js always uses this, or use it on Vite if you'd rather not wire up the plugin yet:
103
96
 
104
97
  ```jsx
105
98
  import { ThemeProvider, KbachReset } from '@kbach/ui';
@@ -114,17 +107,17 @@ export default function Root() {
114
107
  }
115
108
  ```
116
109
 
117
- That's it — done. `<KbachReset />` renders the base reset (see [CSS resets](#css-resets)) as real markup instead of waiting on client JS — matters most for SSR, where it avoids a flash of unstyled browser defaults before hydration.
110
+ That's it — done. `<KbachReset />` renders the base reset as real markup instead of waiting on client JS — matters most for SSR, where it avoids a flash of unstyled browser defaults before hydration.
118
111
 
119
- Using a custom `kbach.config.js`? Pass it to `ThemeProvider` too — see [Wiring the config in](#wiring-the-config-in--required-for-both-setups).
112
+ Using a custom `kbach.config.js`? Pass it to `ThemeProvider` — see [Wiring a custom config in](#wiring-a-custom-config-in).
120
113
 
121
114
  Don't also set up Static CSS above in the same app — pick one.
122
115
 
123
- ## Next.js setup
116
+ ### Next.js
124
117
 
125
- Next.js is always [Runtime setup](#runtime-setup) above — Static CSS doesn't apply (webpack/Turbopack, not Vite). The `tsconfig.json` step from [Setup](#setup) applies as-is; SWC reads `jsxImportSource` the same way Vite does.
118
+ Always [Runtime setup](#runtime) above — Static CSS doesn't apply (webpack/Turbopack, not Vite). The `tsconfig.json` step from [Setup](#setup) applies as-is; SWC reads `jsxImportSource` the same way Vite does.
126
119
 
127
- The one Next.js-specific detail: render `<KbachReset />` once in the root App Router `layout.tsx` (inside `<head>`, or right after `<ThemeProvider>` opens) so the Server Component HTML has the base reset without waiting on hydration:
120
+ The one Next.js-specific detail: render `<KbachReset />` once in the root App Router `layout.tsx`:
128
121
 
129
122
  ```jsx
130
123
  // app/layout.tsx
@@ -144,13 +137,11 @@ export default function RootLayout({ children }) {
144
137
  }
145
138
  ```
146
139
 
147
- Without `<KbachReset />` there, expect a flash of raw browser defaults (native button border, arrow-less `<select>`, etc.) on first paint until hydration completes. Utility classes beyond the base reset still wait on hydration either way a known limitation of the runtime-only path (Static CSS isn't available for webpack/Turbopack), not a per-project bug.
140
+ Without it, expect a flash of raw browser defaults on first paint until hydration completes. `@kbach/ui`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, `<ThemeProvider>`, and `<KbachReset>` directly no manual `'use client'` wrapper needed.
148
141
 
149
- `@kbach/ui`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, `<ThemeProvider>`, and `<KbachReset>` directly — no manual `'use client'` wrapper needed anywhere in your own components.
142
+ ### React Native / Expo
150
143
 
151
- ## React Native / Expo setup
152
-
153
- Same `npm install @kbach/ui` — no separate package. Everything below (API, modifiers, color system) is the same import as web; only setup differs.
144
+ Same `npm install @kbach/ui` no separate package. Everything else (API, modifiers, color system) is the same import as web; only setup differs.
154
145
 
155
146
  **1. babel.config.js:**
156
147
 
@@ -166,12 +157,12 @@ module.exports = function (api) {
166
157
  };
167
158
  ```
168
159
 
169
- Or the one-liner helper: `const { createKbachConfig } = require('@kbach/ui/native'); module.exports = createKbachConfig();` — identical result. Merging into an existing config: `withKbachBabel({ presets: [...] })`, also from `@kbach/ui/native`. After changing this file, clear the Metro cache: `npx expo start --clear`.
160
+ Or the one-liner helper: `const { createKbachConfig } = require('@kbach/ui/native'); module.exports = createKbachConfig();`. After changing this file, clear the Metro cache: `npx expo start --clear`.
170
161
 
171
162
  **2. Wrap your app:**
172
163
 
173
164
  ```jsx
174
- import { ThemeProvider } from '@kbach/ui/native';
165
+ import { ThemeProvider } from '@kbach/ui';
175
166
 
176
167
  export default function App() {
177
168
  return (
@@ -182,292 +173,33 @@ export default function App() {
182
173
  }
183
174
  ```
184
175
 
185
- This is a native-aware `ThemeProvider` reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Import it from `@kbach/ui/native`, not the plain `ThemeProvider` from `@kbach/ui` — that one has no automatic RN wiring (colors scheme/window width would need to be passed in by hand).
186
-
187
- ### Platform differences
188
-
189
- A handful of utilities are native-only or web-only:
190
-
191
- | | |
192
- |---|---|
193
- | Native-only | `tint-{color}` (Image/icon tinting), `perspective-{n}`, `backface-hidden`, `text-shadow`/`text-shadow-lg` |
194
- | Web-only, ignored on native (no warning) | `caret-*` `accent-*` `stroke-*` `fill-*` `touch-*` `float-*` `clear-*` `line-clamp-*` `scroll-*` `animate-*` `transition` `filter` `backdrop-filter` `print:` `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` `landscape:` `portrait:` `motion-reduce:` `motion-safe:` `contrast-more:` `contrast-less:` `rtl:` `ltr:` `grid` `grid-cols-*` `ring-offset-*` `outline-*` `cursor-*` `bg-gradient-*` |
195
-
196
- `ring`/`ring-{n}`/`ring-{color}` is a partial exception — RN has no box-shadow, so it falls back to `borderWidth`/`borderColor`, which *does* affect layout and shares properties with `border-*` (whichever class comes last wins if you combine both).
176
+ `ThemeProvider` auto-detects React Native at render time and reads `useColorScheme()`/`useWindowDimensions()` automatically same import as web, no `/native` subpath needed.
197
177
 
198
- CSS inheritance doesn't exist in React Native apply font utilities to each `Text`, or define a styled component once: `const Body = styled(Text, 'font-sans text-gray-10 dark:text-white');`.
178
+ A handful of utilities are native-only or web-only, and Expo Web/React Native Web has its own notes see [KBACH.md](./KBACH.md#native-only-utilities) for the full platform-differences reference.
199
179
 
200
- ### Expo Web / React Native Web
180
+ ### Wiring a custom config in
201
181
 
202
- In a browser (Expo Web, Metro web), `@kbach/ui` switches to the same CSS-class strategy as plain web automatically:
203
-
204
- - RN components substitute to HTML: `View`/`ScrollView`→`div`, `Text`→`span`, `TextInput`→`input`/`textarea`, `Image`→`img`, `Pressable`/`TouchableOpacity`→`div[role=button]`
205
- - RN-only props (`onChangeText`, `source`, `secureTextEntry`, …) map to HTML equivalents
206
- - Register more: `registerWebElement(Animated.View, 'div')`
207
- - Recommended: use the Vite plugin same as [Static CSS setup](#static-css-setup) above — `import { kbach } from '@kbach/ui/vite'` — and import `kbach.css` in your entry file, for zero runtime cost on the web target too
208
- - Not using the Vite plugin (the common case for Expo/Metro web, which has no Vite build step)? Render `<KbachReset />` once near your root — e.g. Expo Router's root `app/_layout.tsx`, inside `<ThemeProvider>`:
209
-
210
- ```jsx
211
- import { KbachReset } from '@kbach/ui';
212
- import { ThemeProvider } from '@kbach/ui/native';
213
-
214
- export default function RootLayout() {
215
- return (
216
- <ThemeProvider defaultMode="system">
217
- <KbachReset />
218
- <Slot />
219
- </ThemeProvider>
220
- );
221
- }
222
- ```
223
-
224
- This ships the base reset as real markup instead of relying solely on the runtime injector.
225
-
226
- ## Dark mode
227
-
228
- `<ThemeProvider>` powers every `dark:` class — detects OS color scheme, persists the user's choice, re-renders on change.
229
-
230
- ```jsx
231
- <ThemeProvider
232
- defaultMode="system" // 'light' | 'dark' | 'system'
233
- disablePersistence={false} // true = don't remember across reloads
234
- >
235
- <App />
236
- </ThemeProvider>
237
- ```
238
-
239
- | Prop | Type | Default | Description |
240
- |---|---|---|---|
241
- | `defaultMode` | `'light' \| 'dark' \| 'system'` | `'system'` | Starting mode |
242
- | `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` (web) / `AsyncStorage` (native) |
243
- | `config` | `FrameworkConfig` | global config | Scope a different config to this subtree |
244
-
245
- `darkMode` in `kbach.config.js` picks the matching strategy: `'attribute'` (default), `'class'`, or `'media'` (system-only). Toggle it with `useTheme()`'s `toggle()`/`setMode()` — see [API](#api).
246
-
247
- ## API
248
-
249
- ### className / kb
250
-
251
- `kb` is an alias for `className` — works on any element.
252
-
253
- ```jsx
254
- <button className="bg-blue-7 hover:bg-blue-8 pressed:bg-blue-9 rounded-lg px-4 py-2" />
255
- ```
256
-
257
- ### styled(Component, classes)
258
-
259
- ```jsx
260
- import { styled } from '@kbach/ui';
261
-
262
- const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
263
- const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
264
-
265
- <Card kb="mt-4">
266
- <Button kb="w-full">Submit</Button>
267
- </Card>
268
- ```
269
-
270
- Extra classes at use time via `kb` merge with the base classes.
271
-
272
- ### cx(...classes)
273
-
274
- ```jsx
275
- import { cx } from '@kbach/ui';
276
-
277
- <div className={cx('p-4 rounded-xl', isSelected && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
278
- ```
279
-
280
- Falsy values ignored. Also works as pre-built style constants:
281
-
282
- ```ts
283
- export const container = cx('flex-1 bg-white dark:bg-gray-9 p-4');
284
- ```
285
-
286
- ### useStyles(classes, state?)
287
-
288
- ```jsx
289
- const style = useStyles('bg-blue-6 dark:bg-indigo-6 px-3 py-1 rounded-full');
290
- const style2 = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
291
- ```
292
-
293
- ### kb(classes)
294
-
295
- Resolve outside a component:
296
-
297
- ```js
298
- const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
299
- ```
300
-
301
- ### useTheme()
302
-
303
- ```js
304
- const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
305
- ```
306
-
307
- | Value | Type | Description |
308
- |---|---|---|
309
- | `mode` | `'light' \| 'dark' \| 'system'` | User-selected mode |
310
- | `resolvedMode` | `'light' \| 'dark'` | Resolved after system lookup |
311
- | `isDark` | `boolean` | `resolvedMode === 'dark'` |
312
- | `setMode` | `fn` | Set mode explicitly |
313
- | `toggle` | `fn` | Toggle light/dark |
314
- | `config` | `ResolvedConfig` | Full resolved config |
315
-
316
- ### useIsDark() / useColors()
317
-
318
- ```js
319
- const isDark = useIsDark();
320
-
321
- const colors = useColors();
322
- colors.blue[6] // '#3b82f6'
323
- colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
324
- colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
325
- ```
326
-
327
- ### Typed theme tokens
328
-
329
- `useColors()` and `useSpacing()` are typed against the built-in theme by default (`DefaultColorName`/`DefaultSpacingKey`), so TypeScript autocompletes real color/spacing names and flags a typo (`colors.blu`, `spacing.ful`) as an error — no setup needed if you're on the default theme.
330
-
331
- ```ts
332
- import { useSpacing } from '@kbach/ui';
333
-
334
- const spacing = useSpacing();
335
- spacing[4] // 16
336
- spacing.full // '100%'
337
- spacing['1/2'] // '50%'
338
- ```
339
-
340
- A customized `kbach.config.js` isn't visible to TypeScript — it's a plain `.js` file loaded at runtime, not a statically-analyzed module — so a project with extra colors or spacing keys needs to widen the type parameter by hand:
341
-
342
- ```ts
343
- import { useColors, type DefaultColorName } from '@kbach/ui';
344
-
345
- const colors = useColors<DefaultColorName | 'brand'>();
346
- colors.brand[6] // now type-checks
347
- ```
348
-
349
- This only affects the exported *types* — `useColors()`/`useSpacing()` called with no type argument behave exactly as before at runtime. If existing code was relying on a color/spacing name TypeScript couldn't previously catch (the old types had a blanket `[key: string]: any`), this may surface a new type error — the fix is the escape-hatch pattern above, not a code change.
350
-
351
- ## Modifiers
352
-
353
- Chain in any order: `<div className="dark:sm:hover:p-4" />`
354
-
355
- | Category | Modifiers |
356
- |---|---|
357
- | Theme | `dark:` `light:` / `not-dark:` |
358
- | Interactive | `hover:` `focus:` `pressed:` `active:` `disabled:` `checked:` `visited:` `placeholder:` (all have `not-` variants) |
359
- | Structural | `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:` |
360
- | Pseudo-elements | `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` |
361
- | Responsive | `sm:`(576px) `md:`(768px) `lg:`(1024px) `xl:`(1280px) `2xl:`(1536px) |
362
- | Other | `print:` `landscape:`/`portrait:` `motion-reduce:`/`motion-safe:` `contrast-more:`/`contrast-less:` `rtl:`/`ltr:` `!` (important) |
363
-
364
- ```jsx
365
- <div className="before:content-['*'] before:text-red-6 relative" />
366
- ```
367
-
368
- **Group / peer:**
369
-
370
- ```jsx
371
- <div className="group">
372
- <span className="opacity-0 group-hover:opacity-100 transition" />
373
- </div>
374
- ```
375
-
376
- Nested groups need names (`group/card`, `group-hover/card:`) or the inner element reacts to whichever `.group` is nearest, not necessarily the one you meant.
377
-
378
- ## Arbitrary values
379
-
380
- ```jsx
381
- <div className="bg-[#6366f1] p-[14px] w-[calc(100%-2rem)] text-[18px]" />
382
- ```
383
-
384
- For a property with no named utility: `[property:value]` — e.g. `[mask-type:luminance]`, `[--my-var:10px]`. Underscores become spaces: `[background:url(/a.png)_no-repeat]`.
385
-
386
- ## Color system
387
-
388
- 12-shade scale, 1 lightest → 12 darkest: `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
389
-
390
- Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
391
- Special: `transparent` `current` `black` `white`
392
- Opacity: `bg-blue-6/50` or `bg-blue-6/[0.15]`
393
-
394
- ## CSS resets
395
-
396
- Included in `kbach.css`, runtime injection, and `<KbachReset />` alike:
397
-
398
- - Border-box everywhere; `border-*` utilities work without needing `border-solid`
399
- - `body` margin/padding cleared; headings/`p`/`ul`/`ol`/`a` styling cleared to inherit
400
- - `img`/`video`/`svg` block + max-width 100%
401
- - `button`/text inputs/`textarea` stripped of native appearance so `bg-`/`rounded-`/`p-` fully restyle them
402
- - Checkbox/radio/`select` keep native rendering (just typography/spacing normalized + `accent-color: currentColor`)
403
-
404
- ## Configuration
405
-
406
- ```js
407
- // kbach.config.js
408
- module.exports = {
409
- darkMode: 'attribute', // 'attribute' | 'class' | 'media'
410
-
411
- theme: {
412
- colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } }, // replaces the section
413
- },
414
-
415
- extend: {
416
- colors: { brand: { 6: '#6366f1' } }, // adds to defaults
417
- spacing: { 18: '72px' },
418
- screens: { '3xl': '1920px' },
419
- fontFamily: { sans: 'Inter, sans-serif' },
420
- keyframes: {
421
- wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
422
- },
423
- animation: { wiggle: 'wiggle 1s ease-in-out infinite' },
424
- },
425
-
426
- plugins: [
427
- ({ addUtility, addVariant, theme }) => {
428
- addUtility('border-brand', { borderColor: theme('colors.brand.6'), borderWidth: 2 });
429
- addVariant('hocus', ':hover, :focus');
430
- },
431
- ],
432
- };
433
- ```
434
-
435
- - `fontFamily.sans` set to anything but `'System'` auto-injects `body { font-family: … }`
436
- - Custom `@keyframes` are used as `animate-{name}`, and can be overridden inline: `animate-[wiggle_2s_ease-in-out]`
437
- - Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
438
- - Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
439
-
440
- ### Wiring the config in — required for both setups
441
-
442
- `kbach.config.js` isn't picked up automatically. It has to be imported and passed in explicitly, and **where** depends on which of the two things it affects:
443
-
444
- - **Runtime** — dark mode strategy, `useColors()`, custom `@keyframes`/`animation`, and (for SSR) the default-font fallback rendered before your stylesheet takes over. Needed by **both** [Runtime setup](#runtime-setup) and [Static CSS setup](#static-css-setup) — pass it to `ThemeProvider`:
182
+ `kbach.config.js` isn't picked up automatically it has to be passed in explicitly, and **where** depends on what it affects:
445
183
 
184
+ - **Runtime** (dark mode, `useColors()`, animations) — needed by every setup above, pass it to `ThemeProvider`:
446
185
  ```jsx
447
186
  import { ThemeProvider } from '@kbach/ui';
448
187
  import kbachConfig from '../kbach.config';
449
188
 
450
- <ThemeProvider defaultMode="system" config={kbachConfig}>
451
- <App />
452
- </ThemeProvider>
189
+ <ThemeProvider defaultMode="system" config={kbachConfig}><App /></ThemeProvider>
453
190
  ```
454
-
455
- (Equivalent to calling `updateConfig(kbachConfig)` once before anything renders — `ThemeProvider`'s `config` prop does this for you and keeps the global store in sync if it ever changes.)
456
-
457
- - **Build-time** — what the Vite plugin actually scans your source against and generates CSS for. Only relevant to [Static CSS setup](#static-css-setup) — pass it to the plugin itself:
458
-
191
+ - **Build-time** (what the Vite plugin scans against) — only for [Static CSS](#static-css), pass it to the plugin:
459
192
  ```ts
460
- // vite.config.ts
461
193
  import { kbach } from '@kbach/ui/vite';
462
194
  import kbachConfig from './kbach.config';
463
195
 
464
196
  export default defineConfig({ plugins: [kbach(kbachConfig)] });
465
197
  ```
466
198
 
467
- Skipping the runtime one is an easy mistake under Static CSS setup specifically — the generated `kbach.css` will look correct (it *does* have your customizations baked in), while dark mode, `useColors()`, and custom animations silently fall back to Kbach's defaults instead of your config, since nothing ever told the running app what you'd customized.
199
+ Skipping the runtime one under Static CSS is an easy mistake — the generated `kbach.css` looks correct, but dark mode/`useColors()`/animations silently fall back to defaults since nothing told the running app what you customized.
468
200
 
469
- ## Full reference
201
+ ## More information
470
202
 
471
- [kbach-ui.md](./kbach-ui.md) — complete utility list, every modifier, all config options, covers web and React Native/Expo.
203
+ [kbach-ui.md](./kbach-ui.md) — the complete reference: `ThemeProvider`/`useTheme`/dark mode, the full API (`styled`, `cx`, `useStyles`, `kb`, `useColors`, typed theme tokens), every modifier, the color system, CSS resets, and all `kbach.config.js` options covers web and React Native/Expo.
472
204
 
473
205
  `@kbach/native` is deprecated and no longer maintained — its last published npm version is frozen as a compatibility shim re-exporting this package. Install `@kbach/ui` directly for new projects.
@@ -26,7 +26,7 @@ import {
26
26
  transformToWebProps,
27
27
  useConditionalGlobalDarkMode,
28
28
  useConditionalWidth
29
- } from "./chunk-BPCFICND.mjs";
29
+ } from "./chunk-VBK6ORYJ.mjs";
30
30
 
31
31
  // src/jsx-runtime.tsx
32
32
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -1,4 +1,10 @@
1
1
  'use client';
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/core/platform.ts
4
10
  var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
@@ -2900,8 +2906,25 @@ var BASE_RESET = [
2900
2906
  "table { border-collapse: collapse; border-spacing: 0; }"
2901
2907
  ].join("\n");
2902
2908
 
2909
+ // src/core/globalSingleton.ts
2910
+ function getGlobalSingleton(key, create) {
2911
+ const symbolKey = /* @__PURE__ */ Symbol.for(`__kbach_${key}__`);
2912
+ const g = globalThis;
2913
+ let value = g[symbolKey];
2914
+ if (value === void 0) {
2915
+ value = create();
2916
+ g[symbolKey] = value;
2917
+ }
2918
+ return value;
2919
+ }
2920
+
2903
2921
  // src/core/responsiveStore.ts
2904
- var store = { width: 0, notifiedWidth: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
2922
+ var store = getGlobalSingleton("responsiveStore", () => ({
2923
+ width: 0,
2924
+ notifiedWidth: 0,
2925
+ screens: {},
2926
+ listeners: /* @__PURE__ */ new Set()
2927
+ }));
2905
2928
  function syncGlobalWidth(width) {
2906
2929
  store.width = width;
2907
2930
  }
@@ -2959,12 +2982,12 @@ function getSortedEntries(resolved) {
2959
2982
  }
2960
2983
  return sorted;
2961
2984
  }
2962
- var _defaultFontFamily;
2985
+ var _fontFamilyHolder = getGlobalSingleton("defaultFontFamily", () => ({ value: void 0 }));
2963
2986
  function setDefaultFontFamily(font) {
2964
- _defaultFontFamily = font;
2987
+ _fontFamilyHolder.value = font;
2965
2988
  }
2966
2989
  function getDefaultFontFamily() {
2967
- return _defaultFontFamily;
2990
+ return _fontFamilyHolder.value;
2968
2991
  }
2969
2992
  var _styleEl = null;
2970
2993
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
@@ -2997,12 +3020,12 @@ function evictInjectedRule(rule) {
2997
3020
  }
2998
3021
  }
2999
3022
  var _injectedRules = new LRUCache(5e4, evictInjectedRule);
3000
- var _runtimeCSSDisabled = false;
3023
+ var _runtimeCSSHolder = getGlobalSingleton("runtimeCSSDisabled", () => ({ value: false }));
3001
3024
  function disableRuntimeCSS() {
3002
- _runtimeCSSDisabled = true;
3025
+ _runtimeCSSHolder.value = true;
3003
3026
  }
3004
3027
  function isRuntimeCSSDisabled() {
3005
- return _runtimeCSSDisabled;
3028
+ return _runtimeCSSHolder.value;
3006
3029
  }
3007
3030
  function getStyleEl() {
3008
3031
  if (_styleEl) return _styleEl;
@@ -3362,7 +3385,10 @@ function deepMerge(base, override) {
3362
3385
  }
3363
3386
  return result;
3364
3387
  }
3365
- var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set() };
3388
+ var configStore = getGlobalSingleton("configStore", () => ({
3389
+ resolved: null,
3390
+ listeners: /* @__PURE__ */ new Set()
3391
+ }));
3366
3392
  function getConfig() {
3367
3393
  if (configStore.resolved) return configStore.resolved;
3368
3394
  configStore.resolved = buildConfig({});
@@ -3531,7 +3557,11 @@ function initConfig(userConfig) {
3531
3557
  }
3532
3558
 
3533
3559
  // src/core/darkModeStore.ts
3534
- var store2 = { isDark: false, notifiedIsDark: false, subscribers: /* @__PURE__ */ new Set() };
3560
+ var store2 = getGlobalSingleton("darkModeStore", () => ({
3561
+ isDark: false,
3562
+ notifiedIsDark: false,
3563
+ subscribers: /* @__PURE__ */ new Set()
3564
+ }));
3535
3565
  function syncGlobalDarkMode(isDark) {
3536
3566
  store2.isDark = isDark;
3537
3567
  }
@@ -4128,6 +4158,8 @@ var InteractiveWrapper = forwardRef(
4128
4158
  InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
4129
4159
 
4130
4160
  export {
4161
+ __require,
4162
+ getGlobalSingleton,
4131
4163
  isModeAwareColor,
4132
4164
  isWeb,
4133
4165
  isNative,