kern-ui 0.1.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/LICENSE +21 -0
- package/README.md +1078 -0
- package/dist/kern.css +705 -0
- package/dist/kern.js +529 -0
- package/dist/kern.min.css +1 -0
- package/dist/kern.min.js +17 -0
- package/package.json +43 -0
- package/src/base/index.css +3 -0
- package/src/base/reset.css +9 -0
- package/src/base/typography.css +26 -0
- package/src/components/accordion.css +21 -0
- package/src/components/alert.css +13 -0
- package/src/components/avatar.css +9 -0
- package/src/components/badge.css +18 -0
- package/src/components/breadcrumb.css +8 -0
- package/src/components/button.css +43 -0
- package/src/components/card.css +13 -0
- package/src/components/dialog.css +19 -0
- package/src/components/drawer.css +12 -0
- package/src/components/dropdown.css +22 -0
- package/src/components/form.css +53 -0
- package/src/components/index.css +22 -0
- package/src/components/pagination.css +6 -0
- package/src/components/progress.css +15 -0
- package/src/components/sidebar.css +16 -0
- package/src/components/skeleton.css +8 -0
- package/src/components/stepper.css +12 -0
- package/src/components/switch.css +11 -0
- package/src/components/table.css +17 -0
- package/src/components/tabs.css +20 -0
- package/src/components/toast.css +31 -0
- package/src/components/tooltip.css +31 -0
- package/src/kern.css +14 -0
- package/src/kern.js +427 -0
- package/src/layout/divider.css +5 -0
- package/src/layout/grid.css +12 -0
- package/src/layout/index.css +4 -0
- package/src/layout/stack.css +13 -0
- package/src/tokens/accent.css +20 -0
- package/src/tokens/colors.css +37 -0
- package/src/tokens/components.css +17 -0
- package/src/tokens/index.css +10 -0
- package/src/tokens/motion.css +12 -0
- package/src/tokens/radius.css +20 -0
- package/src/tokens/shadow.css +25 -0
- package/src/tokens/spacing.css +13 -0
- package/src/tokens/typography.css +10 -0
- package/src/tokens/z-index.css +9 -0
- package/src/utils/helpers.css +13 -0
- package/src/utils/index.css +4 -0
- package/src/utils/keyframes.css +11 -0
- package/src/utils/responsive.css +8 -0
- package/src-js/api.js +103 -0
- package/src-js/behaviors/accordion.js +51 -0
- package/src-js/behaviors/table-sort.js +61 -0
- package/src-js/behaviors/toggle.js +27 -0
- package/src-js/boot.js +37 -0
- package/src-js/components/dialog.js +72 -0
- package/src-js/components/drawer.js +60 -0
- package/src-js/components/dropdown.js +88 -0
- package/src-js/components/tabs.js +92 -0
- package/src-js/components/toaster.js +89 -0
- package/src-js/kern.js +34 -0
- package/src-js/utils.js +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
# kern ui
|
|
2
|
+
|
|
3
|
+
**Attribute-driven, zero-build UI library.**
|
|
4
|
+
Configure in HTML. Theme in CSS. Control in JS. No build step. Ever.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<link rel="stylesheet" href="https://raw.githubusercontent.com/AnandPilania/kern-ui/refs/heads/main/dist/kern.min.css">
|
|
8
|
+
<script src="https://raw.githubusercontent.com/AnandPilania/kern-ui/refs/heads/main/dist/kern.min.js"></script>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**41 KB CSS · 12 KB JS — 7 KB + 3 KB gzipped — Zero dependencies**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Why Kern](#why-kern)
|
|
18
|
+
- [Quick start](#quick-start)
|
|
19
|
+
- [Theming](#theming)
|
|
20
|
+
- [Components A–Z](#components)
|
|
21
|
+
- [Layout utilities](#layout)
|
|
22
|
+
- [JavaScript API](#javascript-api)
|
|
23
|
+
- [Modular imports](#modular-imports)
|
|
24
|
+
- [Project structure](#project-structure)
|
|
25
|
+
- [Browser support](#browser-support)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why Kern
|
|
30
|
+
|
|
31
|
+
Most UI libraries demand a build step, a framework, or hundreds of KB of JS. Kern does not.
|
|
32
|
+
|
|
33
|
+
- **Attributes, not classes.** `<button data-variant="outline">` instead of `class="btn btn-outline btn-md"`.
|
|
34
|
+
- **Zero build step.** Two files via CDN. Works with Rails, Laravel, Django, plain HTML — anything.
|
|
35
|
+
- **Full dark mode.** One attribute on `<html>`. No per-element class toggling.
|
|
36
|
+
- **HSL accent system.** Change one CSS variable to repaint your entire UI.
|
|
37
|
+
- **Modular by design.** Import only the CSS and JS you need. ESM tree-shakeable.
|
|
38
|
+
- **Accessible out of the box.** ARIA attributes, keyboard navigation, and focus trapping built in.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
### CDN (simplest)
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<!DOCTYPE html>
|
|
48
|
+
<html lang="en" data-theme="dark" data-accent="violet">
|
|
49
|
+
<head>
|
|
50
|
+
<link rel="stylesheet" href="https://raw.githubusercontent.com/AnandPilania/kern-ui/refs/heads/main/dist/kern.min.css">
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<button>Primary</button>
|
|
54
|
+
<button data-variant="outline">Outline</button>
|
|
55
|
+
|
|
56
|
+
<kern-dropdown>
|
|
57
|
+
<button data-dropdown-trigger>Options ▾</button>
|
|
58
|
+
<div data-dropdown-content>
|
|
59
|
+
<a data-dropdown-item href="#">Edit</a>
|
|
60
|
+
<a data-dropdown-item data-color="danger" href="#">Delete</a>
|
|
61
|
+
</div>
|
|
62
|
+
</kern-dropdown>
|
|
63
|
+
|
|
64
|
+
<script src="https://raw.githubusercontent.com/AnandPilania/kern-ui/refs/heads/main/dist/kern.min.js"></script>
|
|
65
|
+
<script>
|
|
66
|
+
Kern.toast({ title: 'Hello!', message: 'Kern is ready.', color: 'success' });
|
|
67
|
+
</script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### npm
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install kern-ui
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import 'kern-ui'; // full library
|
|
80
|
+
import { KernDialog } from 'kern-ui/components/dialog.js'; // single component
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
@import 'kern-ui/dist/kern.css'; /* full CSS */
|
|
85
|
+
@import 'kern-ui/src/components/button.css'; /* single component */
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Theming
|
|
91
|
+
|
|
92
|
+
### Dark / light mode
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<html data-theme="dark"> <!-- force dark -->
|
|
96
|
+
<html data-theme="light"> <!-- force light -->
|
|
97
|
+
<html> <!-- system default (prefers-color-scheme) -->
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
Kern.setTheme('dark'); // sets attribute + persists to localStorage
|
|
102
|
+
Kern.setTheme('light');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Accent colors
|
|
106
|
+
|
|
107
|
+
Six built-in presets. One attribute change repaints every interactive element.
|
|
108
|
+
|
|
109
|
+
| Value | Hue |
|
|
110
|
+
| -------- | ----------------- |
|
|
111
|
+
| `amber` | 38° warm gold |
|
|
112
|
+
| `blue` | 214° classic blue |
|
|
113
|
+
| `violet` | 262° purple |
|
|
114
|
+
| `green` | 142° emerald |
|
|
115
|
+
| `rose` | 346° pink-red |
|
|
116
|
+
| `cyan` | 192° teal |
|
|
117
|
+
| `orange` | 24° orange |
|
|
118
|
+
| `red` | 4° red |
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<html data-accent="violet">
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
Kern.setAccent('violet');
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Custom accent — one CSS variable change:**
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
:root {
|
|
132
|
+
--k-accent-h: 220; /* hue: 0–360 */
|
|
133
|
+
--k-accent-s: 85%;
|
|
134
|
+
--k-accent-l: 52%;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Border radius presets
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<html data-radius="none"> <!-- 0px — sharp everywhere -->
|
|
142
|
+
<html data-radius="sharp"> <!-- 2px — subtle rounding -->
|
|
143
|
+
<!-- no attribute --> <!-- 6px — default -->
|
|
144
|
+
<html data-radius="round"> <!-- 12px — soft corners -->
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
Kern.setRadius('round');
|
|
149
|
+
Kern.setRadius('default'); // removes the attribute
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Key CSS custom properties
|
|
153
|
+
|
|
154
|
+
Override any token at `:root` or scoped to a selector:
|
|
155
|
+
|
|
156
|
+
```css
|
|
157
|
+
:root {
|
|
158
|
+
/* Fonts */
|
|
159
|
+
--k-font-sans: 'Inter', sans-serif;
|
|
160
|
+
--k-font-mono: 'JetBrains Mono', monospace;
|
|
161
|
+
|
|
162
|
+
/* Component sizing */
|
|
163
|
+
--k-btn-height: 36px;
|
|
164
|
+
--k-btn-height-sm: 28px;
|
|
165
|
+
--k-btn-height-lg: 44px;
|
|
166
|
+
--k-input-height: 36px;
|
|
167
|
+
--k-card-padding: 1.25rem;
|
|
168
|
+
--k-sidebar-width: 260px;
|
|
169
|
+
|
|
170
|
+
/* Colors (dark mode example) */
|
|
171
|
+
--k-bg: hsl(0, 0%, 5%);
|
|
172
|
+
--k-surface: hsl(0, 0%, 8%);
|
|
173
|
+
--k-surface-2: hsl(0, 0%, 11%);
|
|
174
|
+
--k-text: hsl(0, 0%, 95%);
|
|
175
|
+
--k-text-2: hsl(0, 0%, 65%);
|
|
176
|
+
--k-text-3: hsl(0, 0%, 40%);
|
|
177
|
+
--k-border: hsl(0, 0%, 16%);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Full token reference: [`src/tokens/`](src/tokens/)
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Components
|
|
186
|
+
|
|
187
|
+
### Button
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<!-- Variants -->
|
|
191
|
+
<button>Primary</button>
|
|
192
|
+
<button data-variant="secondary">Secondary</button>
|
|
193
|
+
<button data-variant="outline">Outline</button>
|
|
194
|
+
<button data-variant="ghost">Ghost</button>
|
|
195
|
+
<button data-variant="danger">Danger</button>
|
|
196
|
+
<button data-variant="link">Link</button>
|
|
197
|
+
|
|
198
|
+
<!-- Sizes -->
|
|
199
|
+
<button data-size="sm">Small</button>
|
|
200
|
+
<button>Default</button>
|
|
201
|
+
<button data-size="lg">Large</button>
|
|
202
|
+
<button data-size="icon">★</button>
|
|
203
|
+
|
|
204
|
+
<!-- States -->
|
|
205
|
+
<button data-loading>Saving…</button>
|
|
206
|
+
<button disabled>Disabled</button>
|
|
207
|
+
<button data-color="success">Success</button>
|
|
208
|
+
<button data-color="warning">Warning</button>
|
|
209
|
+
<button data-color="info">Info</button>
|
|
210
|
+
|
|
211
|
+
<!-- Button group -->
|
|
212
|
+
<div role="group">
|
|
213
|
+
<button data-variant="outline">Day</button>
|
|
214
|
+
<button>Week</button>
|
|
215
|
+
<button data-variant="outline">Month</button>
|
|
216
|
+
</div>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Form inputs
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<!-- Field wrapper with label, hint, and validation -->
|
|
225
|
+
<div data-field>
|
|
226
|
+
<label data-required>Email address</label>
|
|
227
|
+
<input type="email" placeholder="you@example.com">
|
|
228
|
+
<span data-hint>We'll never share your email.</span>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- Validation states -->
|
|
232
|
+
<input type="text" data-state="success" value="Valid value">
|
|
233
|
+
<span data-hint data-state="success">Looks good!</span>
|
|
234
|
+
|
|
235
|
+
<input type="text" data-state="error" value="bad@">
|
|
236
|
+
<span data-hint data-state="error">Enter a valid email address.</span>
|
|
237
|
+
|
|
238
|
+
<!-- Sizes: sm | default | lg -->
|
|
239
|
+
<input data-size="sm" type="text" placeholder="Small">
|
|
240
|
+
<input type="text" placeholder="Default">
|
|
241
|
+
<input data-size="lg" type="text" placeholder="Large">
|
|
242
|
+
|
|
243
|
+
<!-- Textarea -->
|
|
244
|
+
<textarea rows="4" placeholder="Write something…"></textarea>
|
|
245
|
+
<textarea disabled>Cannot edit</textarea>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Select
|
|
251
|
+
|
|
252
|
+
```html
|
|
253
|
+
<div data-field>
|
|
254
|
+
<label>Country</label>
|
|
255
|
+
<select>
|
|
256
|
+
<option>United States</option>
|
|
257
|
+
<option>United Kingdom</option>
|
|
258
|
+
<option>Germany</option>
|
|
259
|
+
</select>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<select disabled><option>Locked</option></select>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### Checkbox & Radio
|
|
268
|
+
|
|
269
|
+
```html
|
|
270
|
+
<label><input type="checkbox" checked> Notifications</label>
|
|
271
|
+
<label><input type="checkbox"> Marketing emails</label>
|
|
272
|
+
<label><input type="checkbox" disabled> Disabled</label>
|
|
273
|
+
|
|
274
|
+
<label><input type="radio" name="plan" checked> Starter</label>
|
|
275
|
+
<label><input type="radio" name="plan"> Pro</label>
|
|
276
|
+
<label><input type="radio" name="plan" disabled> Unavailable</label>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### Switch
|
|
282
|
+
|
|
283
|
+
```html
|
|
284
|
+
<label data-switch>
|
|
285
|
+
<input type="checkbox" checked>
|
|
286
|
+
<div data-switch-track></div>
|
|
287
|
+
Dark mode
|
|
288
|
+
</label>
|
|
289
|
+
|
|
290
|
+
<!-- Small -->
|
|
291
|
+
<label data-switch data-size="sm">
|
|
292
|
+
<input type="checkbox">
|
|
293
|
+
<div data-switch-track></div>
|
|
294
|
+
Compact switch
|
|
295
|
+
</label>
|
|
296
|
+
|
|
297
|
+
<!-- Disabled -->
|
|
298
|
+
<label data-switch>
|
|
299
|
+
<input type="checkbox" disabled>
|
|
300
|
+
<div data-switch-track></div>
|
|
301
|
+
<span style="color:var(--k-text-3)">Disabled</span>
|
|
302
|
+
</label>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### Range
|
|
308
|
+
|
|
309
|
+
```html
|
|
310
|
+
<div data-field>
|
|
311
|
+
<label>Volume — 70%</label>
|
|
312
|
+
<input type="range" min="0" max="100" value="70">
|
|
313
|
+
</div>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### Input group
|
|
319
|
+
|
|
320
|
+
```html
|
|
321
|
+
<!-- Prefix -->
|
|
322
|
+
<div data-input-group>
|
|
323
|
+
<span data-addon>https://</span>
|
|
324
|
+
<input type="text" placeholder="yoursite.com">
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Suffix -->
|
|
328
|
+
<div data-input-group>
|
|
329
|
+
<input type="text" placeholder="username">
|
|
330
|
+
<span data-addon>@kern.ui</span>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<!-- Both sides -->
|
|
334
|
+
<div data-input-group>
|
|
335
|
+
<span data-addon>$</span>
|
|
336
|
+
<input type="number" placeholder="0.00">
|
|
337
|
+
<span data-addon>USD</span>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<!-- Button suffix -->
|
|
341
|
+
<div data-input-group>
|
|
342
|
+
<input type="text" placeholder="Search…">
|
|
343
|
+
<button>Search</button>
|
|
344
|
+
</div>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### Card
|
|
350
|
+
|
|
351
|
+
```html
|
|
352
|
+
<!-- Structured slots: header, title, desc, footer -->
|
|
353
|
+
<div data-card>
|
|
354
|
+
<div data-card-header>
|
|
355
|
+
<div>
|
|
356
|
+
<div data-card-title>Card title</div>
|
|
357
|
+
<div data-card-desc>Supporting description.</div>
|
|
358
|
+
</div>
|
|
359
|
+
<span data-badge data-color="accent">New</span>
|
|
360
|
+
</div>
|
|
361
|
+
<p>Card body content.</p>
|
|
362
|
+
<div data-card-footer>
|
|
363
|
+
<button>Confirm</button>
|
|
364
|
+
<button data-variant="ghost">Cancel</button>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<!-- Variants -->
|
|
369
|
+
<div data-card data-variant="flat">…</div> <!-- no shadow, border only -->
|
|
370
|
+
<div data-card data-variant="raised">…</div> <!-- stronger shadow -->
|
|
371
|
+
<div data-card data-variant="inset">…</div> <!-- recessed surface -->
|
|
372
|
+
<div data-card data-variant="ghost">…</div> <!-- transparent, no border -->
|
|
373
|
+
<div data-card data-clickable>…</div> <!-- hover lift + cursor -->
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
### Badge
|
|
379
|
+
|
|
380
|
+
```html
|
|
381
|
+
<!-- Colors: accent | success | warning | danger | info | (default) -->
|
|
382
|
+
<span data-badge>Default</span>
|
|
383
|
+
<span data-badge data-color="accent">Accent</span>
|
|
384
|
+
<span data-badge data-color="success">Success</span>
|
|
385
|
+
<span data-badge data-color="warning">Warning</span>
|
|
386
|
+
<span data-badge data-color="danger">Danger</span>
|
|
387
|
+
<span data-badge data-color="info">Info</span>
|
|
388
|
+
|
|
389
|
+
<!-- Variants -->
|
|
390
|
+
<span data-badge data-variant="solid" data-color="accent">Solid</span>
|
|
391
|
+
<span data-badge data-variant="pill" data-color="success">New</span>
|
|
392
|
+
|
|
393
|
+
<!-- Status dot -->
|
|
394
|
+
<span data-badge data-color="success" data-dot>Online</span>
|
|
395
|
+
<span data-badge data-color="danger" data-dot>Critical</span>
|
|
396
|
+
<span data-badge data-dot>Offline</span>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### Alert
|
|
402
|
+
|
|
403
|
+
```html
|
|
404
|
+
<div data-alert>
|
|
405
|
+
<div>
|
|
406
|
+
<div data-alert-title>Heads up</div>
|
|
407
|
+
This is an informational alert.
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<div data-alert data-color="success">…</div>
|
|
412
|
+
<div data-alert data-color="warning">…</div>
|
|
413
|
+
<div data-alert data-color="danger">…</div>
|
|
414
|
+
<div data-alert data-color="neutral">…</div>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
### Avatar
|
|
420
|
+
|
|
421
|
+
```html
|
|
422
|
+
<!-- Sizes: sm | default | lg | xl -->
|
|
423
|
+
<div data-avatar data-size="sm">JD</div>
|
|
424
|
+
<div data-avatar>JD</div>
|
|
425
|
+
<div data-avatar data-size="lg">JD</div>
|
|
426
|
+
<div data-avatar data-size="xl">JD</div>
|
|
427
|
+
|
|
428
|
+
<!-- Custom color -->
|
|
429
|
+
<div data-avatar style="background:hsl(262,30%,22%);color:hsl(262,75%,65%)">AM</div>
|
|
430
|
+
|
|
431
|
+
<!-- Overlapping group -->
|
|
432
|
+
<div data-avatar-group>
|
|
433
|
+
<div data-avatar>JD</div>
|
|
434
|
+
<div data-avatar>AM</div>
|
|
435
|
+
<div data-avatar>SR</div>
|
|
436
|
+
<div data-avatar style="font-size:.7rem">+4</div>
|
|
437
|
+
</div>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### Tooltip
|
|
443
|
+
|
|
444
|
+
Pure CSS — no JavaScript required.
|
|
445
|
+
|
|
446
|
+
```html
|
|
447
|
+
<!-- Default: top -->
|
|
448
|
+
<button data-tooltip="Saved to clipboard">Copy</button>
|
|
449
|
+
|
|
450
|
+
<!-- Placements -->
|
|
451
|
+
<button data-tooltip="Below me" data-placement="bottom">Bottom</button>
|
|
452
|
+
<button data-tooltip="Left of me" data-placement="left">Left</button>
|
|
453
|
+
<button data-tooltip="Right of me" data-placement="right">Right</button>
|
|
454
|
+
|
|
455
|
+
<!-- Works on any element -->
|
|
456
|
+
<span data-badge data-color="info" data-tooltip="3 pending tasks">3</span>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### Tabs
|
|
462
|
+
|
|
463
|
+
Web Component. Arrow keys, Home, End keyboard navigation built in.
|
|
464
|
+
|
|
465
|
+
```html
|
|
466
|
+
<kern-tabs>
|
|
467
|
+
<div data-tabs-list>
|
|
468
|
+
<button data-tab>Overview</button>
|
|
469
|
+
<button data-tab>Analytics</button>
|
|
470
|
+
<button data-tab>Settings</button>
|
|
471
|
+
<button data-tab>Billing</button>
|
|
472
|
+
</div>
|
|
473
|
+
<div data-tab-panel>Overview content</div>
|
|
474
|
+
<div data-tab-panel>Analytics content</div>
|
|
475
|
+
<div data-tab-panel>Settings content</div>
|
|
476
|
+
<div data-tab-panel>Billing content</div>
|
|
477
|
+
</kern-tabs>
|
|
478
|
+
|
|
479
|
+
<!-- Pills variant -->
|
|
480
|
+
<kern-tabs data-variant="pills">…</kern-tabs>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**JS API:**
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
const tabs = document.querySelector('kern-tabs');
|
|
487
|
+
tabs.goto(2); // switch to panel at index 2 (with focus)
|
|
488
|
+
tabs.setActive(0); // switch without moving focus
|
|
489
|
+
|
|
490
|
+
// Event emitted on every tab change
|
|
491
|
+
tabs.addEventListener('kern:tab-change', e => {
|
|
492
|
+
console.log('Active index:', e.detail.index);
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### Accordion
|
|
499
|
+
|
|
500
|
+
```html
|
|
501
|
+
<!-- Single open (default) — opening one closes others -->
|
|
502
|
+
<div data-accordion>
|
|
503
|
+
<div data-accordion-item>
|
|
504
|
+
<button data-accordion-trigger>What is Kern UI?</button>
|
|
505
|
+
<div data-accordion-content>
|
|
506
|
+
<div data-accordion-body>
|
|
507
|
+
An attribute-driven UI library. No build step required.
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
<div data-accordion-item>
|
|
512
|
+
<button data-accordion-trigger>How do I install it?</button>
|
|
513
|
+
<div data-accordion-content>
|
|
514
|
+
<div data-accordion-body>Drop in one CSS and one JS file via CDN or npm.</div>
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<!-- Multi-open — all panels independent -->
|
|
520
|
+
<div data-accordion data-multi>…</div>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
### Breadcrumb
|
|
526
|
+
|
|
527
|
+
```html
|
|
528
|
+
<!-- Slash separator (default) -->
|
|
529
|
+
<nav data-breadcrumb>
|
|
530
|
+
<a href="#">Home</a>
|
|
531
|
+
<span data-breadcrumb-sep></span>
|
|
532
|
+
<a href="#">Products</a>
|
|
533
|
+
<span data-breadcrumb-sep></span>
|
|
534
|
+
<span aria-current="page">Kern UI</span>
|
|
535
|
+
</nav>
|
|
536
|
+
|
|
537
|
+
<!-- Other separators -->
|
|
538
|
+
<nav data-breadcrumb data-sep="dot">…</nav>
|
|
539
|
+
<nav data-breadcrumb data-sep="arrow">…</nav>
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
### Pagination
|
|
545
|
+
|
|
546
|
+
```html
|
|
547
|
+
<div data-pagination>
|
|
548
|
+
<button data-page disabled>←</button>
|
|
549
|
+
<button data-page aria-current="page">1</button>
|
|
550
|
+
<button data-page>2</button>
|
|
551
|
+
<button data-page>3</button>
|
|
552
|
+
<span data-page style="border:none;background:none;cursor:default">…</span>
|
|
553
|
+
<button data-page>12</button>
|
|
554
|
+
<button data-page>→</button>
|
|
555
|
+
</div>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
### Stepper
|
|
561
|
+
|
|
562
|
+
```html
|
|
563
|
+
<div data-stepper>
|
|
564
|
+
<div data-step data-done>
|
|
565
|
+
<div data-step-marker></div>
|
|
566
|
+
<div data-step-label>Account</div>
|
|
567
|
+
</div>
|
|
568
|
+
<div data-step data-active>
|
|
569
|
+
<div data-step-marker></div>
|
|
570
|
+
<div data-step-label>Profile</div>
|
|
571
|
+
</div>
|
|
572
|
+
<div data-step>
|
|
573
|
+
<div data-step-marker></div>
|
|
574
|
+
<div data-step-label>Payment</div>
|
|
575
|
+
</div>
|
|
576
|
+
<div data-step>
|
|
577
|
+
<div data-step-marker></div>
|
|
578
|
+
<div data-step-label>Done</div>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
| State | Attr | Appearance |
|
|
584
|
+
| -------- | ------------- | -------------------- |
|
|
585
|
+
| Upcoming | *(none)* | Numbered circle |
|
|
586
|
+
| Current | `data-active` | Accent-filled circle |
|
|
587
|
+
| Complete | `data-done` | ✓ checkmark |
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
### Dialog
|
|
592
|
+
|
|
593
|
+
Wraps native `<dialog>`. Provides focus trapping, ESC-to-close, backdrop click to close, and auto-focus on first focusable element.
|
|
594
|
+
|
|
595
|
+
```html
|
|
596
|
+
<!-- Trigger — any element -->
|
|
597
|
+
<button data-toggle="confirm-dialog">Delete</button>
|
|
598
|
+
|
|
599
|
+
<!-- Dialog -->
|
|
600
|
+
<kern-dialog>
|
|
601
|
+
<dialog id="confirm-dialog" data-size="sm">
|
|
602
|
+
<div data-dialog-header>
|
|
603
|
+
<div>
|
|
604
|
+
<div data-dialog-title>Delete workspace?</div>
|
|
605
|
+
<div data-dialog-desc>This cannot be undone.</div>
|
|
606
|
+
</div>
|
|
607
|
+
<button data-dialog-close>✕</button>
|
|
608
|
+
</div>
|
|
609
|
+
<div data-dialog-body>
|
|
610
|
+
<div data-alert data-color="danger">
|
|
611
|
+
<div>All projects and data will be permanently deleted.</div>
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
<div data-dialog-footer>
|
|
615
|
+
<button data-variant="ghost" data-dialog-close>Cancel</button>
|
|
616
|
+
<button data-variant="danger">Delete</button>
|
|
617
|
+
</div>
|
|
618
|
+
</dialog>
|
|
619
|
+
</kern-dialog>
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**Sizes:** `data-size="sm"` · *(default)* · `data-size="lg"` · `data-size="xl"` · `data-size="full"`
|
|
623
|
+
|
|
624
|
+
**JS API:**
|
|
625
|
+
|
|
626
|
+
```js
|
|
627
|
+
// Open by ID
|
|
628
|
+
Kern.dialog('confirm-dialog');
|
|
629
|
+
|
|
630
|
+
// Via element reference
|
|
631
|
+
const wc = document.querySelector('kern-dialog');
|
|
632
|
+
wc.open();
|
|
633
|
+
wc.close();
|
|
634
|
+
wc.toggle();
|
|
635
|
+
|
|
636
|
+
// Events
|
|
637
|
+
wc.addEventListener('kern:dialog-open', () => {});
|
|
638
|
+
wc.addEventListener('kern:dialog-close', () => {});
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
### Drawer
|
|
644
|
+
|
|
645
|
+
```html
|
|
646
|
+
<kern-drawer>
|
|
647
|
+
<div data-drawer-backdrop></div>
|
|
648
|
+
<div data-drawer-panel data-side="right" data-size="default">
|
|
649
|
+
<div data-drawer-header>
|
|
650
|
+
<span data-drawer-title>Notifications</span>
|
|
651
|
+
<button data-drawer-close>✕</button>
|
|
652
|
+
</div>
|
|
653
|
+
<div data-drawer-body>
|
|
654
|
+
<!-- content -->
|
|
655
|
+
</div>
|
|
656
|
+
<div data-drawer-footer>
|
|
657
|
+
<button>Mark all read</button>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
</kern-drawer>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Sides:** `data-side="right"` *(default)* · `data-side="left"`
|
|
664
|
+
**Sizes:** `data-size="sm"` · *(default)* · `data-size="lg"`
|
|
665
|
+
|
|
666
|
+
```js
|
|
667
|
+
Kern.drawer('my-drawer');
|
|
668
|
+
|
|
669
|
+
const wc = document.querySelector('kern-drawer');
|
|
670
|
+
wc.open();
|
|
671
|
+
wc.close();
|
|
672
|
+
wc.toggle();
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
### Dropdown
|
|
678
|
+
|
|
679
|
+
Web Component. Click outside, Escape, and full keyboard navigation handled automatically.
|
|
680
|
+
|
|
681
|
+
```html
|
|
682
|
+
<kern-dropdown>
|
|
683
|
+
<button data-dropdown-trigger>Options ▾</button>
|
|
684
|
+
<div data-dropdown-content>
|
|
685
|
+
<div data-dropdown-label>Actions</div>
|
|
686
|
+
<a data-dropdown-item href="#">Edit</a>
|
|
687
|
+
<a data-dropdown-item href="#">Duplicate</a>
|
|
688
|
+
<a data-dropdown-item href="#">Archive</a>
|
|
689
|
+
<div data-dropdown-sep></div>
|
|
690
|
+
<a data-dropdown-item data-color="danger" href="#">Delete</a>
|
|
691
|
+
</div>
|
|
692
|
+
</kern-dropdown>
|
|
693
|
+
|
|
694
|
+
<!-- Align to right edge of trigger -->
|
|
695
|
+
<kern-dropdown data-align="end">…</kern-dropdown>
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**JS API:**
|
|
699
|
+
|
|
700
|
+
```js
|
|
701
|
+
const dd = document.querySelector('kern-dropdown');
|
|
702
|
+
dd.open();
|
|
703
|
+
dd.close();
|
|
704
|
+
dd.toggle();
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
### Toast
|
|
710
|
+
|
|
711
|
+
Global API — no HTML boilerplate required. `<kern-toaster>` is created automatically.
|
|
712
|
+
|
|
713
|
+
```js
|
|
714
|
+
// Quick string
|
|
715
|
+
Kern.toast('Changes saved.');
|
|
716
|
+
|
|
717
|
+
// Full options
|
|
718
|
+
Kern.toast({
|
|
719
|
+
title: 'Payment received',
|
|
720
|
+
message: '$1,240 from Alex Morgan.',
|
|
721
|
+
color: 'success', // success | warning | danger | accent | (default)
|
|
722
|
+
duration: 4000, // ms — set 0 for persistent toast
|
|
723
|
+
dismissible: true,
|
|
724
|
+
position: 'bottom-right', // top-right | top-left | top-center
|
|
725
|
+
// bottom-right | bottom-left | bottom-center
|
|
726
|
+
});
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**Optional explicit placement:**
|
|
730
|
+
|
|
731
|
+
```html
|
|
732
|
+
<kern-toaster data-position="top-right"></kern-toaster>
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
**JS API on the element:**
|
|
736
|
+
|
|
737
|
+
```js
|
|
738
|
+
const toaster = document.querySelector('kern-toaster');
|
|
739
|
+
toaster.add({ title: 'Done', color: 'success' });
|
|
740
|
+
toaster.clear(); // dismiss all toasts
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
### Progress & Spinner
|
|
746
|
+
|
|
747
|
+
```html
|
|
748
|
+
<!-- Basic bar -->
|
|
749
|
+
<div data-progress>
|
|
750
|
+
<div data-progress-bar style="width: 65%"></div>
|
|
751
|
+
</div>
|
|
752
|
+
|
|
753
|
+
<!-- Colors: success | warning | danger | (default = accent) -->
|
|
754
|
+
<div data-progress data-color="success">
|
|
755
|
+
<div data-progress-bar style="width: 80%"></div>
|
|
756
|
+
</div>
|
|
757
|
+
|
|
758
|
+
<!-- Sizes: sm | default | lg -->
|
|
759
|
+
<div data-progress data-size="sm">…</div>
|
|
760
|
+
<div data-progress data-size="lg">…</div>
|
|
761
|
+
|
|
762
|
+
<!-- Animated stripe -->
|
|
763
|
+
<div data-progress data-animated>
|
|
764
|
+
<div data-progress-bar style="width: 70%"></div>
|
|
765
|
+
</div>
|
|
766
|
+
|
|
767
|
+
<!-- Indeterminate (infinite loop) -->
|
|
768
|
+
<div data-progress data-indeterminate>
|
|
769
|
+
<div data-progress-bar></div>
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<!-- Spinner -->
|
|
773
|
+
<div data-spinner></div>
|
|
774
|
+
<div data-spinner data-size="sm"></div>
|
|
775
|
+
<div data-spinner data-size="lg"></div>
|
|
776
|
+
<div data-spinner data-color="text"></div>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
### Skeleton
|
|
782
|
+
|
|
783
|
+
```html
|
|
784
|
+
<!-- Rectangle (default) -->
|
|
785
|
+
<div data-skeleton style="height: 120px; border-radius: var(--k-r-md)"></div>
|
|
786
|
+
|
|
787
|
+
<!-- Circle -->
|
|
788
|
+
<div data-skeleton data-shape="circle" style="width: 40px; height: 40px"></div>
|
|
789
|
+
|
|
790
|
+
<!-- Text line -->
|
|
791
|
+
<div data-skeleton data-shape="text" style="width: 60%; height: .875rem"></div>
|
|
792
|
+
|
|
793
|
+
<!-- Typical card loading pattern -->
|
|
794
|
+
<div data-stack data-gap="sm">
|
|
795
|
+
<div data-row data-gap="sm">
|
|
796
|
+
<div data-skeleton data-shape="circle" style="width:40px;height:40px;flex-shrink:0"></div>
|
|
797
|
+
<div data-stack data-gap="sm" style="flex:1">
|
|
798
|
+
<div data-skeleton data-shape="text" style="width:60%;height:.875rem"></div>
|
|
799
|
+
<div data-skeleton data-shape="text" style="width:40%;height:.75rem"></div>
|
|
800
|
+
</div>
|
|
801
|
+
</div>
|
|
802
|
+
<div data-skeleton style="height:120px;border-radius:var(--k-r-md)"></div>
|
|
803
|
+
<div data-skeleton data-shape="text" style="height:.875rem"></div>
|
|
804
|
+
<div data-skeleton data-shape="text" style="width:75%;height:.875rem"></div>
|
|
805
|
+
</div>
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
### Table
|
|
811
|
+
|
|
812
|
+
Client-side column sort via `data-sort` on `<th>`. Click cycles: unsorted → ascending → descending.
|
|
813
|
+
|
|
814
|
+
```html
|
|
815
|
+
<div data-table-wrap>
|
|
816
|
+
<table data-hover>
|
|
817
|
+
<thead>
|
|
818
|
+
<tr>
|
|
819
|
+
<th>Name</th>
|
|
820
|
+
<th data-sort>Role</th>
|
|
821
|
+
<th data-sort>Status</th>
|
|
822
|
+
<th data-sort>Joined</th>
|
|
823
|
+
<th></th>
|
|
824
|
+
</tr>
|
|
825
|
+
</thead>
|
|
826
|
+
<tbody>
|
|
827
|
+
<tr>
|
|
828
|
+
<td>Jane Doe</td>
|
|
829
|
+
<td>Admin</td>
|
|
830
|
+
<td><span data-badge data-color="success" data-dot>Active</span></td>
|
|
831
|
+
<td>Jan 12, 2024</td>
|
|
832
|
+
<td><button data-variant="ghost" data-size="sm">Edit</button></td>
|
|
833
|
+
</tr>
|
|
834
|
+
</tbody>
|
|
835
|
+
</table>
|
|
836
|
+
</div>
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
**Table variants:**
|
|
840
|
+
|
|
841
|
+
| Attribute | Effect |
|
|
842
|
+
| ---------------------------- | -------------------------- |
|
|
843
|
+
| `data-hover` | Row highlight on hover |
|
|
844
|
+
| `data-variant="striped"` | Alternating row background |
|
|
845
|
+
| `data-variant="bordered"` | Cell borders |
|
|
846
|
+
| `data-variant="compact"` | Reduced padding |
|
|
847
|
+
| `data-variant="comfortable"` | Increased padding |
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## Layout
|
|
852
|
+
|
|
853
|
+
### Grid
|
|
854
|
+
|
|
855
|
+
Responsive column grid. Collapses to 1 column on mobile.
|
|
856
|
+
|
|
857
|
+
```html
|
|
858
|
+
<div data-grid="2">…</div>
|
|
859
|
+
<div data-grid="3">…</div>
|
|
860
|
+
<div data-grid="4">…</div>
|
|
861
|
+
<div data-grid="auto">…</div> <!-- auto-fill, min 240px per col -->
|
|
862
|
+
|
|
863
|
+
<!-- Gap sizes: xs | sm | md (default) | lg | xl -->
|
|
864
|
+
<div data-grid="3" data-gap="sm">…</div>
|
|
865
|
+
<div data-grid="3" data-gap="lg">…</div>
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Stack & Row
|
|
869
|
+
|
|
870
|
+
```html
|
|
871
|
+
<!-- Vertical stack -->
|
|
872
|
+
<div data-stack>…</div>
|
|
873
|
+
<div data-stack data-gap="sm">…</div>
|
|
874
|
+
<div data-stack data-gap="lg">…</div>
|
|
875
|
+
|
|
876
|
+
<!-- Horizontal row -->
|
|
877
|
+
<div data-row>…</div>
|
|
878
|
+
<div data-row data-gap="md">…</div>
|
|
879
|
+
<div data-row data-align="center">…</div>
|
|
880
|
+
<div data-row data-justify="between">…</div>
|
|
881
|
+
<div data-row data-justify="center">…</div>
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### Divider
|
|
885
|
+
|
|
886
|
+
```html
|
|
887
|
+
<div data-divider></div>
|
|
888
|
+
|
|
889
|
+
<!-- With text label -->
|
|
890
|
+
<div data-divider>Or continue with</div>
|
|
891
|
+
<div data-divider>Section break</div>
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## JavaScript API
|
|
897
|
+
|
|
898
|
+
The `Kern` global is available immediately after the script loads.
|
|
899
|
+
|
|
900
|
+
```js
|
|
901
|
+
// Notifications
|
|
902
|
+
Kern.toast(messageOrOptions)
|
|
903
|
+
|
|
904
|
+
// Overlays
|
|
905
|
+
Kern.dialog(idOrElement) // open a kern-dialog
|
|
906
|
+
Kern.drawer(idOrElement) // open a kern-drawer
|
|
907
|
+
|
|
908
|
+
// Theming (all persist to localStorage)
|
|
909
|
+
Kern.setTheme('dark' | 'light')
|
|
910
|
+
Kern.setAccent('violet' | 'amber' | 'blue' | 'green' | 'rose' | 'cyan')
|
|
911
|
+
Kern.setRadius('none' | 'sharp' | 'default' | 'round')
|
|
912
|
+
|
|
913
|
+
// Dynamic content — re-run behavior init on injected HTML
|
|
914
|
+
Kern.init(rootElement) // default: document
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
**Re-initializing dynamic content:**
|
|
918
|
+
|
|
919
|
+
Kern's `MutationObserver` handles most dynamic insertion automatically. For content injected outside the observed tree, call `Kern.init()` manually:
|
|
920
|
+
|
|
921
|
+
```js
|
|
922
|
+
const partial = document.getElementById('server-partial');
|
|
923
|
+
Kern.init(partial);
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## Modular imports
|
|
929
|
+
|
|
930
|
+
### CSS — import only what you need
|
|
931
|
+
|
|
932
|
+
```css
|
|
933
|
+
/* Tokens only (for custom components using Kern variables) */
|
|
934
|
+
@import 'kern-ui/src/tokens/index.css';
|
|
935
|
+
|
|
936
|
+
/* Tokens + base + specific components */
|
|
937
|
+
@import 'kern-ui/src/tokens/index.css';
|
|
938
|
+
@import 'kern-ui/src/base/index.css';
|
|
939
|
+
@import 'kern-ui/src/components/button.css';
|
|
940
|
+
@import 'kern-ui/src/components/card.css';
|
|
941
|
+
@import 'kern-ui/src/components/form.css';
|
|
942
|
+
@import 'kern-ui/src/components/table.css';
|
|
943
|
+
|
|
944
|
+
/* Full library */
|
|
945
|
+
@import 'kern-ui/src/kern.css';
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### JS — import only what you need
|
|
949
|
+
|
|
950
|
+
```js
|
|
951
|
+
// Individual web components
|
|
952
|
+
import { KernTabs } from 'kern-ui/src-js/components/tabs.js';
|
|
953
|
+
import { KernDialog } from 'kern-ui/src-js/components/dialog.js';
|
|
954
|
+
import { KernDrawer } from 'kern-ui/src-js/components/drawer.js';
|
|
955
|
+
import { KernDropdown } from 'kern-ui/src-js/components/dropdown.js';
|
|
956
|
+
import { KernToaster } from 'kern-ui/src-js/components/toaster.js';
|
|
957
|
+
|
|
958
|
+
// Behaviors (no web components, pure vanilla JS)
|
|
959
|
+
import { initAccordions } from 'kern-ui/src-js/behaviors/accordion.js';
|
|
960
|
+
import { initToggles } from 'kern-ui/src-js/behaviors/toggle.js';
|
|
961
|
+
import { initTableSort } from 'kern-ui/src-js/behaviors/table-sort.js';
|
|
962
|
+
|
|
963
|
+
// API object only (no auto-boot)
|
|
964
|
+
import { Kern } from 'kern-ui/src-js/api.js';
|
|
965
|
+
|
|
966
|
+
// Full library (registers WCs, boots, sets window.Kern)
|
|
967
|
+
import 'kern-ui/src-js/kern.js';
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Project structure
|
|
973
|
+
|
|
974
|
+
```
|
|
975
|
+
kern-ui/
|
|
976
|
+
├── src/ CSS source — 42 modular files
|
|
977
|
+
│ ├── tokens/
|
|
978
|
+
│ │ ├── colors.css Neutral scale, semantic surfaces, status colors
|
|
979
|
+
│ │ ├── accent.css HSL accent system + 8 presets
|
|
980
|
+
│ │ ├── typography.css Font family + size scale
|
|
981
|
+
│ │ ├── spacing.css Space scale (4px base grid)
|
|
982
|
+
│ │ ├── radius.css Radius scale + preset overrides
|
|
983
|
+
│ │ ├── shadow.css Shadow scale (sm → xl)
|
|
984
|
+
│ │ ├── motion.css Easing curves + duration tokens
|
|
985
|
+
│ │ ├── z-index.css Z-index scale
|
|
986
|
+
│ │ ├── components.css Per-component size/spacing tokens
|
|
987
|
+
│ │ └── index.css
|
|
988
|
+
│ ├── base/
|
|
989
|
+
│ │ ├── reset.css
|
|
990
|
+
│ │ ├── typography.css h1–h6, p, a, code, pre, kbd, mark, blockquote
|
|
991
|
+
│ │ └── index.css
|
|
992
|
+
│ ├── components/ 22 component files (one per component)
|
|
993
|
+
│ │ ├── button.css
|
|
994
|
+
│ │ ├── form.css
|
|
995
|
+
│ │ ├── card.css
|
|
996
|
+
│ │ ├── badge.css
|
|
997
|
+
│ │ ├── alert.css
|
|
998
|
+
│ │ ├── avatar.css
|
|
999
|
+
│ │ ├── tooltip.css
|
|
1000
|
+
│ │ ├── tabs.css
|
|
1001
|
+
│ │ ├── accordion.css
|
|
1002
|
+
│ │ ├── breadcrumb.css
|
|
1003
|
+
│ │ ├── pagination.css
|
|
1004
|
+
│ │ ├── stepper.css
|
|
1005
|
+
│ │ ├── dialog.css
|
|
1006
|
+
│ │ ├── drawer.css
|
|
1007
|
+
│ │ ├── dropdown.css
|
|
1008
|
+
│ │ ├── toast.css
|
|
1009
|
+
│ │ ├── progress.css
|
|
1010
|
+
│ │ ├── skeleton.css
|
|
1011
|
+
│ │ ├── table.css
|
|
1012
|
+
│ │ ├── switch.css
|
|
1013
|
+
│ │ ├── sidebar.css
|
|
1014
|
+
│ │ └── index.css
|
|
1015
|
+
│ ├── layout/
|
|
1016
|
+
│ │ ├── grid.css
|
|
1017
|
+
│ │ ├── stack.css
|
|
1018
|
+
│ │ ├── divider.css
|
|
1019
|
+
│ │ └── index.css
|
|
1020
|
+
│ ├── utils/
|
|
1021
|
+
│ │ ├── helpers.css sr-only, truncate, container, surface utils
|
|
1022
|
+
│ │ ├── keyframes.css All @keyframes (spin, pulse, slide-in, etc.)
|
|
1023
|
+
│ │ ├── responsive.css Mobile breakpoint overrides
|
|
1024
|
+
│ │ └── index.css
|
|
1025
|
+
│ └── kern.css Root entry — @imports everything
|
|
1026
|
+
│
|
|
1027
|
+
├── src-js/ JS source — 12 ESM modules
|
|
1028
|
+
│ ├── components/
|
|
1029
|
+
│ │ ├── tabs.js <kern-tabs> — keyboard nav, ARIA, goto() API
|
|
1030
|
+
│ │ ├── dropdown.js <kern-dropdown> — positioning, keyboard, outside-click
|
|
1031
|
+
│ │ ├── dialog.js <kern-dialog> — focus trap, backdrop, auto-focus
|
|
1032
|
+
│ │ ├── drawer.js <kern-drawer> — slide panels, body scroll lock
|
|
1033
|
+
│ │ └── toaster.js <kern-toaster> — toast queue, 6 positions, auto-dismiss
|
|
1034
|
+
│ ├── behaviors/
|
|
1035
|
+
│ │ ├── accordion.js initAccordions(root) — single/multi open
|
|
1036
|
+
│ │ ├── toggle.js initToggles(root) — data-toggle="id" attr
|
|
1037
|
+
│ │ └── table-sort.js initTableSort(root) — asc/desc, numeric detection
|
|
1038
|
+
│ ├── utils.js emit(), esc(), firstFocusable(), $, $$
|
|
1039
|
+
│ ├── api.js Kern.toast/dialog/drawer/setTheme/setAccent/setRadius/init
|
|
1040
|
+
│ ├── boot.js DOMContentLoaded listener + MutationObserver
|
|
1041
|
+
│ └── kern.js Root ESM entry — re-exports all, runs boot
|
|
1042
|
+
│
|
|
1043
|
+
├── dist/ Production builds
|
|
1044
|
+
│ ├── kern.css Readable concatenated CSS (43 KB)
|
|
1045
|
+
│ ├── kern.min.css Minified CSS (41 KB · 7 KB gzipped)
|
|
1046
|
+
│ ├── kern.js IIFE bundle (15 KB)
|
|
1047
|
+
│ └── kern.min.js Minified IIFE (12 KB · 3 KB gzipped)
|
|
1048
|
+
│
|
|
1049
|
+
├── docs/
|
|
1050
|
+
│ ├── demo.html Complete demo — all 26 components + 7 app pages
|
|
1051
|
+
│ └── showcase.html Themed app showcase (analytics/inbox/travel/SaaS/shop)
|
|
1052
|
+
│
|
|
1053
|
+
├── scripts/
|
|
1054
|
+
│ └── build.js CSS build — resolves @imports, concatenates, minifies
|
|
1055
|
+
│
|
|
1056
|
+
├── package.json
|
|
1057
|
+
└── README.md
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
1062
|
+
## Browser support
|
|
1063
|
+
|
|
1064
|
+
Kern targets all evergreen browsers. No Internet Explorer. No polyfills.
|
|
1065
|
+
|
|
1066
|
+
| Feature used | Chrome | Firefox | Safari |
|
|
1067
|
+
| --------------------- | ------ | ------- | ------ |
|
|
1068
|
+
| CSS custom properties | 49 | 31 | 9.1 |
|
|
1069
|
+
| Custom Elements v1 | 54 | 63 | 10.1 |
|
|
1070
|
+
| Native `<dialog>` | 37 | 98 | 15.4 |
|
|
1071
|
+
| `:has()` selector | 105 | 121 | 15.4 |
|
|
1072
|
+
| `@layer` (optional) | 99 | 97 | 15.4 |
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
## License
|
|
1077
|
+
|
|
1078
|
+
MIT © Kern UI
|