@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.md ADDED
@@ -0,0 +1,1044 @@
1
+ # Kbach Framework — Complete AI Reference
2
+
3
+ Kbach is a Tailwind-like utility CSS framework for React (web) and React Native. Classes are written as `className` strings and resolved to inline styles at render time. On web, stateful and structural CSS rules are injected into a `<style>` tag so they work with the browser cascade. On native, only inline-compatible styles are applied.
4
+
5
+ One package, `@kbach/ui`, covers both platforms — React web, React Native, and Expo (Expo Go, Expo web, and native builds). The React Native/Expo pieces (a native-aware `ThemeProvider`, the Babel preset, Metro/Babel config helpers) live at the `@kbach/ui/native` and `@kbach/ui/babel` subpaths; everything else is the same import regardless of platform.
6
+
7
+ `@kbach/native` is deprecated and no longer maintained — its last published npm version stays available as a compatibility shim re-exporting `@kbach/ui`, but new setups should install `@kbach/ui` directly (see below).
8
+
9
+ ---
10
+
11
+ ## Setup — Web
12
+
13
+ ### tsconfig.json
14
+ ```json
15
+ { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/ui" } }
16
+ ```
17
+
18
+ ### vite.config.ts (plain Vite only — skip if a meta-framework already provides its own Vite/React plugin, e.g. React Router's reactRouter())
19
+ Not installed by `@kbach/ui` itself: `npm install -D vite @vitejs/plugin-react`.
20
+ ```ts
21
+ import react from '@vitejs/plugin-react';
22
+ export default { plugins: [react({ jsxImportSource: '@kbach/ui' })] };
23
+ ```
24
+
25
+ ### Per-file (no config needed)
26
+ ```jsx
27
+ /** @jsxImportSource @kbach/ui */
28
+ ```
29
+
30
+ ### Wrap app
31
+ ```jsx
32
+ import { ThemeProvider, KbachReset } from '@kbach/ui';
33
+ <ThemeProvider defaultMode="system"><KbachReset /><App /></ThemeProvider>
34
+ ```
35
+ `<KbachReset />` renders Kbach's base browser-default reset (borderless button/input, visible checkbox/radio, no arrow-less `<select>`, `a`/`h1-h6`/`p`/`ul`/`ol`/`img` normalized, etc.) as a real `<style>` tag — needed for SSR under runtime-only setups (no JS runs server-side, but this is just JSX so it still renders), a no-op-but-harmless nice-to-have for plain CSR. Skip it if using static `kbach.css` (below) — that file already includes the same reset, and the runtime injector auto-detects `<KbachReset />` and skips re-adding it either way, so having both is harmless too.
36
+
37
+ ### Next.js
38
+ - tsconfig `jsxImportSource` setup above applies as-is (SWC reads it like Vite does).
39
+ - No Vite plugin / static `kbach.css` for Next.js (webpack/Turbopack, not Vite) — always runtime-only. Render `<KbachReset />` once in the root App Router `layout.tsx` (in `<head>`, or right after `<ThemeProvider>` opens) so Server Component HTML has the base reset without waiting on hydration; without it, expect a flash of raw browser defaults (native button border, arrow-less `<select>`, etc.) on first paint until hydration completes. Utility classes beyond the base reset still wait on hydration either way — this is a known limitation of the runtime-only path, not a per-project bug.
40
+ - `@kbach/ui`'s compiled output ships its own `"use client"` directive (`dist/index.js`, `dist/jsx-runtime.js`, `dist/jsx-dev-runtime.js`), so App Router Server Components can use `className`, `styled()`, hooks, `<ThemeProvider>`, and `<KbachReset>` directly — no manual `'use client'` wrapper needed.
41
+
42
+ ### React Router
43
+ - Framework mode (v7+, SSR): do NOT add `@vitejs/plugin-react` — `reactRouter()` 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; `reactRouter()` reads it the same way plain Vite does:
44
+ ```ts
45
+ import { reactRouter } from '@react-router/dev/vite';
46
+ import { kbach } from '@kbach/ui/vite'; // omit if not using static CSS
47
+ export default { plugins: [kbach(), reactRouter()] };
48
+ ```
49
+ Default scan dirs already include `app/`. The Static CSS setup is recommended here since it's SSR — it avoids the runtime-injection FOUC gap entirely, for every class, not just the base reset. If skipping it in favor of Runtime setup, render `<KbachReset />` in `root.tsx`'s `<Layout>` at minimum.
50
+ - Library mode (client-only, no meta-framework Vite plugin involved): identical to any Vite + React app — the vite.config.ts section above.
51
+
52
+ ---
53
+
54
+ ## Setup — React Native / Expo
55
+
56
+ Same `npm install @kbach/ui` as web — no separate package.
57
+
58
+ ### babel.config.js
59
+ ```js
60
+ module.exports = function (api) {
61
+ api.cache(true);
62
+ return {
63
+ presets: [
64
+ 'babel-preset-expo',
65
+ '@kbach/ui/babel',
66
+ ],
67
+ };
68
+ };
69
+ ```
70
+ Or the one-liner helper: `const { createKbachConfig } = require('@kbach/ui/native'); module.exports = createKbachConfig();` — identical result. Merging into an existing config: `withKbachBabel({ presets: [...] })` (also from `@kbach/ui/native`).
71
+
72
+ ### Wrap app
73
+ ```jsx
74
+ import { ThemeProvider } from '@kbach/ui/native';
75
+ <ThemeProvider defaultMode="system"><AppContent /></ThemeProvider>
76
+ ```
77
+ This is a native-aware `ThemeProvider` that wraps the base one — reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Don't import the plain `ThemeProvider` from `@kbach/ui` on native; it doesn't have the automatic RN wiring.
78
+
79
+ After changing babel.config.js: `npx expo start --clear`
80
+
81
+ Platform-specific utility differences: see [Native-only Utilities](#native-only-utilities) and [Web-only Utilities](#web-only-utilities-gracefully-ignored-on-native) further down. `ring`/`ring-{n}`/`ring-{color}` is a partial exception — it falls back to `borderWidth`/`borderColor` on native (RN has no box-shadow), which does affect layout there and shares properties with `border-*`, so combining both on one element means whichever class comes last wins.
82
+
83
+ ### Expo Web / React Native Web
84
+ In a browser (Expo Web, Metro web), `@kbach/ui` switches to the same CSS-class strategy used on plain web automatically:
85
+ - RN components substitute to HTML: `View`/`ScrollView`→`div`, `Text`→`span`, `TextInput`→`input`/`textarea`, `Image`→`img`, `Pressable`/`TouchableOpacity`→`div[role=button]`
86
+ - RN-only props (`onChangeText`, `source`, `secureTextEntry`, …) map to HTML equivalents
87
+ - Register more: `registerWebElement(Animated.View, 'div')`
88
+ - Recommended: use the Vite plugin same as the web Static CSS setup above — `import { kbach } from '@kbach/ui/vite'` — and import `kbach.css` in your entry file, for zero runtime cost on the web target too
89
+ - Not using the Vite plugin (the common case for Expo/Metro web, no Vite build step)? Render `<KbachReset />` once near your root — e.g. Expo Router's root `app/_layout.tsx`, inside `<ThemeProvider>`:
90
+ ```jsx
91
+ import { KbachReset } from '@kbach/ui';
92
+ import { ThemeProvider } from '@kbach/ui/native';
93
+ <ThemeProvider defaultMode="system"><KbachReset /><Slot /></ThemeProvider>
94
+ ```
95
+
96
+ ### CSS inheritance
97
+ 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');`
98
+
99
+ ---
100
+
101
+ ## Core API
102
+
103
+ ### className prop
104
+ Works on any element once the JSX runtime is active.
105
+ ```jsx
106
+ <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
107
+ <p className="text-gray-10 text-lg font-bold" />
108
+ <button className="bg-blue-7 hover:bg-blue-8 rounded-lg px-4 py-2" />
109
+ ```
110
+
111
+ ### styled(Component, baseClasses)
112
+ Pre-style a component. Returns a new component that accepts a `kb` prop for extra classes.
113
+ ```jsx
114
+ import { styled } from '@kbach/ui'; // same import on web and React Native
115
+
116
+ const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
117
+ const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
118
+
119
+ <Card kb="mt-4"> // merges mt-4 with base classes
120
+ <Button kb="w-full" /> // merges w-full with base classes
121
+ ```
122
+
123
+ On web, `styled()` forwards the full class string as `className` so CSS rules (group-hover:, before:, print:) match the element.
124
+
125
+ ### useStyles(classes)
126
+ Resolve classes to a style object inside a component.
127
+ ```jsx
128
+ import { useStyles } from '@kbach/ui';
129
+ const style = useStyles('bg-blue-6 px-3 py-1 rounded-full');
130
+ return <span style={style}>Badge</span>;
131
+ ```
132
+
133
+ ### kb(classes)
134
+ Resolve outside a component (static contexts).
135
+ ```js
136
+ import { kb } from '@kbach/ui';
137
+ const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
138
+ ```
139
+
140
+ ### cx(...classes)
141
+ Conditionally join class strings. Falsy values ignored.
142
+ ```jsx
143
+ import { cx } from '@kbach/ui';
144
+ <div className={cx('p-4', isActive && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
145
+ ```
146
+
147
+ ### useTheme()
148
+ ```ts
149
+ const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
150
+ // mode: 'light' | 'dark' | 'system'
151
+ // resolvedMode: 'light' | 'dark'
152
+ // isDark: boolean
153
+ // setMode(mode): void
154
+ // toggle(): void
155
+ // config: ResolvedConfig
156
+ ```
157
+
158
+ ### useIsDark()
159
+ ```ts
160
+ const isDark = useIsDark(); // boolean
161
+ ```
162
+
163
+ ### useColors()
164
+ Returns a proxy over the active theme's color palette.
165
+ ```ts
166
+ const colors = useColors();
167
+ colors.blue[6] // '#3b82f6'
168
+ colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
169
+ colors.white // '#ffffff'
170
+ colors['white/20'] // 'rgba(255,255,255,0.2)'
171
+ colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Modifier System
177
+
178
+ Up to 3 modifiers can be chained in any order before the utility name.
179
+
180
+ ```
181
+ [modifier:][modifier:][modifier:]utility-value
182
+ dark:hover:bg-blue-8
183
+ sm:dark:text-lg
184
+ motion-reduce:transition-none
185
+ rtl:text-right
186
+ ```
187
+
188
+ ### Theme modifiers
189
+ | Modifier | Condition |
190
+ |---|---|
191
+ | `dark:` | Dark mode active |
192
+ | `light:` | Light mode active |
193
+ | `not-dark:` | Light mode active (alias) |
194
+ | `not-light:` | Dark mode active (alias) |
195
+
196
+ Dark mode strategy set in `ThemeProvider` or `kbach.config.js`:
197
+ - `'attribute'` (default) — `[data-theme="dark"]` on a wrapper element
198
+ - `'class'` — `.dark` class on a wrapper element
199
+ - `'media'` — `@media (prefers-color-scheme: dark)`
200
+
201
+ ### Interaction modifiers
202
+ | Modifier | Triggers on |
203
+ |---|---|
204
+ | `hover:` | Mouse hover |
205
+ | `focus:` | Element focused |
206
+ | `focus-within:` | Focus anywhere inside element |
207
+ | `focus-visible:` | Keyboard focus ring |
208
+ | `active:` | Active state |
209
+ | `pressed:` | Click / touch pressed (alias for active on native) |
210
+ | `visited:` | Visited link |
211
+ | `disabled:` | Disabled element |
212
+ | `checked:` | Checkbox / radio checked |
213
+ | `placeholder:` | Input placeholder text |
214
+
215
+ Negated: `not-hover:`, `not-focus:`, `not-active:`, `not-pressed:`, `not-visited:`, `not-disabled:`, `not-checked:`
216
+
217
+ ### Structural modifiers (CSS-injection only)
218
+ | Modifier | Pseudo-class |
219
+ |---|---|
220
+ | `first:` | `:first-child` |
221
+ | `last:` | `:last-child` |
222
+ | `odd:` | `:nth-child(odd)` |
223
+ | `even:` | `:nth-child(even)` |
224
+ | `only:` | `:only-child` |
225
+
226
+ ### Responsive modifiers
227
+ | Modifier | Min-width |
228
+ |---|---|
229
+ | `sm:` | 576 px |
230
+ | `md:` | 768 px |
231
+ | `lg:` | 1024 px |
232
+ | `xl:` | 1280 px |
233
+ | `2xl:` | 1536 px |
234
+
235
+ On web, responsive styles are handled entirely via `@media (min-width)` CSS rules — no JS breakpoint tracking needed. On native, breakpoints are resolved from the current window width.
236
+
237
+ ### Group / peer modifiers (CSS-injection only, web only)
238
+ Mark a parent with `group` (standalone class), then use `group-hover:` etc. on children.
239
+ Mark a previous sibling with `peer`, then use `peer-hover:` etc. on the next sibling.
240
+
241
+ ```jsx
242
+ <div className="group">
243
+ <span className="opacity-0 group-hover:opacity-100 transition" />
244
+ </div>
245
+ ```
246
+
247
+ | Modifier | Fires when |
248
+ |---|---|
249
+ | `group-hover:` | Ancestor `.group` is hovered |
250
+ | `group-focus:` | Ancestor `.group` is focused |
251
+ | `peer-hover:` | Previous sibling `.peer` is hovered |
252
+ | `peer-focus:` | Previous sibling `.peer` is focused |
253
+
254
+ **Named groups/peers** — when groups nest, an unnamed `group-hover:` matches
255
+ the NEAREST `.group` ancestor, so an inner element reacts to whichever group
256
+ (inner or outer) is hovered, not necessarily the one you meant. Name the
257
+ marker (`group/{name}`) and the modifier (`group-hover/{name}:`) to scope it
258
+ to that specific ancestor — same for `peer/{name}` + `peer-hover/{name}:` /
259
+ `peer-focus/{name}:`. `{name}` can be any string (`card`, `sidebar`, …).
260
+
261
+ ```jsx
262
+ <div className="group/card">
263
+ <div className="group/icon">
264
+ <span className="group-hover/icon:opacity-100" /> {/* only inner group */}
265
+ </div>
266
+ <span className="group-hover/card:underline" /> {/* only outer group */}
267
+ </div>
268
+ ```
269
+
270
+ ### Pseudo-element modifiers (CSS-injection only, web only)
271
+ ```jsx
272
+ <div className="before:content-['*'] before:text-red-6 relative" />
273
+ <p className="first-letter:text-4xl first-letter:font-bold" />
274
+ <p className="selection:bg-blue-3" />
275
+ <input className="placeholder:text-gray-5" />
276
+ ```
277
+
278
+ | Modifier | CSS selector |
279
+ |---|---|
280
+ | `before:` | `::before` |
281
+ | `after:` | `::after` |
282
+ | `selection:` | `::selection` |
283
+ | `first-letter:` | `::first-letter` |
284
+ | `first-line:` | `::first-line` |
285
+ | `marker:` | `::marker` |
286
+ | `placeholder:` | `::placeholder` |
287
+
288
+ ### Print modifier (CSS-injection only, web only)
289
+ ```jsx
290
+ <div className="print:hidden" />
291
+ <div className="print:text-black print:bg-white" />
292
+ ```
293
+
294
+ ### Orientation modifiers (CSS-injection only, web only)
295
+ ```jsx
296
+ <div className="landscape:flex-row portrait:flex-col" />
297
+ ```
298
+
299
+ | Modifier | Media query |
300
+ |---|---|
301
+ | `landscape:` | `@media (orientation: landscape)` |
302
+ | `portrait:` | `@media (orientation: portrait)` |
303
+
304
+ ### Accessibility modifiers (CSS-injection only, web only)
305
+ ```jsx
306
+ <div className="motion-reduce:transition-none motion-safe:transition-all duration-300" />
307
+ <div className="contrast-more:border-2 contrast-more:border-black" />
308
+ ```
309
+
310
+ | Modifier | Media query |
311
+ |---|---|
312
+ | `motion-reduce:` | `@media (prefers-reduced-motion: reduce)` |
313
+ | `motion-safe:` | `@media (prefers-reduced-motion: no-preference)` |
314
+ | `contrast-more:` | `@media (prefers-contrast: more)` |
315
+ | `contrast-less:` | `@media (prefers-contrast: less)` |
316
+
317
+ ### Directionality modifiers (CSS-injection only, web only)
318
+ ```jsx
319
+ <div className="rtl:text-right ltr:text-left" />
320
+ <div className="rtl:pl-4 ltr:pr-4" />
321
+ ```
322
+
323
+ | Modifier | CSS selector scope |
324
+ |---|---|
325
+ | `rtl:` | `[dir="rtl"] .cls` |
326
+ | `ltr:` | `[dir="ltr"] .cls` |
327
+
328
+ ### Important modifier
329
+ Prefix any class with `!` to add `!important` to every CSS declaration it produces.
330
+ ```jsx
331
+ <div className="!p-0 !m-0 !bg-transparent" />
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Arbitrary Values
337
+
338
+ Wrap any value in `[]` to use it directly.
339
+ ```jsx
340
+ <div className="bg-[#6366f1]" />
341
+ <div className="p-[14px]" />
342
+ <div className="w-[calc(100%-2rem)]" />
343
+ <div className="text-[18px]" />
344
+ <div className="rounded-[20px]" />
345
+ <div className="bg-[rgba(99,102,241,0.15)]" />
346
+ <div className="grid-cols-[1fr_2fr_1fr]" />
347
+ <div className="will-change-[transform,opacity]" />
348
+ ```
349
+
350
+ ### Arbitrary properties (web only)
351
+ No utility prefix — `[property:value]` on its own, for a CSS property with no named utility:
352
+ ```jsx
353
+ <div className="[mask-type:luminance]" />
354
+ <div className="[--my-var:10px]" /> {/* custom property */}
355
+ ```
356
+ Underscores → spaces like any other arbitrary value. Only the FIRST `:` splits property from value, so `[background:url(http://x/a.png)]` keeps the URL's own `:` intact.
357
+
358
+ ---
359
+
360
+ ## Negative Values
361
+ Prefix any spacing utility with `-` for negative values.
362
+ ```jsx
363
+ <div className="-mt-4" /> // marginTop: -16
364
+ <div className="-mx-2" /> // marginHorizontal: -8
365
+ <div className="-translate-x-2" />
366
+ ```
367
+
368
+ ---
369
+
370
+ ## Color with Opacity
371
+ Append `/opacity` to any color utility. Opacity is 0–100 (integer) or arbitrary.
372
+ ```jsx
373
+ <div className="bg-blue-6/50" /> // 50% opacity
374
+ <div className="text-gray-10/75" /> // 75% opacity
375
+ <div className="bg-black/[0.15]" /> // arbitrary opacity
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Color System
381
+
382
+ ### 12-shade scale
383
+ 1 = lightest, 12 = darkest. Maps to Tailwind v3 (50 → 1, 100 → 2, … 950 → 11, extra-dark → 12).
384
+
385
+ ```
386
+ shade 1 2 3 4 5 6 7 8 9 10 11 12
387
+ ─────────────────────────────────────────────
388
+ light dark
389
+ ```
390
+
391
+ Usage: `bg-blue-6`, `text-gray-10`, `border-red-4/50`
392
+
393
+ ### Color families (22 total)
394
+ Grays: `slate`, `gray`, `zinc`, `neutral`, `stone`
395
+ Colors: `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`
396
+ Special: `transparent`, `current` (currentColor), `black`, `white`
397
+
398
+ ---
399
+
400
+ ## Utility Reference
401
+
402
+ ### Background
403
+ ```
404
+ bg-{color} backgroundColor
405
+ bg-{color}/{opacity} backgroundColor with alpha
406
+ bg-transparent backgroundColor: transparent
407
+ bg-clip-border -webkit-background-clip: border-box
408
+ bg-clip-padding -webkit-background-clip: padding-box
409
+ bg-clip-content -webkit-background-clip: content-box
410
+ bg-clip-text -webkit-background-clip: text (web only)
411
+ bg-gradient-to-{dir} background linear gradient direction (web only)
412
+ directions: t, tr, r, br, b, bl, l, tl
413
+ use with: from-{color}, via-{color}, to-{color}
414
+ bg-blend-{mode} background-blend-mode (web only)
415
+ modes: normal, multiply, screen, overlay, darken, lighten,
416
+ color-dodge, color-burn, hard-light, soft-light,
417
+ difference, exclusion, hue, saturation, color, luminosity
418
+ ```
419
+
420
+ ### Text
421
+ ```
422
+ text-{size} fontSize: xs(12) sm(14) base(16) lg(18) xl(20)
423
+ 2xl(24) 3xl(30) 4xl(36) 5xl(48)
424
+ 6xl(60) 7xl(72) 8xl(96) 9xl(128)
425
+ text-{color} color
426
+ text-left/right/center/justify textAlign
427
+ text-wrap text-wrap: wrap (web only)
428
+ text-nowrap text-wrap: nowrap (web only)
429
+ text-balance text-wrap: balance (web only)
430
+ text-pretty text-wrap: pretty (web only)
431
+ ```
432
+
433
+ ### Font
434
+ ```
435
+ font-thin/extralight/light/normal/medium/semibold/bold/extrabold/black
436
+ font-{family} fontFamily (sans, mono, serif, or custom)
437
+ ```
438
+
439
+ ### Text decoration
440
+ ```
441
+ underline textDecorationLine: underline
442
+ overline textDecorationLine: overline (web only)
443
+ line-through textDecorationLine: line-through
444
+ no-underline textDecorationLine: none
445
+ decoration-{color} textDecorationColor (web only)
446
+ decoration-solid/dashed/dotted/double/wavy textDecorationStyle (web only)
447
+ decoration-0/1/2/4/8/auto/from-font textDecorationThickness (web only)
448
+ underline-offset-0/1/2/4/8/auto textUnderlineOffset (web only)
449
+ ```
450
+
451
+ ### Text transform / case
452
+ ```
453
+ uppercase textTransform: uppercase
454
+ lowercase textTransform: lowercase
455
+ capitalize textTransform: capitalize
456
+ normal-case textTransform: none
457
+ italic fontStyle: italic
458
+ not-italic fontStyle: normal
459
+ ```
460
+
461
+ ### Text overflow
462
+ ```
463
+ truncate overflow: hidden; white-space: nowrap; text-overflow: ellipsis
464
+ overflow-ellipsis text-overflow: ellipsis (web only)
465
+ line-clamp-{n} -webkit-line-clamp (web only, n = 1–20)
466
+ line-clamp-none removes line-clamp (web only)
467
+ whitespace-normal/nowrap/pre/pre-wrap/pre-line whiteSpace
468
+ break-normal/words/all/keep word-break / overflow-wrap
469
+ ```
470
+
471
+ ### Typography misc
472
+ ```
473
+ leading-{value} lineHeight: none(1) tight(1.25) snug(1.375) normal(1.5)
474
+ relaxed(1.625) loose(2), or numeric 3–10 (12–40px)
475
+ tracking-{value} letterSpacing: tighter(-0.8) tight(-0.4) normal(0)
476
+ wide(0.4) wider(0.8) widest(1.6)
477
+ antialiased -webkit-font-smoothing: antialiased (web only)
478
+ subpixel-antialiased -webkit-font-smoothing: subpixel-antialiased (web only)
479
+ ```
480
+
481
+ ### Spacing — Padding
482
+ ```
483
+ p-{n} padding (all sides)
484
+ px-{n} paddingHorizontal
485
+ py-{n} paddingVertical
486
+ pt-{n} paddingTop
487
+ pr-{n} paddingRight
488
+ pb-{n} paddingBottom
489
+ pl-{n} paddingLeft
490
+ ```
491
+
492
+ ### Spacing — Margin
493
+ ```
494
+ m-{n} margin (all sides)
495
+ mx-{n} marginHorizontal (mx-auto centers on web)
496
+ my-{n} marginVertical
497
+ mt-{n} marginTop
498
+ mr-{n} marginRight
499
+ mb-{n} marginBottom
500
+ ml-{n} marginLeft
501
+ ```
502
+
503
+ Spacing scale (1 unit = 4px):
504
+ `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`
505
+
506
+ ### Sizing
507
+ ```
508
+ w-{n} width
509
+ h-{n} height
510
+ size-{n} width + height
511
+ min-w-{n} minWidth
512
+ min-h-{n} minHeight
513
+ max-w-{n} maxWidth
514
+ max-h-{n} maxHeight
515
+
516
+ Named max-w sizes:
517
+ max-w-none(none) max-w-xs(320) max-w-sm(384) max-w-md(448)
518
+ max-w-lg(512) max-w-xl(576) max-w-2xl(672) max-w-3xl(768)
519
+ max-w-4xl(896) max-w-5xl(1024) max-w-6xl(1152) max-w-7xl(1280)
520
+ max-w-prose(65ch, web only)
521
+
522
+ Screen sizes (dvw/dvh — dynamic viewport units, correct on mobile where
523
+ browser chrome changes visible viewport size; vw/vh are pinned to the
524
+ largest viewport and overflow behind a shown address bar):
525
+ w-screen(100dvw) h-screen(100dvh) size-screen not available
526
+ min-w-screen(100dvw) max-w-screen(100dvw)
527
+ min-h-screen(100dvh) max-h-screen(100dvh)
528
+ ```
529
+
530
+ ### Display
531
+ ```
532
+ block display: block
533
+ inline display: inline
534
+ inline-block display: inline-block
535
+ flex display: flex
536
+ inline-flex display: inline-flex
537
+ grid display: grid (web only)
538
+ inline-grid display: inline-grid (web only)
539
+ hidden display: none
540
+ contents display: contents (web only)
541
+ flow-root display: flow-root (web only)
542
+ ```
543
+
544
+ ### Flex
545
+ ```
546
+ flex-row/col/row-reverse/col-reverse flexDirection
547
+ flex-wrap/nowrap/wrap-reverse flexWrap
548
+ flex-1 flex: 1
549
+ flex-auto flex: 1 1 auto
550
+ flex-initial flex: 0 1 auto
551
+ flex-none flex: none
552
+ flex-grow / flex-grow-0
553
+ flex-shrink / flex-shrink-0
554
+ grow / grow-0 flexGrow: 1/0
555
+ shrink / shrink-0 flexShrink: 1/0
556
+ basis-{n} flexBasis
557
+ items-start/end/center/baseline/stretch alignItems
558
+ justify-start/end/center/between/around/evenly justifyContent
559
+ self-start/end/center/auto/stretch alignSelf
560
+ content-start/end/center/between/around/evenly alignContent
561
+ justify-items-start/end/center/stretch justifyItems (web only)
562
+ justify-self-start/end/center/auto justifySelf (web only)
563
+ order-{n} order
564
+ gap-{n} gap
565
+ gap-x-{n} columnGap
566
+ gap-y-{n} rowGap
567
+ ```
568
+
569
+ ### Grid (web only)
570
+ ```
571
+ grid-cols-{n} gridTemplateColumns: repeat(n, minmax(0,1fr))
572
+ grid-cols-none gridTemplateColumns: none
573
+ grid-rows-{n} gridTemplateRows
574
+ grid-rows-none gridTemplateRows: none
575
+ grid-flow-row/col/dense/row-dense/col-dense gridAutoFlow
576
+ auto-cols-auto/min/max/fr gridAutoColumns
577
+ auto-rows-auto/min/max/fr gridAutoRows
578
+ col-span-{n} gridColumn: span n / span n
579
+ col-span-full gridColumn: 1 / -1
580
+ col-start-{n}/auto gridColumnStart
581
+ col-end-{n}/auto gridColumnEnd
582
+ row-span-{n} gridRow: span n / span n
583
+ row-span-full gridRow: 1 / -1
584
+ row-start-{n}/auto gridRowStart
585
+ row-end-{n}/auto gridRowEnd
586
+ place-items-start/end/center/stretch place-items
587
+ place-content-start/end/center/between/around/evenly/stretch place-content
588
+ place-self-start/end/center/auto/stretch place-self
589
+
590
+ Responsive grid example:
591
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
592
+ <div className="col-span-1 md:col-span-2">wide card</div>
593
+ </div>
594
+ ```
595
+
596
+ ### Position
597
+ ```
598
+ static / relative / absolute / fixed / sticky position
599
+ inset-{n} top + right + bottom + left
600
+ inset-x-{n} left + right
601
+ inset-y-{n} top + bottom
602
+ top-{n} / right-{n} / bottom-{n} / left-{n}
603
+ z-{n} zIndex: 0 10 20 30 40 50 auto
604
+ ```
605
+
606
+ ### Overflow
607
+ ```
608
+ overflow-hidden/visible/scroll/auto/clip
609
+ overflow-x-hidden/visible/scroll/auto/clip
610
+ overflow-y-hidden/visible/scroll/auto/clip
611
+ ```
612
+
613
+ ### Border
614
+ ```
615
+ border borderWidth: 1
616
+ border-{n} borderWidth: 0 2 4 8
617
+ border-t/r/b/l border on one side
618
+ border-{color} borderColor
619
+ border-opacity-{n} border color opacity
620
+ border-solid/dashed/dotted/none borderStyle
621
+ rounded borderRadius: 4
622
+ rounded-none/sm/md/lg/xl/2xl/3xl/full
623
+ rounded-t/r/b/l border radius on one side
624
+ rounded-tl/tr/bl/br border radius on one corner
625
+ ```
626
+
627
+ ### Shadow
628
+ ```
629
+ shadow-sm/DEFAULT/md/lg/xl/2xl/none
630
+ ```
631
+
632
+ ### Opacity
633
+ ```
634
+ opacity-0/5/10/15/20/25/30/40/50/60/70/75/80/90/95/100
635
+ ```
636
+
637
+ ### Ring
638
+ Web: box-shadow ring (no layout impact). Native: approximated via
639
+ borderWidth/borderColor (RN has no box-shadow) — this DOES affect layout
640
+ there, and shares its properties with `border-*`, so combining `border-*`
641
+ and `ring-*` on the same native element means whichever class comes last
642
+ wins. `ring-offset-*` stays web-only — there's no native way to add a gap
643
+ around an element without an extra wrapper view.
644
+ ```
645
+ ring ring (2px)
646
+ ring-{n} ring width: 0 1 2 4 8
647
+ ring-{color} ring color
648
+ ring-opacity-{n} ring opacity
649
+ ring-offset-{n} ring offset: 0 1 2 4 8 (web only)
650
+ ring-offset-{color} (web only)
651
+ ring-inset inset ring (web only — native has no inset/outset distinction)
652
+ ```
653
+
654
+ ### Outline (web only)
655
+ ```
656
+ outline-none outline: none
657
+ outline outline: 2px solid transparent + offset 2px
658
+ outline-{n} outlineWidth: 0 1 2 4 8
659
+ outline-{color} outlineColor
660
+ outline-offset-{n} outlineOffset: 0 1 2 4 8
661
+ ```
662
+
663
+ ### Transforms
664
+ ```
665
+ scale-{n} scale (0–150, step varies)
666
+ scale-x-{n} scaleX
667
+ scale-y-{n} scaleY
668
+ rotate-{n} rotate in degrees
669
+ translate-x-{n} translateX (uses spacing scale)
670
+ translate-y-{n} translateY
671
+ skew-x-{n} skewX
672
+ skew-y-{n} skewY
673
+ origin-center/top/top-right/right/bottom-right/bottom/bottom-left/left/top-left
674
+ ```
675
+
676
+ ### Filters (web only)
677
+ ```
678
+ blur-{n} filter: blur
679
+ brightness-{n} filter: brightness
680
+ contrast-{n} filter: contrast
681
+ grayscale / grayscale-0
682
+ hue-rotate-{n} filter: hue-rotate
683
+ invert / invert-0
684
+ saturate-{n} filter: saturate
685
+ sepia / sepia-0
686
+ drop-shadow-{size} filter: drop-shadow
687
+
688
+ backdrop-blur-{n}
689
+ backdrop-brightness-{n}
690
+ backdrop-contrast-{n}
691
+ backdrop-grayscale
692
+ backdrop-hue-rotate-{n}
693
+ backdrop-invert
694
+ backdrop-opacity-{n}
695
+ backdrop-saturate-{n}
696
+ backdrop-sepia
697
+ backdrop-filter (enable backdrop filter)
698
+ filter (enable filter)
699
+ ```
700
+
701
+ ### Animation & Transition
702
+ ```
703
+ animate-spin rotate 360deg loop
704
+ animate-ping scale + fade ping
705
+ animate-pulse opacity pulse
706
+ animate-bounce translate-y bounce
707
+ animate-{name} custom, from theme.extend.animation (see Theme Configuration)
708
+ animate-[value] arbitrary animation shorthand, e.g. animate-[wiggle_2s_ease-in-out]
709
+ — auto-injects theme.extend.keyframes[name] if the first word matches one
710
+
711
+ transition transition: all 150ms ease (web only)
712
+ duration-{n} transitionDuration: 75 100 150 200 300 500 700 1000
713
+ delay-{n} transitionDelay: 75 100 150 200 300 500 700 1000
714
+ ```
715
+
716
+ ### Cursor (web only)
717
+ ```
718
+ cursor-auto / cursor-default / cursor-pointer / cursor-wait
719
+ cursor-text / cursor-move / cursor-not-allowed
720
+ cursor-grab / cursor-grabbing
721
+ cursor-zoom-in / cursor-zoom-out
722
+ cursor-crosshair / cursor-help / cursor-none
723
+ ```
724
+
725
+ ### Pointer events / User select (web only)
726
+ ```
727
+ pointer-events-none / pointer-events-auto
728
+ select-none / select-text / select-all / select-auto
729
+ ```
730
+
731
+ ### Touch action (web only)
732
+ ```
733
+ touch-auto / touch-none / touch-pan-x / touch-pan-y
734
+ touch-pan-left / touch-pan-right / touch-pan-up / touch-pan-down
735
+ touch-pinch-zoom / touch-manipulation
736
+ ```
737
+
738
+ ### Scroll (web only)
739
+ ```
740
+ scroll-smooth scroll-behavior: smooth
741
+ scroll-auto scroll-behavior: auto
742
+ ```
743
+
744
+ ### Float & Clear (web only)
745
+ ```
746
+ float-left / float-right / float-start / float-end / float-none
747
+ clear-left / clear-right / clear-both / clear-start / clear-end / clear-none
748
+ ```
749
+
750
+ ### Vertical align (web only)
751
+ ```
752
+ align-baseline / align-top / align-middle / align-bottom
753
+ align-text-top / align-text-bottom / align-sub / align-super
754
+ ```
755
+
756
+ ### Visibility
757
+ ```
758
+ visible visibility: visible
759
+ invisible visibility: hidden
760
+ sr-only visually hidden but screen-reader accessible
761
+ not-sr-only reverses sr-only
762
+ ```
763
+
764
+ ### List style
765
+ ```
766
+ list-none / list-disc / list-decimal
767
+ ```
768
+
769
+ ### Appearance / Resize (web only)
770
+ ```
771
+ appearance-none
772
+ resize / resize-none / resize-x / resize-y
773
+ ```
774
+
775
+ ### Box sizing (web only)
776
+ ```
777
+ box-border box-sizing: border-box
778
+ box-content box-sizing: content-box
779
+ ```
780
+
781
+ ### Object fit (web only)
782
+ ```
783
+ object-contain / object-cover / object-fill / object-none / object-scale-down
784
+ ```
785
+
786
+ ### Aspect ratio (web only)
787
+ ```
788
+ aspect-auto aspect-ratio: auto
789
+ aspect-square aspect-ratio: 1 / 1
790
+ aspect-video aspect-ratio: 16 / 9
791
+ aspect-{arbitrary} e.g. aspect-[4/3]
792
+ ```
793
+
794
+ ### Columns (web only)
795
+ ```
796
+ columns-{n} column-count: 1–12
797
+ columns-auto column-count: auto
798
+ columns-{size} column-width: 3xs(16rem) 2xs(18rem) xs(20rem) sm(24rem)
799
+ md(28rem) lg(32rem) xl(36rem) 2xl(42rem)
800
+ 3xl(48rem) 4xl(56rem) 5xl(64rem) 6xl(72rem) 7xl(80rem)
801
+ ```
802
+
803
+ ### Caret / Accent (web only)
804
+ ```
805
+ caret-{color} caret-color
806
+ caret-auto caret-color: auto
807
+ caret-transparent caret-color: transparent
808
+ accent-{color} accent-color
809
+ accent-auto accent-color: auto
810
+ ```
811
+
812
+ ### SVG stroke / fill (web only)
813
+ Native excluded: react-native-svg takes stroke/fill as component props, not
814
+ style entries, so there's no reliable way to apply these through `style`.
815
+ ```
816
+ stroke-{color} stroke
817
+ stroke-{n} strokeWidth (numeric, not a color)
818
+ stroke-none stroke: none
819
+ fill-{color} fill
820
+ fill-none fill: none
821
+ ```
822
+
823
+ ### Mix blend / Background blend (web only)
824
+ ```
825
+ mix-blend-{mode} mix-blend-mode
826
+ bg-blend-{mode} background-blend-mode
827
+ modes: normal multiply screen overlay darken lighten color-dodge
828
+ color-burn hard-light soft-light difference exclusion
829
+ hue saturation color luminosity (plus-lighter for mix-blend only)
830
+ ```
831
+
832
+ ### Will-change (web only)
833
+ ```
834
+ will-change-auto / will-change-scroll / will-change-contents / will-change-transform
835
+ will-change-[transform,opacity] arbitrary
836
+ ```
837
+
838
+ ### Divide (web only, uses child combinator CSS)
839
+ ```
840
+ divide-x-{n} border between horizontal children
841
+ divide-y-{n} border between vertical children
842
+ divide-{color} divider color
843
+ divide-solid/dashed/dotted
844
+ ```
845
+
846
+ ### Space between
847
+ ```
848
+ space-x-{n} margin-left on children (> * + *)
849
+ space-y-{n} margin-top on children
850
+ ```
851
+
852
+ ### Group / Peer standalones
853
+ ```
854
+ group standalone marker class (no styles, silences dev warning)
855
+ peer standalone marker class
856
+ ```
857
+
858
+ ---
859
+
860
+ ## Native-only Utilities
861
+
862
+ These only work on React Native / Expo:
863
+ ```
864
+ tint-{color} tintColor (Image / icon tinting)
865
+ perspective-{n} perspective transform
866
+ backface-hidden backfaceVisibility: hidden
867
+ text-shadow text shadow (small)
868
+ text-shadow-lg text shadow (large)
869
+ ```
870
+
871
+ ---
872
+
873
+ ## Web-only Utilities (gracefully ignored on native)
874
+
875
+ The following resolve to `null` on React Native and produce no warning:
876
+ `caret-*`, `accent-*`, `touch-*`, `float-*`, `clear-*`, `align-*` (vertical),
877
+ `line-clamp-*`, `scroll-smooth`, `scroll-auto`, `overflow-clip`,
878
+ `overflow-ellipsis`, `bg-clip-text`, `bg-gradient-to-*`,
879
+ `animate-*`, `transition`, `filter`, `backdrop-filter`,
880
+ `print:`, `before:`, `after:`, `selection:`, `first-letter:`, `first-line:`, `marker:`,
881
+ `landscape:`, `portrait:`, `motion-reduce:`, `motion-safe:`,
882
+ `contrast-more:`, `contrast-less:`, `rtl:`, `ltr:`,
883
+ `mix-blend-*`, `bg-blend-*`, `will-change-*`, `columns-*`, `aspect-*`,
884
+ `object-*`, `resize-*`, `appearance-none`, `box-border`, `box-content`,
885
+ `subpixel-antialiased`, `overflow-ellipsis`, `flow-root`, `contents`,
886
+ `grid`, `inline-grid`, `grid-cols-*`, etc.
887
+
888
+ ---
889
+
890
+ ## Theme Configuration
891
+
892
+ ### kbach.config.js (project root)
893
+ ```js
894
+ module.exports = {
895
+ darkMode: 'attribute', // 'attribute' | 'class' | 'media'
896
+
897
+ theme: {
898
+ // Fully replace a scale
899
+ colors: {
900
+ brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a8a' },
901
+ },
902
+ },
903
+
904
+ extend: {
905
+ theme: {
906
+ // Merge into existing scale
907
+ colors: { brand: { 6: '#6366f1' } },
908
+ spacing: { 18: 72, 22: 88 },
909
+ fontSize: { '10xl': 160 },
910
+ // Custom @keyframes (web only) — declaration values are plain CSS strings.
911
+ // Reference by name from `animation`, then use that name as animate-{name}.
912
+ keyframes: {
913
+ wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
914
+ },
915
+ animation: {
916
+ wiggle: 'wiggle 1s ease-in-out infinite',
917
+ },
918
+ },
919
+ },
920
+
921
+ plugins: [
922
+ ({ addUtility, theme }) => {
923
+ addUtility('border-brand', {
924
+ borderColor: theme('colors.brand.6'),
925
+ borderWidth: 2,
926
+ });
927
+ },
928
+ ],
929
+ };
930
+ ```
931
+
932
+ ### Runtime update
933
+ ```js
934
+ import { updateConfig } from '@kbach/ui';
935
+ updateConfig({ extend: { theme: { colors: { brand: { 6: '#6366f1' } } } } });
936
+ // Always call clearCache() after updateConfig() to flush stale resolved styles.
937
+ ```
938
+
939
+ ### Default theme values
940
+ ```
941
+ spacing: 1 unit = 4px (see Spacing section above)
942
+ fontSize: xs–9xl (12–128px)
943
+ fontFamily: sans('System') mono('Courier New') serif('Georgia')
944
+ fontWeight: thin(100) extralight(200) light(300) normal(400) medium(500)
945
+ semibold(600) bold(700) extrabold(800) black(900)
946
+ borderRadius: none(0) sm(2) DEFAULT(4) md(6) lg(8) xl(12) 2xl(16) 3xl(24) full(9999)
947
+ borderWidth: DEFAULT(1) 0 2 4 8
948
+ opacity: 0 5 10 15 20 25 30 40 50 60 70 75 80 90 95 100
949
+ lineHeight: none(1) tight(1.25) snug(1.375) normal(1.5) relaxed(1.625) loose(2) + 3–10 (12–40px)
950
+ letterSpacing:tighter(-0.8) tight(-0.4) normal(0) wide(0.4) wider(0.8) widest(1.6)
951
+ zIndex: auto 0 10 20 30 40 50
952
+ screens: sm(576) md(768) lg(1024) xl(1280) 2xl(1536)
953
+ ```
954
+
955
+ ---
956
+
957
+ ## Common Patterns
958
+
959
+ ### Dark mode card
960
+ ```jsx
961
+ <div className="bg-white dark:bg-gray-9 rounded-2xl p-6 shadow-md">
962
+ <h2 className="text-2xl font-bold text-gray-10 dark:text-white">Title</h2>
963
+ <p className="text-gray-6 dark:text-gray-4 mt-2">Body text</p>
964
+ </div>
965
+ ```
966
+
967
+ ### Interactive button
968
+ ```jsx
969
+ <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" />
970
+ ```
971
+
972
+ ### Responsive layout
973
+ ```jsx
974
+ <div className="flex flex-col md:flex-row gap-4">
975
+ <aside className="w-full md:w-64 lg:w-80">…</aside>
976
+ <main className="flex-1">…</main>
977
+ </div>
978
+ ```
979
+
980
+ ### Responsive grid
981
+ ```jsx
982
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
983
+ {items.map(item => <Card key={item.id} />)}
984
+ </div>
985
+ ```
986
+
987
+ ### Group hover reveal
988
+ ```jsx
989
+ <div className="group relative overflow-hidden rounded-xl">
990
+ <img src="…" className="transition group-hover:scale-105" />
991
+ <div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition flex items-center justify-center">
992
+ <span className="text-white font-bold">View</span>
993
+ </div>
994
+ </div>
995
+ ```
996
+
997
+ ### Reduced-motion safe animation
998
+ ```jsx
999
+ <div className="motion-safe:animate-spin motion-reduce:opacity-75" />
1000
+ ```
1001
+
1002
+ ### RTL-aware spacing
1003
+ ```jsx
1004
+ <div className="ltr:pl-4 rtl:pr-4 ltr:text-left rtl:text-right" />
1005
+ ```
1006
+
1007
+ ### Before/after pseudo-elements
1008
+ ```jsx
1009
+ <div className="relative before:absolute before:inset-0 before:bg-blue-6/10 before:rounded-xl" />
1010
+ ```
1011
+
1012
+ ### Input with caret and focus ring
1013
+ ```jsx
1014
+ <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" />
1015
+ ```
1016
+
1017
+ ### Print-specific styles
1018
+ ```jsx
1019
+ <nav className="print:hidden" />
1020
+ <article className="print:text-black print:bg-white print:shadow-none" />
1021
+ ```
1022
+
1023
+ ### Contrast accessibility
1024
+ ```jsx
1025
+ <button className="bg-blue-6 contrast-more:bg-blue-9 contrast-more:border-2 contrast-more:border-blue-11 text-white">
1026
+ Submit
1027
+ </button>
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## Caching
1033
+
1034
+ The resolver uses an LRU cache (10,000 entries). Cache is automatically cleared on `updateConfig()`. Manually:
1035
+ ```js
1036
+ import { clearCache } from '@kbach/ui';
1037
+ clearCache();
1038
+ ```
1039
+
1040
+ ---
1041
+
1042
+ ## Package Versions
1043
+ - `@kbach/ui`: see `packages/ui/package.json` — the one package for web, React Native, and Expo
1044
+ - `@kbach/native`: deprecated, no longer in this repo — frozen at its last published npm version (compatibility shim re-exporting `@kbach/ui`)