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