@kbach/react 0.6.15 → 0.6.16

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,6 +1,6 @@
1
1
  # @kbach/react
2
2
 
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.
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.
4
4
 
5
5
  ```jsx
6
6
  <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
@@ -12,36 +12,37 @@ Tailwind-like utility classes for React (web). Write classes as `className` stri
12
12
 
13
13
  ## Install
14
14
 
15
- [npm package](https://www.npmjs.com/package/@kbach/react)
16
-
17
15
  ```
18
16
  npm install @kbach/react
19
17
  ```
20
18
 
19
+ [npm package](https://www.npmjs.com/package/@kbach/react)
20
+
21
21
  ## Setup
22
22
 
23
- Step 1 is always required. After that, pick **one** of the two setups below — Quick if you just want styles working, Static CSS if you're shipping to production and/or using SSR.
23
+ Two steps: configure the JSX runtime (always), then pick one styling method.
24
24
 
25
- ### 1. Configure the JSX runtime
25
+ ### Step 1 — JSX runtime
26
26
 
27
- **tsconfig.json** (works for Vite, Next.js, React Router, and most other tooling — it's the one setting that everything reads):
27
+ **tsconfig.json:**
28
28
 
29
29
  ```json
30
- {
31
- "compilerOptions": {
32
- "jsx": "react-jsx",
33
- "jsxImportSource": "@kbach/react"
34
- }
35
- }
30
+ { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/react" } }
36
31
  ```
37
32
 
38
- Don't also pass `jsxImportSource` to `@vitejs/plugin-react` (or any other bundler plugin) unless you have a specific reason to — the tsconfig setting alone is enough, and pointing two different places at the JSX runtime is a common source of "it's not applying" confusion (or, worse, plugin conflicts — see the React Router note below).
33
+ That's the only setting needed — Vite, Next.js, and React Router all read it. Don't *also* set `jsxImportSource` on a bundler plugin (e.g. `@vitejs/plugin-react`); one source of truth avoids conflicts.
39
34
 
40
- Alternatives if you can't use tsconfig: a per-file `/** @jsxImportSource @kbach/react */` pragma comment, or `['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }]` in `babel.config.js` for non-Vite/non-SWC toolchains.
35
+ ### Step 2 — Styling method
41
36
 
42
- ### 2a. Quick setup (runtime CSS injection)
37
+ Not sure which? Check your framework first:
43
38
 
44
- No build plugin, works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …). Styles are generated and injected into a `<style>` tag by client-side JS the moment `ThemeProvider` mounts.
39
+ | Framework | Use |
40
+ |---|---|
41
+ | Next.js | **Quick setup** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
42
+ | React Router, framework mode | **Static CSS setup** — and skip `@vitejs/plugin-react`, see note below |
43
+ | Vite, React Router library mode, CRA, other | Either — Quick is faster to try, Static CSS is zero-cost for production |
44
+
45
+ #### Quick setup — any bundler, client-side CSS injection
45
46
 
46
47
  ```jsx
47
48
  import { ThemeProvider, KbachReset } from '@kbach/react';
@@ -56,32 +57,20 @@ export default function Root() {
56
57
  }
57
58
  ```
58
59
 
59
- `<KbachReset />` renders Kbach's base browser-default reset (borderless buttons/inputs, visible checkboxes/radios, no arrow-less `<select>`, etc. — see [CSS resets](#css-resets) below) as a plain `<style>` tag instead of waiting for the client-side injector. On a plain client-rendered app (Vite CSR, Create React App, …) it's a nice-to-have, since the injector already runs before first paint there. **On SSR** (Next.js, React Router framework mode, Remix, …) it's the difference between correct styling from the first byte and a flash of raw browser defaults (default button border, unstyled `<select>`, serif-ish fonts, …) that only clears up once hydration finishes — the server has no JS to run the injector, but it *can* render this component, since it's just JSX. Put it as high in `<head>` as your framework allows; the snippet above (right after `ThemeProvider` opens) works everywhere `<head>` isn't directly reachable.
60
-
61
- That's the whole setup. `@kbach/react` ships its own `"use client"` directive, so both of these work directly in a Next.js App Router Server Component tree — no manual client wrapper needed.
60
+ 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. `@kbach/react` ships its own `"use client"` directive, so this works in a Next.js Server Component tree with no wrapper needed — in Next.js, render it once in the root `layout.tsx`.
62
61
 
63
- ### 2b. Static CSS setup (recommended for production / SSR, Vite only)
62
+ #### Static CSS setup — Vite only, zero runtime cost
64
63
 
65
- Zero runtime cost: a Vite plugin scans your source at build time and writes real CSS into a file you import, so styles ship as an ordinary stylesheet with no client-side generation step at all — nothing to flash-of-unstyled before. This is the better choice for any Vite-based SSR framework (React Router, SvelteKit-style meta-frameworks, etc.) over the Quick setup + `<KbachReset />` combination, since it covers *every* class, not just the base reset.
64
+ A build-time plugin writes real CSS into a file you import — nothing generated client-side.
66
65
 
67
66
  ```ts
68
67
  // vite.config.ts
69
68
  import { defineConfig } from 'vite';
70
69
  import { kbach } from '@kbach/react/vite';
71
70
 
72
- export default defineConfig({
73
- plugins: [
74
- // ...your framework's own plugin(s) — see the framework notes below
75
- // before adding @vitejs/plugin-react yourself.
76
- kbach(),
77
- ],
78
- });
71
+ export default defineConfig({ plugins: [kbach()] });
79
72
  ```
80
73
 
81
- Adding the plugin also declares `@kbach/react` in Vite's `optimizeDeps.include` upfront, so Vite pre-bundles it during its initial dependency scan instead of discovering it lazily on the first page that imports it — avoiding a forced full-reload mid-render, which is what a rare "Invalid hook call"-style crash on first load (but never again after) usually turns out to be.
82
-
83
- Then create an empty `kbach.css` anywhere in your project and import it once in your app entry:
84
-
85
74
  ```css
86
75
  /* src/kbach.css */
87
76
  /* kbach:start */
@@ -95,142 +84,106 @@ import './kbach.css';
95
84
 
96
85
  ```jsx
97
86
  import { ThemeProvider } from '@kbach/react';
98
- // No <KbachReset /> needed — kbach.css already includes the same base reset.
87
+ // No <KbachReset /> — kbach.css already includes the reset.
99
88
 
100
89
  export default function Root() {
101
- return (
102
- <ThemeProvider defaultMode="system">
103
- <App />
104
- </ThemeProvider>
105
- );
90
+ return <ThemeProvider defaultMode="system"><App /></ThemeProvider>;
106
91
  }
107
92
  ```
108
93
 
109
- The plugin scans your source files and writes generated CSS between the `/* kbach:start */`/`/* kbach:end */` markers. Importing `kbach.css` automatically disables runtime injection — all styles come from the file instead. On HMR, only the changed file is rescanned; unchanged tokens reuse cached CSS.
94
+ The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection. 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.
110
95
 
111
- While it's scanning, the plugin also indexes every `.css`/`.scss`/`.sass`/`.less` file under the same directories and warns in the terminal (not the browser console) for any class that's neither a real Kbach utility nor defined anywhere in those stylesheets — likely a typo. A class you've defined yourself elsewhere (CSS Modules, styled-components, a third-party component's class) is recognized as soon as something in the project literally has a `.that-class-name` rule, so it won't get flagged just because Kbach itself doesn't know it. Each warning includes a `file:line:column` location that most terminals (VS Code's integrated terminal included) turn into a clickable link straight to that class.
112
-
113
- ## Next.js
114
-
115
- Step 1 (tsconfig `jsxImportSource`) above applies as-is — Next.js's SWC compiler reads `jsxImportSource` from `tsconfig.json` the same way Vite does. For step 2, use **Quick setup (2a)** — Next.js builds with webpack/Turbopack, not Vite, so the static-CSS plugin (2b) doesn't apply. Render `<KbachReset />` once in the root App Router `layout.tsx` (inside `<head>`, or right after `<ThemeProvider>` opens) so Server Component HTML gets the base reset without waiting on hydration; `@kbach/react` shipping its own `"use client"` directive means both `ThemeProvider` and `KbachReset` work directly in that Server Component tree with no wrapper of your own needed.
116
-
117
- ## React Router
118
-
119
- **Framework mode (v7+, SSR by default):** `@react-router/dev`'s own `reactRouter()` Vite plugin already includes its own React JSX transform and Fast Refresh integration — **do not also add `@vitejs/plugin-react`.** Having both active makes each one inject its own Fast Refresh preamble into the same module, which crashes the page at runtime (`Identifier 'RefreshRuntime' has already been declared`) before React ever hydrates — every class on the page will silently fail to style because the app never actually mounts. The correct setup is just:
96
+ **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`).
120
97
 
