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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1078 -0
  3. package/dist/kern.css +705 -0
  4. package/dist/kern.js +529 -0
  5. package/dist/kern.min.css +1 -0
  6. package/dist/kern.min.js +17 -0
  7. package/package.json +43 -0
  8. package/src/base/index.css +3 -0
  9. package/src/base/reset.css +9 -0
  10. package/src/base/typography.css +26 -0
  11. package/src/components/accordion.css +21 -0
  12. package/src/components/alert.css +13 -0
  13. package/src/components/avatar.css +9 -0
  14. package/src/components/badge.css +18 -0
  15. package/src/components/breadcrumb.css +8 -0
  16. package/src/components/button.css +43 -0
  17. package/src/components/card.css +13 -0
  18. package/src/components/dialog.css +19 -0
  19. package/src/components/drawer.css +12 -0
  20. package/src/components/dropdown.css +22 -0
  21. package/src/components/form.css +53 -0
  22. package/src/components/index.css +22 -0
  23. package/src/components/pagination.css +6 -0
  24. package/src/components/progress.css +15 -0
  25. package/src/components/sidebar.css +16 -0
  26. package/src/components/skeleton.css +8 -0
  27. package/src/components/stepper.css +12 -0
  28. package/src/components/switch.css +11 -0
  29. package/src/components/table.css +17 -0
  30. package/src/components/tabs.css +20 -0
  31. package/src/components/toast.css +31 -0
  32. package/src/components/tooltip.css +31 -0
  33. package/src/kern.css +14 -0
  34. package/src/kern.js +427 -0
  35. package/src/layout/divider.css +5 -0
  36. package/src/layout/grid.css +12 -0
  37. package/src/layout/index.css +4 -0
  38. package/src/layout/stack.css +13 -0
  39. package/src/tokens/accent.css +20 -0
  40. package/src/tokens/colors.css +37 -0
  41. package/src/tokens/components.css +17 -0
  42. package/src/tokens/index.css +10 -0
  43. package/src/tokens/motion.css +12 -0
  44. package/src/tokens/radius.css +20 -0
  45. package/src/tokens/shadow.css +25 -0
  46. package/src/tokens/spacing.css +13 -0
  47. package/src/tokens/typography.css +10 -0
  48. package/src/tokens/z-index.css +9 -0
  49. package/src/utils/helpers.css +13 -0
  50. package/src/utils/index.css +4 -0
  51. package/src/utils/keyframes.css +11 -0
  52. package/src/utils/responsive.css +8 -0
  53. package/src-js/api.js +103 -0
  54. package/src-js/behaviors/accordion.js +51 -0
  55. package/src-js/behaviors/table-sort.js +61 -0
  56. package/src-js/behaviors/toggle.js +27 -0
  57. package/src-js/boot.js +37 -0
  58. package/src-js/components/dialog.js +72 -0
  59. package/src-js/components/drawer.js +60 -0
  60. package/src-js/components/dropdown.js +88 -0
  61. package/src-js/components/tabs.js +92 -0
  62. package/src-js/components/toaster.js +89 -0
  63. package/src-js/kern.js +34 -0
  64. 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