plutonium 0.45.3 → 0.46.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +146 -0
  3. data/.claude/skills/plutonium-assets/SKILL.md +248 -157
  4. data/.claude/skills/{plutonium-rodauth → plutonium-auth}/SKILL.md +195 -229
  5. data/.claude/skills/plutonium-controller/SKILL.md +9 -2
  6. data/.claude/skills/plutonium-create-resource/SKILL.md +22 -1
  7. data/.claude/skills/plutonium-definition/SKILL.md +521 -7
  8. data/.claude/skills/plutonium-entity-scoping/SKILL.md +317 -0
  9. data/.claude/skills/plutonium-forms/SKILL.md +8 -1
  10. data/.claude/skills/plutonium-installation/SKILL.md +25 -2
  11. data/.claude/skills/plutonium-interaction/SKILL.md +9 -2
  12. data/.claude/skills/plutonium-invites/SKILL.md +11 -7
  13. data/.claude/skills/plutonium-model/SKILL.md +50 -50
  14. data/.claude/skills/plutonium-nested-resources/SKILL.md +8 -1
  15. data/.claude/skills/plutonium-package/SKILL.md +8 -1
  16. data/.claude/skills/plutonium-policy/SKILL.md +69 -78
  17. data/.claude/skills/plutonium-portal/SKILL.md +26 -70
  18. data/.claude/skills/plutonium-views/SKILL.md +9 -2
  19. data/CHANGELOG.md +28 -0
  20. data/app/assets/plutonium.css +1 -1
  21. data/app/views/rodauth/_login_form.html.erb +0 -3
  22. data/app/views/rodauth/confirm_password.html.erb +0 -4
  23. data/app/views/rodauth/create_account.html.erb +0 -3
  24. data/app/views/rodauth/logout.html.erb +0 -3
  25. data/docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md +481 -0
  26. data/docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md +236 -0
  27. data/gemfiles/rails_7.gemfile.lock +1 -1
  28. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  29. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  30. data/lib/generators/pu/core/update/update_generator.rb +8 -0
  31. data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +56 -0
  32. data/lib/generators/pu/invites/install_generator.rb +8 -1
  33. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +43 -0
  34. data/lib/generators/pu/profile/concerns/profile_arguments.rb +10 -4
  35. data/lib/generators/pu/profile/conn_generator.rb +9 -12
  36. data/lib/generators/pu/profile/install_generator.rb +5 -2
  37. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  38. data/lib/generators/pu/saas/portal_generator.rb +4 -9
  39. data/lib/generators/pu/saas/welcome/templates/app/views/welcome/onboarding.html.erb.tt +2 -2
  40. data/lib/plutonium/engine.rb +18 -5
  41. data/lib/plutonium/ui/layout/rodauth_layout.rb +6 -1
  42. data/lib/plutonium/version.rb +1 -1
  43. data/package.json +1 -1
  44. metadata +7 -8
  45. data/.claude/skills/plutonium/skill.md +0 -130
  46. data/.claude/skills/plutonium-definition-actions/SKILL.md +0 -424
  47. data/.claude/skills/plutonium-definition-query/SKILL.md +0 -364
  48. data/.claude/skills/plutonium-profile/SKILL.md +0 -276
  49. data/.claude/skills/plutonium-theming/SKILL.md +0 -424
@@ -1,33 +1,43 @@
1
1
  ---
2
2
  name: plutonium-assets
3
- description: Use when setting up TailwindCSS, registering Stimulus controllers, or theming Phlexi components in a Plutonium app
3
+ description: Use BEFORE configuring Tailwind, registering a Stimulus controller, or editing design tokens / theming in a Plutonium app. Also when running pu:core:assets or editing tailwind.config.js. Covers the full frontend toolchain.
4
4
  ---
5
5
 
6
- # Plutonium Assets & Setup
6
+ # Plutonium Assets, Stimulus & Theming
7
7
 
