nanoui 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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +28 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +542 -0
  5. data/lib/generators/nanoui/component_generator.rb +132 -0
  6. data/lib/generators/nanoui/install_generator.rb +53 -0
  7. data/lib/generators/nanoui/templates/css/base/_globals.css +41 -0
  8. data/lib/generators/nanoui/templates/css/base/_reset.css +8 -0
  9. data/lib/generators/nanoui/templates/css/components/_accordion.css +74 -0
  10. data/lib/generators/nanoui/templates/css/components/_alert.css +78 -0
  11. data/lib/generators/nanoui/templates/css/components/_badge.css +48 -0
  12. data/lib/generators/nanoui/templates/css/components/_button.css +116 -0
  13. data/lib/generators/nanoui/templates/css/components/_card.css +48 -0
  14. data/lib/generators/nanoui/templates/css/components/_checkbox.css +86 -0
  15. data/lib/generators/nanoui/templates/css/components/_dialog.css +122 -0
  16. data/lib/generators/nanoui/templates/css/components/_dropdown.css +84 -0
  17. data/lib/generators/nanoui/templates/css/components/_input.css +82 -0
  18. data/lib/generators/nanoui/templates/css/components/_label.css +11 -0
  19. data/lib/generators/nanoui/templates/css/components/_progress.css +66 -0
  20. data/lib/generators/nanoui/templates/css/components/_radio.css +95 -0
  21. data/lib/generators/nanoui/templates/css/components/_select.css +45 -0
  22. data/lib/generators/nanoui/templates/css/components/_switch.css +42 -0
  23. data/lib/generators/nanoui/templates/css/components/_table.css +53 -0
  24. data/lib/generators/nanoui/templates/css/components/_tabs.css +51 -0
  25. data/lib/generators/nanoui/templates/css/components/_toast.css +128 -0
  26. data/lib/generators/nanoui/templates/css/components/_tooltip.css +87 -0
  27. data/lib/generators/nanoui/templates/css/fonts/inter-variable.ttf +0 -0
  28. data/lib/generators/nanoui/templates/css/nanoui.css +34 -0
  29. data/lib/generators/nanoui/templates/css/nanoui.install.css +16 -0
  30. data/lib/generators/nanoui/templates/css/tokens/_colors.css +61 -0
  31. data/lib/generators/nanoui/templates/css/tokens/_radius.css +10 -0
  32. data/lib/generators/nanoui/templates/css/tokens/_shadows.css +7 -0
  33. data/lib/generators/nanoui/templates/css/tokens/_spacing.css +17 -0
  34. data/lib/generators/nanoui/templates/css/tokens/_transitions.css +10 -0
  35. data/lib/generators/nanoui/templates/css/tokens/_typography.css +28 -0
  36. data/lib/generators/nanoui/templates/css/tokens/_z-index.css +10 -0
  37. data/lib/generators/nanoui/templates/js/controllers/accordion_controller.js +21 -0
  38. data/lib/generators/nanoui/templates/js/controllers/dialog_controller.js +40 -0
  39. data/lib/generators/nanoui/templates/js/controllers/dropdown_controller.js +101 -0
  40. data/lib/generators/nanoui/templates/js/controllers/switch_controller.js +10 -0
  41. data/lib/generators/nanoui/templates/js/controllers/tabs_controller.js +72 -0
  42. data/lib/generators/nanoui/templates/js/controllers/toast_controller.js +39 -0
  43. data/lib/generators/nanoui/templates/js/controllers/tooltip_controller.js +34 -0
  44. data/lib/generators/nanoui/templates/views/components/_accordion.html.erb +40 -0
  45. data/lib/generators/nanoui/templates/views/components/_alert.html.erb +33 -0
  46. data/lib/generators/nanoui/templates/views/components/_badge.html.erb +18 -0
  47. data/lib/generators/nanoui/templates/views/components/_button.html.erb +27 -0
  48. data/lib/generators/nanoui/templates/views/components/_card.html.erb +43 -0
  49. data/lib/generators/nanoui/templates/views/components/_checkbox.html.erb +36 -0
  50. data/lib/generators/nanoui/templates/views/components/_dialog.html.erb +65 -0
  51. data/lib/generators/nanoui/templates/views/components/_dropdown.html.erb +29 -0
  52. data/lib/generators/nanoui/templates/views/components/_input.html.erb +42 -0
  53. data/lib/generators/nanoui/templates/views/components/_label.html.erb +20 -0
  54. data/lib/generators/nanoui/templates/views/components/_progress.html.erb +29 -0
  55. data/lib/generators/nanoui/templates/views/components/_radio_group.html.erb +46 -0
  56. data/lib/generators/nanoui/templates/views/components/_select.html.erb +67 -0
  57. data/lib/generators/nanoui/templates/views/components/_switch.html.erb +32 -0
  58. data/lib/generators/nanoui/templates/views/components/_table.html.erb +41 -0
  59. data/lib/generators/nanoui/templates/views/components/_tabs.html.erb +46 -0
  60. data/lib/generators/nanoui/templates/views/components/_toast.html.erb +33 -0
  61. data/lib/generators/nanoui/templates/views/components/_toast_container.html.erb +17 -0
  62. data/lib/generators/nanoui/templates/views/components/_tooltip.html.erb +28 -0
  63. data/lib/nanoui/version.rb +3 -0
  64. data/lib/nanoui.rb +5 -0
  65. metadata +134 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c33ae0d2c94bdda9bddcf979a57177770c5c50f7ff1602d6744431402507f49d