121
98
  ```ts
122
99
  // vite.config.ts
123
100
  import { reactRouter } from '@react-router/dev/vite';
124
101
  import { defineConfig } from 'vite';
125
- import { kbach } from '@kbach/react/vite'; // omit if you're not using static CSS
102
+ import { kbach } from '@kbach/react/vite'; // omit if using Quick setup instead
126
103
 
127
- export default defineConfig({
128
- plugins: [kbach(), reactRouter()],
129
- });
104
+ export default defineConfig({ plugins: [kbach(), reactRouter()] });
130
105
  ```
131
106
 
132
- `jsxImportSource` in `tsconfig.json` (step 1 above) is all that's needed for the JSX runtime — React Router's Vite plugin reads it the same way plain Vite does. `include`'s default scan dirs (`['src', 'app', 'pages', 'components', 'views', 'layouts']`) already cover React Router's `app/` routes convention if you're using the static-CSS plugin.
107
+ (React Router library mode — `createBrowserRouter`, no SSR — has no such conflict; set it up like any Vite + React app.)
108
+
109
+ ## Dark mode
133
110
 
134
- If you do add `kbach()` to `vite.config.ts` and it detects both plugins active together, it prints a warning explaining exactly this at dev-server startup — so this doesn't have to be a silent, hard-to-diagnose blank page.
111
+ `<ThemeProvider>` powers every `dark:` class — detects OS color scheme, persists the user's choice, re-renders on change.
135
112
 
