@kbach/react 0.5.45 → 0.5.46

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/README.md CHANGED
@@ -1,13 +1,10 @@
1
1
  # @kbach/react
2
2
 
3
- Tailwind-like utility classes for **React (web)**. Classes are resolved to inline styles at render time through a custom JSX runtime — no separate stylesheet step required. Responsive, dark mode, hover, and pseudo-element rules are injected as real CSS so they work with the full CSS cascade.
3
+ Tailwind-like utility classes for React (web). Write classes as `className` strings — the custom JSX runtime resolves them at render time. A Vite plugin generates a static `kbach.css` file so styles ship as real CSS with zero runtime overhead in production.
4
4
 
5
5
  ```jsx
6
- /** @jsxImportSource @kbach/react */
7
-
8
6
  <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
9
7
  <div className="bg-blue-7 hover:bg-blue-8 dark:bg-indigo-6 rounded-lg px-6 py-3" />
10
- <div className="bg-[#6366f1] p-[14px] rounded-[20px]" />
11
8
  <div className="group">
12
9
  <span className="opacity-0 group-hover:opacity-100 transition" />
13
10
  </div>
@@ -34,7 +31,24 @@ npm install @kbach/react
34
31
  }
35
32
  ```
36
33
 
37
- **Vite**
34
+ Or per-file (no config change needed):
35
+
36
+ ```js
37
+ /** @jsxImportSource @kbach/react */
38
+ ```
39
+
40
+ **Babel (non-Vite)**
41
+
42
+ ```js
43
+ // babel.config.js
44
+ module.exports = {
45
+ presets: [
46
+ ['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }],
47
+ ],
48
+ };
49
+ ```
50
+
51
+ ### 2. Add the Vite plugin
38
52
 
39
53
  ```ts
40
54
  // vite.config.ts
@@ -45,31 +59,32 @@ import { kbach } from '@kbach/react/vite';
45
59
  export default defineConfig({
46
60
  plugins: [
47
61
  react({ jsxImportSource: '@kbach/react' }),
48
- kbach(), // generates CSS and writes it to your main CSS file
62
+ kbach(),
49
63
  ],
50
64
  });
51
65
  ```
52
66
 
53
- The `kbach()` Vite plugin scans your source files for class strings (including ternary expressions, template literals, and `clsx`/`cn` call patterns) and writes the generated CSS between `/* kbach:start */` / `/* kbach:end */` markers in your main CSS file (`src/index.css`, `app/app.css`, etc.). On HMR only the changed file is rescanned — unchanged class tokens reuse their cached CSS.
67
+ The plugin scans your source files and writes generated CSS between `/* kbach:start */` / `/* kbach:end */` markers inside any `kbach.css` file it finds in your project. On HMR only the changed file is rescanned — unchanged tokens reuse cached CSS.
54
68
 
55
- **Babel**
69
+ ### 3. Create and import kbach.css
56
70
 
57
- ```js
58
- // babel.config.js
59
- module.exports = {
60
- presets: [
61
- ['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }],
62
- ],
63
- };
71
+ Create `kbach.css` anywhere in your project (e.g. `src/kbach.css`):
72
+
73
+ ```css
74
+ /* kbach:start */
75
+ /* kbach:end */
64
76
  ```
65
77
 
66
- **Per-file pragma** (no config change required)
78
+ Import it in your app entry:
67
79
 
68
- ```js
69
- /** @jsxImportSource @kbach/react */
80
+ ```ts
81
+ // main.tsx
82
+ import './kbach.css';
70
83
  ```
71
84
 
72
- ### 2. Wrap your app
85
+ Importing `kbach.css` automatically disables runtime style injection — all styles come from the CSS file instead.
86
+
87
+ ### 4. Wrap your app
73
88
 
74
89
  ```jsx
75
90
  import { ThemeProvider } from '@kbach/react';
@@ -87,46 +102,74 @@ export default function Root() {
87
102
 
88
103
  ### className / kb prop
89
104
 
90
- Works on any HTML element or React component once the JSX runtime is configured. Both `className` and `kb` are equivalent — `kb` is shorter and avoids conflicts with library components that forward `className` to their own DOM elements.
105
+ Works on any HTML element or React component once the JSX runtime is configured. `kb` is an alias for `className`.
91
106
 
92
107
  ```jsx
93
108
  <div className="bg-white dark:bg-gray-10 p-4 rounded-xl" />
94
109
  <p kb="text-gray-10 dark:text-white text-lg font-bold" />
95
110
  <button className="bg-blue-7 hover:bg-blue-8 pressed:bg-blue-9 rounded-lg px-4 py-2" />