4
+ data.tar.gz: d8d0c324e74ed81eb1fd84f985a81414ee3c343c034824457d84ee1856fe7fc4
5
+ SHA512:
6
+ metadata.gz: b53749cc0036143533f008abf49da63c4a5de75d38b04958b196224461f0c962bcbfce352d18a584825bdf3bd3e119b54541597a5850b2bb3fc6b3aca8b29ec7
7
+ data.tar.gz: 73b2f16489e7669236551edf07f873ab2f3053ce96b40771efb54f49aa169cc62a1d84d9ad0b55e766a50c0e0c3b31705d65a4ea5bdd14279a2d846ea06a6859
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-03-09)
4
+
5
+ ### Initial release
6
+
7
+ **Design tokens:** colors, typography, spacing, radius, shadows, transitions, z-index
8
+
9
+ **Base styles:** reset, globals
10
+
11
+ **Components (18):**
12
+ - button, input, label, card
13
+ - checkbox, radio, switch, select
14
+ - badge, alert
15
+ - dialog, dropdown, tooltip, toast
16
+ - table, tabs, accordion, progress
17
+
18
+ **Component groups:**
19
+ - `essentials` — button, input, label, card, badge, alert
20
+ - `forms` — button, input, label, checkbox, radio, switch, select, badge, alert
21
+ - `overlays` — dialog, dropdown, tooltip, toast
22
+ - `data` — table, tabs, accordion, progress
23
+
24
+ **Stimulus controllers (7):** dialog, dropdown, tooltip, toast, tabs, accordion, switch
25
+
26
+ **Generators:**
27
+ - `nanoui:install` — copies tokens, base styles, fonts, and entry point CSS
28
+ - `nanoui:component` — copies component CSS, Stimulus controllers, and ERB partials
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Denis Omerovic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,542 @@
1
+ # NanoUI
2
+
3
+ Vanilla CSS + Stimulus component library for Rails. Zero runtime dependencies.
4
+
5
+ 18 components. Semantic HTML. Accessible by default. No build step.
6
+
7
+ ## Installation
8
+
9
+ ### Option A: Rubygem with Generators (recommended)
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "nanoui", group: :development
14
+ ```
15
+
16
+ ```bash
17
+ bundle install
18
+ rails generate nanoui:install # Tokens, base, fonts
19
+ rails generate nanoui:component --all # All components
20
+ ```
21
+
22
+ ### Option B: Manual Installation
23
+
24
+ Copy files directly into your Rails 8 app:
25
+
26
+ ```bash
27
+ # 1. CSS (tokens, base, components, Inter Variable font)
28
+ cp -r lib/generators/nanoui/templates/css/ app/assets/stylesheets/nanoui/
29
+
30
+ # 2. Stimulus controllers
31
+ cp lib/generators/nanoui/templates/js/controllers/*_controller.js \
32
+ app/javascript/controllers/
33
+
34
+ # 3. ERB partials (optional — you can use CSS classes directly)
35
+ cp lib/generators/nanoui/templates/views/components/ app/views/components/
36
+ ```
37
+
38
+ ### Import CSS
39
+
40
+ Add to `app/assets/stylesheets/application.css`:
41
+
42
+ ```css
43
+ @import "nanoui/nanoui.css";
44
+ ```
45
+
46
+ `rails generate nanoui:install` now creates a foundation-only `nanoui.css`, and each `nanoui:component` run rewrites the component imports so the file only references CSS that actually exists.
47
+
48
+ NanoUI ships with Inter Variable and expects it at `app/assets/stylesheets/nanoui/fonts/inter-variable.ttf`, which `rails generate nanoui:install` now copies automatically.
49
+
50
+ Or if using Propshaft, add the import to your layout:
51
+
52
+ ```erb
53
+ <%= stylesheet_link_tag "nanoui/nanoui.css" %>
54
+ ```
55
+
56
+ ### Troubleshooting Fonts
57
+
58
+ If NanoUI falls back to system fonts instead of Inter:
59
+
60
+ - Confirm the font file exists at `app/assets/stylesheets/nanoui/fonts/inter-variable.ttf`
61
+ - Confirm `app/assets/stylesheets/nanoui/base/_globals.css` still points to `url("../fonts/inter-variable.ttf")`
62
+ - If you installed NanoUI before this change, rerun `rails generate nanoui:install` or copy `lib/generators/nanoui/templates/css/fonts/inter-variable.ttf` into `app/assets/stylesheets/nanoui/fonts/`
63
+ - In the browser devtools Network tab, verify `inter-variable.ttf` loads successfully instead of returning `404`
64
+
65
+ ### Register Stimulus Controllers
66
+
67
+ With Rails 8 + importmap + `eagerLoadControllersFrom`, controllers auto-register by file name. Rename the controllers with a `nanoui_` prefix:
68
+
69
+ ```
70
+ nanoui_dialog_controller.js → data-controller="nanoui-dialog"
71
+ nanoui_dropdown_controller.js → data-controller="nanoui-dropdown"
72
+ nanoui_tooltip_controller.js → data-controller="nanoui-tooltip"
73
+ nanoui_toast_controller.js → data-controller="nanoui-toast"
74
+ nanoui_tabs_controller.js → data-controller="nanoui-tabs"
75
+ nanoui_accordion_controller.js → data-controller="nanoui-accordion"
76
+ nanoui_switch_controller.js → data-controller="nanoui-switch"
77
+ ```
78
+
79
+ Or register manually:
80
+
81
+ ```js
82
+ // app/javascript/controllers/index.js
83
+ import DialogController from "./nanoui_dialog_controller"
84
+ application.register("nanoui-dialog", DialogController)
85
+ // ... repeat for each controller
86
+ ```
87
+
88
+ ### Dark Mode
89
+
90
+ Add the `.dark` class to `<html>` to toggle dark mode. All color tokens swap automatically.
91
+
92
+ ```erb
93
+ <!-- In your layout -->
94
+ <html class="<%= "dark" if user_prefers_dark? %>">
95
+ ```
96
+
97
+ Or toggle via JavaScript:
98
+
99
+ ```js
100
+ document.documentElement.classList.toggle("dark")
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Component Reference
106
+
107
+ ### Essentials
108
+
109
+ #### Button
110
+
111
+ 6 variants, 3 sizes, icon-only, loading state.
112
+
113
+ ```erb
114
+ <%= render "components/button", variant: "primary" do %>
115
+ Save Changes
116
+ <% end %>
117
+
118
+ <%= render "components/button", variant: "outline", size: "sm" do %>
119
+ Cancel
120
+ <% end %>
121
+
122
+ <%= render "components/button", variant: "destructive" do %>
123
+ Delete
124
+ <% end %>
125
+
126
+ <%# Link styled as button %>
127
+ <%= render "components/button", href: "/about", variant: "ghost" do %>
128
+ Learn More
129
+ <% end %>
130
+ ```
131
+
132
+ Or use classes directly:
133
+
134
+ ```html
135
+ <button class="button button--primary" type="button">Save</button>
136
+ <button class="button button--outline button--sm" type="button">Cancel</button>
137
+ <button class="button button--primary" disabled>Disabled</button>
138
+ ```
139
+
140
+ **Options:** `variant` (primary, secondary, destructive, outline, ghost, link), `size` (sm, lg, icon), `tag` (:button), `href`, `class`, `html`
141
+
142
+ #### Input
143
+
144
+ Text fields wrapped in `.field` with label and error support.
145
+
146
+ ```erb
147
+ <%= render "components/input", label: "Email", type: "email", required: true,
148
+ html: { placeholder: "you@example.com" } %>
149
+
150
+ <%= render "components/input", label: "Name", error: "Name is required" %>
151
+ ```
152
+
153
+ ```html
154
+ <div class="field">
155
+ <label for="email" class="label label--required">Email</label>
156
+ <input id="email" type="email" class="input" placeholder="you@example.com" required>
157
+ </div>
158
+ ```
159
+
160
+ **Options:** `label`, `type` ("text"), `required`, `error`, `id`, `class`, `html`
161
+
162
+ #### Label
163
+
164
+ ```erb
165
+ <%= render "components/label", for: "name", required: true do %>Name<% end %>
166
+ ```
167
+
168
+ ```html
169
+ <label for="name" class="label label--required">Name</label>
170
+ ```
171
+
172
+ #### Card
173
+
174
+ Container with header, content, and footer sections.
175
+
176
+ ```erb
177
+ <%= render "components/card",
178
+ title: "Settings",
179
+ description: "Manage your account.",
180
+ footer: render("components/button", variant: "primary") { "Save" } do %>
181
+ <p>Card body content here.</p>
182
+ <% end %>
183
+ ```
184
+
185
+ ```html
186
+ <article class="card">
187
+ <div class="card__header">
188
+ <h3 class="card__title">Settings</h3>
189
+ <p class="card__description">Manage your account.</p>
190
+ </div>
191
+ <div class="card__content">...</div>
192
+ <div class="card__footer">...</div>
193
+ </article>
194
+ ```
195
+
196
+ **Variants:** default, `elevated`, `bordered`
197
+
198
+ #### Badge
199
+
200
+ Inline status indicators.
201
+
202
+ ```erb
203
+ <%= render "components/badge", variant: "success" do %>Active<% end %>
204
+ <%= render "components/badge", variant: "warning" do %>Pending<% end %>
205
+ ```
206
+
207
+ ```html
208
+ <span class="badge badge--success">Active</span>
209
+ <span class="badge badge--destructive">Failed</span>
210
+ ```
211
+
212
+ **Variants:** primary, secondary, destructive, outline, success, warning
213
+
214
+ #### Alert
215
+
216
+ Contextual feedback with icon, title, and description.
217
+
218
+ ```erb
219
+ <%= render "components/alert", variant: "success", title: "Saved!" do %>
220
+ Your changes have been saved successfully.
221
+ <% end %>
222
+ ```
223
+
224
+ ```html
225
+ <div class="alert alert--success" role="alert">
226
+ <div class="alert__icon"><!-- SVG --></div>
227
+ <div class="alert__content">
228
+ <p class="alert__title">Saved!</p>
229
+ <p class="alert__description">Your changes have been saved.</p>
230
+ </div>
231
+ </div>
232
+ ```
233
+
234
+ **Variants:** default, destructive, success, warning
235
+
236
+ ---
237
+
238
+ ### Forms
239
+
240
+ #### Checkbox
241
+
242
+ ```erb
243
+ <%= render "components/checkbox", label: "Accept terms", name: "tos", error: true %>
244
+ <%= render "components/checkbox", label: "Remember me", checked: true %>
245
+ ```
246
+
247
+ ```html
248
+ <div class="checkbox">
249
+ <input type="checkbox" id="tos" class="checkbox__input" name="tos">
250
+ <label for="tos" class="checkbox__label">Accept terms</label>
251
+ </div>
252
+ ```
253
+
254
+ **Options:** `label`, `name`, `value`, `checked`, `disabled`, `error`, `id`
255
+
256
+ #### Radio Group
257
+
258
+ ```erb
259
+ <%= render "components/radio_group",
260
+ legend: "Plan",
261
+ name: "plan",
262
+ options: [
263
+ { label: "Free", value: "free", checked: true },
264
+ { label: "Pro", value: "pro" },
265
+ { label: "Enterprise", value: "enterprise" }
266
+ ] %>
267
+ ```
268
+
269
+ ```html
270
+ <fieldset class="radio-group">
271
+ <legend class="radio-group__legend">Plan</legend>
272
+ <div class="radio">
273
+ <input type="radio" id="plan-0" name="plan" value="free" class="radio__input" checked>
274
+ <label for="plan-0" class="radio__label">Free</label>
275
+ </div>
276
+ <!-- ... -->
277
+ </fieldset>
278
+ ```
279
+
280
+ #### Switch
281
+
282
+ Toggle switch with Stimulus controller.
283
+
284
+ ```erb
285
+ <%= render "components/switch", label: "Enable notifications", checked: true %>
286
+ ```
287
+
288
+ ```html
289
+ <button type="button" role="switch" aria-checked="true" class="switch"
290
+ data-controller="nanoui-switch" data-action="nanoui-switch#toggle">
291
+ <span class="switch__thumb"></span>
292
+ <span class="sr-only">Enable notifications</span>
293
+ </button>
294
+ ```
295
+
296
+ #### Select
297
+
298
+ Native `<select>` with custom styling.
299
+
300
+ ```erb
301
+ <%= render "components/select",
302
+ label: "Country",
303
+ placeholder: "Select a country",
304
+ options: ["United States", "Canada", "United Kingdom"] %>
305
+
306
+ <%= render "components/select",
307
+ label: "Role",
308
+ required: true,
309
+ options: [
310
+ { label: "Admin", value: "admin" },
311
+ { label: "Editor", value: "editor" }
312
+ ] %>
313
+ ```
314
+
315
+ **Options:** `label`, `placeholder`, `options`, `required`, `error`, `disabled`, `name`, `id`
316
+
317
+ ---
318
+
319
+ ### Overlays
320
+
321
+ #### Dialog
322
+
323
+ Native `<dialog>` with `showModal()` — free focus trap, Escape close, and `::backdrop`.
324
+
325
+ ```erb
326
+ <%= render "components/dialog",
327
+ title: "Edit Profile",
328
+ description: "Update your info.",
329
+ trigger: render("components/button") { "Open" },
330
+ footer: safe_join([
331
+ render("components/button", variant: "outline", html: { data: { action: "nanoui-dialog#close" } }) { "Cancel" },
332
+ render("components/button", variant: "primary") { "Save" }
333
+ ]) do %>
334
+ <p>Dialog body content.</p>
335
+ <% end %>
336
+ ```
337
+
338
+ ```html
339
+ <div data-controller="nanoui-dialog">
340
+ <button data-action="nanoui-dialog#open">Open</button>
341
+
342
+ <dialog data-nanoui-dialog-target="modal" class="dialog"
343
+ aria-labelledby="dialog-title">
344
+ <div class="dialog__content">
345
+ <header class="dialog__header">
346
+ <h2 id="dialog-title" class="dialog__title">Title</h2>
347
+ </header>
348
+ <div class="dialog__body">...</div>
349
+ <footer class="dialog__footer">...</footer>
350
+ <button class="dialog__close" data-action="nanoui-dialog#close"
351
+ aria-label="Close dialog">...</button>
352
+ </div>
353
+ </dialog>
354
+ </div>
355
+ ```
356
+
357
+ **Sizes:** `sm` (24rem), default/`md` (32rem), `lg` (42rem), `full`
358
+
359
+ #### Dropdown
360
+
361
+ Click-activated menu with keyboard navigation.
362
+
363
+ ```erb
364
+ <%= render "components/dropdown",
365
+ trigger: render("components/button", variant: "outline") { "Options" } do %>
366
+ <button class="dropdown__item">Profile</button>
367
+ <button class="dropdown__item">Settings</button>
368
+ <div class="dropdown__separator"></div>
369
+ <button class="dropdown__item">Log out</button>
370
+ <% end %>
371
+ ```
372
+
373
+ Keyboard: Arrow Up/Down navigates items, Escape closes, click outside closes.
374
+
375
+ #### Tooltip
376
+
377
+ Hover/focus tooltip with configurable delay.
378
+
379
+ ```erb
380
+ <%= render "components/tooltip", text: "Add to favorites", position: "top" do %>
381
+ <%= render "components/button", variant: "primary", size: "icon" do %>
382
+ <!-- heart icon SVG -->
383
+ <% end %>
384
+ <% end %>
385
+ ```
386
+
387
+ **Positions:** top (default), bottom, left, right. **Delay:** 200ms default.
388
+
389
+ #### Toast
390
+
391
+ Auto-dismissing notifications stacked bottom-right.
392
+
393
+ Place the container once in your layout:
394
+
395
+ ```erb
396
+ <%# app/views/layouts/application.html.erb %>
397
+ <%= render "components/toast_container" %>
398
+ ```
399
+
400
+ Add toasts dynamically (via Turbo Stream or JS):
401
+
402
+ ```erb
403
+ <%= render "components/toast", variant: "success",
404
+ title: "Saved!", description: "Changes applied." %>
405
+ ```
406
+
407
+ **Variants:** default, success, destructive, warning. **Auto-dismiss:** 5000ms default.
408
+
409
+ ---
410
+
411
+ ### Data Display
412
+
413
+ #### Table
414
+
415
+ Semantic table with responsive scroll wrapper.
416
+
417
+ ```erb
418
+ <%= render "components/table",
419
+ headers: ["Name", "Email", "Status"],
420
+ striped: true, hoverable: true do %>
421
+ <tr class="table__row">
422
+ <td class="table__cell">Jane Doe</td>
423
+ <td class="table__cell">jane@example.com</td>
424
+ <td class="table__cell">
425
+ <%= render "components/badge", variant: "success" do %>Active<% end %>
426
+ </td>
427
+ </tr>
428
+ <% end %>
429
+ ```
430
+
431
+ **Options:** `headers`, `striped`, `hoverable`, `class`
432
+
433
+ #### Tabs
434
+
435
+ WAI-ARIA tabs pattern with arrow key navigation.
436
+
437
+ ```erb
438
+ <%= render "components/tabs", label: "Settings", tabs: [
439
+ { id: "general", label: "General", content: tag.p("General settings..."), active: true },
440
+ { id: "security", label: "Security", content: tag.p("Security settings...") },
441
+ { id: "billing", label: "Billing", content: tag.p("Billing info...") }
442
+ ] %>
443
+ ```
444
+
445
+ **Options:** `tabs` (array), `label` (aria-label), `hash` (URL hash sync)
446
+
447
+ #### Accordion
448
+
449
+ Native `<details>`/`<summary>` with optional single-open mode.
450
+
451
+ ```erb
452
+ <%= render "components/accordion", single: true, items: [
453
+ { title: "Is it free?", content: "Yes, MIT licensed.", open: true },
454
+ { title: "Build step?", content: "No, vanilla CSS." },
455
+ { title: "Dark mode?", content: "Add .dark class to <html>." }
456
+ ] %>
457
+ ```
458
+
459
+ **Options:** `items` (array), `single` (single-open mode), `class`
460
+
461
+ #### Progress
462
+
463
+ Native `<progress>` element with custom styling.
464
+
465
+ ```erb
466
+ <%= render "components/progress", value: 65, label: "65%", aria_label: "Upload progress" %>
467
+ <%= render "components/progress", value: 100, variant: "success", label: "Complete" %>
468
+ ```
469
+
470
+ **Variants:** default (primary), success, warning, destructive
471
+
472
+ ---
473
+
474
+ ## Design Tokens
475
+
476
+ Customize your theme by editing the CSS custom properties:
477
+
478
+ | Token File | What it controls |
479
+ |---|---|
480
+ | `tokens/_colors.css` | All colors (HSL), dark mode overrides |
481
+ | `tokens/_typography.css` | Font families, sizes, weights, line heights |
482
+ | `tokens/_spacing.css` | Spacing scale (0 to 16) |
483
+ | `tokens/_radius.css` | Border radii (sm to full) |
484
+ | `tokens/_shadows.css` | Box shadows (sm to xl) |
485
+ | `tokens/_transitions.css` | Durations and easings |
486
+ | `tokens/_z-index.css` | Z-index scale (dropdown to toast) |
487
+
488
+ ### Changing your brand color
489
+
490
+ Edit one line in `tokens/_colors.css`:
491
+
492
+ ```css
493
+ --color-primary: 220 70% 50%; /* Change this HSL value */
494
+ ```
495
+
496
+ All components update automatically, including dark mode.
497
+
498
+ ---
499
+
500
+ ## Component Groups
501
+
502
+ | Group | Components |
503
+ |---|---|
504
+ | **Essentials** | Button, Input, Label, Card, Badge, Alert |
505
+ | **Forms** | Checkbox, Radio, Switch, Select |
506
+ | **Overlays** | Dialog, Dropdown, Tooltip, Toast |
507
+ | **Data** | Table, Tabs, Accordion, Progress |
508
+
509
+ ```bash
510
+ rails generate nanoui:component --group essentials
511
+ rails generate nanoui:component --group forms
512
+ rails generate nanoui:component --group overlays
513
+ rails generate nanoui:component --group data
514
+ rails generate nanoui:component --all
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Philosophy
520
+
521
+ - **Semantic HTML first** — `<dialog>`, `<details>`, `<progress>`, `<fieldset>`, `<output>`
522
+ - **Accessibility is not optional** — ARIA attributes, keyboard navigation, focus management, screen reader support
523
+ - **No build step** — No Tailwind, no PostCSS, no webpack. Vanilla CSS with native nesting
524
+ - **You own the code** — Generator copies files into your app. Edit freely, no runtime dependency
525
+ - **BEM naming** — `.block`, `.block--modifier`, `.block__element`
526
+ - **CSS custom properties** — One file to theme everything. Dark mode with a single class swap
527
+
528
+ ## Browser Support
529
+
530
+ Chrome 120+, Firefox 117+, Safari 17.2+ (native CSS nesting and `<dialog>` support).
531
+
532
+ ## Icons
533
+
534
+ NanoUI works great with [Lucide Icons](https://lucide.dev) (MIT licensed). Use inline SVGs — copy what you need.
535
+
536
+ ## Credits
537
+
538
+ Icons from [Lucide](https://lucide.dev).
539
+
540
+ ## License
541
+
542
+ MIT