8
- Plutonium uses TailwindCSS 4 for styling with a customizable theme system for components.
8
+ ## 🚨 Critical (read first)
9
+ - **Use the generator.** `pu:core:assets` wires Tailwind, imports Plutonium CSS, registers Stimulus controllers, and updates the Plutonium config. Never hand-roll this.
10
+ - **Always register Stimulus controllers.** `registerControllers(application)` is required — Plutonium's controllers (color-mode, form, slim-select, flatpickr, easymde, etc.) are dead without it. Custom controllers must also be explicitly registered.
11
+ - **Use `plutoniumTailwindConfig.merge`** when overriding theme keys. Plain object merge drops Plutonium's defaults.
12
+ - **Prefer `.pu-*` classes and CSS tokens** over hardcoded `gray-*/dark:gray-*` pairs — they switch with dark mode automatically.
13
+ - **Related skills:** `plutonium-views` (layout customization), `plutonium-forms` (form theming), `plutonium-installation` (initial setup).
9
14
 
10
- > **Note:** For CSS design tokens (`--pu-*` variables) and component classes (`.pu-btn`, `.pu-input`, etc.), see the `plutonium-theming` skill.
15
+ Plutonium uses TailwindCSS 4 for styling with a customizable theme system for components, and ships its own Stimulus controllers and CSS design token system.
11
16
 