96
- <input className="caret-blue-6 focus:ring-2" />
97
111
  ```
98
112
 
99
113
  ### styled(Component, classes)
100
114
 
101
- Pre-style any component. Returns a new component that merges the base classes with any `kb` prop passed at use time. On web, the class string is forwarded as `className` so CSS rules (`group-hover:`, `before:`, `print:`, etc.) match the element.
115
+ Pre-style any component. Returns a new component that merges the base classes with any `kb` prop passed at use time.
102
116
 
103
117
  ```jsx
104
118
  import { styled } from '@kbach/react';
105
119
 
106
120
  const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
107
- const Title = styled('h2', 'text-2xl font-bold text-gray-10 dark:text-white');
108
121
  const Button = styled(
109
122
  'button',
110
123
  'bg-blue-7 hover:bg-blue-8 dark:bg-indigo-6 rounded-xl px-6 py-3'
111
124
  );
112
125
 
113
- // Pass extra classes at use time
114
- <Card kb="mt-4 mb-2">
115
- <Title kb="text-3xl">Hello</Title>
126
+ <Card kb="mt-4">
127
+ <Button kb="w-full">Submit</Button>
116
128
  </Card>
117
129
  ```
118
130
 
131
+ ### cx(...classes)
132
+
133
+ Conditionally join class strings. Falsy values are ignored. The Vite plugin scans `cx()` calls when building the static CSS.
134
+
135
+ ```jsx
136
+ import { cx } from '@kbach/react';
137
+
138
+ <div className={cx(
139
+ 'p-4 rounded-xl',
140
+ isSelected && 'border-2 border-blue-6',
141
+ isDisabled && 'opacity-50',
142
+ )} />
143
+ ```
144
+
145
+ Pre-built style constants (also scanned by the Vite plugin):
146
+
147
+ ```ts
148
+ // styles.ts
149
+ import { cx } from '@kbach/react';
150
+
151
+ export const container = cx('flex-1 bg-white dark:bg-gray-9 p-4');
152
+ export const heading = cx('text-2xl font-bold text-gray-10 dark:text-white');
153
+ ```
154
+
155
+ ```jsx
156
+ import { container, heading } from './styles';
157
+
158
+ <div className={container}>
159
+ <h1 className={heading}>Hello</h1>
160
+ </div>
161
+ ```
162
+
119
163
  ### useStyles(classes, state?)
120
164
 
121
- Resolve classes to a style object inside a component. Accepts a string, an array of strings (merged left-to-right), and an optional interaction state object.
165
+ Resolve classes to a style object inside a component.
122
166
 
123
167
  ```jsx
124
168
  import { useStyles } from '@kbach/react';
125
169
 
126
- // Basic
127
170
  const style = useStyles('bg-blue-6 dark:bg-indigo-6 px-3 py-1 rounded-full');
128
171
 
129
- // Multiple class strings merged
172
+ // Multiple strings merged left-to-right
130
173
  const style = useStyles(['bg-white p-4', 'dark:bg-gray-9 rounded-xl']);
131
174
 
132
175
  // With interaction state
@@ -134,18 +177,9 @@ const [pressed, setPressed] = useState(false);
134
177
  const style = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
135
178
  ```
136
179
 
137
- ### useResolvedStyle(classes)
138
-
139
- Returns the raw bucket map (`{ base, dark, hover, … }`) without flattening. Useful when you need to pass different buckets to different elements, or animate between states with `Animated`.
140
-
141
- ```js
142
- const resolved = useResolvedStyle('bg-white dark:bg-gray-9 p-4');
143
- // { base: { backgroundColor: '#fff', padding: 16 }, dark: { backgroundColor: '#1f2937' } }
144
- ```
145
-
146
180
  ### kb(classes)
147
181
 
148
- Resolve classes outside a component (static contexts, `StyleSheet.create()`, etc.).
182
+ Resolve classes outside a component (static contexts).
149
183
 
150
184
  ```js
151
185
  import { kb } from '@kbach/react';
@@ -153,20 +187,6 @@ import { kb } from '@kbach/react';
153
187
  const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
154
188
  ```
155
189
 
156
- ### cx(...classes)
157
-
158
- Conditionally join class strings. Falsy values are ignored.
159
-
160
- ```jsx
161
- import { cx } from '@kbach/react';
162
-
163
- <div className={cx(
164
- 'p-4 rounded-xl',
165
- isSelected && 'border-2 border-blue-6',
166
- isDisabled && 'opacity-50',
167
- )} />
168
- ```
169
-
170
190
  ### useTheme()
171
191
 
172
192
  ```js
@@ -182,7 +202,7 @@ const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
182
202
  | `isDark` | `boolean` | Shorthand for `resolvedMode === 'dark'` |
183
203
  | `setMode` | `fn` | Set mode explicitly |
184
204
  | `toggle` | `fn` | Toggle between light and dark |
185
- | `config` | `ResolvedConfig` | Full resolved config (theme, darkMode, plugins) |
205
+ | `config` | `ResolvedConfig` | Full resolved config |
186
206
 
187
207
  ### useIsDark()
