@kbach/ui 0.1.0-beta.6 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/KBACH.md DELETED
@@ -1,1052 +0,0 @@
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). `ThemeProvider` auto-detects native at render time, so it's the same import (`@kbach/ui`) on every platform. The Babel preset and Metro/Babel config helpers (Node-only build tooling) live at the `@kbach/ui/native` and `@kbach/ui/babel` subpaths.
6
-
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. Not needed if using static `kbach.css` (below) — that file already includes the same reset. `<KbachReset />` checks `disableRuntimeCSS()` (which the Vite plugin calls automatically wherever `kbach.css` gets imported) and renders nothing once it's active, so leaving it mounted alongside the static setup is safe rather than a duplicate reset on every page load.
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';
75
- <ThemeProvider defaultMode="system"><AppContent /></ThemeProvider>
76
- ```
77
- `ThemeProvider` auto-detects React Native at render time and reads `useColorScheme()`/`useWindowDimensions()` automatically — no extra props, no separate `/native` import needed.
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, ThemeProvider } from '@kbach/ui';
92
- <ThemeProvider defaultMode="system"><KbachReset /><Slot /></ThemeProvider>
93
- ```
94
-
95
- ### CSS inheritance
96
- 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');`
97
-
98
- ---
99
-
100
- ## Core API
101
-
102
- ### className prop
103
- Works on any element once the JSX runtime is active.
104
- ```jsx
105
- <div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
106
- <p className="text-gray-10 text-lg font-bold" />
107
- <button className="bg-blue-7 hover:bg-blue-8 rounded-lg px-4 py-2" />
108
- ```
109
-
110
- ### styled(Component, baseClasses)
111
- Pre-style a component. Returns a new component that accepts a `kb` prop for extra classes.
112
- ```jsx
113
- import { styled } from '@kbach/ui'; // same import on web and React Native
114
-
115
- const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
116
- const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
117
-
118
- <Card kb="mt-4"> // merges mt-4 with base classes
119
- <Button kb="w-full" /> // merges w-full with base classes
120
- ```
121
-
122
- On web, `styled()` forwards the full class string as `className` so CSS rules (group-hover:, before:, print:) match the element.
123
-
124
- ### useStyles(classes)
125
- Resolve classes to a style object inside a component.
126
- ```jsx
127
- import { useStyles } from '@kbach/ui';
128
- const style = useStyles('bg-blue-6 px-3 py-1 rounded-full');
129
- return <span style={style}>Badge</span>;
130
- ```
131
-
132
- ### kb(classes)
133
- Resolve outside a component (static contexts).
134
- ```js
135
- import { kb } from '@kbach/ui';
136
- const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
137
- ```
138
-
139
- ### cx(...classes)
140
- Conditionally join class strings. Falsy values ignored.
141
- ```jsx
142
- import { cx } from '@kbach/ui';
143
- <div className={cx('p-4', isActive && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
144
- ```
145
-
146
- ### useTheme()
147
- ```ts
148
- const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
149
- // mode: 'light' | 'dark' | 'system'
150
- // resolvedMode: 'light' | 'dark'
151
- // isDark: boolean
152
- // setMode(mode): void
153
- // toggle(): void
154
- // config: ResolvedConfig
155
- ```
156
-
157
- ### useIsDark()
158
- ```ts
159
- const isDark = useIsDark(); // boolean
160
- ```
161
-
162
- ### useColors()
163
- Returns a proxy over the active theme's color palette.
164
- ```ts
165
- const colors = useColors();
166
- colors.blue[6] // '#3b82f6'
167
- colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
168
- colors.white // '#ffffff'
169
- colors['white/20'] // 'rgba(255,255,255,0.2)'
170
- colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
171
- ```
172
-
173
- ---
174
-
175
- ## Modifier System
176
-
177
- Up to 3 modifiers can be chained in any order before the utility name.
178
-
179
- ```
180
- [modifier:][modifier:][modifier:]utility-value
181
- dark:hover:bg-blue-8
182
- sm:dark:text-lg
183
- motion-reduce:transition-none
184
- rtl:text-right
185
- ```
186
-
187
- ### Theme modifiers
188
- | Modifier | Condition |
189
- |---|---|
190
- | `dark:` | Dark mode active |
191
- | `light:` | Light mode active |
192
- | `not-dark:` | Light mode active (alias) |
193
- | `not-light:` | Dark mode active (alias) |
194
-
195
- Dark mode strategy set in `ThemeProvider` or `kbach.config.js`:
196
- - `'attribute'` (default) — `[data-theme="dark"]` on a wrapper element
197
- - `'class'` — `.dark` class on a wrapper element
198
- - `'media'` — `@media (prefers-color-scheme: dark)`
199
-
200
- ### Interaction modifiers
201
- | Modifier | Triggers on |
202
- |---|---|
203
- | `hover:` | Mouse hover |
204
- | `focus:` | Element focused |
205
- | `focus-within:` | Focus anywhere inside element |
206
- | `focus-visible:` | Keyboard focus ring |
207
- | `active:` | Active state |
208
- | `pressed:` | Click / touch pressed (alias for active on native) |
209
- | `visited:` | Visited link |
210
- | `disabled:` | Disabled element |
211
- | `checked:` | Checkbox / radio checked |
212
- | `placeholder:` | Input placeholder text |
213
-
214
- Negated: `not-hover:`, `not-focus:`, `not-active:`, `not-pressed:`, `not-visited:`, `not-disabled:`, `not-checked:`
215
-
216
- ### Structural modifiers (CSS-injection only)
217
- | Modifier | Pseudo-class |
218
- |---|---|
219
- | `first:` | `:first-child` |
220
- | `last:` | `:last-child` |
221
- | `odd:` | `:nth-child(odd)` |
222
- | `even:` | `:nth-child(even)` |
223
- | `only:` | `:only-child` |
224
-
225
- ### Responsive modifiers
226
- | Modifier | Min-width |
227
- |---|---|
228
- | `sm:` | 576 px |
229
- | `md:` | 768 px |
230
- | `lg:` | 1024 px |
231
- | `xl:` | 1280 px |
232
- | `2xl:` | 1536 px |
233
-
234
- 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.
235
-
236
- ### Group / peer modifiers (CSS-injection only, web only)
237
- Mark a parent with `group` (standalone class), then use `group-hover:` etc. on children.
238
- Mark a previous sibling with `peer`, then use `peer-hover:` etc. on the next sibling.
239
-
240
- ```jsx
241
- <div className="group">
242
- <span className="opacity-0 group-hover:opacity-100 transition" />
243
- </div>
244
- ```
245
-
246
- | Modifier | Fires when |
247
- |---|---|
248
- | `group-hover:` | Ancestor `.group` is hovered |
249
- | `group-focus:` | Ancestor `.group` is focused |
250
- | `peer-hover:` | Previous sibling `.peer` is hovered |
251
- | `peer-focus:` | Previous sibling `.peer` is focused |
252
-
253
- **Named groups/peers** — when groups nest, an unnamed `group-hover:` matches
254
- the NEAREST `.group` ancestor, so an inner element reacts to whichever group
255
- (inner or outer) is hovered, not necessarily the one you meant. Name the
256
- marker (`group/{name}`) and the modifier (`group-hover/{name}:`) to scope it
257
- to that specific ancestor — same for `peer/{name}` + `peer-hover/{name}:` /
258
- `peer-focus/{name}:`. `{name}` can be any string (`card`, `sidebar`, …).
259
-
260
- ```jsx
261
- <div className="group/card">
262
- <div className="group/icon">
263
- <span className="group-hover/icon:opacity-100" /> {/* only inner group */}
264
- </div>
265
- <span className="group-hover/card:underline" /> {/* only outer group */}
266
- </div>
267
- ```
268
-
269
- ### Pseudo-element modifiers (CSS-injection only, web only)
270
- ```jsx
271
- <div className="before:content-['*'] before:text-red-6 relative" />
272
- <p className="first-letter:text-4xl first-letter:font-bold" />
273
- <p className="selection:bg-blue-3" />
274
- <input className="placeholder:text-gray-5" />
275
- ```
276
-
277
- | Modifier | CSS selector |
278
- |---|---|
279
- | `before:` | `::before` |
280
- | `after:` | `::after` |
281
- | `selection:` | `::selection` |
282
- | `first-letter:` | `::first-letter` |
283
- | `first-line:` | `::first-line` |
284
- | `marker:` | `::marker` |
285
- | `placeholder:` | `::placeholder` |
286
-
287
- ### Print modifier (CSS-injection only, web only)
288
- ```jsx
289
- <div className="print:hidden" />
290
- <div className="print:text-black print:bg-white" />
291
- ```
292
-
293
- ### Orientation modifiers (CSS-injection only, web only)
294
- ```jsx
295
- <div className="landscape:flex-row portrait:flex-col" />
296
- ```
297
-
298
- | Modifier | Media query |
299
- |---|---|
300
- | `landscape:` | `@media (orientation: landscape)` |
301
- | `portrait:` | `@media (orientation: portrait)` |
302
-
303
- ### Accessibility modifiers (CSS-injection only, web only)
304
- ```jsx
305
- <div className="motion-reduce:transition-none motion-safe:transition-all duration-300" />
306
- <div className="contrast-more:border-2 contrast-more:border-black" />
307
- ```
308
-
309
- | Modifier | Media query |
310
- |---|---|
311
- | `motion-reduce:` | `@media (prefers-reduced-motion: reduce)` |
312
- | `motion-safe:` | `@media (prefers-reduced-motion: no-preference)` |
313
- | `contrast-more:` | `@media (prefers-contrast: more)` |
314
- | `contrast-less:` | `@media (prefers-contrast: less)` |
315
-
316
- ### Directionality modifiers (CSS-injection only, web only)
317
- ```jsx
318
- <div className="rtl:text-right ltr:text-left" />
319
- <div className="rtl:pl-4 ltr:pr-4" />
320
- ```
321
-
322
- | Modifier | CSS selector scope |
323
- |---|---|
324
- | `rtl:` | `[dir="rtl"] .cls` |
325
- | `ltr:` | `[dir="ltr"] .cls` |
326
-
327
- ### Important modifier
328
- Prefix any class with `!` to add `!important` to every CSS declaration it produces.
329
- ```jsx
330
- <div className="!p-0 !m-0 !bg-transparent" />
331
- ```
332
-
333
- ---
334
-
335
- ## Arbitrary Values
336
-
337
- Wrap any value in `[]` to use it directly.
338
- ```jsx
339
- <div className="bg-[#6366f1]" />
340
- <div className="p-[14px]" />
341
- <div className="w-[calc(100%-2rem)]" />
342
- <div className="text-[18px]" />
343
- <div className="rounded-[20px]" />
344
- <div className="bg-[rgba(99,102,241,0.15)]" />
345
- <div className="grid-cols-[1fr_2fr_1fr]" />
346
- <div className="will-change-[transform,opacity]" />
347
- ```
348
-
349
- ### Arbitrary properties (web only)
350
- No utility prefix — `[property:value]` on its own, for a CSS property with no named utility:
351
- ```jsx
352
- <div className="[mask-type:luminance]" />
353
- <div className="[--my-var:10px]" /> {/* custom property */}
354
- ```
355
- 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.
356
-
357
- ---
358
-
359
- ## Negative Values
360
- Prefix any spacing utility with `-` for negative values.
361
- ```jsx
362
- <div className="-mt-4" /> // marginTop: -16
363
- <div className="-mx-2" /> // marginHorizontal: -8
364
- <div className="-translate-x-2" />
365
- ```
366
-
367
- ---
368
-
369
- ## Color with Opacity
370
- Append `/opacity` to any color utility. Opacity is 0–100 (integer) or arbitrary.
371
- ```jsx
372
- <div className="bg-blue-6/50" /> // 50% opacity
373
- <div className="text-gray-10/75" /> // 75% opacity
374
- <div className="bg-black/[0.15]" /> // arbitrary opacity
375
- ```
376
-
377
- ---
378
-
379
- ## Color System
380
-
381
- ### 12-shade scale
382
- 1 = lightest, 12 = darkest. Maps to Tailwind v3 (50 → 1, 100 → 2, … 950 → 11, extra-dark → 12).
383
-
384
- ```
385
- shade 1 2 3 4 5 6 7 8 9 10 11 12
386
- ─────────────────────────────────────────────
387
- light dark
388
- ```
389
-
390
- Usage: `bg-blue-6`, `text-gray-10`, `border-red-4/50`
391
-
392
- ### Color families (22 total)
393
- Grays: `slate`, `gray`, `zinc`, `neutral`, `stone`
394
- Colors: `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`
395
- Special: `transparent`, `current` (currentColor), `black`, `white`
396
-
397
- ---
398
-
399
- ## Utility Reference
400
-
401
- ### Background
402
- ```
403
- bg-{color} backgroundColor
404
- bg-{color}/{opacity} backgroundColor with alpha
405
- bg-transparent backgroundColor: transparent
406
- bg-clip-border -webkit-background-clip: border-box
407
- bg-clip-padding -webkit-background-clip: padding-box
408
- bg-clip-content -webkit-background-clip: content-box
409
- bg-clip-text -webkit-background-clip: text (web only)
410
- bg-gradient-to-{dir} background linear gradient direction (web only)
411
- directions: t, tr, r, br, b, bl, l, tl
412
- use with: from-{color}, via-{color}, to-{color}
413
- bg-blend-{mode} background-blend-mode (web only)
414
- modes: normal, multiply, screen, overlay, darken, lighten,
415
- color-dodge, color-burn, hard-light, soft-light,
416
- difference, exclusion, hue, saturation, color, luminosity
417
- ```
418
-
419
- ### Text
420
- ```
421
- text-{size} fontSize: xs(12) sm(14) base(16) lg(18) xl(20)
422
- 2xl(24) 3xl(30) 4xl(36) 5xl(48)
423
- 6xl(60) 7xl(72) 8xl(96) 9xl(128)
424
- text-{color} color
425
- text-left/right/center/justify textAlign
426
- text-wrap text-wrap: wrap (web only)
427
- text-nowrap text-wrap: nowrap (web only)
428
- text-balance text-wrap: balance (web only)
429
- text-pretty text-wrap: pretty (web only)
430
- ```
431
-
432
- ### Font
433
- ```
434
- font-thin/extralight/light/normal/medium/semibold/bold/extrabold/black
435
- font-{family} fontFamily (sans, mono, serif, or custom)
436
- ```
437
-
438
- ### Text decoration
439
- ```
440
- underline textDecorationLine: underline
441
- overline textDecorationLine: overline (web only)
442
- line-through textDecorationLine: line-through
443
- no-underline textDecorationLine: none
444
- decoration-{color} textDecorationColor (web only)
445
- decoration-solid/dashed/dotted/double/wavy textDecorationStyle (web only)
446
- decoration-0/1/2/4/8/auto/from-font textDecorationThickness (web only)
447
- underline-offset-0/1/2/4/8/auto textUnderlineOffset (web only)
448
- ```
449
-
450
- ### Text transform / case
451
- ```
452
- uppercase textTransform: uppercase
453
- lowercase textTransform: lowercase
454
- capitalize textTransform: capitalize
455
- normal-case textTransform: none
456
- italic fontStyle: italic
457
- not-italic fontStyle: normal
458
- ```
459
-
460
- ### Text overflow
461
- ```
462
- truncate overflow: hidden; white-space: nowrap; text-overflow: ellipsis
463
- overflow-ellipsis text-overflow: ellipsis (web only)
464
- line-clamp-{n} -webkit-line-clamp (web only, n = 1–20)
465
- line-clamp-none removes line-clamp (web only)
466
- whitespace-normal/nowrap/pre/pre-wrap/pre-line whiteSpace
467
- break-normal/words/all/keep word-break / overflow-wrap
468
- ```
469
-
470
- ### Typography misc
471
- ```
472
- leading-{value} lineHeight: none(1) tight(1.25) snug(1.375) normal(1.5)
473
- relaxed(1.625) loose(2), or numeric 3–10 (12–40px)
474
- tracking-{value} letterSpacing: tighter(-0.8) tight(-0.4) normal(0)
475
- wide(0.4) wider(0.8) widest(1.6)
476
- antialiased -webkit-font-smoothing: antialiased (web only)
477
- subpixel-antialiased -webkit-font-smoothing: subpixel-antialiased (web only)
478
- ```
479
-
480
- ### Spacing — Padding
481
- ```
482
- p-{n} padding (all sides)
483
- px-{n} paddingHorizontal
484
- py-{n} paddingVertical
485
- pt-{n} paddingTop
486
- pr-{n} paddingRight
487
- pb-{n} paddingBottom
488
- pl-{n} paddingLeft
489
- ```
490
-
491
- ### Spacing — Margin
492
- ```
493
- m-{n} margin (all sides)
494
- mx-{n} marginHorizontal (mx-auto centers on web)
495
- my-{n} marginVertical
496
- mt-{n} marginTop
497
- mr-{n} marginRight
498
- mb-{n} marginBottom
499
- ml-{n} marginLeft
500
- ```
501
-
502
- Spacing scale (1 unit = 4px):
503
- `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`
504
-
505
- ### Sizing
506
- ```
507
- w-{n} width
508
- h-{n} height
509
- size-{n} width + height
510
- min-w-{n} minWidth
511
- min-h-{n} minHeight
512
- max-w-{n} maxWidth
513
- max-h-{n} maxHeight
514
-
515
- Named max-w sizes:
516
- max-w-none(none) max-w-xs(320) max-w-sm(384) max-w-md(448)
517
- max-w-lg(512) max-w-xl(576) max-w-2xl(672) max-w-3xl(768)
518
- max-w-4xl(896) max-w-5xl(1024) max-w-6xl(1152) max-w-7xl(1280)
519
- max-w-prose(65ch, web only)
520
-
521
- Screen sizes (dvw/dvh — dynamic viewport units, correct on mobile where
522
- browser chrome changes visible viewport size; vw/vh are pinned to the
523
- largest viewport and overflow behind a shown address bar):
524
- w-screen(100dvw) h-screen(100dvh) size-screen not available
525
- min-w-screen(100dvw) max-w-screen(100dvw)
526
- min-h-screen(100dvh) max-h-screen(100dvh)
527
- ```
528
-
529
- ### Display
530
- ```
531
- block display: block
532
- inline display: inline
533
- inline-block display: inline-block
534
- flex display: flex
535
- inline-flex display: inline-flex
536
- grid display: grid (web only)
537
- inline-grid display: inline-grid (web only)
538
- hidden display: none
539
- contents display: contents (web only)
540
- flow-root display: flow-root (web only)
541
- ```
542
-
543
- ### Flex
544
- ```
545
- flex-row/col/row-reverse/col-reverse flexDirection
546
- flex-wrap/nowrap/wrap-reverse flexWrap
547
- flex-1 flex: 1
548
- flex-auto flex: 1 1 auto
549
- flex-initial flex: 0 1 auto
550
- flex-none flex: none
551
- flex-grow / flex-grow-0
552
- flex-shrink / flex-shrink-0
553
- grow / grow-0 flexGrow: 1/0
554
- shrink / shrink-0 flexShrink: 1/0
555
- basis-{n} flexBasis
556
- items-start/end/center/baseline/stretch alignItems
557
- justify-start/end/center/between/around/evenly justifyContent
558
- self-start/end/center/auto/stretch alignSelf
559
- content-start/end/center/between/around/evenly alignContent
560
- justify-items-start/end/center/stretch justifyItems (web only)
561
- justify-self-start/end/center/auto justifySelf (web only)
562
- order-{n} order
563
- gap-{n} gap
564
- gap-x-{n} columnGap
565
- gap-y-{n} rowGap
566
- ```
567
-
568
- ### Grid (web only)
569
- ```
570
- grid-cols-{n} gridTemplateColumns: repeat(n, minmax(0,1fr))
571
- grid-cols-none gridTemplateColumns: none
572
- grid-rows-{n} gridTemplateRows
573
- grid-rows-none gridTemplateRows: none
574
- grid-flow-row/col/dense/row-dense/col-dense gridAutoFlow
575
- auto-cols-auto/min/max/fr gridAutoColumns
576
- auto-rows-auto/min/max/fr gridAutoRows
577
- col-span-{n} gridColumn: span n / span n
578
- col-span-full gridColumn: 1 / -1
579
- col-start-{n}/auto gridColumnStart
580
- col-end-{n}/auto gridColumnEnd
581
- row-span-{n} gridRow: span n / span n
582
- row-span-full gridRow: 1 / -1
583
- row-start-{n}/auto gridRowStart
584
- row-end-{n}/auto gridRowEnd
585
- place-items-start/end/center/stretch place-items
586
- place-content-start/end/center/between/around/evenly/stretch place-content
587
- place-self-start/end/center/auto/stretch place-self
588
-
589
- Responsive grid example:
590
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
591
- <div className="col-span-1 md:col-span-2">wide card</div>
592
- </div>
593
- ```
594
-
595
- ### Position
596
- ```
597
- static / relative / absolute / fixed / sticky position
598
- inset-{n} top + right + bottom + left
599
- inset-x-{n} left + right
600
- inset-y-{n} top + bottom
601
- top-{n} / right-{n} / bottom-{n} / left-{n}
602
- z-{n} zIndex: 0 10 20 30 40 50 auto
603
- ```
604
-
605
- ### Overflow
606
- ```
607
- overflow-hidden/visible/scroll/auto/clip
608
- overflow-x-hidden/visible/scroll/auto/clip
609
- overflow-y-hidden/visible/scroll/auto/clip
610
- ```
611
-
612
- ### Border
613
- ```
614
- border borderWidth: 1
615
- border-{n} borderWidth: 0 2 4 8
616
- border-t/r/b/l border on one side
617
- border-{color} borderColor
618
- border-opacity-{n} border color opacity
619
- border-solid/dashed/dotted/none borderStyle
620
- rounded borderRadius: 4
621
- rounded-none/sm/md/lg/xl/2xl/3xl/full
622
- rounded-t/r/b/l border radius on one side
623
- rounded-tl/tr/bl/br border radius on one corner
624
- ```
625
-
626
- ### Shadow
627
- ```
628
- shadow-sm/DEFAULT/md/lg/xl/2xl/inner/none
629
- ```
630
- Web: real `box-shadow` (Tailwind's own default values). Native: RN's shadowColor/shadowOffset/shadowOpacity/shadowRadius/elevation, tuned independently for RN's elevation model — not derived from the web value. `shadow-inner` is web-only (no native inset-shadow equivalent). Customize/add sizes via `theme.extend.shadow` — each preset key merges independently (see Theme Configuration below).
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
- // Each preset key merges independently — overriding lg's boxShadow
911
- // (the web value) leaves its native shadowColor/shadowOffset/etc.
912
- // untouched, and a brand-new key (e.g. '3xl') adds a new shadow-3xl
913
- // utility alongside the defaults.
914
- shadow: {
915
- lg: { boxShadow: '0 10px 40px -10px rgba(99, 102, 241, 0.4)' },
916
- '3xl': { boxShadow: '0 35px 60px -15px rgba(0, 0, 0, 0.3)' },
917
- },
918
- // Custom @keyframes (web only) — declaration values are plain CSS strings.
919
- // Reference by name from `animation`, then use that name as animate-{name}.
920
- keyframes: {
921
- wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
922
- },
923
- animation: {
924
- wiggle: 'wiggle 1s ease-in-out infinite',
925
- },
926
- },
927
- },
928
-
929
- plugins: [
930
- ({ addUtility, theme }) => {
931
- addUtility('border-brand', {
932
- borderColor: theme('colors.brand.6'),
933
- borderWidth: 2,
934
- });
935
- },
936
- ],
937
- };
938
- ```
939
-
940
- ### Runtime update
941
- ```js
942
- import { updateConfig } from '@kbach/ui';
943
- updateConfig({ extend: { theme: { colors: { brand: { 6: '#6366f1' } } } } });
944
- // Always call clearCache() after updateConfig() to flush stale resolved styles.
945
- ```
946
-
947
- ### Default theme values
948
- ```
949
- spacing: 1 unit = 4px (see Spacing section above)
950
- fontSize: xs–9xl (12–128px)
951
- fontFamily: sans('System') mono('Courier New') serif('Georgia')
952
- fontWeight: thin(100) extralight(200) light(300) normal(400) medium(500)
953
- semibold(600) bold(700) extrabold(800) black(900)
954
- borderRadius: none(0) sm(2) DEFAULT(4) md(6) lg(8) xl(12) 2xl(16) 3xl(24) full(9999)
955
- borderWidth: DEFAULT(1) 0 2 4 8
956
- opacity: 0 5 10 15 20 25 30 40 50 60 70 75 80 90 95 100
957
- lineHeight: none(1) tight(1.25) snug(1.375) normal(1.5) relaxed(1.625) loose(2) + 3–10 (12–40px)
958
- letterSpacing:tighter(-0.8) tight(-0.4) normal(0) wide(0.4) wider(0.8) widest(1.6)
959
- zIndex: auto 0 10 20 30 40 50
960
- screens: sm(576) md(768) lg(1024) xl(1280) 2xl(1536)
961
- ```
962
-
963
- ---
964
-
965
- ## Common Patterns
966
-
967
- ### Dark mode card
968
- ```jsx
969
- <div className="bg-white dark:bg-gray-9 rounded-2xl p-6 shadow-md">
970
- <h2 className="text-2xl font-bold text-gray-10 dark:text-white">Title</h2>
971
- <p className="text-gray-6 dark:text-gray-4 mt-2">Body text</p>
972
- </div>
973
- ```
974
-
975
- ### Interactive button
976
- ```jsx
977
- <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" />
978
- ```
979
-
980
- ### Responsive layout
981
- ```jsx
982
- <div className="flex flex-col md:flex-row gap-4">
983
- <aside className="w-full md:w-64 lg:w-80">…</aside>
984
- <main className="flex-1">…</main>
985
- </div>
986
- ```
987
-
988
- ### Responsive grid
989
- ```jsx
990
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
991
- {items.map(item => <Card key={item.id} />)}
992
- </div>
993
- ```
994
-
995
- ### Group hover reveal
996
- ```jsx
997
- <div className="group relative overflow-hidden rounded-xl">
998
- <img src="…" className="transition group-hover:scale-105" />
999
- <div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition flex items-center justify-center">
1000
- <span className="text-white font-bold">View</span>
1001
- </div>
1002
- </div>
1003
- ```
1004
-
1005
- ### Reduced-motion safe animation
1006
- ```jsx
1007
- <div className="motion-safe:animate-spin motion-reduce:opacity-75" />
1008
- ```
1009
-
1010
- ### RTL-aware spacing
1011
- ```jsx
1012
- <div className="ltr:pl-4 rtl:pr-4 ltr:text-left rtl:text-right" />
1013
- ```
1014
-
1015
- ### Before/after pseudo-elements
1016
- ```jsx
1017
- <div className="relative before:absolute before:inset-0 before:bg-blue-6/10 before:rounded-xl" />
1018
- ```
1019
-
1020
- ### Input with caret and focus ring
1021
- ```jsx
1022
- <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" />
1023
- ```
1024
-
1025
- ### Print-specific styles
1026
- ```jsx
1027
- <nav className="print:hidden" />
1028
- <article className="print:text-black print:bg-white print:shadow-none" />
1029
- ```
1030
-
1031
- ### Contrast accessibility
1032
- ```jsx
1033
- <button className="bg-blue-6 contrast-more:bg-blue-9 contrast-more:border-2 contrast-more:border-blue-11 text-white">
1034
- Submit
1035
- </button>
1036
- ```
1037
-
1038
- ---
1039
-
1040
- ## Caching
1041
-
1042
- The resolver uses an LRU cache (10,000 entries). Cache is automatically cleared on `updateConfig()`. Manually:
1043
- ```js
1044
- import { clearCache } from '@kbach/ui';
1045
- clearCache();
1046
- ```
1047
-
1048
- ---
1049
-
1050
- ## Package Versions
1051
- - `@kbach/ui`: see `packages/ui/package.json` — the one package for web, React Native, and Expo
1052
- - `@kbach/native`: deprecated, no longer in this repo — frozen at its last published npm version (compatibility shim re-exporting `@kbach/ui`)