136
- Framework mode is SSR, so skipping the static-CSS plugin (2b) in favor of Quick setup (2a) means the initial server-rendered HTML has no CSS at all until hydration — render `<KbachReset />` in `root.tsx`'s `<Layout>` (inside `<head>`, alongside `<Links />`/`<Meta />`) if you go that route, otherwise expect raw browser defaults (e.g. the native `<button>` border) on first paint.
113
+ ```jsx
114
+ <ThemeProvider
115
+ defaultMode="system" // 'light' | 'dark' | 'system'
116
+ disablePersistence={false} // true = don't remember across reloads
117
+ >
118
+ <App />
119
+ </ThemeProvider>
120
+ ```
121
+
122
+ | Prop | Type | Default | Description |
123
+ |---|---|---|---|
124
+ | `defaultMode` | `'light' \| 'dark' \| 'system'` | `'system'` | Starting mode |
125
+ | `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` |
126
+ | `config` | `FrameworkConfig` | global config | Scope a different config to this subtree |
137
127
 
138
- **Library mode (client-only, `createBrowserRouter`/`<BrowserRouter>`):** no SSR involved — set it up exactly like any other Vite + React app. Since there's no meta-framework Vite plugin providing JSX handling here, you likely do need `@vitejs/plugin-react({ jsxImportSource: '@kbach/react' })` (or rely on tsconfig alone — Vite's default esbuild-based transform reads it too).
128
+ `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).
139
129
 
140
130
  ## API
141
131
 
142
- ### className / kb prop
132
+ ### className / kb
143
133
 
144
- Works on any HTML element or React component once the JSX runtime is configured. `kb` is an alias for `className`.
134
+ `kb` is an alias for `className` — works on any element.
145
135
 
146
136
  ```jsx