188
208
 
@@ -191,37 +211,23 @@ import { useIsDark } from '@kbach/react';
191
211
  const isDark = useIsDark();
192
212
  ```
193
213
 
194
- ### useBreakpoint() / useResponsive()
195
-
196
- ```js
197
- import { useBreakpoint, useResponsive } from '@kbach/react';
198
-
199
- const bp = useBreakpoint(); // 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
200
-
201
- const { sm, md, lg } = useResponsive();
202
- const padding = lg ? 32 : sm ? 16 : 8;
203
- ```
204
-
205
214
  ### useColors()
206
215
 
207
- Returns the active theme's color palette as a smart proxy. Supports shade access, `/opacity` modifier, and arbitrary CSS color manipulation. Handles 3, 4, 6, and 8-digit hex values.
216
+ Returns the active theme's color palette as a smart proxy.
208
217
 
209
218
  ```js
210
219
  import { useColors } from '@kbach/react';
211
220
 
212
221
  const colors = useColors();
213
-
214
- colors.blue[6] // '#3b82f6'
215
- colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
216
- colors.white // '#ffffff'
217
- colors['white/20'] // 'rgba(255,255,255,0.2)'
222
+ colors.blue[6] // '#3b82f6'
223
+ colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
224
+ colors.white // '#ffffff'
225
+ colors['white/20'] // 'rgba(255,255,255,0.2)'
218
226
  colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
219
227
  ```
220
228
 
221
229
  ### ThemeToggle
222
230
 
223
- Drop-in toggle component.
224
-
225
231
  ```jsx
226
232
  <ThemeToggle /> // button (default)
227
233
  <ThemeToggle variant="switch" /> // toggle switch
@@ -231,66 +237,50 @@ Drop-in toggle component.
231
237
 
232
238
  ## Modifiers
233
239
 
234
- Modifiers are chainable with no hard cap — as many as needed in any order:
240
+ Chain modifiers in any order before the utility name:
235
241
 
236
242
  ```jsx
237
- <div className="dark:sm:hover:focus:p-4" />
238
- <div className="dark:group-hover:not-disabled:text-blue-6" />
243
+ <div className="dark:sm:hover:p-4" />
244
+ <input className="focus:ring-2 focus:ring-blue-5 focus:outline-none" />
239
245
  ```
240
246
 
241
- ### Theme & mode
247
+ ### Theme
242
248
 
243
249
  | Modifier | Trigger |
244
250
  |---|---|
245
- | `dark:` | Dark mode active |
246
- | `not-dark:` / `light:` | Light mode active |
247
- | `not-light:` | Dark mode active (alias) |
251
+ | `dark:` | Dark mode |
252
+ | `light:` / `not-dark:` | Light mode |
248
253
 
249
- ### Interactive — JS state + CSS pseudo
254
+ ### Interactive
250
255
 
251
256
  | Modifier | Trigger |
252
257
  |---|---|
253
- | `hover:` / `not-hover:` | Mouse hover |
254
- | `focus:` / `not-focus:` | Focused |
255
- | `pressed:` / `not-pressed:` | Click / touch down |
256
- | `active:` / `not-active:` | Active state |
257
- | `disabled:` / `not-disabled:` | Disabled |
258
- | `checked:` / `not-checked:` | Checkbox / radio checked |
259
- | `visited:` / `not-visited:` | Visited link |
258
+ | `hover:` | Mouse hover |
259
+ | `focus:` | Element focused |
260
+ | `pressed:` | Click / touch down |
261
+ | `active:` | Active state |
262
+ | `disabled:` | Disabled |
263
+ | `checked:` | Checkbox / radio checked |
264
+ | `visited:` | Visited link |
260
265
  | `placeholder:` | Placeholder text |
261
266
 
262
- ### CSS-only pseudo-classes (structural)
267
+ Negated: `not-hover:`, `not-focus:`, `not-pressed:`, `not-active:`, `not-disabled:`, `not-checked:`
263
268
 
264
- | Modifier | CSS |
265
- |---|---|
266
- | `first:` | `:first-child` |
267
- | `last:` | `:last-child` |
268
- | `odd:` | `:nth-child(odd)` |
269
- | `even:` | `:nth-child(even)` |
270
- | `only:` | `:only-child` |
271
- | `focus-within:` | `:focus-within` |
272
- | `focus-visible:` | `:focus-visible` |
269
+ ### Structural
270
+
271
+ `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:`
273
272
 
274
273
  ### Pseudo-elements
275
274
 
275
+ `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` `placeholder:`
276
+
276
277
  ```jsx
277
278
  <div className="before:content-['*'] before:text-red-6 relative" />
278
- <p className="first-letter:text-4xl first-letter:font-bold" />
279
- <input className="placeholder:text-gray-5" />
280
279
  <p className="selection:bg-blue-3" />
280
+ <input className="placeholder:text-gray-5" />
281
281
  ```
