@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 +1044 -0
- package/README.md +473 -0
- package/dist/chunk-BPCFICND.mjs +4187 -0
- package/dist/chunk-UE54W6ZG.mjs +208 -0
- package/dist/core/index.d.ts +1346 -0
- package/dist/core/index.js +3712 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +1191 -0
- package/dist/index.mjs +589 -0
- package/dist/jsx-dev-runtime.d.ts +21 -0
- package/dist/jsx-dev-runtime.js +780 -0
- package/dist/jsx-dev-runtime.mjs +21 -0
- package/dist/jsx-runtime.d.ts +17 -0
- package/dist/jsx-runtime.js +779 -0
- package/dist/jsx-runtime.mjs +17 -0
- package/dist/native.d.ts +314 -0
- package/dist/native.js +99 -0
- package/dist/vite-plugin.d.mts +155 -0
- package/dist/vite-plugin.d.ts +155 -0
- package/dist/vite-plugin.js +3939 -0
- package/dist/vite-plugin.mjs +3903 -0
- package/dist/web-substitute-6xH1WxpZ.d.ts +22 -0
- package/jsx-dev-runtime.js +3 -0
- package/jsx-runtime.js +3 -0
- package/kbach-ui.md +889 -0
- package/package.json +104 -0
- package/scripts/postinstall.js +28 -0
- package/src/native/babel/index.js +30 -0
- package/src/native/babel-plugin/index.js +605 -0
- package/src/native/babel-plugin/index.test.ts +86 -0
- package/types.d.ts +1 -0
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`)
|