147
- <div className="bg-white dark:bg-gray-10 p-4 rounded-xl" />
148
- <p kb="text-gray-10 dark:text-white text-lg font-bold" />
149
137
  <button className="bg-blue-7 hover:bg-blue-8 pressed:bg-blue-9 rounded-lg px-4 py-2" />
150
138
  ```
151
139
 
152
140
  ### styled(Component, classes)
153
141
 
154
- Pre-style any component. Returns a new component that merges the base classes with any `kb` prop passed at use time.
155
-
156
142
  ```jsx
157
143
  import { styled } from '@kbach/react';
158
144
 
159
145
  const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
160
- const Button = styled(
161
- 'button',
162
- 'bg-blue-7 hover:bg-blue-8 dark:bg-indigo-6 rounded-xl px-6 py-3'
163
- );
146
+ const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
164
147
 
165
148
  <Card kb="mt-4">
166
149
  <Button kb="w-full">Submit</Button>
167
150
  </Card>
168
151
  ```
169
152
 
170
- ### cx(...classes)
153
+ Extra classes at use time via `kb` merge with the base classes.
171
154
 
172
- Conditionally join class strings. Falsy values are ignored. The Vite plugin scans `cx()` calls when building the static CSS.
155
+ ### cx(...classes)
173
156
 
174
157
  ```jsx
175
158
  import { cx } from '@kbach/react';
176
159
 