282
282
 
283
- | Modifier | CSS |
284
- |---|---|
285
- | `before:` | `::before` |
286
- | `after:` | `::after` |
287
- | `selection:` | `::selection` |
288
- | `first-letter:` | `::first-letter` |
289
- | `first-line:` | `::first-line` |
290
- | `marker:` | `::marker` |
291
- | `placeholder:` | `::placeholder` |
292
-
293
- ### Group / peer ancestor selectors
283
+ ### Group / peer
294
284
 
295
285
  ```jsx
296
286
  <div className="group">
@@ -300,13 +290,6 @@ Modifiers are chainable with no hard cap — as many as needed in any order:
300
290
  <p className="peer-focus:text-blue-6" />
301
291
  ```
302
292
 
303
- | Modifier | Fires when |
304
- |---|---|
305
- | `group-hover:` | Ancestor `.group` is hovered |
306
- | `group-focus:` | Ancestor `.group` is focused |
307
- | `peer-hover:` | Previous sibling `.peer` is hovered |
308
- | `peer-focus:` | Previous sibling `.peer` is focused |
309
-
310
293
  ### Responsive
311
294
 
312
295
  | Modifier | Min-width |
@@ -317,92 +300,48 @@ Modifiers are chainable with no hard cap — as many as needed in any order:
317
300
  | `xl:` | 1280 px |
318
301
  | `2xl:` | 1536 px |
319
302
 
320
- ### Print
321
-
322
- ```jsx
323
- <div className="print:hidden" />
324
- <div className="print:text-black print:bg-white" />
325
- ```
326
-
327
- ### Orientation & accessibility media
303
+ ### Other
328
304
 
329
- | Modifier | Media query |
305
+ | Modifier | Effect |
330
306
  |---|---|
331
- | `landscape:` | `(orientation: landscape)` |
332
- | `portrait:` | `(orientation: portrait)` |
333
- | `motion-reduce:` | `(prefers-reduced-motion: reduce)` |
334
- | `motion-safe:` | `(prefers-reduced-motion: no-preference)` |
335
- | `contrast-more:` | `(prefers-contrast: more)` |
336
- | `contrast-less:` | `(prefers-contrast: less)` |
337
-
338
- ### Directionality
339
-
340
- ```jsx
341
- <div className="rtl:text-right ltr:text-left" />
342
- ```
343
-
344
- ### Important
345
-
346
- Prefix any class with `!` to add `!important` to every declaration it produces:
347
-
348
- ```jsx
349
- <div className="!p-0 !m-0" />
350
- ```
307
+ | `print:hidden` | Visible only on screen |
308
+ | `landscape:` `portrait:` | Orientation |
309
+ | `motion-reduce:` `motion-safe:` | Reduced-motion preference |
310
+ | `contrast-more:` `contrast-less:` | Contrast preference |
311
+ | `rtl:` `ltr:` | Text direction |
312
+ | `!p-0` | `!important` on every declaration |
351
313
 
352
314
  ## Arbitrary values
353
315
 
354
316
  ```jsx
355
317
  <div className="bg-[#6366f1]" />
356
318
  <div className="p-[14px]" />
357
- <div className="text-[18px]" />
358
319
  <div className="w-[calc(100%-2rem)]" />
359
320
  <div className="bg-[rgba(99,102,241,0.15)]" />
321
+ <div className="text-[18px]" />
360
322
  ```
361
323
 
362
- ## Color with opacity
324
+ ## Color system
363
325
 
