@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/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# @kbach/ui
|
|
2
|
+
|
|
3
|
+
Tailwind-like utility classes for React (web). Write `className` strings — a custom JSX runtime resolves them at render time. An optional Vite plugin outputs a static `kbach.css` for zero runtime cost.
|
|
4
|
+
|
|
5
|
+
```jsx
|
|
6
|
+
<div className="bg-white dark:bg-gray-10 p-4 rounded-xl shadow" />
|
|
7
|
+
<div className="bg-blue-7 hover:bg-blue-8 dark:bg-indigo-6 rounded-lg px-6 py-3" />
|
|
8
|
+
<div className="group">
|
|
9
|
+
<span className="opacity-0 group-hover:opacity-100 transition" />
|
|
10
|
+
</div>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install @kbach/ui
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
[npm package](https://www.npmjs.com/package/@kbach/ui)
|
|
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/ui" } }
|
|
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 | **[Next.js setup](#nextjs-setup)** — Runtime setup, plus one App Router-specific detail |
|
|
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:
|
|
47
|
+
|
|
48
|
+
**1. Add the plugin:**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// vite.config.ts
|
|
52
|
+
import { defineConfig } from 'vite';
|
|
53
|
+
import { kbach } from '@kbach/ui/vite';
|
|
54
|
+
|
|
55
|
+
export default defineConfig({ plugins: [kbach()] });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**2. Create an empty stylesheet with the markers, and import it once:**
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
/* src/kbach.css */
|
|
62
|
+
/* kbach:start */
|
|
63
|
+
/* kbach:end */
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// main.tsx
|
|
68
|
+
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
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
import { ThemeProvider } from '@kbach/ui';
|
|
77
|
+
|
|
78
|
+
export default function Root() {
|
|
79
|
+
return <ThemeProvider defaultMode="system"><App /></ThemeProvider>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Done. The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection, so there's no double-styling between this and Runtime setup below. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
|
|
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`).
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// vite.config.ts
|
|
91
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
92
|
+
import { defineConfig } from 'vite';
|
|
93
|
+
import { kbach } from '@kbach/ui/vite'; // omit if using Runtime setup instead
|
|
94
|
+
|
|
95
|
+
export default defineConfig({ plugins: [kbach(), reactRouter()] });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
(React Router library mode — `createBrowserRouter`, no SSR — has no such conflict; set it up like any Vite + React app.)
|
|
99
|
+
|
|
100
|
+
## Runtime setup
|
|
101
|
+
|
|
102
|
+
Client-side CSS injection — works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …), no build plugin. Next.js always uses this (see [Next.js setup](#nextjs-setup) below for the one extra detail), or use it on Vite if you'd rather not wire up the plugin yet. This is the whole setup:
|
|
103
|
+
|
|
104
|
+
```jsx
|
|
105
|
+
import { ThemeProvider, KbachReset } from '@kbach/ui';
|
|
106
|
+
|
|
107
|
+
export default function Root() {
|
|
108
|
+
return (
|
|
109
|
+
<ThemeProvider defaultMode="system">
|
|
110
|
+
<KbachReset />
|
|
111
|
+
<App />
|
|
112
|
+
</ThemeProvider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That's it — done. `<KbachReset />` renders the base reset (see [CSS resets](#css-resets)) as real markup instead of waiting on client JS — matters most for SSR, where it avoids a flash of unstyled browser defaults before hydration.
|
|
118
|
+
|
|
119
|
+
Using a custom `kbach.config.js`? Pass it to `ThemeProvider` too — see [Wiring the config in](#wiring-the-config-in--required-for-both-setups).
|
|
120
|
+
|
|
121
|
+
Don't also set up Static CSS above in the same app — pick one.
|
|
122
|
+
|
|
123
|
+
## Next.js setup
|
|
124
|
+
|
|
125
|
+
Next.js is always [Runtime setup](#runtime-setup) above — Static CSS doesn't apply (webpack/Turbopack, not Vite). The `tsconfig.json` step from [Setup](#setup) applies as-is; SWC reads `jsxImportSource` the same way Vite does.
|
|
126
|
+
|
|
127
|
+
The one Next.js-specific detail: render `<KbachReset />` once in the root App Router `layout.tsx` (inside `<head>`, or right after `<ThemeProvider>` opens) so the Server Component HTML has the base reset without waiting on hydration:
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
// app/layout.tsx
|
|
131
|
+
import { ThemeProvider, KbachReset } from '@kbach/ui';
|
|
132
|
+
|
|
133
|
+
export default function RootLayout({ children }) {
|
|
134
|
+
return (
|
|
135
|
+
<html lang="en">
|
|
136
|
+
<body>
|
|
137
|
+
<ThemeProvider defaultMode="system">
|
|
138
|
+
<KbachReset />
|
|
139
|
+
{children}
|
|
140
|
+
</ThemeProvider>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Without `<KbachReset />` there, 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 — a known limitation of the runtime-only path (Static CSS isn't available for webpack/Turbopack), not a per-project bug.
|
|
148
|
+
|
|
149
|
+
`@kbach/ui`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, `<ThemeProvider>`, and `<KbachReset>` directly — no manual `'use client'` wrapper needed anywhere in your own components.
|
|
150
|
+
|
|
151
|
+
## React Native / Expo setup
|
|
152
|
+
|
|
153
|
+
Same `npm install @kbach/ui` — no separate package. Everything below (API, modifiers, color system) is the same import as web; only setup differs.
|
|
154
|
+
|
|
155
|
+
**1. babel.config.js:**
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
module.exports = function (api) {
|
|
159
|
+
api.cache(true);
|
|
160
|
+
return {
|
|
161
|
+
presets: [
|
|
162
|
+
'babel-preset-expo',
|
|
163
|
+
'@kbach/ui/babel',
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
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`. After changing this file, clear the Metro cache: `npx expo start --clear`.
|
|
170
|
+
|
|
171
|
+
**2. Wrap your app:**
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
import { ThemeProvider } from '@kbach/ui/native';
|
|
175
|
+
|
|
176
|
+
export default function App() {
|
|
177
|
+
return (
|
|
178
|
+
<ThemeProvider defaultMode="system">
|
|
179
|
+
<AppContent />
|
|
180
|
+
</ThemeProvider>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This is a native-aware `ThemeProvider` — reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Import it from `@kbach/ui/native`, not the plain `ThemeProvider` from `@kbach/ui` — that one has no automatic RN wiring (colors scheme/window width would need to be passed in by hand).
|
|
186
|
+
|
|
187
|
+
### Platform differences
|
|
188
|
+
|
|
189
|
+
A handful of utilities are native-only or web-only:
|
|
190
|
+
|
|
191
|
+
| | |
|
|
192
|
+
|---|---|
|
|
193
|
+
| Native-only | `tint-{color}` (Image/icon tinting), `perspective-{n}`, `backface-hidden`, `text-shadow`/`text-shadow-lg` |
|
|
194
|
+
| 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-*` |
|
|
195
|
+
|
|
196
|
+
`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).
|
|
197
|
+
|
|
198
|
+
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');`.
|
|
199
|
+
|
|
200
|
+
### Expo Web / React Native Web
|
|
201
|
+
|
|
202
|
+
In a browser (Expo Web, Metro web), `@kbach/ui` switches to the same CSS-class strategy as plain web automatically:
|
|
203
|
+
|
|
204
|
+
- RN components substitute to HTML: `View`/`ScrollView`→`div`, `Text`→`span`, `TextInput`→`input`/`textarea`, `Image`→`img`, `Pressable`/`TouchableOpacity`→`div[role=button]`
|
|
205
|
+
- RN-only props (`onChangeText`, `source`, `secureTextEntry`, …) map to HTML equivalents
|
|
206
|
+
- Register more: `registerWebElement(Animated.View, 'div')`
|
|
207
|
+
- Recommended: use the Vite plugin same as [Static CSS setup](#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
|
|
208
|
+
- 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>`:
|
|
209
|
+
|
|
210
|
+
```jsx
|
|
211
|
+
import { KbachReset } from '@kbach/ui';
|
|
212
|
+
import { ThemeProvider } from '@kbach/ui/native';
|
|
213
|
+
|
|
214
|
+
export default function RootLayout() {
|
|
215
|
+
return (
|
|
216
|
+
<ThemeProvider defaultMode="system">
|
|
217
|
+
<KbachReset />
|
|
218
|
+
<Slot />
|
|
219
|
+
</ThemeProvider>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This ships the base reset as real markup instead of relying solely on the runtime injector.
|
|
225
|
+
|
|
226
|
+
## Dark mode
|
|
227
|
+
|
|
228
|
+
`<ThemeProvider>` powers every `dark:` class — detects OS color scheme, persists the user's choice, re-renders on change.
|
|
229
|
+
|
|
230
|
+
```jsx
|
|
231
|
+
<ThemeProvider
|
|
232
|
+
defaultMode="system" // 'light' | 'dark' | 'system'
|
|
233
|
+
disablePersistence={false} // true = don't remember across reloads
|
|
234
|
+
>
|
|
235
|
+
<App />
|
|
236
|
+
</ThemeProvider>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
| Prop | Type | Default | Description |
|
|
240
|
+
|---|---|---|---|
|
|
241
|
+
| `defaultMode` | `'light' \| 'dark' \| 'system'` | `'system'` | Starting mode |
|
|
242
|
+
| `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` (web) / `AsyncStorage` (native) |
|
|
243
|
+
| `config` | `FrameworkConfig` | global config | Scope a different config to this subtree |
|
|
244
|
+
|
|
245
|
+
`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).
|
|
246
|
+
|
|
247
|
+
## API
|
|
248
|
+
|
|
249
|
+
### className / kb
|
|
250
|
+
|
|
251
|
+
`kb` is an alias for `className` — works on any element.
|
|
252
|
+
|
|
253
|
+
```jsx
|
|
254
|
+
<button className="bg-blue-7 hover:bg-blue-8 pressed:bg-blue-9 rounded-lg px-4 py-2" />
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### styled(Component, classes)
|
|
258
|
+
|
|
259
|
+
```jsx
|
|
260
|
+
import { styled } from '@kbach/ui';
|
|
261
|
+
|
|
262
|
+
const Card = styled('div', 'bg-white dark:bg-gray-9 rounded-2xl p-6 shadow');
|
|
263
|
+
const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3');
|
|
264
|
+
|
|
265
|
+
<Card kb="mt-4">
|
|
266
|
+
<Button kb="w-full">Submit</Button>
|
|
267
|
+
</Card>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Extra classes at use time via `kb` merge with the base classes.
|
|
271
|
+
|
|
272
|
+
### cx(...classes)
|
|
273
|
+
|
|
274
|
+
```jsx
|
|
275
|
+
import { cx } from '@kbach/ui';
|
|
276
|
+
|
|
277
|
+
<div className={cx('p-4 rounded-xl', isSelected && 'border-2 border-blue-6', isDisabled && 'opacity-50')} />
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Falsy values ignored. Also works as pre-built style constants:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
export const container = cx('flex-1 bg-white dark:bg-gray-9 p-4');
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### useStyles(classes, state?)
|
|
287
|
+
|
|
288
|
+
```jsx
|
|
289
|
+
const style = useStyles('bg-blue-6 dark:bg-indigo-6 px-3 py-1 rounded-full');
|
|
290
|
+
const style2 = useStyles('bg-blue-5 pressed:bg-blue-7 rounded-lg', { pressed });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### kb(classes)
|
|
294
|
+
|
|
295
|
+
Resolve outside a component:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### useTheme()
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
| Value | Type | Description |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `mode` | `'light' \| 'dark' \| 'system'` | User-selected mode |
|
|
310
|
+
| `resolvedMode` | `'light' \| 'dark'` | Resolved after system lookup |
|
|
311
|
+
| `isDark` | `boolean` | `resolvedMode === 'dark'` |
|
|
312
|
+
| `setMode` | `fn` | Set mode explicitly |
|
|
313
|
+
| `toggle` | `fn` | Toggle light/dark |
|
|
314
|
+
| `config` | `ResolvedConfig` | Full resolved config |
|
|
315
|
+
|
|
316
|
+
### useIsDark() / useColors()
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
const isDark = useIsDark();
|
|
320
|
+
|
|
321
|
+
const colors = useColors();
|
|
322
|
+
colors.blue[6] // '#3b82f6'
|
|
323
|
+
colors.blue['6/50'] // 'rgba(59,130,246,0.5)'
|
|
324
|
+
colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Typed theme tokens
|
|
328
|
+
|
|
329
|
+
`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.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { useSpacing } from '@kbach/ui';
|
|
333
|
+
|
|
334
|
+
const spacing = useSpacing();
|
|
335
|
+
spacing[4] // 16
|
|
336
|
+
spacing.full // '100%'
|
|
337
|
+
spacing['1/2'] // '50%'
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
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:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { useColors, type DefaultColorName } from '@kbach/ui';
|
|
344
|
+
|
|
345
|
+
const colors = useColors<DefaultColorName | 'brand'>();
|
|
346
|
+
colors.brand[6] // now type-checks
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
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.
|
|
350
|
+
|
|
351
|
+
## Modifiers
|
|
352
|
+
|
|
353
|
+
Chain in any order: `<div className="dark:sm:hover:p-4" />`
|
|
354
|
+
|
|
355
|
+
| Category | Modifiers |
|
|
356
|
+
|---|---|
|
|
357
|
+
| Theme | `dark:` `light:` / `not-dark:` |
|
|
358
|
+
| Interactive | `hover:` `focus:` `pressed:` `active:` `disabled:` `checked:` `visited:` `placeholder:` (all have `not-` variants) |
|
|
359
|
+
| Structural | `first:` `last:` `odd:` `even:` `only:` `focus-within:` `focus-visible:` |
|
|
360
|
+
| Pseudo-elements | `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` |
|
|
361
|
+
| Responsive | `sm:`(576px) `md:`(768px) `lg:`(1024px) `xl:`(1280px) `2xl:`(1536px) |
|
|
362
|
+
| Other | `print:` `landscape:`/`portrait:` `motion-reduce:`/`motion-safe:` `contrast-more:`/`contrast-less:` `rtl:`/`ltr:` `!` (important) |
|
|
363
|
+
|
|
364
|
+
```jsx
|
|
365
|
+
<div className="before:content-['*'] before:text-red-6 relative" />
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Group / peer:**
|
|
369
|
+
|
|
370
|
+
```jsx
|
|
371
|
+
<div className="group">
|
|
372
|
+
<span className="opacity-0 group-hover:opacity-100 transition" />
|
|
373
|
+
</div>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
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.
|
|
377
|
+
|
|
378
|
+
## Arbitrary values
|
|
379
|
+
|
|
380
|
+
```jsx
|
|
381
|
+
<div className="bg-[#6366f1] p-[14px] w-[calc(100%-2rem)] text-[18px]" />
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
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]`.
|
|
385
|
+
|
|
386
|
+
## Color system
|
|
387
|
+
|
|
388
|
+
12-shade scale, 1 lightest → 12 darkest: `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
|
|
389
|
+
|
|
390
|
+
Families: `slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose`
|
|
391
|
+
Special: `transparent` `current` `black` `white`
|
|
392
|
+
Opacity: `bg-blue-6/50` or `bg-blue-6/[0.15]`
|
|
393
|
+
|
|
394
|
+
## CSS resets
|
|
395
|
+
|
|
396
|
+
Included in `kbach.css`, runtime injection, and `<KbachReset />` alike:
|
|
397
|
+
|
|
398
|
+
- Border-box everywhere; `border-*` utilities work without needing `border-solid`
|
|
399
|
+
- `body` margin/padding cleared; headings/`p`/`ul`/`ol`/`a` styling cleared to inherit
|
|
400
|
+
- `img`/`video`/`svg` block + max-width 100%
|
|
401
|
+
- `button`/text inputs/`textarea` stripped of native appearance so `bg-`/`rounded-`/`p-` fully restyle them
|
|
402
|
+
- Checkbox/radio/`select` keep native rendering (just typography/spacing normalized + `accent-color: currentColor`)
|
|
403
|
+
|
|
404
|
+
## Configuration
|
|
405
|
+
|
|
406
|
+
```js
|
|
407
|
+
// kbach.config.js
|
|
408
|
+
module.exports = {
|
|
409
|
+
darkMode: 'attribute', // 'attribute' | 'class' | 'media'
|
|
410
|
+
|
|
411
|
+
theme: {
|
|
412
|
+
colors: { brand: { 1: '#eff6ff', 6: '#3b82f6', 10: '#1e3a5f' } }, // replaces the section
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
extend: {
|
|
416
|
+
colors: { brand: { 6: '#6366f1' } }, // adds to defaults
|
|
417
|
+
spacing: { 18: '72px' },
|
|
418
|
+
screens: { '3xl': '1920px' },
|
|
419
|
+
fontFamily: { sans: 'Inter, sans-serif' },
|
|
420
|
+
keyframes: {
|
|
421
|
+
wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
|
|
422
|
+
},
|
|
423
|
+
animation: { wiggle: 'wiggle 1s ease-in-out infinite' },
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
plugins: [
|
|
427
|
+
({ addUtility, addVariant, theme }) => {
|
|
428
|
+
addUtility('border-brand', { borderColor: theme('colors.brand.6'), borderWidth: 2 });
|
|
429
|
+
addVariant('hocus', ':hover, :focus');
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
};
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- `fontFamily.sans` set to anything but `'System'` auto-injects `body { font-family: … }`
|
|
436
|
+
- Custom `@keyframes` are used as `animate-{name}`, and can be overridden inline: `animate-[wiggle_2s_ease-in-out]`
|
|
437
|
+
- Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
|
|
438
|
+
- Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
|
|
439
|
+
|
|
440
|
+
### Wiring the config in — required for both setups
|
|
441
|
+
|
|
442
|
+
`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:
|
|
443
|
+
|
|
444
|
+
- **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`:
|
|
445
|
+
|
|
446
|
+
```jsx
|
|
447
|
+
import { ThemeProvider } from '@kbach/ui';
|
|
448
|
+
import kbachConfig from '../kbach.config';
|
|
449
|
+
|
|
450
|
+
<ThemeProvider defaultMode="system" config={kbachConfig}>
|
|
451
|
+
<App />
|
|
452
|
+
</ThemeProvider>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
(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.)
|
|
456
|
+
|
|
457
|
+
- **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:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
// vite.config.ts
|
|
461
|
+
import { kbach } from '@kbach/ui/vite';
|
|
462
|
+
import kbachConfig from './kbach.config';
|
|
463
|
+
|
|
464
|
+
export default defineConfig({ plugins: [kbach(kbachConfig)] });
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
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.
|
|
468
|
+
|
|
469
|
+
## Full reference
|
|
470
|
+
|
|
471
|
+
[kbach-ui.md](./kbach-ui.md) — complete utility list, every modifier, all config options, covers web and React Native/Expo.
|
|
472
|
+
|
|
473
|
+
`@kbach/native` is deprecated and no longer maintained — its last published npm version is frozen as a compatibility shim re-exporting this package. Install `@kbach/ui` directly for new projects.
|