12
- ## Asset Configuration
17
+ ## Contents
18
+ - [Asset configuration](#asset-configuration)
19
+ - [TailwindCSS configuration](#tailwindcss-configuration)
20
+ - [CSS imports](#css-imports)
21
+ - [Phlexi component themes](#phlexi-component-themes)
22
+ - [Stimulus controllers](#stimulus-controllers)
23
+ - [Design tokens and theming](#design-tokens-and-theming)
24
+ - [Component classes (`.pu-*`)](#component-classes)
25
+ - [Gotchas](#gotchas)
13
26
 
14
- Configure assets in the initializer:
27
+ ## Asset configuration
15
28
 
16
29
  ```ruby
17
30
  # config/initializers/plutonium.rb
18
31
  Plutonium.configure do |config|
19
32
  config.load_defaults 1.0
20
33
 
21
- # Custom assets
22
34
  config.assets.stylesheet = "application" # Your CSS file
23
35
  config.assets.script = "application" # Your JS file
24
- config.assets.logo = "my_logo.png" # Logo image
25
- config.assets.favicon = "my_favicon.ico" # Favicon
36
+ config.assets.logo = "my_logo.png"
37
+ config.assets.favicon = "my_favicon.ico"
26
38
  end
27
39
  ```
28
40
 
29
- ## Setup Custom Assets
30
-
31
41
  Run the assets generator to set up your own TailwindCSS build:
32
42
 
33
43
  ```bash
@@ -41,7 +51,7 @@ This:
41
51
  4. Registers Plutonium's Stimulus controllers
42
52
  5. Updates Plutonium config to use your assets
43
53
 
44
- ## TailwindCSS Configuration
54
+ ## TailwindCSS configuration
45
55
 
46
56
  ### Generated Config
47
57
 
@@ -53,9 +63,7 @@ const plutoniumTailwindConfig = require(`${plutoniumGemPath}/tailwind.options.js
53
63
 
54
64
  module.exports = {
55
65
  darkMode: plutoniumTailwindConfig.darkMode,
56
- plugins: [
57
- // Add your plugins here
58
- ].concat(plutoniumTailwindConfig.plugins),
66
+ plugins: [].concat(plutoniumTailwindConfig.plugins),
59
67
  theme: plutoniumTailwindConfig.merge(
60
68
  plutoniumTailwindConfig.theme,
61
69
  {
@@ -72,30 +80,15 @@ module.exports = {
72
80
 
73
81
  ### Customizing Colors
74
82
 
75
- Override Plutonium's color palette:
76
-
77
83
  ```javascript
78
- // tailwind.config.js
79
84
  theme: plutoniumTailwindConfig.merge(
80
85
  plutoniumTailwindConfig.theme,
81
86
  {
82
87
  extend: {
83
88
  colors: {
84
89
  primary: {
85
- 50: '#eff6ff',
86
- 100: '#dbeafe',
87
- 200: '#bfdbfe',
88
- 300: '#93c5fd',
89
- 400: '#60a5fa',
90
- 500: '#3b82f6', // Your brand color
91
- 600: '#2563eb',
92
- 700: '#1d4ed8',
93
- 800: '#1e40af',
94
- 900: '#1e3a8a',
95
- 950: '#172554',
96
- },
97
- secondary: {
98
- // Your secondary palette
90
+ 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a',
91
+ // ...
99
92
  },
100
93
  },
101
94
  },
@@ -105,8 +98,6 @@ theme: plutoniumTailwindConfig.merge(
105
98
 
106
99
  ### Default Color Palette
107
100
 
108
- Plutonium includes these semantic colors:
109
-
110
101
  | Color | Usage |
111
102
  |-------|-------|
112
103
  | `primary` | Primary brand color (turquoise by default) |
@@ -119,24 +110,9 @@ Plutonium includes these semantic colors:
119
110
 
120
111
  ### Dark Mode
121
112
 
122
- Plutonium uses `selector` strategy for dark mode:
123
-
124
- ```javascript
125
- darkMode: "selector"
126
- ```
127
-
128
- Toggle dark mode by adding/removing the `dark` class on `<html>`:
129
-
130
- ```javascript
131
- // Toggle dark mode
132
- document.documentElement.classList.toggle('dark');
133
- ```
134
-
135
- Plutonium includes a color mode selector component that handles this automatically.
136
-
137
- ## CSS Imports
113
+ Plutonium uses `selector` strategy for dark mode. Toggle by adding/removing `dark` class on `<html>`. Plutonium includes a color mode selector component that handles this automatically.
138
114
 
139
- ### Application Stylesheet
115
+ ## CSS imports
140
116
 
141
117
  ```css
142
118
  /* app/assets/stylesheets/application.tailwind.css */
@@ -148,20 +124,12 @@ Plutonium includes a color mode selector component that handles this automatical
148
124
  /* Your custom styles */
149
125
  ```
150
126
 
151
- ### What Plutonium CSS Includes
127
+ Plutonium CSS includes: core utility classes, EasyMDE (markdown editor) styles, Slim Select styles, International telephone input styles, Flatpickr (date picker) styles.
152
128
 
153
- - Core utility classes
154
- - EasyMDE (markdown editor) styles
155
- - Slim Select styles
156
- - International telephone input styles
157
- - Flatpickr (date picker) styles
158
-
159
- ## Component Themes (Phlexi)
129
+ ## Phlexi component themes
160
130
 
161
131
  Plutonium components use a theme system based on Phlexi for customizing Form, Display, and Table components. Each component type has a theme class with named style tokens.
162
132
 
163
- > **Note:** This is different from the CSS design token system (`.pu-*` classes). For pre-built component classes, see `plutonium-theming`.
164
-
165
133
  ### Form Theme
166
134
 
167
135
  ```ruby
@@ -170,28 +138,15 @@ class PostDefinition < ResourceDefinition
170
138
  class Theme < Plutonium::UI::Form::Theme
171
139
  def self.theme
172
140
  super.merge({
173
- # Container
174
141
  base: "bg-white dark:bg-gray-800 shadow-md rounded-lg p-6",
175
142
  fields_wrapper: "grid grid-cols-2 gap-6",
176
143
  actions_wrapper: "flex justify-end mt-6 space-x-2",
177
-
178
- # Labels
179
144
  label: "block mb-2 text-base font-bold",
180
145
  invalid_label: "text-red-700 dark:text-red-500",
181
- valid_label: "text-green-700 dark:text-green-500",
182
- neutral_label: "text-gray-500 dark:text-gray-400",
183
-
184
- # Inputs
185
146
  input: "w-full p-2 border rounded-md shadow-sm",
186
147
  invalid_input: "bg-red-50 border-red-500 text-red-900",
187
- valid_input: "bg-green-50 border-green-500 text-green-900",
188
- neutral_input: "border-gray-300 dark:border-gray-600",
189
-
190
- # Hints & Errors
191
148
  hint: "mt-2 text-sm text-gray-500",
192
149
  error: "mt-2 text-sm text-red-600",
193
-
194
- # Buttons
195
150
  button: "px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700",
196
151
  })
197
152
  end
@@ -243,61 +198,13 @@ end
243
198
 
244
199
  ### Theme Keys Reference
245
200
 
246
- #### Form Theme Keys
247
-
248
- | Key | Description |
249
- |-----|-------------|
250
- | `base` | Form container |
251
- | `fields_wrapper` | Grid wrapper for fields |
252
- | `actions_wrapper` | Submit button container |
253
- | `wrapper` | Individual field wrapper |
254
- | `inner_wrapper` | Inner field wrapper |
255
- | `label` | Label base styles |
256
- | `invalid_label` | Label when field invalid |
257
- | `valid_label` | Label when field valid |
258
- | `neutral_label` | Label default state |
259
- | `input` | Input base styles |
260
- | `invalid_input` | Input when invalid |
261
- | `valid_input` | Input when valid |
262
- | `neutral_input` | Input default state |
263
- | `hint` | Hint text |
264
- | `error` | Error message |
265
- | `button` | Submit button |
266
- | `checkbox` | Checkbox input |
267
- | `select` | Select dropdown |
268
-
269
- #### Display Theme Keys
270
-
271
- | Key | Description |
272
- |-----|-------------|
273
- | `fields_wrapper` | Grid wrapper |
274
- | `label` | Field label |
275
- | `description` | Field description |
276
- | `string` | String values |
277
- | `text` | Text values |
278
- | `link` | URL links |
279
- | `email` | Email links |
280
- | `phone` | Phone links |
281
- | `markdown` | Markdown content |
282
- | `json` | JSON display |
283
-
284
- #### Table Theme Keys
285
-
286
- | Key | Description |
287
- |-----|-------------|
288
- | `wrapper` | Table container |
289
- | `base` | Table element |
290
- | `header` | Header row |
291
- | `header_cell` | Header cell |
292
- | `body_row` | Body row |
293
- | `body_cell` | Body cell |
294
- | `sort_icon` | Sort indicator |
295
-
296
- ## Using Tokens in Components
297
-
298
- ### The `tokens` Helper
299
-
300
- Conditionally apply classes:
201
+ **Form theme keys:** `base`, `fields_wrapper`, `actions_wrapper`, `wrapper`, `inner_wrapper`, `label`, `invalid_label`, `valid_label`, `neutral_label`, `input`, `invalid_input`, `valid_input`, `neutral_input`, `hint`, `error`, `button`, `checkbox`, `select`.
202
+
203
+ **Display theme keys:** `fields_wrapper`, `label`, `description`, `string`, `text`, `link`, `email`, `phone`, `markdown`, `json`.
204
+
205
+ **Table theme keys:** `wrapper`, `base`, `header`, `header_cell`, `body_row`, `body_cell`, `sort_icon`.
206
+
207
+ ### Using `tokens` and `classes` helpers
301
208
 
302
209
  ```ruby
303
210
  class MyComponent < Plutonium::UI::Component::Base
@@ -310,9 +217,7 @@ class MyComponent < Plutonium::UI::Component::Base
310
217
  "base-class",
311
218
  active?: "bg-primary-500 text-white",
312
219
  inactive?: "bg-gray-200 text-gray-700"
313
- )) {
314
- "Content"
315
- }
220
+ )) { "Content" }
316
221
  end
317
222
 
318
223
  private
@@ -322,27 +227,19 @@ class MyComponent < Plutonium::UI::Component::Base
322
227
  end
323
228
  ```
324
229
 
325
- ### The `classes` Helper
326
-
327
- Returns a hash suitable for splatting:
328
-
329
230
  ```ruby
330
231
  div(**classes("p-4", "rounded", active?: "ring-2")) { }
331
232
  # => <div class="p-4 rounded ring-2">
332
- ```
333
-
334
- ### Conditional Tokens with Hash
335
233
 
336
- ```ruby
337
234
  tokens(
338
235
  "base",
339
236
  condition?: { then: "if-true", else: "if-false" }
340
237
  )
341
238
  ```
342
239
 
343
- ## Stimulus Controllers
240
+ ## Stimulus controllers
344
241
 
345
- Plutonium includes Stimulus controllers. Register them in your application:
242
+ Register Plutonium's Stimulus controllers in your application:
346
243
 
347
244
  ```javascript
348
245
  // app/javascript/controllers/index.js
@@ -354,7 +251,7 @@ registerControllers(application)
354
251
  // Your custom controllers...
355
252
  ```
356
253
 
357
- ### Available Controllers
254
+ ### Available controllers
358
255
 
359
256
  - `color-mode` - Dark/light mode toggle
360
257
  - `form` - Form handling (pre-submit, etc.)
@@ -364,9 +261,7 @@ registerControllers(application)
364
261
  - `easymde` - Markdown editor
365
262
  - Various UI controllers
366
263
 
367
- ## Custom Stimulus Controllers
368
-
369
- Add your own controllers alongside Plutonium's:
264
+ ### Custom Stimulus controllers
370
265
 
371
266
  ```javascript
372
267
  // app/javascript/controllers/custom_controller.js
@@ -379,31 +274,26 @@ export default class extends Controller {
379
274
  }
380
275
  ```
381
276
 
382
- Register in your index:
277
+ Register:
383
278
 
384
279
  ```javascript
385
280
  import CustomController from "./custom_controller"
386
281
  application.register("custom", CustomController)
387
282
  ```
388
283
 
389
- ## Typography
284
+ ### Typography
390
285
 
391
- Plutonium uses Lato font by default. The layout loads it from Google Fonts.
392
-
393
- Override in your layout:
286
+ Plutonium uses Lato by default. Override:
394
287
 
395
288
  ```ruby
396
289
  class MyLayout < Plutonium::UI::Layout::ResourceLayout
397
290
  def render_fonts
398
- # Your custom fonts
399
291
  link(rel: "preconnect", href: "https://fonts.googleapis.com")
400
292
  link(href: "https://fonts.googleapis.com/css2?family=Inter&display=swap", rel: "stylesheet")
401
293
  end
402
294
  end
403
295
  ```
404
296
 
405
- Update Tailwind config:
406
-
407
297
  ```javascript
408
298
  theme: {
409
299
  fontFamily: {
@@ -413,9 +303,210 @@ theme: {
413
303
  }
414
304
  ```
415
305
 
416
- ## Related Skills
306
+ ## Design tokens and theming
307
+
308
+ Plutonium uses a comprehensive CSS design token system for consistent, themeable UI components — CSS custom properties and reusable component classes that automatically support light and dark modes. Tokens are defined in `src/css/tokens.css`.
309
+
310
+ ### Surface & background colors
311
+
312
+ ```css
313
+ /* Light */
314
+ --pu-body: #f8fafc;
315
+ --pu-surface: #ffffff;
316
+ --pu-surface-alt: #f1f5f9;
317
+ --pu-surface-raised: #ffffff;
318
+ --pu-surface-overlay: rgba(255, 255, 255, 0.95);
319
+
320
+ /* Dark (.dark class) */
321
+ --pu-body: #0f172a;
322
+ --pu-surface: #1e293b;
323
+ --pu-surface-alt: #0f172a;
324
+ --pu-surface-raised: #334155;
325
+ --pu-surface-overlay: rgba(30, 41, 59, 0.95);
326
+ ```
327
+
328
+ ### Text colors
329
+
330
+ ```css
331
+ /* Light */
332
+ --pu-text: #0f172a;
333
+ --pu-text-muted: #64748b;
334
+ --pu-text-subtle: #94a3b8;
335
+
336
+ /* Dark */
337
+ --pu-text: #f8fafc;
338
+ --pu-text-muted: #94a3b8;
339
+ --pu-text-subtle: #64748b;
340
+ ```
341
+
342
+ ### Border colors
343
+
344
+ ```css
345
+ --pu-border: #e2e8f0;
346
+ --pu-border-muted: #f1f5f9;
347
+ --pu-border-strong: #cbd5e1;
348
+ ```
349
+
350
+ ### Form tokens
351
+
352
+ ```css
353
+ --pu-input-bg: #ffffff;
354
+ --pu-input-border: #e2e8f0;
355
+ --pu-input-focus-ring: theme(colors.primary.500);
356
+ --pu-input-placeholder: #94a3b8;
357
+ ```
358
+
359
+ ### Card tokens
360
+
361
+ ```css
362
+ --pu-card-bg: #ffffff;
363
+ --pu-card-border: #e2e8f0;
364
+ ```
365
+
366
+ ### Shadows
367
+
368
+ ```css
369
+ --pu-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.03), 0 1px 3px 0 rgb(0 0 0 / 0.05);
370
+ --pu-shadow-md: 0 2px 4px -1px rgb(0 0 0 / 0.04), 0 4px 6px -1px rgb(0 0 0 / 0.06);
371
+ --pu-shadow-lg: 0 4px 6px -2px rgb(0 0 0 / 0.03), 0 10px 15px -3px rgb(0 0 0 / 0.08);
372
+ ```
373
+
374
+ ### Radius, spacing, transitions
375
+
376
+ ```css
377
+ --pu-radius-sm: 0.375rem;
378
+ --pu-radius-md: 0.5rem;
379
+ --pu-radius-lg: 0.75rem;
380
+ --pu-radius-xl: 1rem;
381
+ --pu-radius-full: 9999px;
382
+
383
+ --pu-space-xs: 0.25rem;
384
+ --pu-space-sm: 0.5rem;
385
+ --pu-space-md: 1rem;
386
+ --pu-space-lg: 1.5rem;
387
+ --pu-space-xl: 2rem;
388
+
389
+ --pu-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
390
+ --pu-transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);
391
+ --pu-transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
392
+ ```
393
+
394
+ ### Customizing tokens
395
+
396
+ ```css
397
+ /* app/assets/stylesheets/application.tailwind.css */
398
+ @import "gem:plutonium/src/css/plutonium.css";
399
+ @import "tailwindcss";
400
+
401
+ :root {
402
+ --pu-surface: #fafafa;
403
+ --pu-border: #d1d5db;
404
+ }
405
+
406
+ .dark {
407
+ --pu-surface: #111827;
408
+ --pu-border: #374151;
409
+ }
410
+ ```
411
+
412
+ ## Component classes
413
+
414
+ Component classes are defined in `src/css/components.css` for ready-to-use styled components.
415
+
416
+ ### Buttons
417
+
418
+ ```
419
+ .pu-btn /* Base */
420
+ .pu-btn-md / .pu-btn-sm / .pu-btn-xs
421
+ .pu-btn-primary / -secondary / -danger / -success / -warning / -info / -accent
422
+ .pu-btn-ghost / -outline
423
+ .pu-btn-soft-primary / -soft-danger / ...
424
+ ```
425
+
426
+ ```erb
427
+ <%= form.submit "Save", class: "pu-btn pu-btn-md pu-btn-primary" %>
428
+ ```
429
+
430
+ ### Inputs & labels
431
+
432
+ ```
433
+ .pu-input / .pu-input-invalid / .pu-input-valid
434
+ .pu-label / .pu-label-required
435
+ .pu-hint / .pu-error
436
+ .pu-checkbox
437
+ ```
438
+
439
+ ### Cards, panels, tables, toolbar, empty state
440
+
441
+ ```
442
+ .pu-card / .pu-card-body
443
+ .pu-panel-header / .pu-panel-title / .pu-panel-description
444
+ .pu-table-wrapper / .pu-table / .pu-table-header / .pu-table-header-cell /
445
+ .pu-table-body-row / .pu-table-body-row-selected / .pu-table-body-cell / .pu-selection-cell
446
+ .pu-toolbar / .pu-toolbar-text / .pu-toolbar-actions
447
+ .pu-empty-state / .pu-empty-state-icon / .pu-empty-state-title / .pu-empty-state-description
448
+ ```
449
+
450
+ ### Ruby component class constants
451
+
452
+ The `Plutonium::UI::ComponentClasses` module (in `lib/plutonium/ui/component_classes.rb`) provides Ruby constants for consistent class usage:
453
+
454
+ ```ruby
455
+ ComponentClasses::Button.classes(variant: :primary, size: :default, soft: false)
456
+ # => "pu-btn pu-btn-md pu-btn-primary"
457
+
458
+ ComponentClasses::Form::INPUT # "pu-input"
459
+ ComponentClasses::Form::LABEL # "pu-label"
460
+ ComponentClasses::Table::WRAPPER # "pu-table-wrapper"
461
+ ComponentClasses::Card::BASE # "pu-card"
462
+ ```
463
+
464
+ ### Using tokens in templates
465
+
466
+ ```erb
467
+ <h1 class="text-[var(--pu-text)]">Title</h1>
468
+ <p class="text-[var(--pu-text-muted)]">Description</p>
469
+
470
+ <div class="bg-[var(--pu-surface)] border border-[var(--pu-border)] rounded-[var(--pu-radius-lg)]">
471
+ Content
472
+ </div>
473
+ ```
474
+
475
+ ```ruby
476
+ class MyComponent < Plutonium::UI::Component::Base
477
+ def view_template
478
+ div(class: "bg-[var(--pu-surface)] border border-[var(--pu-border)] rounded-[var(--pu-radius-lg)]",
479
+ style: "box-shadow: var(--pu-shadow-md)") {
480
+ h2(class: "text-lg font-semibold text-[var(--pu-text)]") { "Title" }
481
+ p(class: "text-[var(--pu-text-muted)]") { "Description" }
482
+ }
483
+ end
484
+ end
485
+ ```
486
+
487
+ ### Migration from hardcoded classes
488
+
489
+ | Old | New |
490
+ |-----|-----|
491
+ | `text-gray-900 dark:text-white` | `text-[var(--pu-text)]` |
492
+ | `text-gray-500 dark:text-gray-400` | `text-[var(--pu-text-muted)]` |
493
+ | `bg-gray-50 dark:bg-gray-700` | `bg-[var(--pu-surface)]` |
494
+ | `border-gray-300 dark:border-gray-600` | `border-[var(--pu-border)]` |
495
+ | long input class | `pu-input` |
496
+ | `block mb-2 text-sm font-semibold ...` | `pu-label` |
497
+ | `text-red-600 dark:text-red-400` | `pu-error` |
498
+ | long button class | `pu-btn pu-btn-md pu-btn-primary` |
499
+
500
+ ## Gotchas
501
+
502
+ - **Always register Stimulus controllers** — Plutonium controllers won't work without `registerControllers(application)`. Custom controllers must also be registered.
503
+ - **Use `plutoniumTailwindConfig.merge`** when overriding theme — plain object merge drops Plutonium's defaults.
504
+ - **Dark mode uses `selector`**, not `class`. Toggle via `document.documentElement.classList.toggle('dark')`.
505
+ - **Tokens are CSS variables**, not Tailwind keys — use `bg-[var(--pu-surface)]`, not `bg-pu-surface`.
506
+ - **Prefer `.pu-*` component classes and tokens** over hardcoded `gray-*/dark:gray-*` pairs — they switch automatically with dark mode.
507
+
508
+ ## Related skills
417
509
 
418
- - `plutonium-theming` - CSS design tokens and component classes (`.pu-*`)
419
510
  - `plutonium-views` - Layout customization
420
- - `plutonium-forms` - Form theming
511
+ - `plutonium-forms` - Form theming and custom inputs
421
512
  - `plutonium-installation` - Initial setup