364
- ```jsx
365
- <div className="bg-blue-6/50" /> // 50% opacity
366
- <div className="bg-gray-10/75" /> // 75% opacity
367
- ```
326
+ 12-shade scale — 1 lightest, 12 darkest. Use as `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
368
327
 
369
- ## Color scale
328
+ Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
329
+ Special: `transparent` `current` `black` `white`
370
330
 
371
- Colors use a 12-shade scale — 1 is lightest, 12 is darkest.
331
+ Opacity modifier: `bg-blue-6/50` (50% alpha), `bg-blue-6/[0.15]` (arbitrary)
372
332
 
373
- ```
374
- shade 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11 · 12
375
- light ──────────────────────────────────────────────────────────────── dark
376
- ```
333
+ ## CSS resets
377
334
 
378
- Available families: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose.
335
+ Included automatically in `kbach.css` and in runtime injection:
379
336
 
380
- ## Common utilities
381
-
382
- | Category | Examples |
383
- |---|---|
384
- | Background | `bg-white`, `bg-gray-10`, `bg-blue-6`, `bg-[#fff]` |
385
- | Text | `text-sm`, `text-2xl`, `text-gray-10`, `font-bold`, `font-sans` |
386
- | Text wrap | `text-balance`, `text-pretty`, `text-nowrap`, `text-wrap` |
387
- | Line clamp | `line-clamp-3`, `line-clamp-none` |
388
- | Padding | `p-4`, `px-6`, `py-3` |
389
- | Margin | `m-4`, `mx-auto`, `mt-2`, `-mt-4` |
390
- | Size | `size-12`, `w-full`, `h-screen`, `w-screen`, `w-[200px]` |
391
- | Flex | `flex-1`, `flex-row`, `items-center`, `justify-between`, `gap-4` |
392
- | Grid | `grid-3`, `col-span-2`, `grid-flow-col` |
393
- | Border | `border`, `border-2`, `border-gray-4`, `rounded-xl` |
394
- | Shadow | `shadow`, `shadow-md`, `shadow-lg` |
395
- | Opacity | `opacity-50`, `opacity-75` |
396
- | Overflow | `overflow-hidden`, `overflow-clip`, `overflow-x-auto` |
397
- | Cursor | `cursor-pointer`, `cursor-grab`, `cursor-zoom-in` |
398
- | Scroll | `scroll-smooth`, `scroll-auto` |
399
- | Float / clear | `float-left`, `float-right`, `clear-both` |
400
- | Vertical align | `align-middle`, `align-top`, `align-baseline` |
401
- | Touch action | `touch-none`, `touch-pan-x`, `touch-manipulation` |
402
- | Caret | `caret-blue-6`, `caret-transparent` |
403
- | Accent | `accent-blue-6`, `accent-auto` |
404
- | Animation | `animate-spin`, `animate-pulse`, `animate-bounce`, `animate-ping` |
405
- | Transition | `transition`, `duration-300`, `delay-150` |
337
+ - `*, *::before, *::after { box-sizing: border-box }`
338
+ - `body { margin: 0; padding: 0 }`
339
+ - `h1–h6` — margin, font-size, font-weight cleared (inherit from parent)
340
+ - `p`, `ul`, `ol` — margin and list style cleared
341
+ - `a` — color and underline cleared (inherit from parent)
342
+ - `img`, `video`, `svg` — `display: block; max-width: 100%`
343
+ - `input`, `textarea`, `select`, `button`, `fieldset` — appearance, border, padding cleared
344
+ - `table` — `border-collapse: collapse`
406
345
 
407
346
  ## Configuration
408
347
 
@@ -412,108 +351,56 @@ module.exports = {
412
351
  darkMode: 'attribute', // 'attribute' | 'class' | 'media'
413
352
 
414
353
  theme: {
415
- // Replace a theme section entirely
416
- colors: {
417
- brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' },
418
- },
354
+ // Replace a section entirely
355
+ colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } },
419
356
  },
420
357
 
421
358
  extend: {
422
- // Add to defaults without replacing them
359
+ // Add to defaults
423
360
  colors: { brand: { 6: '#6366f1' } },
424
361
  spacing: { 18: '72px' },
425
362
  screens: { '3xl': '1920px' },
363
+ fontFamily: {
364
+ sans: 'Inter, sans-serif',
365
+ 'sans-bold': 'Inter_700Bold, sans-serif',
366
+ },
426
367
  },
427
368
 
428
369
  plugins: [
429
370
  ({ addUtility, addVariant, theme }) => {
430
- // Custom utility
431
371
  addUtility('border-brand', {
432
372
  borderColor: theme('colors.brand.6'),
433
373
  borderWidth: 2,
434
374
  });
435
-
436
- // Custom variant — selector string (auto-converted to CSS rules)
437
375
  addVariant('hocus', ':hover, :focus');
438
- addVariant('supports-grid', '@media (display: grid)');
439
- addVariant('dark-theme', '.dark-theme');
440
-
441
- // Custom variant — full ModifierDef for JS-tracked interactive states
442
- addVariant('long-press', {
443
- pseudo: ':active',
444
- jsBehavior: 'interactive',
445
- jsMatch: (_, state) => !!state.longPressed,
446
- });
447
376
  },
448
377
  ],
449
378
  };
450
379
  ```
451
380
 
452
- ### Color aliases
381
+ Setting `fontFamily.sans` to anything other than `'System'` auto-injects `body { font-family: … }`.
453
382
 
454
- Color values can reference other palette entries — chains up to 5 levels deep are resolved:
383
+ Color aliases:
455
384
 
456
385
  ```js
457
386
  extend: {
458
387
  colors: {
459
- primary: 'blue-6', // → resolved hex of blue shade 6
460
- brand: { 6: 'primary' }, // → follows through to blue-6's hex
388
+ primary: 'blue-6', // resolves to blue shade 6's hex
389
+ brand: { 6: 'primary' }, // follows through to blue-6's hex
461
390
  },
462
391
  },
