@kbach/react 0.6.50 → 1.0.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/README.md +90 -390
- package/dist/chunk-AFY36PRX.mjs +414 -0
- package/dist/chunk-G3RJJNSY.mjs +100 -0
- package/dist/chunk-VBPQAIZL.mjs +32 -0
- package/dist/index.d.mts +117 -0
- package/dist/index.d.ts +84 -295
- package/dist/index.js +618 -1101
- package/dist/index.mjs +143 -550
- package/dist/jsx-dev-runtime.d.mts +22 -0
- package/dist/jsx-dev-runtime.d.ts +6 -5
- package/dist/jsx-dev-runtime.js +453 -695
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.d.mts +22 -0
- package/dist/jsx-runtime.d.ts +11 -6
- package/dist/jsx-runtime.js +455 -706
- package/dist/jsx-runtime.mjs +4 -9
- package/dist/theme-BKAb78KP.d.mts +20 -0
- package/dist/theme-BKAb78KP.d.ts +20 -0
- package/dist/vite-plugin.d.mts +20 -147
- package/dist/vite-plugin.d.ts +20 -147
- package/dist/vite-plugin.js +1380 -3722
- package/dist/vite-plugin.mjs +994 -3713
- package/package.json +20 -46
- package/KBACH.md +0 -1044
- package/dist/chunk-KRRGLJLA.mjs +0 -4187
- package/dist/chunk-LIUJPOTS.mjs +0 -208
- package/dist/core/index.d.ts +0 -1346
- package/dist/core/index.js +0 -3712
- package/dist/native.d.ts +0 -314
- package/dist/native.js +0 -99
- package/dist/web-substitute-CfpYFnE7.d.ts +0 -22
- package/jsx-dev-runtime.js +0 -3
- package/jsx-runtime.js +0 -3
- package/kbach-react.md +0 -889
- package/scripts/postinstall.js +0 -28
- package/src/native/babel/index.js +0 -30
- package/src/native/babel-plugin/index.js +0 -605
- package/src/native/babel-plugin/index.test.ts +0 -86
- package/types.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,61 +1,55 @@
|
|
|
1
1
|
# @kbach/react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
A utility-first styling engine for React, powered by a real Rust/WASM core.
|
|
4
|
+
Kbach aims for full [Tailwind CSS v4](https://tailwindcss.com) feature
|
|
5
|
+
parity — grid, transforms, filters, gradients, animations, container
|
|
6
|
+
queries, arbitrary values, and a parameterized variant system (`has-*`,
|
|
7
|
+
`data-*`, `aria-*`, `not-*`, generalized `group-*`/`peer-*`, and more) — with
|
|
8
|
+
its own 22-family + metals color palette as the one deliberate difference.
|
|
9
|
+
|
|
10
|
+
> **Beta.** The API is close to stable but may still change before `1.0.0`.
|
|
11
|
+
> Feedback and issues are very welcome.
|
|
12
|
+
|
|
13
|
+
## Why Kbach
|
|
14
|
+
|
|
15
|
+
- **A real engine, not a lookup table.** Every class is resolved by the same
|
|
16
|
+
Rust/WASM core at build time (via the Vite plugin) and at runtime (via
|
|
17
|
+
`kb()`), so build-time and runtime output can never drift apart.
|
|
18
|
+
- **Static by default.** The Vite plugin scans your source and writes a real,
|
|
19
|
+
plain `kbach.css` file — no runtime style injection needed, no
|
|
20
|
+
flash-of-unstyled-content, no client-side CSS-in-JS overhead.
|
|
21
|
+
- **Dark mode that works without a provider.** Toggle/set/subscribe to dark
|
|
22
|
+
mode from anywhere — a plain function, an event handler, a component that
|
|
23
|
+
never wants React Context — with an optional `<ThemeProvider>` as a thin
|
|
24
|
+
wrapper around the exact same store.
|
|
25
|
+
- **Composable by nature.** Utility class strings are just strings — combine
|
|
26
|
+
them with plain arrays, ternaries, or template literals. No bespoke
|
|
27
|
+
composition API required.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```sh
|
|
16
32
|
npm install @kbach/react
|
|
17
33
|
```
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Setup
|
|
22
|
-
|
|
23
|
-
One step is always required, then pick **one** of the two setups below — they're independent, don't mix them.
|
|
24
|
-
|
|
25
|
-
### Step 1 — JSX runtime (always required)
|
|
26
|
-
|
|
27
|
-
**tsconfig.json:**
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/react" } }
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
That's the only setting needed — Vite, Next.js, and React Router all read it. Don't *also* set `jsxImportSource` on a bundler plugin (e.g. `@vitejs/plugin-react`); one source of truth avoids conflicts.
|
|
34
|
-
|
|
35
|
-
### Which setup do I need?
|
|
36
|
-
|
|
37
|
-
| Framework | Use |
|
|
38
|
-
|---|---|
|
|
39
|
-
| Vite, React Router library mode, CRA, other Vite-based | **[Static CSS setup](#static-css-setup)** (recommended) — zero runtime cost, catches typos at build time. [Runtime setup](#runtime-setup) is there if you'd rather skip the plugin for now. |
|
|
40
|
-
| React Router, framework mode | **[Static CSS setup](#static-css-setup)** — and skip `@vitejs/plugin-react`, see note in that section |
|
|
41
|
-
| Next.js | **[Runtime setup](#runtime-setup)** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
|
|
42
|
-
| React Native, Expo | **[React Native / Expo setup](#react-native--expo-setup)** — different setup entirely (Babel preset, not the JSX runtime step above) |
|
|
43
|
-
|
|
44
|
-
## Static CSS setup
|
|
45
|
-
|
|
46
|
-
Vite only, and the recommended setup for any Vite-based app — a build-time plugin writes real CSS into a file you import at build time, so nothing is generated client-side and there's zero runtime cost. Three pieces, all required:
|
|
35
|
+
## Quick start
|
|
47
36
|
|
|
48
|
-
|
|
37
|
+
Add the Vite plugin so your utility classes are resolved to real CSS at
|
|
38
|
+
build time:
|
|
49
39
|
|
|
50
40
|
```ts
|
|
51
41
|
// vite.config.ts
|
|
52
42
|
import { defineConfig } from 'vite';
|
|
43
|
+
import react from '@vitejs/plugin-react';
|
|
53
44
|
import { kbach } from '@kbach/react/vite';
|
|
54
45
|
|
|
55
|
-
export default defineConfig({
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
plugins: [kbach(), react()],
|
|
48
|
+
});
|
|
56
49
|
```
|
|
57
50
|
|
|
58
|
-
|
|
51
|
+
Create `src/kbach.css` with the markers the plugin writes between, and
|
|
52
|
+
import it once:
|
|
59
53
|
|
|
60
54
|
```css
|
|
61
55
|
/* src/kbach.css */
|
|
@@ -63,383 +57,89 @@ export default defineConfig({ plugins: [kbach()] });
|
|
|
63
57
|
/* kbach:end */
|
|
64
58
|
```
|
|
65
59
|
|
|
66
|
-
```
|
|
60
|
+
```tsx
|
|
67
61
|
// main.tsx
|
|
62
|
+
import { initKbach } from '@kbach/react';
|
|
63
|
+
import { createRoot } from 'react-dom/client';
|
|
64
|
+
import App from './App';
|
|
68
65
|
import './kbach.css';
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
This import is what actually switches the app over to Static CSS — the plugin alone only generates the file; without importing it, runtime injection stays active and you get both at once.
|
|
72
|
-
|
|
73
|
-
**3. Wrap your app — no `<KbachReset />` here, `kbach.css` already includes the reset:**
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export default function Root() {
|
|
79
|
-
return <ThemeProvider defaultMode="system"><App /></ThemeProvider>;
|
|
80
|
-
}
|
|
67
|
+
initKbach().then(() => {
|
|
68
|
+
createRoot(document.getElementById('root')!).render(<App />);
|
|
69
|
+
});
|
|
81
70
|
```
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Using a custom `kbach.config.js`? It needs to be wired in **twice** here — once to `kbach()` above (step 1) so the generated CSS reflects it, and once to `ThemeProvider` (step 3) so dark mode/`useColors()`/animations do too. See [Wiring the config in](#wiring-the-config-in--required-for-both-setups) — easy to only do one and have the other silently fall back to defaults.
|
|
86
|
-
|
|
87
|
-
**React Router framework mode:** don't add `@vitejs/plugin-react` — `reactRouter()` already provides JSX handling, and both together crash the page (`Identifier 'RefreshRuntime' has already been declared`).
|
|
72
|
+
Then just use utility classes:
|
|
88
73
|
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
import { kbach } from '@kbach/react/vite'; // omit if using Runtime setup instead
|
|
94
|
-
|
|
95
|
-
export default defineConfig({ plugins: [kbach(), reactRouter()] });
|
|
74
|
+
```tsx
|
|
75
|
+
<div className="flex items-center gap-4 bg-blue-6 text-white p-4 rounded-lg">
|
|
76
|
+
Hello Kbach
|
|
77
|
+
</div>
|
|
96
78
|
```
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## Runtime setup
|
|
80
|
+
## Dark mode
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
Works standalone, with zero provider:
|
|
103
83
|
|
|
104
|
-
```
|
|
105
|
-
import {
|
|
84
|
+
```tsx
|
|
85
|
+
import { useGlobalDarkMode, toggleGlobalDarkMode } from '@kbach/react';
|
|
106
86
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<KbachReset />
|
|
111
|
-
<App />
|
|
112
|
-
</ThemeProvider>
|
|
113
|
-
);
|
|
87
|
+
function ThemeToggle() {
|
|
88
|
+
const isDark = useGlobalDarkMode();
|
|
89
|
+
return <button onClick={toggleGlobalDarkMode}>{isDark ? 'Dark' : 'Light'}</button>;
|
|
114
90
|
}
|
|
115
91
|
```
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
Or opt into the optional `<ThemeProvider>` / `useTheme()` pair — both read
|
|
94
|
+
and write the exact same global store, so they always stay in sync with the
|
|
95
|
+
standalone functions above:
|
|
120
96
|
|
|
121
|
-
|
|
97
|
+
```tsx
|
|
98
|
+
import { ThemeProvider, useTheme } from '@kbach/react';
|
|
122
99
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
**1. babel.config.js:**
|
|
128
|
-
|
|
129
|
-
```js
|
|
130
|
-
module.exports = function (api) {
|
|
131
|
-
api.cache(true);
|
|
132
|
-
return {
|
|
133
|
-
presets: [
|
|
134
|
-
'babel-preset-expo',
|
|
135
|
-
'@kbach/react/babel',
|
|
136
|
-
],
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Or the one-liner helper: `const { createKbachConfig } = require('@kbach/react/native'); module.exports = createKbachConfig();` — identical result. Merging into an existing config: `withKbachBabel({ presets: [...] })`, also from `@kbach/react/native`. After changing this file, clear the Metro cache: `npx expo start --clear`.
|
|
142
|
-
|
|
143
|
-
**2. Wrap your app:**
|
|
144
|
-
|
|
145
|
-
```jsx
|
|
146
|
-
import { ThemeProvider } from '@kbach/react/native';
|
|
147
|
-
|
|
148
|
-
export default function App() {
|
|
149
|
-
return (
|
|
150
|
-
<ThemeProvider defaultMode="system">
|
|
151
|
-
<AppContent />
|
|
152
|
-
</ThemeProvider>
|
|
153
|
-
);
|
|
100
|
+
function Toggle() {
|
|
101
|
+
const { mode, isDark, setMode, toggle } = useTheme();
|
|
102
|
+
return <button onClick={toggle}>{mode}</button>;
|
|
154
103
|
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
This is a native-aware `ThemeProvider` — reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Import it from `@kbach/react/native`, not the plain `ThemeProvider` from `@kbach/react` — that one has no automatic RN wiring (colors scheme/window width would need to be passed in by hand).
|
|
158
|
-
|
|
159
|
-
### Platform differences
|
|
160
|
-
|
|
161
|
-
A handful of utilities are native-only or web-only:
|
|
162
|
-
|
|
163
|
-
| | |
|
|
164
|
-
|---|---|
|
|
165
|
-
| Native-only | `tint-{color}` (Image/icon tinting), `perspective-{n}`, `backface-hidden`, `text-shadow`/`text-shadow-lg` |
|
|
166
|
-
| Web-only, ignored on native (no warning) | `caret-*` `accent-*` `stroke-*` `fill-*` `touch-*` `float-*` `clear-*` `line-clamp-*` `scroll-*` `animate-*` `transition` `filter` `backdrop-filter` `print:` `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` `landscape:` `portrait:` `motion-reduce:` `motion-safe:` `contrast-more:` `contrast-less:` `rtl:` `ltr:` `grid` `grid-cols-*` `ring-offset-*` `outline-*` `cursor-*` `bg-gradient-*` |
|
|
167
|
-
|
|
168
|
-
`ring`/`ring-{n}`/`ring-{color}` is a partial exception — RN has no box-shadow, so it falls back to `borderWidth`/`borderColor`, which *does* affect layout and shares properties with `border-*` (whichever class comes last wins if you combine both).
|
|
169
|
-
|
|
170
|
-
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');`.
|
|
171
|
-
|
|
172
|
-
### Expo Web / React Native Web
|
|
173
|
-
|
|
174
|
-
In a browser (Expo Web, Metro web), `@kbach/react` switches to the same CSS-class strategy as plain web automatically:
|
|
175
|
-
|
|
176
|
-
- RN components substitute to HTML: `View`/`ScrollView`→`div`, `Text`→`span`, `TextInput`→`input`/`textarea`, `Image`→`img`, `Pressable`/`TouchableOpacity`→`div[role=button]`
|
|
177
|
-
- RN-only props (`onChangeText`, `source`, `secureTextEntry`, …) map to HTML equivalents
|
|
178
|
-
- Register more: `registerWebElement(Animated.View, 'div')`
|
|
179
|
-
- Recommended: use the Vite plugin same as [Static CSS setup](#static-css-setup) above — `import { kbach } from '@kbach/react/vite'` — and import `kbach.css` in your entry file, for zero runtime cost on the web target too
|
|
180
|
-
- Not using the Vite plugin (the common case for Expo/Metro web, which has no Vite build step)? Render `<KbachReset />` once near your root — e.g. Expo Router's root `app/_layout.tsx`, inside `<ThemeProvider>`:
|
|
181
|
-
|
|
182
|
-
```jsx
|
|
183
|
-
import { KbachReset } from '@kbach/react';
|
|
184
|
-
import { ThemeProvider } from '@kbach/react/native';
|
|
185
|
-
|
|
186
|
-
export default function RootLayout() {
|
|
187
|
-
return (
|
|
188
|
-
<ThemeProvider defaultMode="system">
|
|
189
|
-
<KbachReset />
|
|
190
|
-
<Slot />
|
|
191
|
-
</ThemeProvider>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
This ships the base reset as real markup instead of relying solely on the runtime injector.
|
|
197
|
-
|
|
198
|
-
## Dark mode
|
|
199
|
-
|
|
200
|
-
`<ThemeProvider>` powers every `dark:` class — detects OS color scheme, persists the user's choice, re-renders on change.
|
|
201
|
-
|
|
202
|
-
```jsx
|
|
203
|
-
<ThemeProvider
|
|
204
|
-
defaultMode="system" // 'light' | 'dark' | 'system'
|
|
205
|
-
disablePersistence={false} // true = don't remember across reloads
|
|
206
|
-
>
|
|
207
|
-
<App />
|
|
208
|
-
</ThemeProvider>
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
| Prop | Type | Default | Description |
|
|
212
|
-
|---|---|---|---|
|
|
213
|
-
| `defaultMode` | `'light' \| 'dark' \| 'system'` | `'system'` | Starting mode |
|
|
214
|
-
| `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` (web) / `AsyncStorage` (native) |
|
|
215
|
-
| `config` | `FrameworkConfig` | global config | Scope a different config to this subtree |
|
|
216
|
-
|
|
217
|
-
`darkMode` in `kbach.config.js` picks the matching strategy: `'attribute'` (default), `'class'`, or `'media'` (system-only). Toggle it with `useTheme()`'s `toggle()`/`setMode()` — see [API](#api).
|
|
218
|
-
|
|
219
|
-
## API
|
|
220
|
-
|
|
221
|
-
### className / kb
|
|
222
|
-
|
|
223
|
-
`kb` is an alias for `className` — works on any element.
|
|
224
|
-
|
|
225
|
-
```jsx
|
|
226
|
-
<button className="bg-blue-7 hover:bg-blue-8 pressed:bg-blue-9 rounded-lg px-4 py-2" />
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### styled(Component, classes)
|
|
230
|
-
|
|
231
|
-
```jsx
|
|
232
|
-
import { styled } from '@kbach/react';
|
|
233
104
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
<Card kb="mt-4">
|
|
238
|
-
<Button kb="w-full">Submit</Button>
|
|
239
|
-
</Card>
|
|
105
|
+
<ThemeProvider defaultMode="system">
|
|
106
|
+
<Toggle />
|
|
107
|
+
</ThemeProvider>;
|
|
240
108
|
```
|
|
241
109
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
### cx(...classes)
|
|
110
|
+
## Theming
|
|
245
111
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Falsy values ignored. Also works as pre-built style constants:
|
|
112
|
+
Customize the color palette, spacing scale, breakpoints, and dark-mode
|
|
113
|
+
strategy via `setTheme()` (call once, before your first render) — the same
|
|
114
|
+
theme object is used by both the runtime and the Vite plugin, so the two
|
|
115
|
+
never fall out of sync:
|
|
253
116
|
|
|
254
117
|
```ts
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### useStyles(classes, state?)
|
|
259
|
-
|
|
260
|
-
```jsx
|
|
261
|
-
const style = useStyles('bg-blue-6 dark:bg-indigo-6 px-3 py-1 rounded-full');
|
|
262
|
-
const style2 = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### kb(classes)
|
|
266
|
-
|
|
267
|
-
Resolve outside a component:
|
|
268
|
-
|
|
269
|
-
```js
|
|
270
|
-
const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### useTheme()
|
|
118
|
+
import { setTheme, defaultTheme } from '@kbach/react';
|
|
274
119
|
|
|
275
|
-
|
|
276
|
-
|
|
120
|
+
setTheme({
|
|
121
|
+
...defaultTheme,
|
|
122
|
+
colors: { ...defaultTheme.colors, brand: '#ff6b35' },
|
|
123
|
+
});
|
|
277
124
|
```
|
|
278
125
|
|
|
279
|
-
|
|
280
|
-
|---|---|---|
|
|
281
|
-
| `mode` | `'light' \| 'dark' \| 'system'` | User-selected mode |
|
|
282
|
-
| `resolvedMode` | `'light' \| 'dark'` | Resolved after system lookup |
|
|
283
|
-
| `isDark` | `boolean` | `resolvedMode === 'dark'` |
|
|
284
|
-
| `setMode` | `fn` | Set mode explicitly |
|
|
285
|
-
| `toggle` | `fn` | Toggle light/dark |
|
|
286
|
-
| `config` | `ResolvedConfig` | Full resolved config |
|
|
126
|
+
## Runtime & dynamic classes
|
|
287
127
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const colors = useColors();
|
|
294
|
-
colors.blue[6] // '#3b82f6'
|
|
295
|
-
colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
|
|
296
|
-
colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Typed theme tokens
|
|
300
|
-
|
|
301
|
-
`useColors()` and `useSpacing()` are typed against the built-in theme by default (`DefaultColorName`/`DefaultSpacingKey`), so TypeScript autocompletes real color/spacing names and flags a typo (`colors.blu`, `spacing.ful`) as an error — no setup needed if you're on the default theme.
|
|
302
|
-
|
|
303
|
-
```ts
|
|
304
|
-
import { useSpacing } from '@kbach/react';
|
|
305
|
-
|
|
306
|
-
const spacing = useSpacing();
|
|
307
|
-
spacing[4] // 16
|
|
308
|
-
spacing.full // '100%'
|
|
309
|
-
spacing['1/2'] // '50%'
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
A customized `kbach.config.js` isn't visible to TypeScript — it's a plain `.js` file loaded at runtime, not a statically-analyzed module — so a project with extra colors or spacing keys needs to widen the type parameter by hand:
|
|
128
|
+
Every class name that appears literally in your source is resolved at build
|
|
129
|
+
time. For a class name assembled from truly dynamic data (e.g. user input)
|
|
130
|
+
with no static occurrence anywhere in your source, call `kb()` — it resolves
|
|
131
|
+
and injects the CSS at runtime and returns the class name unchanged:
|
|
313
132
|
|
|
314
133
|
```ts
|
|
315
|
-
import {
|
|
316
|
-
|
|
317
|
-
const colors = useColors<DefaultColorName | 'brand'>();
|
|
318
|
-
colors.brand[6] // now type-checks
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
This only affects the exported *types* — `useColors()`/`useSpacing()` called with no type argument behave exactly as before at runtime. If existing code was relying on a color/spacing name TypeScript couldn't previously catch (the old types had a blanket `[key: string]: any`), this may surface a new type error — the fix is the escape-hatch pattern above, not a code change.
|
|
322
|
-
|
|
323
|
-
## Modifiers
|
|
324
|
-
|
|
325
|
-
Chain in any order: `<div className="dark:sm:hover:p-4" />`
|
|
326
|
-
|
|
327
|
-
| Category | Modifiers |
|
|
328
|
-
|---|---|
|
|
329
|
-
| Theme | `dark:` `light:` / `not-dark:` |
|
|
330
|
-
| Interactive | `hover:` `focus:` `pressed:` `active:` `disabled:` `checked:` `visited:` `placeholder:` (all have `not-` variants) |
|
|
331
|
-
| Structural | `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:` |
|
|
332
|
-
| Pseudo-elements | `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` |
|
|
333
|
-
| Responsive | `sm:`(576px) `md:`(768px) `lg:`(1024px) `xl:`(1280px) `2xl:`(1536px) |
|
|
334
|
-
| Other | `print:` `landscape:`/`portrait:` `motion-reduce:`/`motion-safe:` `contrast-more:`/`contrast-less:` `rtl:`/`ltr:` `!` (important) |
|
|
134
|
+
import { kb } from '@kbach/react';
|
|
335
135
|
|
|
336
|
-
|
|
337
|
-
<div className="before:content-['*'] before:text-red-6 relative" />
|
|
136
|
+
const className = kb(`bg-${userColor}-6`);
|
|
338
137
|
```
|
|
339
138
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<div className="group">
|
|
344
|
-
<span className="opacity-0 group-hover:opacity-100 transition" />
|
|
345
|
-
</div>
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Nested groups need names (`group/card`, `group-hover/card:`) or the inner element reacts to whichever `.group` is nearest, not necessarily the one you meant.
|
|
349
|
-
|
|
350
|
-
## Arbitrary values
|
|
351
|
-
|
|
352
|
-
```jsx
|
|
353
|
-
<div className="bg-[#6366f1] p-[14px] w-[calc(100%-2rem)] text-[18px]" />
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
For a property with no named utility: `[property:value]` — e.g. `[mask-type:luminance]`, `[--my-var:10px]`. Underscores become spaces: `[background:url(/a.png)_no-repeat]`.
|
|
357
|
-
|
|
358
|
-
## Color system
|
|
359
|
-
|
|
360
|
-
12-shade scale, 1 lightest → 12 darkest: `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
|
|
361
|
-
|
|
362
|
-
Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
|
|
363
|
-
Special: `transparent` `current` `black` `white`
|
|
364
|
-
Opacity: `bg-blue-6/50` or `bg-blue-6/[0.15]`
|
|
365
|
-
|
|
366
|
-
## CSS resets
|
|
367
|
-
|
|
368
|
-
Included in `kbach.css`, runtime injection, and `<KbachReset />` alike:
|
|
369
|
-
|
|
370
|
-
- Border-box everywhere; `border-*` utilities work without needing `border-solid`
|
|
371
|
-
- `body` margin/padding cleared; headings/`p`/`ul`/`ol`/`a` styling cleared to inherit
|
|
372
|
-
- `img`/`video`/`svg` block + max-width 100%
|
|
373
|
-
- `button`/text inputs/`textarea` stripped of native appearance so `bg-`/`rounded-`/`p-` fully restyle them
|
|
374
|
-
- Checkbox/radio/`select` keep native rendering (just typography/spacing normalized + `accent-color: currentColor`)
|
|
375
|
-
|
|
376
|
-
## Configuration
|
|
377
|
-
|
|
378
|
-
```js
|
|
379
|
-
// kbach.config.js
|
|
380
|
-
module.exports = {
|
|
381
|
-
darkMode: 'attribute', // 'attribute' | 'class' | 'media'
|
|
382
|
-
|
|
383
|
-
theme: {
|
|
384
|
-
colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } }, // replaces the section
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
extend: {
|
|
388
|
-
colors: { brand: { 6: '#6366f1' } }, // adds to defaults
|
|
389
|
-
spacing: { 18: '72px' },
|
|
390
|
-
screens: { '3xl': '1920px' },
|
|
391
|
-
fontFamily: { sans: 'Inter, sans-serif' },
|
|
392
|
-
keyframes: {
|
|
393
|
-
wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
|
|
394
|
-
},
|
|
395
|
-
animation: { wiggle: 'wiggle 1s ease-in-out infinite' },
|
|
396
|
-
},
|
|
397
|
-
|
|
398
|
-
plugins: [
|
|
399
|
-
({ addUtility, addVariant, theme }) => {
|
|
400
|
-
addUtility('border-brand', { borderColor: theme('colors.brand.6'), borderWidth: 2 });
|
|
401
|
-
addVariant('hocus', ':hover, :focus');
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
};
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
- `fontFamily.sans` set to anything but `'System'` auto-injects `body { font-family: … }`
|
|
408
|
-
- Custom `@keyframes` are used as `animate-{name}`, and can be overridden inline: `animate-[wiggle_2s_ease-in-out]`
|
|
409
|
-
- Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
|
|
410
|
-
- Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
|
|
411
|
-
|
|
412
|
-
### Wiring the config in — required for both setups
|
|
413
|
-
|
|
414
|
-
`kbach.config.js` isn't picked up automatically. It has to be imported and passed in explicitly, and **where** depends on which of the two things it affects:
|
|
415
|
-
|
|
416
|
-
- **Runtime** — dark mode strategy, `useColors()`, custom `@keyframes`/`animation`, and (for SSR) the default-font fallback rendered before your stylesheet takes over. Needed by **both** [Runtime setup](#runtime-setup) and [Static CSS setup](#static-css-setup) — pass it to `ThemeProvider`:
|
|
417
|
-
|
|
418
|
-
```jsx
|
|
419
|
-
import { ThemeProvider } from '@kbach/react';
|
|
420
|
-
import kbachConfig from '../kbach.config';
|
|
421
|
-
|
|
422
|
-
<ThemeProvider defaultMode="system" config={kbachConfig}>
|
|
423
|
-
<App />
|
|
424
|
-
</ThemeProvider>
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
(Equivalent to calling `updateConfig(kbachConfig)` once before anything renders — `ThemeProvider`'s `config` prop does this for you and keeps the global store in sync if it ever changes.)
|
|
428
|
-
|
|
429
|
-
- **Build-time** — what the Vite plugin actually scans your source against and generates CSS for. Only relevant to [Static CSS setup](#static-css-setup) — pass it to the plugin itself:
|
|
430
|
-
|
|
431
|
-
```ts
|
|
432
|
-
// vite.config.ts
|
|
433
|
-
import { kbach } from '@kbach/react/vite';
|
|
434
|
-
import kbachConfig from './kbach.config';
|
|
435
|
-
|
|
436
|
-
export default defineConfig({ plugins: [kbach(kbachConfig)] });
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
Skipping the runtime one is an easy mistake under Static CSS setup specifically — the generated `kbach.css` will look correct (it *does* have your customizations baked in), while dark mode, `useColors()`, and custom animations silently fall back to Kbach's defaults instead of your config, since nothing ever told the running app what you'd customized.
|
|
440
|
-
|
|
441
|
-
## Full reference
|
|
139
|
+
If you're using the static build (the default with the Vite plugin), prefer
|
|
140
|
+
a `safelist` entry in the plugin config for known-but-dynamic values instead
|
|
141
|
+
— it keeps everything static with zero runtime cost.
|
|
442
142
|
|
|
443
|
-
|
|
143
|
+
## License
|
|
444
144
|
|
|
445
|
-
|
|
145
|
+
MIT
|