177
- <div className={cx(
178
- 'p-4 rounded-xl',
179
- isSelected && 'border-2 border-blue-6',
180
- isDisabled && 'opacity-50',
181
- )} />
160
+ <div className={cx('p-4 rounded-xl', isSelected && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
182
161
  ```
183
162
 
184
- Pre-built style constants (also scanned by the Vite plugin):
163
+ Falsy values ignored. Also works as pre-built style constants:
185
164
 
186
165
  ```ts
187
- // styles.ts
188
- import { cx } from '@kbach/react';
189
-
190
166
  export const container = cx('flex-1 bg-white dark:bg-gray-9 p-4');
191
- export const heading = cx('text-2xl font-bold text-gray-10 dark:text-white');
192
- ```
193
-
194
- ```jsx
195
- import { container, heading } from './styles';
196
-
197
- <div className={container}>
198
- <h1 className={heading}>Hello</h1>
199
- </div>
200
167
  ```
201
168
 
202
169
  ### useStyles(classes, state?)
203
170
 
204
- Resolve classes to a style object inside a component.
205
-
206
171
  ```jsx
207
- import { useStyles } from '@kbach/react';
208
-
209
172
  const style = useStyles('bg-blue-6 dark:bg-indigo-6 px-3 py-1 rounded-full');
210
-
211
- // Multiple strings merged left-to-right
212
- const style = useStyles(['bg-white p-4', 'dark:bg-gray-9 rounded-xl']);
213
-
214
- // With interaction state
215
- const [pressed, setPressed] = useState(false);
216
- const style = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
173
+ const style2 = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
217
174
  ```
218
175
 
219
176
  ### kb(classes)
220
177
 
221
- Resolve classes outside a component (static contexts).
178
+ Resolve outside a component:
222
179
 
223
180
  ```js
224
- import { kb } from '@kbach/react';
225
-
226
181
  const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
227
182
  ```
228
183
 
229
184
  ### useTheme()
230
185
 
231
186
  ```js
232
- import { useTheme } from '@kbach/react';
233
-
234
187
  const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
235
188
  ```
236
189
 
@@ -238,164 +191,74 @@ const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
238
191
  |---|---|---|
239
192
  | `mode` | `'light' \| 'dark' \| 'system'` | User-selected mode |
240
193
  | `resolvedMode` | `'light' \| 'dark'` | Resolved after system lookup |
241
- | `isDark` | `boolean` | Shorthand for `resolvedMode === 'dark'` |
194
+ | `isDark` | `boolean` | `resolvedMode === 'dark'` |
242
195
  | `setMode` | `fn` | Set mode explicitly |
243
- | `toggle` | `fn` | Toggle between light and dark |
196
+ | `toggle` | `fn` | Toggle light/dark |
244
197
  | `config` | `ResolvedConfig` | Full resolved config |
245
198
 
246
- ### useIsDark()
199
+ ### useIsDark() / useColors()
247
200
 
248
201
  ```js
249
- import { useIsDark } from '@kbach/react';
250
202
  const isDark = useIsDark();
251
- ```
252
-
253
- ### useColors()
254
-
255
- Returns the active theme's color palette as a smart proxy.
256
-
257
- ```js
258
- import { useColors } from '@kbach/react';
259
203
 
260
204
  const colors = useColors();
261
205
  colors.blue[6] // '#3b82f6'
262
206
  colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
263
- colors.white // '#ffffff'
264
- colors['white/20'] // 'rgba(255,255,255,0.2)'
265
207
  colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
266
208
  ```
267
209
 
268
210
  ## Modifiers
269
211
 
270
- Chain modifiers in any order before the utility name:
271
-
272
- ```jsx
273
- <div className="dark:sm:hover:p-4" />
274
- <input className="focus:ring-2 focus:ring-blue-5 focus:outline-none" />
275
- ```
276
-
277
- ### Theme
212
+ Chain in any order: `<div className="dark:sm:hover:p-4" />`
278
213
 
279
- | Modifier | Trigger |
214
+ | Category | Modifiers |
280
215
  |---|---|
281
- | `dark:` | Dark mode |
282
- | `light:` / `not-dark:` | Light mode |
283
-
284
- ### Interactive
285
-
286
- | Modifier | Trigger |
287
- |---|---|
288
- | `hover:` | Mouse hover |
289
- | `focus:` | Element focused |
290
- | `pressed:` | Click / touch down |
291
- | `active:` | Active state |
292
- | `disabled:` | Disabled |
293
- | `checked:` | Checkbox / radio checked |
294
- | `visited:` | Visited link |
295
- | `placeholder:` | Placeholder text |
296
-
297
- Negated: `not-hover:`, `not-focus:`, `not-pressed:`, `not-active:`, `not-disabled:`, `not-checked:`
298
-
299
- ### Structural
300
-
301
- `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:`
302
-
303
- ### Pseudo-elements
304
-
305
- `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` `placeholder:`
216
+ | Theme | `dark:` `light:` / `not-dark:` |
217
+ | Interactive | `hover:` `focus:` `pressed:` `active:` `disabled:` `checked:` `visited:` `placeholder:` (all have `not-` variants) |
218
+ | Structural | `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:` |
219
+ | Pseudo-elements | `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` |
220
+ | Responsive | `sm:`(576px) `md:`(768px) `lg:`(1024px) `xl:`(1280px) `2xl:`(1536px) |
221
+ | Other | `print:` `landscape:`/`portrait:` `motion-reduce:`/`motion-safe:` `contrast-more:`/`contrast-less:` `rtl:`/`ltr:` `!` (important) |
306
222
 
307
223
  ```jsx
308
224
  <div className="before:content-['*'] before:text-red-6 relative" />
309
- <p className="selection:bg-blue-3" />
310
- <input className="placeholder:text-gray-5" />
311
225
  ```
312
226
 
313
- ### Group / peer
227
+ **Group / peer:**
314
228
 
315
229
  ```jsx
316
230
  <div className="group">
317
231
  <span className="opacity-0 group-hover:opacity-100 transition" />
318
232
  </div>
319
- <input className="peer" />
320
- <p className="peer-focus:text-blue-6" />
321
233
  ```
322
234
 
323
- Nested groups need names, or the inner element reacts to whichever `.group` is nearest — not necessarily the one you meant:
324
-
325
- ```jsx
326
- <div className="group/card">
327
- <div className="group/icon">
328
- <span className="group-hover/icon:opacity-100" /> {/* only the inner group */}
329
- </div>
330
- <span className="group-hover/card:underline" /> {/* only the outer group */}
331
- </div>
332
- ```
333
-
334
- ### Responsive
335
-
336
- | Modifier | Min-width |
337
- |---|---|
338
- | `sm:` | 576 px |
339
- | `md:` | 768 px |
340
- | `lg:` | 1024 px |
341
- | `xl:` | 1280 px |
342
- | `2xl:` | 1536 px |
343
-
344
- ### Other
345
-
346
- | Modifier | Effect |
347
- |---|---|
348
- | `print:hidden` | Visible only on screen |
349
- | `landscape:` `portrait:` | Orientation |
350
- | `motion-reduce:` `motion-safe:` | Reduced-motion preference |
351
- | `contrast-more:` `contrast-less:` | Contrast preference |
352
- | `rtl:` `ltr:` | Text direction |
353
- | `!p-0` | `!important` on every declaration |
235
+ 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.
354
236
 
355
237
  ## Arbitrary values
356
238
 
357
239
  ```jsx
358
- <div className="bg-[#6366f1]" />
359
- <div className="p-[14px]" />
360
- <div className="w-[calc(100%-2rem)]" />
361
- <div className="bg-[rgba(99,102,241,0.15)]" />
362
- <div className="text-[18px]" />
240
+ <div className="bg-[#6366f1] p-[14px] w-[calc(100%-2rem)] text-[18px]" />
363
241
  ```
364
242
 
365
- ### Arbitrary properties (web only)
366
-
367
- For the rare CSS property with no named utility, write `[property:value]` directly — no utility prefix:
368
-
369
- ```jsx
370
- <div className="[mask-type:luminance]" />
371
- <div className="[-webkit-line-clamp:3]" />
372
- <div className="[--my-var:10px]" /> {/* CSS custom property */}
373
- ```
374
-
375
- Underscores become spaces, same as any other arbitrary value: `[background:url(/a.png)_no-repeat]`. Only the first `:` splits the property from the value, so a value that itself contains a colon (a URL's `http://`) still comes through intact.
243
+ 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]`.
376
244
 
377
245
  ## Color system
378
246
 
379
- 12-shade scale — 1 lightest, 12 darkest. Use as `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
247
+ 12-shade scale, 1 lightest → 12 darkest: `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
380
248
 
381
- Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
249
+ Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
382
250
  Special: `transparent` `current` `black` `white`
383
-
384
- Opacity modifier: `bg-blue-6/50` (50% alpha), `bg-blue-6/[0.15]` (arbitrary)
251
+ Opacity: `bg-blue-6/50` or `bg-blue-6/[0.15]`
385
252
 
386
253
  ## CSS resets
387
254
 
388
- Included automatically in `kbach.css`, in runtime injection, and in `<KbachReset />` (all three render the exact same rules — pick whichever setup you're using from [Setup](#setup) above):
255
+ Included in `kbach.css`, runtime injection, and `<KbachReset />` alike:
389
256
 
390
- - `*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor }` — border utilities like `border-2 border-gray-4` work on any element without also needing `border-solid`
391
- - `body { margin: 0; padding: 0 }`
392
- - `h1–h6` — margin, font-size, font-weight cleared (inherit from parent)
393
- - `p`, `ul`, `ol` — margin and list style cleared
394
- - `a` — color and underline cleared (inherit from parent)
395
- - `img`, `video`, `svg` — `display: block; max-width: 100%`
396
- - `button`, text-like `input`s (not checkbox/radio), `textarea` — appearance, border, padding cleared, so `border-`/`bg-`/`rounded-`/`p-` utilities fully restyle them
397
- - `input[type=checkbox]`, `input[type=radio]`, `select` — native rendering kept (stripping it would hide the checkmark/dot/dropdown-arrow with nothing to replace them); only typography and spacing are normalized, and checkbox/radio/range get `accent-color: currentColor` so they still pick up your text color instead of the OS default blue
398
- - `fieldset`, `table` — padding/margin/border-collapse cleared
257
+ - Border-box everywhere; `border-*` utilities work without needing `border-solid`
258
+ - `body` margin/padding cleared; headings/`p`/`ul`/`ol`/`a` styling cleared to inherit
259
+ - `img`/`video`/`svg` block + max-width 100%
260
+ - `button`/text inputs/`textarea` stripped of native appearance so `bg-`/`rounded-`/`p-` fully restyle them
261
+ - Checkbox/radio/`select` keep native rendering (just typography/spacing normalized + `accent-color: currentColor`)
399
262
 
400
263
  ## Configuration
401
264
 
@@ -405,71 +268,36 @@ module.exports = {
405
268
  darkMode: 'attribute', // 'attribute' | 'class' | 'media'
406
269
 
407
270
  theme: {
408
- // Replace a section entirely
409
- colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } },
271
+ colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } }, // replaces the section
410
272
  },
411
273
 
412
274
  extend: {
413
- // Add to defaults
414
- colors: { brand: { 6: '#6366f1' } },
275
+ colors: { brand: { 6: '#6366f1' } }, // adds to defaults
415
276
  spacing: { 18: '72px' },
416
277
  screens: { '3xl': '1920px' },
417
- fontFamily: {
418
- sans: 'Inter, sans-serif',
419
- 'sans-bold': 'Inter_700Bold, sans-serif',
420
- },
421
- // Custom @keyframes (web only) — reference by name from `animation`,
422
- // then use that name as animate-{name}. Declaration values are plain
423
- // CSS strings, same idea as a style object but for a keyframe step.
278
+ fontFamily: { sans: 'Inter, sans-serif' },
424
279
  keyframes: {
425
280
  wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
426
281
  },
427
- animation: {
428
- wiggle: 'wiggle 1s ease-in-out infinite',
429
- },
282
+ animation: { wiggle: 'wiggle 1s ease-in-out infinite' },
430
283
  },
431
284
 
432
285
  plugins: [
433
286
  ({ addUtility, addVariant, theme }) => {
434
- addUtility('border-brand', {
435
- borderColor: theme('colors.brand.6'),
436
- borderWidth: 2,
437
- });
287
+ addUtility('border-brand', { borderColor: theme('colors.brand.6'), borderWidth: 2 });
438
288
  addVariant('hocus', ':hover, :focus');
439
289
  },
440
290
  ],
441
291
  };
442
292
  ```