463
392
  ```
464
393
 
465
- ### Font Family
466
-
467
- ```js
468
- // kbach.config.js
469
- module.exports = {
470
- extend: {
471
- fontFamily: {
472
- sans: 'Inter_400Regular',
473
- 'sans-md': 'Inter_500Medium',
474
- 'sans-bold': 'Inter_700Bold',
475
- },
476
- },
477
- };
478
- ```
479
-
480
- Use as utilities:
481
-
482
- ```jsx
483
- <p className="font-sans">Regular</p>
484
- <p className="font-sans-md">Medium</p>
485
- <p className="font-sans-bold">Bold</p>
486
- ```
487
-
488
- **Global default font (web):** When `fontFamily.sans` is set to anything other than `'System'`, Kbach automatically injects `body { font-family: <your-font> }` — all text inherits it without `font-sans` on every element.
489
-
490
- Apply at runtime (e.g. from a server-side user preference):
394
+ Runtime update:
491
395
 
492
396
  ```js
493
- import { updateConfig } from '@kbach/react';
494
-
397
+ import { updateConfig, clearCache } from '@kbach/react';
495
398
  updateConfig({ extend: { colors: { brand: { 6: '#6366f1' } } } });
496
- ```
497
-
498
- ## Custom responsive breakpoints
499
-
500
- Add breakpoints in `extend.screens` — they automatically register as new modifier names:
501
-
502
- ```js
503
- // kbach.config.js
504
- module.exports = {
505
- extend: {
506
- screens: { '3xl': '1920px' },
507
- },
508
- };
509
- ```
510
-
511
- ```jsx
512
- <div className="3xl:max-w-screen-3xl 3xl:mx-auto" />
399
+ clearCache();
513
400
  ```
514
401
 
515
402
  ## Full reference
516
403
 
517
- See [kbach-react.md](./kbach-react.md) for the complete utility reference, all modifiers, configuration options, and common patterns.
404
+ See [kbach-react.md](./kbach-react.md) for the complete utility reference, all modifier tables, and configuration options.
518
405
 
519
- For React Native / Expo, see the [`@kbach/native`](https://www.npmjs.com/package/@kbach/native) package.
406
+ For React Native / Expo, see [`@kbach/native`](https://www.npmjs.com/package/@kbach/native).
@@ -2433,6 +2433,21 @@ function findOuterColon(s) {
2433
2433
  return -1;
2434
2434
  }
2435
2435
 
2436
+ // src/core/reset.ts
2437
+ var BASE_RESET = [
2438
+ "*, *::before, *::after { box-sizing: border-box; }",
2439
+ "body { margin: 0; padding: 0; }",
2440
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2441
+ "p { margin: 0; }",
2442
+ "a { color: inherit; text-decoration: none; }",
2443
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
2444
+ "img, video, svg { display: block; max-width: 100%; }",
2445
+ "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2446
+ "button { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2447
+ "fieldset { border: 0; padding: 0; margin: 0; }",
2448
+ "table { border-collapse: collapse; border-spacing: 0; }"
2449
+ ].join("\n");
2450
+
2436
2451
  // src/core/cache.ts
2437
2452
  var LRUCache = class {
2438
2453
  constructor(capacity = 1e4) {
@@ -2582,7 +2597,7 @@ function getGlobalStyleEl() {
2582
2597
  }
2583
2598
  function injectGlobalStyles(theme) {
2584
2599
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2585
- const rules = [];
2600
+ const rules = [BASE_RESET];
2586
2601
  const ff = theme.fontFamily;
2587
2602
  if (ff.sans && ff.sans !== "System") {
2588
2603
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3023,6 +3038,7 @@ export {
3023
3038
  splitClassTokens,
3024
3039
  normalizeClassString,
3025
3040
  parseClasses,
3041
+ BASE_RESET,
3026
3042
  syncGlobalWidth,
3027
3043
  syncGlobalScreens,
3028
3044
  setGlobalWidth,
@@ -6,7 +6,7 @@ import {
6
6
  isNative,
7
7
  isWeb,
8
8
  subscribeGlobalWidth
9
- } from "./chunk-52N32L7A.mjs";
9
+ } from "./chunk-G76344ZQ.mjs";
10
10
 
11
11
  // src/core/darkModeStore.ts
12
12
  var KEY = "__kbach_dark_store__";
@@ -4,7 +4,7 @@ import {
4
4
  hasResponsiveBuckets,
5
5
  useConditionalWidth,
6
6
  useGlobalDarkMode
7
- } from "./chunk-BBI6IU22.mjs";
7
+ } from "./chunk-ISCQQRG5.mjs";
8
8
  import {
9
9
  flatten,
10
10
  getActiveBreakpoints,
@@ -17,7 +17,7 @@ import {
17
17
  isRuntimeCSSDisabled,
18
18
  normalizeClassString,
19
19
  resolve
20
- } from "./chunk-52N32L7A.mjs";
20
+ } from "./chunk-G76344ZQ.mjs";
21
21
 
22
22
  // src/jsx-runtime.tsx
23
23
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
package/dist/index.js CHANGED
@@ -2604,6 +2604,21 @@ function findOuterColon(s) {
2604
2604
  return -1;
2605
2605
  }
2606
2606
 
2607
+ // src/core/reset.ts
2608
+ var BASE_RESET = [
2609
+ "*, *::before, *::after { box-sizing: border-box; }",
2610
+ "body { margin: 0; padding: 0; }",
2611
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2612
+ "p { margin: 0; }",
2613
+ "a { color: inherit; text-decoration: none; }",
2614
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
2615
+ "img, video, svg { display: block; max-width: 100%; }",
2616
+ "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2617
+ "button { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2618
+ "fieldset { border: 0; padding: 0; margin: 0; }",
2619
+ "table { border-collapse: collapse; border-spacing: 0; }"
2620
+ ].join("\n");
2621
+
2607
2622
  // src/core/responsiveStore.ts
2608
2623
  var RESPONSIVE_KEY = "__kbach_responsive__";
2609
2624
  function getStore() {
@@ -2713,7 +2728,7 @@ function getGlobalStyleEl() {
2713
2728
  }
2714
2729
  function injectGlobalStyles(theme) {
2715
2730
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2716
- const rules = [];
2731
+ const rules = [BASE_RESET];
2717
2732
  const ff = theme.fontFamily;
2718
2733
  if (ff.sans && ff.sans !== "System") {
2719
2734
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  useConditionalWidth,
8
8
  useGlobalDarkMode,
9
9
  useGlobalWidth
10
- } from "./chunk-BBI6IU22.mjs";
10
+ } from "./chunk-ISCQQRG5.mjs";
11
11
  import {
12
12
  __require,
13
13
  buildConfig,
@@ -29,7 +29,7 @@ import {
29
29
  syncGlobalScreens,
30
30
  syncGlobalWidth,
31
31
  updateConfig
32
- } from "./chunk-52N32L7A.mjs";
32
+ } from "./chunk-G76344ZQ.mjs";
33
33
 
34
34
  // src/context.tsx
35
35
  import { createContext, useContext } from "react";
@@ -2504,6 +2504,21 @@ function findOuterColon(s) {
2504
2504
  return -1;
2505
2505
  }
2506
2506
 
2507
+ // src/core/reset.ts
2508
+ var BASE_RESET = [
2509
+ "*, *::before, *::after { box-sizing: border-box; }",
2510
+ "body { margin: 0; padding: 0; }",
2511
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2512
+ "p { margin: 0; }",
2513
+ "a { color: inherit; text-decoration: none; }",
2514
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
2515
+ "img, video, svg { display: block; max-width: 100%; }",
2516
+ "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2517
+ "button { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2518
+ "fieldset { border: 0; padding: 0; margin: 0; }",
2519
+ "table { border-collapse: collapse; border-spacing: 0; }"
2520
+ ].join("\n");
2521
+
2507
2522
  // src/core/responsiveStore.ts
2508
2523
  var RESPONSIVE_KEY = "__kbach_responsive__";
2509
2524
  function getStore() {
@@ -2598,7 +2613,7 @@ function getGlobalStyleEl() {
2598
2613
  }
2599
2614
  function injectGlobalStyles(theme) {
2600
2615
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2601
- const rules = [];
2616
+ const rules = [BASE_RESET];
2602
2617
  const ff = theme.fontFamily;
2603
2618
  if (ff.sans && ff.sans !== "System") {
2604
2619
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -2,9 +2,9 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-MIBPTKXT.mjs";
6
- import "./chunk-BBI6IU22.mjs";
7
- import "./chunk-52N32L7A.mjs";
5
+ } from "./chunk-ZN5E7I56.mjs";
6
+ import "./chunk-ISCQQRG5.mjs";
7
+ import "./chunk-G76344ZQ.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -2504,6 +2504,21 @@ function findOuterColon(s) {
2504
2504
  return -1;
2505
2505
  }
2506
2506
 
2507
+ // src/core/reset.ts
2508
+ var BASE_RESET = [
2509
+ "*, *::before, *::after { box-sizing: border-box; }",
2510
+ "body { margin: 0; padding: 0; }",
2511
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2512
+ "p { margin: 0; }",
2513
+ "a { color: inherit; text-decoration: none; }",
2514
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
2515
+ "img, video, svg { display: block; max-width: 100%; }",
2516
+ "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2517
+ "button { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2518
+ "fieldset { border: 0; padding: 0; margin: 0; }",
2519
+ "table { border-collapse: collapse; border-spacing: 0; }"
2520
+ ].join("\n");
2521
+
2507
2522
  // src/core/responsiveStore.ts
2508
2523
  var RESPONSIVE_KEY = "__kbach_responsive__";
2509
2524
  function getStore() {
@@ -2598,7 +2613,7 @@ function getGlobalStyleEl() {
2598
2613
  }
2599
2614
  function injectGlobalStyles(theme) {
2600
2615
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2601
- const rules = [];
2616
+ const rules = [BASE_RESET];
2602
2617
  const ff = theme.fontFamily;
2603
2618
  if (ff.sans && ff.sans !== "System") {
2604
2619
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3,9 +3,9 @@ import {
3
3
  _invalidateDefaultFontCache,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-MIBPTKXT.mjs";
7
- import "./chunk-BBI6IU22.mjs";
8
- import "./chunk-52N32L7A.mjs";
6
+ } from "./chunk-ZN5E7I56.mjs";
7
+ import "./chunk-ISCQQRG5.mjs";
8
+ import "./chunk-G76344ZQ.mjs";
9
9
  export {
10
10
  Fragment,
11
11
  _invalidateDefaultFontCache,
@@ -47,6 +47,21 @@ function escapeCSSSelector(cls) {
47
47
  return cls.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
48
48
  }
49
49
 
50
+ // src/core/reset.ts
51
+ var BASE_RESET = [
52
+ "*, *::before, *::after { box-sizing: border-box; }",
53
+ "body { margin: 0; padding: 0; }",
54
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
55
+ "p { margin: 0; }",
56
+ "a { color: inherit; text-decoration: none; }",
57
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
58
+ "img, video, svg { display: block; max-width: 100%; }",
59
+ "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
60
+ "button { appearance: none; -webkit-appearance: none; background: transparent; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
61
+ "fieldset { border: 0; padding: 0; margin: 0; }",
62
+ "table { border-collapse: collapse; border-spacing: 0; }"
63
+ ].join("\n");
64
+
50
65
  // src/core/theme.ts
51
66
  var defaultColors = {
52
67
  transparent: "transparent",
@@ -2492,7 +2507,7 @@ function getGlobalStyleEl() {
2492
2507
  }
2493
2508
  function injectGlobalStyles(theme) {
2494
2509
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2495
- const rules = [];
2510
+ const rules = [BASE_RESET];
2496
2511
  const ff = theme.fontFamily;
2497
2512
  if (ff.sans && ff.sans !== "System") {
2498
2513
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -2893,10 +2908,13 @@ ${globalCSS}`);
2893
2908
  return out.join("\n");
