@kbach/ui 0.1.0-beta.0

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-ui.md ADDED
@@ -0,0 +1,889 @@
1
+ # Kbach UI — Complete AI Reference
2
+
3
+ Kbach is a Tailwind-like utility CSS framework for React (web). Classes are written as `className` strings and resolved at render time through a custom JSX runtime. On web, stateful and structural CSS rules are injected into the page so they work with the browser cascade. A Vite plugin generates a physical `app.css` file for static CSS delivery.
4
+
5
+ Package: `@kbach/ui`
6
+
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ### tsconfig.json
12
+ ```json
13
+ { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/ui" } }
14
+ ```
15
+
16
+ ### vite.config.ts — Static CSS setup (recommended, plain Vite only — skip @vitejs/plugin-react if a meta-framework already provides its own Vite/React plugin, e.g. React Router's reactRouter(); see below)
17
+ Requires `vite` and `@vitejs/plugin-react` as dev dependencies — not installed by `@kbach/ui` itself: `npm install -D vite @vitejs/plugin-react`.
18
+ ```ts
19
+ import { defineConfig } from 'vite';
20
+ import react from '@vitejs/plugin-react';
21
+ import { kbach } from '@kbach/ui/vite';
22
+
23
+ export default defineConfig({
24
+ plugins: [
25
+ react({ jsxImportSource: '@kbach/ui' }),
26
+ kbach(), // generates / updates app.css on every HMR event
27
+ ],
28
+ });
29
+ ```
30
+ Then create the stylesheet the plugin writes into and import it once — this import is the step that actually disables runtime CSS injection; `kbach()` in the plugins array alone only generates the file:
31
+ ```css
32
+ /* src/kbach.css */
33
+ /* kbach:start */
34
+ /* kbach:end */
35
+ ```
36
+ ```ts
37
+ // main.tsx
38
+ import './kbach.css';
39
+ ```
40
+
41
+ ### Runtime setup (any bundler, no Vite plugin)
42
+ Skip `kbach()` and the `kbach.css` import above. Works with any bundler (Vite, webpack, Turbopack, Metro-for-web) with no build plugin — the only option for Next.js (see below), or for skipping the plugin for now on Vite.
43
+
44
+ ### Babel (non-Vite)
45
+ ```js
46
+ module.exports = {
47
+ presets: [['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/ui' }]],
48
+ };
49
+ ```
50
+
51
+ ### Per-file (no config needed)
52
+ ```jsx
53
+ /** @jsxImportSource @kbach/ui */
54
+ ```
55
+
56
+ ### Wrap app
57
+ ```jsx
58
+ import { ThemeProvider } from '@kbach/ui';
59
+ <ThemeProvider defaultMode="system"><App /></ThemeProvider>
60
+ ```
61
+
62
+ ### Next.js
63
+ tsconfig `jsxImportSource` setup above applies as-is. No Vite plugin for Next.js (webpack/Turbopack) — falls back to runtime CSS injection, which only runs client-side, so expect a brief flash of unstyled content on first paint before hydration. `@kbach/ui`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, and `<ThemeProvider>` directly, with no manual client wrapper needed.
64
+
65
+ ### React Router
66
+ Framework mode (v7+, SSR): do NOT add `@vitejs/plugin-react` — `@react-router/dev`'s `reactRouter()` Vite plugin already includes its own JSX transform + Fast Refresh integration. Adding both makes each inject its own Fast Refresh preamble into the same module, crashing the page (`Identifier 'RefreshRuntime' has already been declared`) before React hydrates — every class on the page silently fails to style because the app never mounts. tsconfig `jsxImportSource` alone is enough:
67
+ ```ts
68
+ import { reactRouter } from '@react-router/dev/vite';
69
+ import { kbach } from '@kbach/ui/vite'; // omit if not using static CSS
70
+ export default { plugins: [kbach(), reactRouter()] };
71
+ ```
72
+ Default scan dirs include `app/`. Library mode (client-only, no meta-framework Vite plugin involved) needs no special handling beyond the standard Vite setup above.
73
+
74
+ ### React Native / Expo
75
+ Same `npm install @kbach/ui` — no separate package. `@kbach/native` is deprecated and no longer maintained; its last published version is frozen as a compatibility shim re-exporting this package.
76
+
77
+ ```js
78
+ // babel.config.js
79
+ module.exports = function (api) {
80
+ api.cache(true);
81
+ return { presets: ['babel-preset-expo', '@kbach/ui/babel'] };
82
+ };
83
+ ```
84
+ One-liner: `const { createKbachConfig } = require('@kbach/ui/native'); module.exports = createKbachConfig();`. Merge into an existing config with `withKbachBabel({ presets: [...] })` (same module). After editing this file: `npx expo start --clear`.
85
+
86
+ ```jsx
87
+ import { ThemeProvider } from '@kbach/ui/native';
88
+ <ThemeProvider defaultMode="system"><App /></ThemeProvider>
89
+ ```
90
+ Native-aware — reads `useColorScheme()`/`useWindowDimensions()` automatically. The plain `ThemeProvider` from `@kbach/ui` (no `/native`) has no automatic RN wiring; import the `/native` one on React Native.
91
+
92
+ `disablePersistence` on `<ThemeProvider>` saves to `AsyncStorage` on native (vs. `localStorage` on web) — same prop, platform-appropriate storage.
93
+
94
+ Utility Reference below is tagged inline: `(web only)` entries no-op silently on native. Native-only additions not in the main tables: `tint-{color}` (Image/icon tinting), `perspective-{n}`, `backface-hidden`, `text-shadow`/`text-shadow-lg`. `ring-*` is a partial exception — falls back to `borderWidth`/`borderColor` on native (no box-shadow in RN), which *does* affect layout and shares properties with `border-*`.
95
+
96
+ In a browser (Expo Web, Metro web), `@kbach/ui` switches to the same CSS-class strategy as plain web automatically — RN components substitute to HTML elements (`View`→`div`, `Text`→`span`, etc.), RN-only props map to HTML equivalents, and either the Vite plugin (recommended, same as Static CSS setup above) or a `<KbachReset />` near the root covers the base reset.
97
+
98
+ 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');`.
99
+
100
+ ---
101
+
102
+ ## Vite Plugin
103
+
104
+ The `kbach()` Vite plugin scans your source files and writes generated CSS between `/* kbach:start */` / `/* kbach:end */` markers in your main CSS file (`app/app.css`, `src/index.css`, etc.).
105
+
106
+ - Runs on `buildStart` (initial load) and on every HMR file change
107
+ - Stateless: rescans all files fresh every time — removed classes are immediately evicted
108
+ - Output is grouped by category with CSS custom properties for theme colors
109
+ - Also indexes every `.css`/`.scss`/`.sass`/`.less` file the same `include` dirs cover, and warns (`console.warn`, dev-server terminal, not the browser) for any class that's neither a real Kbach utility NOR defined anywhere in those stylesheets — a likely typo. Classes intentionally handled elsewhere (CSS Modules, styled-components, a third-party component's own class) are recognized once anything in the project literally defines `.that-class-name` and stay silent. Each warning prints a `file:line:column` location that terminals with clickable-link support (VS Code's included) turn into a jump-to-that-class link.
110
+
111
+ ```ts
112
+ // vite.config.ts
113
+ import { kbach } from '@kbach/ui/vite';
114
+
115
+ kbach({
116
+ darkMode: 'attribute',
117
+ theme: {
118
+ colors: { brand: { 6: '#6366f1' } },
119
+ },
120
+ })
121
+ ```
122
+
123
+ Generated output format:
124
+ ```css
125
+ /* kbach:start */
126
+ /* Generated by Kbach — do not edit */
127
+
128
+ :root {
129
+ --color-blue-6: #3b82f6;
130
+ --color-gray-10: #111827;
131
+ }
132
+
133
+ /* Layout */
134
+ .flex { display: flex }
135
+ .items-center { align-items: center }
136
+
137
+ /* Sizing */
138
+ .w-full { width: 100% }
139
+ .w-\[200px\] { width: 200px }
140
+
141
+ /* Dark Mode */
142
+ [data-theme="dark"] .dark\:bg-gray-10 { background-color: var(--color-gray-10) }
143
+ /* kbach:end */
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Core API
149
+
150
+ ### className prop
151
+ Works on any element once the JSX runtime is active.
152
+ ```jsx
153
+ <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
154
+ <p className="text-gray-10 text-lg font-bold" />
155
+ <button className="bg-blue-7 hover:bg-blue-8 rounded-lg px-4 py-2" />
156
+ ```
157
+
158
+ ### styled(Component, baseClasses)
159
+ Pre-style a component. Returns a new component that accepts a `kb` prop for extra classes. Forwards the full class string as `className` so CSS rules (group-hover:, before:, print:) match the element.
160
+ ```jsx
161
+ import { styled } from '@kbach/ui';
162
+
163
+ const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
164
+ const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
165
+
166
+ <Card kb="mt-4"> // merges mt-4 with base classes
167
+ <Button kb="w-full" /> // merges w-full with base classes
168
+ ```
169
+
170
+ ### useStyles(classes)
171
+ Resolve classes to a style object inside a component.
172
+ ```jsx
173
+ import { useStyles } from '@kbach/ui';
174
+ const style = useStyles('bg-blue-6 px-3 py-1 rounded-full');
175
+ return <span style={style}>Badge</span>;
176
+ ```
177
+
178
+ ### kb(classes)
179
+ Resolve outside a component (static contexts).
180
+ ```js
181
+ import { kb } from '@kbach/ui';
182
+ const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
183
+ ```
184
+
185
+ ### cx(...classes)
186
+ Conditionally join class strings. Falsy values ignored.
187
+ ```jsx
188
+ import { cx } from '@kbach/ui';
189
+ <div className={cx('p-4', isActive && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
190
+ ```
191
+
192
+ ### useTheme()
193
+ ```ts
194
+ const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
195
+ // mode: 'light' | 'dark' | 'system'
196
+ // resolvedMode: 'light' | 'dark'
197
+ // isDark: boolean
198
+ // setMode(mode): void
199
+ // toggle(): void
200
+ // config: ResolvedConfig
201
+ ```
202
+
203
+ ### useIsDark()
204
+ ```ts
205
+ const isDark = useIsDark(); // boolean
206
+ ```
207
+
208
+ ### useColors()
209
+ Returns a proxy over the active theme's color palette.
210
+ ```ts
211
+ const colors = useColors();
212
+ colors.blue[6] // '#3b82f6'
213
+ colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
214
+ colors.white // '#ffffff'
215
+ colors['white/20'] // 'rgba(255,255,255,0.2)'
216
+ colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
217
+ ```
218
+ Typed against the built-in theme by default — `colors.blu` (typo) is a compile error. Custom `kbach.config.js` colors work too, with **zero setup**: the Vite plugin (and the Babel plugin, on React Native) automatically generate a `kbach-types.d.ts` next to your config, kept in sync every time the dev server / Metro picks up an edit to it — safe to add to `.gitignore`. `useColors()`/`useSpacing()` see your custom names immediately, no type parameter needed, same typo-catching as the built-in ones.
219
+
220
+ If you'd rather commit the types instead of generating them (a library package with no dev server/bundler step of its own, for instance), hand-author the same thing in any `.d.ts` your tsconfig includes:
221
+
222
+ ```ts
223
+ import '@kbach/ui'; // or '@kbach/native' — either works, native re-exports react's types
224
+ declare module '@kbach/ui' {
225
+ interface KbachCustomColors {
226
+ brand: ColorScale; // a 1–12 shade scale, like the built-in blue/red
227
+ accent: string; // a flat color, like the built-in white/black — also what a
228
+ // mode-aware { light, dark } config color resolves to at read time
229
+ }
230
+ interface KbachCustomSpacing {
231
+ 18: true; // only the key is read — value is just a placeholder
232
+ }
233
+ }
234
+ ```
235
+ A hand-authored file and the generated one both merge into the exact same interfaces, so either — or both at once — works.
236
+
237
+ ---
238
+
239
+ ## Modifier System
240
+
241
+ Up to 3 modifiers can be chained in any order before the utility name.
242
+
243
+ ```
244
+ dark:hover:bg-blue-8
245
+ sm:dark:text-lg
246
+ motion-reduce:transition-none
247
+ ```
248
+
249
+ ### Theme modifiers
250
+ | Modifier | Condition |
251
+ |---|---|
252
+ | `dark:` | Dark mode active |
253
+ | `light:` | Light mode active |
254
+ | `not-dark:` | Light mode active (alias) |
255
+ | `not-light:` | Dark mode active (alias) |
256
+
257
+ Dark mode strategy set in `ThemeProvider` or config:
258
+ - `'attribute'` (default) — `[data-theme="dark"]` on a wrapper element
259
+ - `'class'` — `.dark` class on a wrapper element
260
+ - `'media'` — `@media (prefers-color-scheme: dark)`
261
+
262
+ ### Interaction modifiers
263
+ | Modifier | Triggers on |
264
+ |---|---|
265
+ | `hover:` | Mouse hover |
266
+ | `focus:` | Element focused |
267
+ | `focus-within:` | Focus anywhere inside element |
268
+ | `focus-visible:` | Keyboard focus ring |
269
+ | `active:` | Active state |
270
+ | `pressed:` | Click / touch pressed |
271
+ | `visited:` | Visited link |
272
+ | `disabled:` | Disabled element |
273
+ | `checked:` | Checkbox / radio checked |
274
+ | `placeholder:` | Input placeholder text |
275
+
276
+ Negated: `not-hover:`, `not-focus:`, `not-active:`, `not-pressed:`, `not-visited:`, `not-disabled:`, `not-checked:`
277
+
278
+ ### Structural modifiers
279
+ | Modifier | Pseudo-class |
280
+ |---|---|
281
+ | `first:` | `:first-child` |
282
+ | `last:` | `:last-child` |
283
+ | `odd:` | `:nth-child(odd)` |
284
+ | `even:` | `:nth-child(even)` |
285
+ | `only:` | `:only-child` |
286
+
287
+ ### Responsive modifiers
288
+ | Modifier | Min-width |
289
+ |---|---|
290
+ | `sm:` | 576 px |
291
+ | `md:` | 768 px |
292
+ | `lg:` | 1024 px |
293
+ | `xl:` | 1280 px |
294
+ | `2xl:` | 1536 px |
295
+
296
+ Responsive styles are handled via `@media (min-width)` CSS rules — no JS breakpoint tracking.
297
+
298
+ ### Group / peer modifiers
299
+ Mark a parent with `group`, then use `group-hover:` etc. on children.
300
+
301
+ ```jsx
302
+ <div className="group">
303
+ <span className="opacity-0 group-hover:opacity-100 transition" />
304
+ </div>
305
+ ```
306
+
307
+ | Modifier | Fires when |
308
+ |---|---|
309
+ | `group-hover:` | Ancestor `.group` is hovered |
310
+ | `group-focus:` | Ancestor `.group` is focused |
311
+ | `peer-hover:` | Previous sibling `.peer` is hovered |
312
+ | `peer-focus:` | Previous sibling `.peer` is focused |
313
+
314
+ **Named groups/peers** — nested groups need names to avoid an inner element
315
+ reacting to the wrong (nearest) ancestor: `group/{name}` + `group-hover/{name}:`,
316
+ same for `peer/{name}` + `peer-hover/{name}:`/`peer-focus/{name}:`.
317
+ ```jsx
318
+ <div className="group/card">
319
+ <div className="group/icon">
320
+ <span className="group-hover/icon:opacity-100" />
321
+ </div>
322
+ <span className="group-hover/card:underline" />
323
+ </div>
324
+ ```
325
+
326
+ ### Pseudo-element modifiers
327
+ ```jsx
328
+ <div className="before:content-['*'] before:text-red-6 relative" />
329
+ <p className="first-letter:text-4xl first-letter:font-bold" />
330
+ <p className="selection:bg-blue-3" />
331
+ <input className="placeholder:text-gray-5" />
332
+ ```
333
+
334
+ | Modifier | CSS selector |
335
+ |---|---|
336
+ | `before:` | `::before` |
337
+ | `after:` | `::after` |
338
+ | `selection:` | `::selection` |
339
+ | `first-letter:` | `::first-letter` |
340
+ | `first-line:` | `::first-line` |
341
+ | `marker:` | `::marker` |
342
+ | `placeholder:` | `::placeholder` |
343
+
344
+ ### Print modifier
345
+ ```jsx
346
+ <div className="print:hidden" />
347
+ <div className="print:text-black print:bg-white" />
348
+ ```
349
+
350
+ ### Orientation modifiers
351
+ | Modifier | Media query |
352
+ |---|---|
353
+ | `landscape:` | `@media (orientation: landscape)` |
354
+ | `portrait:` | `@media (orientation: portrait)` |
355
+
356
+ ### Accessibility modifiers
357
+ | Modifier | Media query |
358
+ |---|---|
359
+ | `motion-reduce:` | `@media (prefers-reduced-motion: reduce)` |
360
+ | `motion-safe:` | `@media (prefers-reduced-motion: no-preference)` |
361
+ | `contrast-more:` | `@media (prefers-contrast: more)` |
362
+ | `contrast-less:` | `@media (prefers-contrast: less)` |
363
+
364
+ ### Directionality modifiers
365
+ | Modifier | CSS selector scope |
366
+ |---|---|
367
+ | `rtl:` | `[dir="rtl"] .cls` |
368
+ | `ltr:` | `[dir="ltr"] .cls` |
369
+
370
+ ### Important modifier
371
+ Prefix any class with `!` to add `!important` to every CSS declaration.
372
+ ```jsx
373
+ <div className="!p-0 !m-0 !bg-transparent" />
374
+ ```
375
+
376
+ ---
377
+
378
+ ## Arbitrary Values
379
+
380
+ Wrap any value in `[]` to use it directly.
381
+ ```jsx
382
+ <div className="bg-[#6366f1]" />
383
+ <div className="p-[14px]" />
384
+ <div className="w-[calc(100%-2rem)]" />
385
+ <div className="text-[18px]" />
386
+ <div className="rounded-[20px]" />
387
+ <div className="bg-[rgba(99,102,241,0.15)]" />
388
+ <div className="grid-cols-[1fr_2fr_1fr]" />
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Negative Values
394
+ ```jsx
395
+ <div className="-mt-4" /> // marginTop: -16
396
+ <div className="-mx-2" /> // marginHorizontal: -8
397
+ <div className="-translate-x-2" />
398
+ <div className="-mt-[10px]" /> // marginTop: -10px
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Color with Opacity
404
+ ```jsx
405
+ <div className="bg-blue-6/50" /> // 50% opacity
406
+ <div className="text-gray-10/75" /> // 75% opacity
407
+ <div className="bg-black/[0.15]" /> // arbitrary opacity
408
+ ```
409
+
410
+ ---
411
+
412
+ ## Color System
413
+
414
+ ### 12-shade scale
415
+ 1 = lightest, 12 = darkest.
416
+
417
+ ```
418
+ shade 1 2 3 4 5 6 7 8 9 10 11 12
419
+ ─────────────────────────────────────────────
420
+ light dark
421
+ ```
422
+
423
+ Usage: `bg-blue-6`, `text-gray-10`, `border-red-4/50`
424
+
425
+ ### Color families (22 total)
426
+ Grays: `slate`, `gray`, `zinc`, `neutral`, `stone`
427
+ Colors: `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`
428
+ Special: `transparent`, `current` (currentColor), `black`, `white`
429
+
430
+ ---
431
+
432
+ ## Utility Reference
433
+
434
+ ### Background
435
+ ```
436
+ bg-{color} backgroundColor
437
+ bg-{color}/{opacity} backgroundColor with alpha
438
+ bg-transparent
439
+ bg-clip-border/padding/content/text
440
+ bg-gradient-to-{dir} linear gradient (t, tr, r, br, b, bl, l, tl)
441
+ use with: from-{color}, via-{color}, to-{color}
442
+ bg-none/auto/cover/contain
443
+ bg-center/top/bottom/left/right/left-top/…
444
+ bg-repeat/no-repeat/repeat-x/repeat-y
445
+ bg-fixed/local/scroll
446
+ bg-blend-{mode} normal, multiply, screen, overlay, darken, lighten, …
447
+ ```
448
+
449
+ ### Text
450
+ ```
451
+ text-{size} xs(12) sm(14) base(16) lg(18) xl(20) 2xl(24) 3xl(30) 4xl(36) 5xl(48) 6xl(60) 7xl(72) 8xl(96) 9xl(128)
452
+ text-{color}
453
+ text-left/right/center/justify/start/end
454
+ text-wrap/nowrap/balance/pretty
455
+ ```
456
+
457
+ ### Font
458
+ ```
459
+ font-thin/extralight/light/normal/medium/semibold/bold/extrabold/black
460
+ font-{family} sans, mono, serif, or custom
461
+ ```
462
+
463
+ ### Text decoration
464
+ ```
465
+ underline / overline / line-through / no-underline
466
+ decoration-{color}
467
+ decoration-solid/dashed/dotted/double/wavy
468
+ decoration-0/1/2/4/8/auto/from-font
469
+ underline-offset-0/1/2/4/8/auto
470
+ ```
471
+
472
+ ### Text transform
473
+ ```
474
+ uppercase / lowercase / capitalize / normal-case
475
+ italic / not-italic
476
+ ```
477
+
478
+ ### Text overflow
479
+ ```
480
+ truncate
481
+ overflow-ellipsis
482
+ line-clamp-{n} n = 1–20
483
+ line-clamp-none
484
+ whitespace-normal/nowrap/pre/pre-wrap/pre-line
485
+ break-normal/words/all
486
+ ```
487
+
488
+ ### Typography misc
489
+ ```
490
+ leading-none/tight/snug/normal/relaxed/loose (+ numeric 3–10)
491
+ tracking-tighter/tight/normal/wide/wider/widest
492
+ antialiased / subpixel-antialiased
493
+ ```
494
+
495
+ ### Spacing — Padding
496
+ ```
497
+ p-{n} px-{n} py-{n} pt-{n} pr-{n} pb-{n} pl-{n}
498
+ ```
499
+
500
+ ### Spacing — Margin
501
+ ```
502
+ m-{n} mx-{n} my-{n} mt-{n} mr-{n} mb-{n} ml-{n}
503
+ mx-auto (centers element)
504
+ ```
505
+
506
+ Spacing scale (1 unit = 4px):
507
+ `px(1) 0 0.5(2) 1(4) 1.5(6) 2(8) 2.5(10) 3(12) 3.5(14) 4(16) 5(20) 6(24) 7(28) 8(32) 9(36) 10(40) 11(44) 12(48) 14(56) 16(64) 20(80) 24(96) 28(112) 32(128) 36(144) 40(160) 44(176) 48(192) 52(208) 56(224) 60(240) 64(256) 72(288) 80(320) 96(384) auto full(100%) 1/2 1/3 2/3 1/4 3/4 screen(100dvh) min max fit`
508
+
509
+ ### Sizing
510
+ ```
511
+ w-{n} h-{n} size-{n} min-w-{n} min-h-{n} max-w-{n} max-h-{n}
512
+
513
+ Named max-w: none xs(320) sm(384) md(448) lg(512) xl(576) 2xl(672) 3xl(768)
514
+ 4xl(896) 5xl(1024) 6xl(1152) 7xl(1280) prose(65ch)
515
+
516
+ Screen (dvw/dvh — correct on mobile where browser chrome resizes the
517
+ visible viewport; vw/vh are pinned to the largest viewport and overflow
518
+ behind a shown address bar): w-screen(100dvw) h-screen(100dvh)
519
+ min-w-screen max-w-screen min-h-screen max-h-screen
520
+ ```
521
+
522
+ ### Display
523
+ ```
524
+ block / inline / inline-block / flex / inline-flex
525
+ grid / inline-grid / hidden / contents / flow-root / table
526
+ ```
527
+
528
+ ### Flex
529
+ ```
530
+ flex-row/col/row-reverse/col-reverse
531
+ flex-wrap/nowrap/wrap-reverse
532
+ flex-1 / flex-auto / flex-initial / flex-none
533
+ flex-grow/grow-0 flex-shrink/shrink-0
534
+ basis-{n}
535
+ items-start/end/center/baseline/stretch
536
+ justify-start/end/center/between/around/evenly
537
+ justify-items-start/end/center/stretch
538
+ justify-self-start/end/center/auto
539
+ self-start/end/center/auto/stretch/baseline
540
+ content-start/end/center/between/around/evenly/stretch
541
+ order-{n}
542
+ gap-{n} gap-x-{n} gap-y-{n}
543
+ ```
544
+
545
+ ### Grid
546
+ ```
547
+ grid-cols-{n} repeat(n, minmax(0, 1fr)) n = 1–12
548
+ grid-rows-{n}
549
+ grid-flow-row/col/dense/row-dense/col-dense
550
+ auto-cols-auto/min/max/fr
551
+ auto-rows-auto/min/max/fr
552
+ col-span-{n} / col-span-full
553
+ col-start-{n}/auto col-end-{n}/auto
554
+ row-span-{n} / row-span-full
555
+ row-start-{n}/auto row-end-{n}/auto
556
+ place-items-start/end/center/stretch
557
+ place-content-start/end/center/between/around/evenly/stretch
558
+ place-self-start/end/center/auto/stretch
559
+ ```
560
+
561
+ ### Position
562
+ ```
563
+ static / relative / absolute / fixed / sticky
564
+ inset-{n} inset-x-{n} inset-y-{n}
565
+ top-{n} right-{n} bottom-{n} left-{n}
566
+ z-0/10/20/30/40/50/auto
567
+ ```
568
+
569
+ ### Overflow
570
+ ```
571
+ overflow-hidden/visible/scroll/auto/clip
572
+ overflow-x-hidden/visible/scroll/auto/clip
573
+ overflow-y-hidden/visible/scroll/auto/clip
574
+ ```
575
+
576
+ ### Border
577
+ ```
578
+ border / border-{n} borderWidth: 0 1 2 4 8
579
+ border-t/r/b/l
580
+ border-{color}
581
+ border-solid/dashed/dotted/none
582
+ border-collapse / border-separate
583
+ rounded / rounded-none/sm/md/lg/xl/2xl/3xl/full
584
+ rounded-t/r/b/l rounded-tl/tr/bl/br
585
+ ```
586
+
587
+ ### Shadow
588
+ ```
589
+ shadow-sm / shadow / shadow-md / shadow-lg / shadow-xl / shadow-2xl / shadow-none
590
+ ```
591
+
592
+ ### Opacity
593
+ ```
594
+ opacity-0/5/10/15/20/25/30/40/50/60/70/75/80/90/95/100
595
+ ```
596
+
597
+ ### Ring
598
+ Web: box-shadow ring. Native: approximated via borderWidth/borderColor
599
+ (no box-shadow on RN) — affects layout there and shares properties with
600
+ `border-*` (last class wins if both are used). `ring-offset-*`/`ring-inset`
601
+ stay web-only, with no native equivalent.
602
+ ```
603
+ ring / ring-{n}(0 1 2 4 8)
604
+ ring-{color}
605
+ ring-inset (web only)
606
+ ring-offset-{n}(0 1 2 4 8) (web only)
607
+ ```
608
+
609
+ ### Outline
610
+ ```
611
+ outline-none / outline / outline-{n}(0 1 2 4 8)
612
+ outline-{color}
613
+ outline-offset-{n}(0 1 2 4 8)
614
+ ```
615
+
616
+ ### Transforms
617
+ ```
618
+ scale-{n} scale-x-{n} scale-y-{n}
619
+ rotate-{n}
620
+ translate-x-{n} translate-y-{n}
621
+ skew-x-{n} skew-y-{n}
622
+ origin-center/top/top-right/right/bottom-right/bottom/bottom-left/left/top-left
623
+ perspective-{n}
624
+ ```
625
+
626
+ ### Filters
627
+ ```
628
+ blur-{sm/md/lg/xl/2xl/3xl}
629
+ brightness-{n} contrast-{n}
630
+ grayscale / grayscale-0
631
+ hue-rotate-{n}
632
+ invert / invert-0
633
+ saturate-{n}
634
+ sepia / sepia-0
635
+ drop-shadow-{sm/md/lg/xl/2xl/none}
636
+
637
+ backdrop-blur-{n} backdrop-brightness-{n} backdrop-contrast-{n}
638
+ backdrop-grayscale backdrop-hue-rotate-{n} backdrop-invert
639
+ backdrop-opacity-{n} backdrop-saturate-{n} backdrop-sepia
640
+ ```
641
+
642
+ ### Animation & Transition
643
+ ```
644
+ animate-spin / animate-ping / animate-pulse / animate-bounce / animate-none
645
+ transition / transition-all/none/colors/opacity/shadow/transform
646
+ duration-75/100/150/200/300/500/700/1000
647
+ delay-75/100/150/200/300/500/700/1000
648
+ ease-linear/in/out/in-out ease-[cubic-bezier(...)]
649
+ ```
650
+
651
+ ### Cursor
652
+ ```
653
+ cursor-auto/default/pointer/wait/text/move/not-allowed
654
+ cursor-grab/grabbing/zoom-in/zoom-out/crosshair/help/none
655
+ ```
656
+
657
+ ### Pointer events / User select
658
+ ```
659
+ pointer-events-none / pointer-events-auto
660
+ select-none / select-text / select-all / select-auto
661
+ ```
662
+
663
+ ### Touch action
664
+ ```
665
+ touch-auto / touch-none / touch-pan-x / touch-pan-y
666
+ touch-pan-left / touch-pan-right / touch-pan-up / touch-pan-down
667
+ touch-pinch-zoom / touch-manipulation
668
+ ```
669
+
670
+ ### Scroll
671
+ ```
672
+ scroll-smooth / scroll-auto
673
+ ```
674
+
675
+ ### Float & Clear
676
+ ```
677
+ float-left / float-right / float-start / float-end / float-none
678
+ clear-left / clear-right / clear-both / clear-start / clear-end / clear-none
679
+ ```
680
+
681
+ ### Vertical align
682
+ ```
683
+ align-baseline / align-top / align-middle / align-bottom
684
+ align-text-top / align-text-bottom / align-sub / align-super
685
+ ```
686
+
687
+ ### Visibility
688
+ ```
689
+ visible / invisible
690
+ sr-only / not-sr-only
691
+ ```
692
+
693
+ ### Lists
694
+ ```
695
+ list-none / list-disc / list-decimal
696
+ list-inside / list-outside
697
+ ```
698
+
699
+ ### Misc
700
+ ```
701
+ appearance-none
702
+ resize / resize-none / resize-x / resize-y
703
+ box-border / box-content
704
+ object-contain/cover/fill/none/scale-down
705
+ object-center/top/bottom/left/right/…
706
+ aspect-auto / aspect-square / aspect-video / aspect-[4/3]
707
+ columns-{n} / columns-auto / columns-{size}
708
+ caret-{color} / caret-auto / caret-transparent
709
+ accent-{color} / accent-auto
710
+ stroke-{color} / stroke-{n} / stroke-none (web only, SVG)
711
+ fill-{color} / fill-none (web only, SVG)
712
+ mix-blend-{mode}
713
+ bg-blend-{mode}
714
+ will-change-auto/scroll/contents/transform
715
+ divide-x-{n} / divide-y-{n} / divide-{color} / divide-solid/dashed/dotted
716
+ space-x-{n} / space-y-{n}
717
+ group / peer (standalone marker classes)
718
+ ```
719
+
720
+ ---
721
+
722
+ ## Theme Configuration
723
+
724
+ ### kbach.config.js (project root)
725
+ ```js
726
+ module.exports = {
727
+ darkMode: 'attribute', // 'attribute' | 'class' | 'media'
728
+
729
+ theme: {
730
+ colors: {
731
+ brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a8a' },
732
+ },
733
+ },
734
+
735
+ extend: {
736
+ theme: {
737
+ colors: { brand: { 6: '#6366f1' } },
738
+ spacing: { 18: 72, 22: 88 },
739
+ fontSize: { '10xl': 160 },
740
+ fontFamily: {
741
+ sans: 'Inter, sans-serif',
742
+ },
743
+ },
744
+ },
745
+
746
+ plugins: [
747
+ ({ addUtility, theme }) => {
748
+ addUtility('border-brand', {
749
+ borderColor: theme('colors.brand.6'),
750
+ borderWidth: 2,
751
+ });
752
+ },
753
+ ],
754
+ };
755
+ ```
756
+
757
+ **Global default font (web):** Setting `fontFamily.sans` to anything other than `'System'` auto-injects `body { font-family: <font> }`.
758
+
759
+ ### Mode-aware colors (dark mode without `dark:`)
760
+
761
+ A color value can be `{ light, dark }` instead of a plain string — define it once, and every class using that color automatically picks the right side, with no `dark:` variant needed at the call site:
762
+
763
+ ```js
764
+ extend: {
765
+ theme: {
766
+ colors: {
767
+ surface: { light: '#ffffff', dark: '#111827' },
768
+ // each side can itself be an alias — resolved independently
769
+ accent: { light: 'blue-6', dark: 'blue-4' },
770
+ // or derived from another color at an opacity — 'name/opacity', resolved
771
+ // once here instead of only being computable at runtime via colors.alpha()
772
+ accentSoft: { light: 'accent/30', dark: 'accent/40' },
773
+ },
774
+ },
775
+ },
776
+ ```
777
+
778
+ ```jsx
779
+ <div className="bg-surface text-accent hover:bg-accentSoft" />
780
+ // equivalent to writing bg-white dark:bg-gray-9 text-blue-6 dark:text-blue-4
781
+ // hover:bg-[rgba(...)] dark:hover:bg-[rgba(...)] by hand
782
+ ```
783
+
784
+ Works everywhere a color does — `useColors()` (returns the active side directly), opacity composition (`bg-surface/50`), per-shade within a scale (`brand: { 6: { light: '#3b82f6', dark: '#60a5fa' } }`), and stacked under an explicit modifier (`dark:hover:bg-accent` correctly uses the dark side, still scoped to dark mode + hover — the modifier doesn't need to be there in the first place, but it's respected if it is).
785
+
786
+ ### Runtime update
787
+ ```js
788
+ import { updateConfig, clearCache } from '@kbach/ui';
789
+ updateConfig({ extend: { theme: { colors: { brand: { 6: '#6366f1' } } } } });
790
+ clearCache(); // always call after updateConfig()
791
+ ```
792
+
793
+ ### Default theme values
794
+ ```
795
+ spacing: 1 unit = 4px
796
+ fontSize: xs(12)–9xl(128)
797
+ borderRadius: none(0) sm(2) DEFAULT(4) md(6) lg(8) xl(12) 2xl(16) 3xl(24) full(9999)
798
+ borderWidth: DEFAULT(1) 0 2 4 8
799
+ opacity: 0 5 10 15 20 25 30 40 50 60 70 75 80 90 95 100
800
+ lineHeight: none(1) tight(1.25) snug(1.375) normal(1.5) relaxed(1.625) loose(2)
801
+ letterSpacing:tighter(-0.8) tight(-0.4) normal(0) wide(0.4) wider(0.8) widest(1.6)
802
+ zIndex: auto 0 10 20 30 40 50
803
+ screens: sm(576) md(768) lg(1024) xl(1280) 2xl(1536)
804
+ ```
805
+
806
+ ---
807
+
808
+ ## Common Patterns
809
+
810
+ ### Dark mode card
811
+ ```jsx
812
+ <div className="bg-white dark:bg-gray-9 rounded-2xl p-6 shadow-md">
813
+ <h2 className="text-2xl font-bold text-gray-10 dark:text-white">Title</h2>
814
+ <p className="text-gray-6 dark:text-gray-4 mt-2">Body text</p>
815
+ </div>
816
+ ```
817
+
818
+ ### Interactive button
819
+ ```jsx
820
+ <button className="bg-blue-7 hover:bg-blue-8 active:bg-blue-9 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold px-6 py-3 rounded-xl transition" />
821
+ ```
822
+
823
+ ### Responsive layout
824
+ ```jsx
825
+ <div className="flex flex-col md:flex-row gap-4">
826
+ <aside className="w-full md:w-64 lg:w-80">…</aside>
827
+ <main className="flex-1">…</main>
828
+ </div>
829
+ ```
830
+
831
+ ### Responsive grid
832
+ ```jsx
833
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
834
+ {items.map(item => <Card key={item.id} />)}
835
+ </div>
836
+ ```
837
+
838
+ ### Group hover reveal
839
+ ```jsx
840
+ <div className="group relative overflow-hidden rounded-xl">
841
+ <img src="…" className="transition group-hover:scale-105" />
842
+ <div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition flex items-center justify-center">
843
+ <span className="text-white font-bold">View</span>
844
+ </div>
845
+ </div>
846
+ ```
847
+
848
+ ### Before/after pseudo-elements
849
+ ```jsx
850
+ <div className="relative before:absolute before:inset-0 before:bg-blue-6/10 before:rounded-xl" />
851
+ ```
852
+
853
+ ### Input with caret and focus ring
854
+ ```jsx
855
+ <input className="caret-blue-6 focus:ring-2 focus:ring-blue-5 focus:outline-none border border-gray-4 rounded-lg px-4 py-2" />
856
+ ```
857
+
858
+ ### Print-specific styles
859
+ ```jsx
860
+ <nav className="print:hidden" />
861
+ <article className="print:text-black print:bg-white print:shadow-none" />
862
+ ```
863
+
864
+ ### Reduced-motion safe animation
865
+ ```jsx
866
+ <div className="motion-safe:animate-spin motion-reduce:opacity-75" />
867
+ ```
868
+
869
+ ### RTL-aware spacing
870
+ ```jsx
871
+ <div className="ltr:pl-4 rtl:pr-4 ltr:text-left rtl:text-right" />
872
+ ```
873
+
874
+ ### Contrast accessibility
875
+ ```jsx
876
+ <button className="bg-blue-6 contrast-more:bg-blue-9 contrast-more:border-2 text-white">
877
+ Submit
878
+ </button>
879
+ ```
880
+
881
+ ---
882
+
883
+ ## Caching
884
+
885
+ The resolver uses an LRU cache (10,000 entries). Cleared automatically on `updateConfig()`. Manually:
886
+ ```js
887
+ import { clearCache } from '@kbach/ui';
888
+ clearCache();
889
+ ```