443
293
 
444
- Setting `fontFamily.sans` to anything other than `'System'` auto-injects `body { font-family: … }`.
445
-
446
- ```jsx
447
- <div className="animate-wiggle" />
448
- {/* Override duration/timing/repeat inline, keeps the same wiggle @keyframes: */}
449
- <div className="animate-[wiggle_2s_ease-in-out]" />
450
- ```
451
-
452
- Color aliases:
453
-
454
- ```js
455
- extend: {
456
- colors: {
457
- primary: 'blue-6', // resolves to blue shade 6's hex
458
- brand: { 6: 'primary' }, // follows through to blue-6's hex
459
- },
460
- },
461
- ```
462
-
463
- Runtime update:
464
-
465
- ```js
466
- import { updateConfig, clearCache } from '@kbach/react';
467
- updateConfig({ extend: { colors: { brand: { 6: '#6366f1' } } } });
468
- clearCache();
469
- ```
294
+ - `fontFamily.sans` set to anything but `'System'` auto-injects `body { font-family: … }`
295
+ - Custom `@keyframes` are used as `animate-{name}`, and can be overridden inline: `animate-[wiggle_2s_ease-in-out]`
296
+ - Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
297
+ - Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
470
298
 
471
299
  ## Full reference
472
300
 
473
- See [kbach-react.md](./kbach-react.md) for the complete utility reference, all modifier tables, and configuration options.
301
+ [kbach-react.md](./kbach-react.md) — complete utility list, every modifier, all config options.
474
302
 
475
- For React Native / Expo, see [`@kbach/native`](https://www.npmjs.com/package/@kbach/native).
303
+ React Native / Expo: [`@kbach/native`](https://www.npmjs.com/package/@kbach/native).