2894
2909
  }
2895
2910
  function buildThemeGlobal(theme) {
2911
+ const lines = [BASE_RESET];
2896
2912
  const ff = theme.fontFamily;
2897
- if (!ff?.sans || ff.sans === "System") return "";
2898
- const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2899
- return `body { font-family: ${family}; }`;
2913
+ if (ff?.sans && ff.sans !== "System") {
2914
+ const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2915
+ lines.push(`body { font-family: ${family}; }`);
2916
+ }
2917
+ return lines.join("\n");
2900
2918
  }
2901
2919
  var SKIP_DIRS = /* @__PURE__ */ new Set([
2902
2920
  "node_modules",
@@ -3014,7 +3032,9 @@ function scanDir(dir, onFile) {
3014
3032
  try {
3015
3033
  entries = (0, import_fs.readdirSync)(dir);
3016
3034
  } catch (err) {
3017
- console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
3035
+ if (err.code !== "ENOENT") {
3036
+ console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
3037
+ }
3018
3038
  return;
3019
3039
  }
3020
3040
  for (const entry of entries) {
@@ -1,9 +1,10 @@
1
1
  import {
2
+ BASE_RESET,
2
3
  buildConfig,
3
4
  generateClassCSS,
4
5
  setCSSGenMode,
5
6
  splitClassTokens
6
- } from "./chunk-52N32L7A.mjs";
7
+ } from "./chunk-G76344ZQ.mjs";
7
8
 
8
9
  // src/vite-plugin.ts
9
10
  import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
@@ -87,10 +88,13 @@ ${globalCSS}`);
87
88
  return out.join("\n");
88
89
  }
89
90
  function buildThemeGlobal(theme) {
91
+ const lines = [BASE_RESET];
90
92
  const ff = theme.fontFamily;
91
- if (!ff?.sans || ff.sans === "System") return "";
92
- const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
93
- return `body { font-family: ${family}; }`;
93
+ if (ff?.sans && ff.sans !== "System") {
94
+ const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
95
+ lines.push(`body { font-family: ${family}; }`);
96
+ }
97
+ return lines.join("\n");
94
98
  }
95
99
  var SKIP_DIRS = /* @__PURE__ */ new Set([
96
100
  "node_modules",
@@ -208,7 +212,9 @@ function scanDir(dir, onFile) {
208
212
  try {
209
213
  entries = readdirSync(dir);
210
214
  } catch (err) {
211
- console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
215
+ if (err.code !== "ENOENT") {
216
+ console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
217
+ }
212
218
  return;
213
219
  }
214
220
  for (const entry of entries) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",