nitro_kit 2.0.0.alpha.3 → 2.0.0.alpha.5

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +194 -0
  3. data/README.md +34 -23
  4. data/STYLE_GUIDE.md +10 -0
  5. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
  6. data/app/assets/stylesheets/nitro_kit.css +1220 -572
  7. data/app/components/nitro_kit/accordion.rb +1 -19
  8. data/app/components/nitro_kit/alert.rb +1 -11
  9. data/app/components/nitro_kit/app_navigation.rb +44 -9
  10. data/app/components/nitro_kit/appearance_picker.rb +10 -1
  11. data/app/components/nitro_kit/badge.rb +13 -4
  12. data/app/components/nitro_kit/combobox.rb +1 -17
  13. data/app/components/nitro_kit/command_palette.rb +1 -0
  14. data/app/components/nitro_kit/dropzone.rb +1 -1
  15. data/app/components/nitro_kit/form_builder.rb +1 -1
  16. data/app/components/nitro_kit/select.rb +1 -14
  17. data/app/components/nitro_kit/tabs.rb +1 -0
  18. data/app/javascript/controllers/nk/command_palette_controller.js +4 -0
  19. data/app/javascript/controllers/nk/tabs_controller.js +13 -1
  20. data/docs/agent_guide.md +85 -138
  21. data/docs/agent_native_spec.md +72 -400
  22. data/docs/browser_support.md +77 -188
  23. data/docs/component_contracts.md +56 -36
  24. data/docs/customization.md +675 -89
  25. data/docs/hotwire.md +61 -99
  26. data/docs/initialization_prompt.md +33 -57
  27. data/docs/migration_1_to_2.md +34 -18
  28. data/docs/patterns/application_foundation.md +45 -121
  29. data/docs/patterns/crud_resource.md +44 -129
  30. data/docs/patterns/destructive_action.md +45 -102
  31. data/docs/patterns/flash_and_toast.md +18 -36
  32. data/docs/patterns/inline_edit.md +21 -44
  33. data/docs/patterns/queryable_collection.md +65 -161
  34. data/docs/patterns/resource_form.md +33 -81
  35. data/docs/rails_conventions.md +38 -78
  36. data/docs/rails_integration.md +96 -474
  37. data/lib/nitro_kit/installation.rb +19 -3
  38. data/lib/nitro_kit/migration_inventory.rb +15 -8
  39. data/lib/nitro_kit/version.rb +1 -1
  40. data/plugins/nitro-kit/.codex-plugin/plugin.json +4 -4
  41. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +12 -0
  42. data/plugins/nitro-kit/skills/nitro-kit-hotwire/agents/openai.yaml +1 -1
  43. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +22 -0
  44. data/plugins/nitro-kit/skills/nitro-kit-rails/agents/openai.yaml +1 -1
  45. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +33 -11
  46. data/plugins/nitro-kit/skills/nitro-kit-ui/agents/openai.yaml +1 -1
  47. data/src/stylesheets/nitro_kit/components/accordion.css +4 -4
  48. data/src/stylesheets/nitro_kit/components/alert.css +20 -29
  49. data/src/stylesheets/nitro_kit/components/app_navigation.css +74 -2
  50. data/src/stylesheets/nitro_kit/components/app_shell.css +3 -3
  51. data/src/stylesheets/nitro_kit/components/appearance_picker.css +20 -8
  52. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  53. data/src/stylesheets/nitro_kit/components/avatar.css +12 -8
  54. data/src/stylesheets/nitro_kit/components/avatar_stack.css +12 -10
  55. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  56. data/src/stylesheets/nitro_kit/components/button.css +42 -25
  57. data/src/stylesheets/nitro_kit/components/card.css +2 -2
  58. data/src/stylesheets/nitro_kit/components/checkbox.css +29 -46
  59. data/src/stylesheets/nitro_kit/components/checkbox_group.css +3 -3
  60. data/src/stylesheets/nitro_kit/components/combobox.css +6 -6
  61. data/src/stylesheets/nitro_kit/components/command_palette.css +62 -4
  62. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  63. data/src/stylesheets/nitro_kit/components/control_group.css +9 -9
  64. data/src/stylesheets/nitro_kit/components/danger_zone.css +6 -6
  65. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  66. data/src/stylesheets/nitro_kit/components/details_table.css +7 -5
  67. data/src/stylesheets/nitro_kit/components/dialog.css +3 -3
  68. data/src/stylesheets/nitro_kit/components/dropdown.css +7 -7
  69. data/src/stylesheets/nitro_kit/components/dropzone.css +7 -7
  70. data/src/stylesheets/nitro_kit/components/empty_state.css +4 -4
  71. data/src/stylesheets/nitro_kit/components/field.css +2 -2
  72. data/src/stylesheets/nitro_kit/components/fieldset.css +2 -2
  73. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  74. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  75. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  76. data/src/stylesheets/nitro_kit/components/input.css +33 -6
  77. data/src/stylesheets/nitro_kit/components/page_header.css +4 -4
  78. data/src/stylesheets/nitro_kit/components/pagination.css +3 -3
  79. data/src/stylesheets/nitro_kit/components/pagination_bar.css +2 -2
  80. data/src/stylesheets/nitro_kit/components/palette.css +140 -72
  81. data/src/stylesheets/nitro_kit/components/progressive_image.css +1 -1
  82. data/src/stylesheets/nitro_kit/components/radio_button.css +28 -33
  83. data/src/stylesheets/nitro_kit/components/radio_button_group.css +6 -6
  84. data/src/stylesheets/nitro_kit/components/rich_text_area.css +2 -2
  85. data/src/stylesheets/nitro_kit/components/select.css +24 -8
  86. data/src/stylesheets/nitro_kit/components/settings_layout.css +5 -5
  87. data/src/stylesheets/nitro_kit/components/settings_section.css +2 -2
  88. data/src/stylesheets/nitro_kit/components/sheet.css +10 -10
  89. data/src/stylesheets/nitro_kit/components/stat_grid.css +1 -1
  90. data/src/stylesheets/nitro_kit/components/switch.css +34 -40
  91. data/src/stylesheets/nitro_kit/components/table.css +8 -4
  92. data/src/stylesheets/nitro_kit/components/tabs.css +5 -1
  93. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  94. data/src/stylesheets/nitro_kit/components/toast.css +11 -58
  95. data/src/stylesheets/nitro_kit/components/toolbar.css +2 -2
  96. data/src/stylesheets/nitro_kit/components/tooltip.css +4 -0
  97. data/src/stylesheets/nitro_kit/components/typeset.css +4 -4
  98. data/src/stylesheets/nitro_kit/reset.css +6 -6
  99. data/src/stylesheets/nitro_kit/tokens.css +497 -74
  100. metadata +1 -2
  101. data/docs/new_app_strategy.md +0 -22
@@ -23,10 +23,26 @@ module NitroKit
23
23
  Each skill resolves the installed gem with `bundle show nitro_kit` and reads
24
24
  its version-matched documentation.
25
25
 
26
+ For greenfield planning or broad product work, check whether Nitro Kit
27
+ catalog or MCP tools are available. When they are, inventory and search the
28
+ catalog by product workflow, retrieve the relevant patterns, and state what
29
+ will be used, adapted, or deferred before implementation. When they are not,
30
+ continue with the bundled skills, documentation, and component contracts;
31
+ catalog access is optional and must never block the work. Nitro Kit Doctor
32
+ verifies integration health, not product completeness.
33
+
34
+ In a greenfield application, run `bin/rails generate phlex:install` and use
35
+ Phlex for the application layout, route views, and reusable UI. In an
36
+ established application, preserve its existing view architecture and
37
+ introduce Phlex and Nitro Kit only at the requested boundary unless an
38
+ application-wide migration is explicitly authorized.
39
+
26
40
  Do not use Nitro Kit 1.x APIs, `nk_*` helpers, copied Nitro components, or
27
- application-owned `controllers/nk`. Compose the installed Phlex Kit and keep
28
- routes, records, authorization, queries, DOM IDs, and server responses in the
29
- application.
41
+ application-owned `controllers/nk`. Include `NitroKit` once in the base Phlex
42
+ component and prefer capitalized Kit methods such as `Button(...)` and
43
+ `Card(...)`; use `.new` only when another API requires a component object.
44
+ Keep routes, records, authorization, queries, DOM IDs, and server responses
45
+ in the application.
30
46
 
31
47
  During migration, replace an existing form control only when Nitro Kit 2 has
32
48
  a genuine semantic and behavioral equivalent. Otherwise preserve the control
@@ -141,13 +141,13 @@ module NitroKit
141
141
 
142
142
  def inspect_table(node)
143
143
  constructor = positional_arguments(node).first
144
- return unless node.name == :render && table_constructor?(constructor) && node.block
144
+ return unless node.name == :render && table_constructor?(constructor)
145
145
 
146
- parameter = node.block.parameters&.parameters&.requireds&.first
147
- return unless parameter.is_a?(Prism::RequiredParameterNode)
148
- return if parameter.name == :table
146
+ receiver_name = required_block_parameter_name(node)
147
+ return unless receiver_name
148
+ return if receiver_name == :table
149
149
 
150
- visitor = TableCompoundVisitor.new(parameter.name)
150
+ visitor = TableCompoundVisitor.new(receiver_name)
151
151
  visitor.visit(node.block.body) if node.block.body
152
152
  issues.concat(visitor.issues)
153
153
  end
@@ -166,14 +166,21 @@ module NitroKit
166
166
  component = constructor.receiver.full_name.delete_prefix("NitroKit::")
167
167
  return unless %w[Dropdown Sheet].include?(component)
168
168
 
169
- parameter = node.block.parameters&.parameters&.requireds&.first
170
- return unless parameter.is_a?(Prism::RequiredParameterNode)
169
+ receiver_name = required_block_parameter_name(node)
170
+ return unless receiver_name
171
171
 
172
- visitor = IconTriggerVisitor.new(parameter.name, component)
172
+ visitor = IconTriggerVisitor.new(receiver_name, component)
173
173
  visitor.visit(node.block.body) if node.block.body
174
174
  issues.concat(visitor.issues)
175
175
  end
176
176
 
177
+ def required_block_parameter_name(node)
178
+ return unless node.block.is_a?(Prism::BlockNode)
179
+
180
+ parameter = node.block.parameters&.parameters&.requireds&.first
181
+ parameter.name if parameter.is_a?(Prism::RequiredParameterNode)
182
+ end
183
+
177
184
  def table_constructor?(node)
178
185
  node.is_a?(Prism::CallNode) && node.name == :new &&
179
186
  node.receiver&.respond_to?(:full_name) && node.receiver.full_name == "NitroKit::Table"
@@ -1,3 +1,3 @@
1
1
  module NitroKit
2
- VERSION = "2.0.0.alpha.3"
2
+ VERSION = "2.0.0.alpha.5"
3
3
  end
@@ -14,15 +14,15 @@
14
14
  "interface": {
15
15
  "displayName": "Nitro Kit",
16
16
  "shortDescription": "Build Rails UI with Nitro Kit and Hotwire",
17
- "longDescription": "Model conventional Rails resources, compose Nitro Kit components, and implement Hotwire interactions from the installed gem version.",
17
+ "longDescription": "Model conventional Rails resources, compose Nitro Kit components, implement Hotwire interactions from the installed gem version, and use optional catalog guidance when available.",
18
18
  "developerName": "Mikkel Malmberg",
19
19
  "category": "Developer",
20
20
  "capabilities": ["Read", "Write"],
21
21
  "websiteURL": "https://nitrokit.dev",
22
22
  "defaultPrompt": [
23
- "Build this Rails feature on the Nitro Kit Rails path.",
24
- "Build this Rails interface with Nitro Kit components.",
25
- "Implement this interaction with Nitro Kit and conventional Hotwire."
23
+ "Build this Rails feature on the Nitro Kit Rails path, using relevant catalog guidance when available.",
24
+ "Build this Rails interface with Nitro Kit components and any relevant catalog guidance available.",
25
+ "Implement this interaction with Nitro Kit, conventional Hotwire, and relevant catalog guidance when available."
26
26
  ]
27
27
  }
28
28
  }
@@ -25,6 +25,18 @@ Make the server response and stable DOM boundary the interaction API. Add Stimul
25
25
  7. Read `NITRO_KIT_ROOT/docs/browser_support.md` for the canonical
26
26
  full/reduced/unavailable no-JavaScript classification.
27
27
 
28
+ ## Discover optional catalog guidance
29
+
30
+ For a broad product workflow, check whether Nitro Kit catalog or MCP tools are
31
+ available. When they are, inventory and search by workflow, retrieve relevant
32
+ patterns, and state what will be used, adapted, or deferred before
33
+ implementation. When they are not, continue with the installed Hotwire recipes,
34
+ component contracts, and source. Catalog access is optional and must never
35
+ block the work or be implied in the result.
36
+
37
+ For a focused interaction, use the catalog only when it is already available
38
+ and a higher-level pattern would materially help.
39
+
28
40
  Do not proceed with a remembered Nitro Kit 1.x API. Do not copy or recreate
29
41
  the installed gem's `nk--*` controllers under `app/javascript/controllers/nk`.
30
42
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Nitro Kit Hotwire"
3
3
  short_description: "Build conventional Nitro Kit Hotwire flows"
4
- default_prompt: "Use $nitro-kit-hotwire to build this interaction with Nitro Kit and conventional Hotwire."
4
+ default_prompt: "Use $nitro-kit-hotwire to build this interaction with Nitro Kit, conventional Hotwire, and relevant catalog guidance when available."
@@ -24,11 +24,30 @@ established application outside the requested scope.
24
24
  9. Read `NITRO_KIT_ROOT/docs/browser_support.md` before claiming an
25
25
  interaction works without JavaScript.
26
26
 
27
+ ## Discover optional catalog guidance
28
+
29
+ For greenfield planning or broad product work, check whether Nitro Kit catalog
30
+ or MCP tools are available. When they are, inventory and search by product
31
+ workflow, retrieve the relevant patterns, and state what will be used, adapted,
32
+ or deferred before implementation. When they are not, continue with the
33
+ installed skills, documentation, component contracts, and source. Catalog
34
+ access is optional and must never block the work or be implied in the result.
35
+
36
+ For a focused Rails change, use the catalog only when it is already available
37
+ and the task could benefit from a higher-level product pattern.
38
+
27
39
  Never use a Nitro Kit 1.x helper, copied component, controller, or Tailwind
28
40
  contract as a substitute for the installed API.
29
41
 
30
42
  ## Follow the application grammar
31
43
 
44
+ - In a greenfield application, run `bin/rails generate phlex:install` and use
45
+ Phlex for the application layout, route-level views, and reusable UI. Render
46
+ `Views::*` objects from controllers instead of creating ERB wrappers around
47
+ Phlex. In an established application, preserve its view architecture and
48
+ introduce Phlex only at the requested boundary unless an application-wide
49
+ migration is explicitly authorized. Judge this from existing view code, not
50
+ the Rails version or apparent age.
32
51
  - Scope tenant data through `Current.team` or `Current.account`; record
33
52
  `Current.user` as the actor.
34
53
  - Put domain behavior on the model before introducing another abstraction.
@@ -63,3 +82,6 @@ Nitro Kit owns component contracts and focused progressive behavior.
63
82
  Run focused model and request tests first. Add a system test only where browser
64
83
  behavior is part of the contract. Assert status, visibility, authorization,
65
84
  and stable DOM boundaries rather than implementation trivia.
85
+
86
+ Nitro Kit Doctor verifies integration and runtime contracts, not product
87
+ completeness. For broad work, verify the agreed workflow coverage separately.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Nitro Kit Rails"
3
3
  short_description: "Build conventional Rails applications with Nitro Kit"
4
- default_prompt: "Use $nitro-kit-rails to build this feature on the Nitro Kit Rails path."
4
+ default_prompt: "Use $nitro-kit-rails to build this feature on the Nitro Kit Rails path, using relevant catalog guidance when available."
@@ -18,32 +18,50 @@ Use the documentation shipped with the application's installed gem as the source
18
18
  classifications in `NITRO_KIT_ROOT/docs/browser_support.md`.
19
19
  7. Inspect the installed component source when constructor or compound-slot details remain unclear. Never guess a component API from memory.
20
20
 
21
+ ## Discover optional catalog guidance
22
+
23
+ For greenfield planning or broad product UI work, check whether Nitro Kit
24
+ catalog or MCP tools are available. When they are, inventory and search by
25
+ product workflow, retrieve the relevant patterns, and state what will be used,
26
+ adapted, or deferred before implementation. When they are not, continue with
27
+ the installed skills, documentation, component contracts, and source. Catalog
28
+ access is optional and must never block the work or be implied in the result.
29
+
30
+ For a focused component change, use the catalog only when it is already
31
+ available and a higher-level composition would materially help.
32
+
21
33
  For a Nitro Kit 1.x migration, read
22
34
  `NITRO_KIT_ROOT/docs/migration_1_to_2.md` before editing. Inventory product
23
35
  flows, behavior, application-owned button classes and Rails button helpers,
24
36
  joined controls, and the existing semantic color, focus, radius, density, and
25
37
  typography tokens first. Capture representative wide and narrow screenshots.
26
- If the Nitro Kit MCP catalog is available, search it by workflow rather than
27
- old component name, then select high-level compositions before replacing
28
- atoms.
38
+ Apply the optional catalog process above, searching by workflow rather than old
39
+ component name and selecting high-level compositions before replacing atoms.
29
40
 
30
41
  If the gem is not installed, say that the skill requires Nitro Kit and follow the application's requested installation scope. Do not substitute APIs from an older Nitro Kit release.
31
42
 
32
43
  ## Build the interface
33
44
 
34
- 1. Reuse the highest-level Nitro block that matches the page region, then compose components inside it.
35
- 2. Include `NitroKit` once in the application's base Phlex component and use capitalized Kit methods such as `Button(...)` and `Card(...)`. Use `.new` only when another API needs a component object. Keep product-specific components under the application's namespace, commonly `UI::*`.
36
- 3. Use `NitroKit::FormBuilder` explicitly with Rails `form_with` for model-backed forms.
37
- 4. Keep routes, authorization, records, query policy, DOM IDs, Turbo boundaries, and response semantics in the application.
38
- 5. Translate the application's semantic theme into documented `--nk-*` properties instead of choosing similar raw palette values. Use `--nk-button-radius` when Button shape intentionally differs from inputs and surfaces.
39
- 6. Verify closed options and required compound declarations before rendering.
40
- 7. For authenticated CRUD, prefer a hybrid `AppShell` with a `Toolbar` that
45
+ 1. In a greenfield application, run `bin/rails generate phlex:install` and use
46
+ Phlex for the application layout, route-level `Views::*`, and reusable UI.
47
+ Do not add ERB wrappers whose only purpose is to render Phlex. In an
48
+ established application with meaningful view conventions, preserve them
49
+ and introduce Phlex and Nitro Kit only at the requested boundary unless an
50
+ application-wide migration is explicitly authorized. Determine this from
51
+ the existing view architecture, not the Rails version or apparent age.
52
+ 2. Reuse the highest-level Nitro block that matches the page region, then compose components inside it.
53
+ 3. Include `NitroKit` once in the application's base Phlex component and use capitalized Kit methods such as `Button(...)` and `Card(...)`. Use `.new` only when another API needs a component object. Keep product-specific components under the application's namespace, commonly `UI::*`.
54
+ 4. Use `NitroKit::FormBuilder` explicitly with Rails `form_with` for model-backed forms.
55
+ 5. Keep routes, authorization, records, query policy, DOM IDs, Turbo boundaries, and response semantics in the application.
56
+ 6. Translate the application's semantic theme into documented `--nk-*` properties instead of choosing similar raw palette values. Use `--nk-button-radius` when Button shape intentionally differs from inputs and surfaces.
57
+ 7. Verify closed options and required compound declarations before rendering.
58
+ 8. For authenticated CRUD, prefer a hybrid `AppShell` with a `Toolbar` that
41
59
  owns the route's single `h1` and basic actions. The shell main region owns
42
60
  one content gutter. Do not repeat that heading in `PageHeader`, or wrap each
43
61
  table, form, and detail region in another Card. At narrow widths, preserve
44
62
  the full title and persistent actions by stacking the trailing actions below
45
63
  the title rather than clipping either region.
46
- 8. For team administration and account settings, read
64
+ 9. For team administration and account settings, read
47
65
  `docs/patterns/application_foundation.md`. Put Settings after an
48
66
  `AppNavigation` spacer and use `SettingsLayout` with plain `SettingsSection`
49
67
  regions instead of a stack of Cards.
@@ -69,4 +87,8 @@ If the gem is not installed, say that the skill requires Nitro Kit and follow th
69
87
 
70
88
  Run the smallest relevant application tests. For component rendering, assert semantic elements and owned `data-nk` or slot attributes rather than private implementation helpers. Exercise invalid and empty states when the UI accepts user input or collections.
71
89
 
90
+ Nitro Kit Doctor verifies integration and runtime contracts, not product
91
+ completeness. Do not use a green Doctor result as proof that every relevant
92
+ screen, state, or catalog workflow has been implemented.
93
+
72
94
  For a migration, Doctor is an inventory, not visual proof. Run representative form and component rendering with `ActiveModel::Translation.raise_on_missing_translations` enabled when the application uses strict i18n. Compare the same representative flows in a browser at wide and narrow widths, exercise keyboard focus, and inspect computed styles for missing application classes, stacked Button content, broken compound corners, double focus rings, clipping, and theme drift. Re-audit rendered native buttons, Rails button helpers, and application-owned button classes before declaring the conversion complete. Search the whole application for `desperately_need_a_class:` and review every result, aiming for zero. Move layout and visual treatment to application-owned wrappers, remove generic class forwarding, accept incidental Nitro defaults, and keep unmatched product UI application-owned; retain only documented external-integration hooks.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Nitro Kit UI"
3
3
  short_description: "Build Rails interfaces with Nitro Kit components"
4
- default_prompt: "Use $nitro-kit-ui to build this Rails interface with the installed Nitro Kit components."
4
+ default_prompt: "Use $nitro-kit-ui to build this Rails interface with the installed Nitro Kit components and any relevant catalog guidance available."
@@ -18,8 +18,8 @@
18
18
  > [data-slot="accordion-trigger"]
19
19
  ) {
20
20
  display: flex;
21
- width: 100%;
22
- min-height: 2.75rem;
21
+ inline-size: 100%;
22
+ min-block-size: var(--nk-control-height-lg);
23
23
  align-items: center;
24
24
  justify-content: space-between;
25
25
  gap: calc(var(--nk-space) * 3);
@@ -58,8 +58,8 @@
58
58
  > [data-slot="accordion-icon"]
59
59
  ) {
60
60
  flex: none;
61
- width: 1rem;
62
- height: 1rem;
61
+ inline-size: var(--nk-icon-size-sm);
62
+ block-size: var(--nk-icon-size-sm);
63
63
  color: var(--nk-color-muted-foreground);
64
64
  transition:
65
65
  color var(--nk-duration-fast) var(--nk-ease),
@@ -3,7 +3,6 @@
3
3
  --_nk-alert-background: var(--nk-color-surface);
4
4
  --_nk-alert-border: var(--nk-color-border);
5
5
  --_nk-alert-foreground: var(--nk-color-foreground);
6
- --_nk-alert-tint-strength: 12%;
7
6
 
8
7
  position: relative;
9
8
  display: grid;
@@ -20,8 +19,8 @@
20
19
  }
21
20
 
22
21
  :where([data-nk="alert"] > [data-slot="alert-title"]) {
23
- font-size: var(--nk-text-base);
24
- font-weight: var(--nk-font-weight-semibold);
22
+ font-size: var(--nk-title-compact-size);
23
+ font-weight: var(--nk-title-compact-weight);
25
24
  line-height: var(--nk-leading-tight);
26
25
  text-wrap: balance;
27
26
  }
@@ -30,11 +29,20 @@
30
29
  grid-template-columns: auto minmax(0, 1fr);
31
30
  }
32
31
 
32
+ /* Centered on the title first line, whatever size the title role
33
+ resolves to, instead of a hand-tuned nudge for one size. */
33
34
  :where([data-nk="alert"] > [data-slot="alert-icon"]) {
35
+ --_nk-alert-icon-size: var(--nk-icon-size-md);
36
+
34
37
  grid-row: 1 / -1;
35
- inline-size: 1.25rem;
36
- block-size: 1.25rem;
37
- margin-block-start: 0.0625rem;
38
+ inline-size: var(--_nk-alert-icon-size);
39
+ block-size: var(--_nk-alert-icon-size);
40
+ margin-block-start: calc(
41
+ (
42
+ var(--nk-title-compact-size) * var(--nk-leading-tight) -
43
+ var(--_nk-alert-icon-size)
44
+ ) / 2
45
+ );
38
46
  }
39
47
 
40
48
  :where(
@@ -54,29 +62,12 @@
54
62
  }
55
63
 
56
64
  @layer nitro-kit.variant {
65
+ /* Colour, tint strength, and appearance switching all come from the shared
66
+ semantic palette, so an Alert and a Toast of the same variant render
67
+ identically. */
57
68
  :where([data-nk="alert"][data-variant]) {
58
- --_nk-alert-background: color-mix(
59
- in oklab,
60
- var(--_nk-palette-tint) var(--_nk-alert-tint-strength),
61
- transparent
62
- );
63
- --_nk-alert-border: color-mix(
64
- in oklab,
65
- var(--_nk-palette-tint) 45%,
66
- transparent
67
- );
68
- --_nk-alert-foreground: var(--_nk-palette-light);
69
- }
70
-
71
- :where([data-theme="dark"] [data-nk="alert"][data-variant]) {
72
- --_nk-alert-foreground: var(--_nk-palette-dark);
73
- --_nk-alert-tint-strength: 20%;
74
- }
75
-
76
- @media (prefers-color-scheme: dark) {
77
- :where(:root:not([data-theme]) [data-nk="alert"][data-variant]) {
78
- --_nk-alert-foreground: var(--_nk-palette-dark);
79
- --_nk-alert-tint-strength: 20%;
80
- }
69
+ --_nk-alert-background: var(--_nk-semantic-surface);
70
+ --_nk-alert-border: var(--_nk-semantic-border);
71
+ --_nk-alert-foreground: var(--_nk-semantic-content);
81
72
  }
82
73
  }
@@ -65,6 +65,58 @@
65
65
  line-height: var(--nk-leading-tight);
66
66
  }
67
67
 
68
+ /* A collapsible label is a row like the items around it: same height,
69
+ same inline padding, symmetric block margins, and the same hover
70
+ treatment, so the disclosure target is as easy to hit as a destination. */
71
+ :where(
72
+ [data-nk="app-navigation"]
73
+ summary[data-slot="app-navigation-section-label"]
74
+ ) {
75
+ display: flex;
76
+ gap: var(--nk-space);
77
+ align-items: center;
78
+ min-block-size: var(--nk-control-height-md);
79
+ margin: var(--nk-space) 0;
80
+ padding: calc(var(--nk-space) * 2) calc(var(--nk-space) * 3);
81
+ list-style: none;
82
+ user-select: none;
83
+ cursor: pointer;
84
+ border-radius: var(--nk-radius-lg);
85
+ transition:
86
+ color var(--nk-duration-normal) var(--nk-ease),
87
+ background-color var(--nk-duration-normal) var(--nk-ease);
88
+ }
89
+
90
+ :where(
91
+ [data-nk="app-navigation"]
92
+ summary[data-slot="app-navigation-section-label"]::-webkit-details-marker
93
+ ) {
94
+ display: none;
95
+ }
96
+
97
+ :where([data-nk="app-navigation"] [data-slot="app-navigation-section-icon"]) {
98
+ flex: none;
99
+ inline-size: var(--nk-icon-size-xs);
100
+ block-size: var(--nk-icon-size-xs);
101
+ transition: transform var(--nk-duration-fast) var(--nk-ease);
102
+ }
103
+
104
+ :where(
105
+ [data-nk="app-navigation"]
106
+ [data-slot="app-navigation-section-disclosure"]:not([open])
107
+ [data-slot="app-navigation-section-icon"]
108
+ ) {
109
+ transform: rotate(-90deg);
110
+ }
111
+
112
+ @media (prefers-reduced-motion: reduce) {
113
+ :where(
114
+ [data-nk="app-navigation"] [data-slot="app-navigation-section-icon"]
115
+ ) {
116
+ transition-duration: 0.01ms;
117
+ }
118
+ }
119
+
68
120
  :where([data-nk="app-navigation"] [data-slot="app-navigation-item"]) {
69
121
  flex: 0 0 auto;
70
122
  min-inline-size: 0;
@@ -80,7 +132,7 @@
80
132
  font-weight: var(--nk-font-weight-medium);
81
133
  line-height: var(--nk-leading-tight);
82
134
  text-decoration: none;
83
- border-radius: var(--nk-radius-md);
135
+ border-radius: var(--nk-radius-lg);
84
136
  transition:
85
137
  color var(--nk-duration-normal) var(--nk-ease),
86
138
  background-color var(--nk-duration-normal) var(--nk-ease);
@@ -96,6 +148,7 @@
96
148
 
97
149
  :where(
98
150
  [data-nk="app-navigation"] [data-slot="app-navigation-item-icon"],
151
+ [data-nk="app-navigation"] [data-slot="app-navigation-item-icon-end"],
99
152
  [data-nk="app-navigation"] [data-slot="app-navigation-item-badge"]
100
153
  ) {
101
154
  flex: 0 0 auto;
@@ -133,13 +186,32 @@
133
186
 
134
187
  @media (hover: hover) {
135
188
  :where(
136
- [data-nk="app-navigation"] [data-slot="app-navigation-item-link"]:hover
189
+ [data-nk="app-navigation"] [data-slot="app-navigation-item-link"]:hover,
190
+ [data-nk="app-navigation"]
191
+ summary[data-slot="app-navigation-section-label"]:hover
137
192
  ) {
138
193
  color: var(--nk-app-shell-sidebar-accent-foreground);
139
194
  background: var(--nk-app-shell-sidebar-accent);
140
195
  }
141
196
  }
142
197
 
198
+ :where(
199
+ [data-nk="app-navigation"]
200
+ summary[data-slot="app-navigation-section-label"]:focus-visible
201
+ ) {
202
+ outline: var(--nk-focus-width) solid var(--nk-color-focus);
203
+ outline-offset: calc(var(--nk-focus-offset) * -1);
204
+ }
205
+
206
+ @media (prefers-reduced-motion: reduce) {
207
+ :where(
208
+ [data-nk="app-navigation"]
209
+ summary[data-slot="app-navigation-section-label"]
210
+ ) {
211
+ transition-duration: 0.01ms;
212
+ }
213
+ }
214
+
143
215
  :where(
144
216
  [data-nk="app-navigation"]
145
217
  [data-slot="app-navigation-item-link"]:focus-visible
@@ -24,7 +24,7 @@
24
24
  color: var(--nk-color-primary-foreground);
25
25
  text-decoration: none;
26
26
  background: var(--nk-color-primary);
27
- border-radius: var(--nk-radius-md);
27
+ border-radius: var(--nk-radius-lg);
28
28
  outline: var(--nk-focus-width) solid var(--nk-color-focus);
29
29
  transform: translateY(calc(-100% - (var(--nk-space) * 4)));
30
30
  transition: transform var(--nk-duration-fast) var(--nk-ease);
@@ -79,7 +79,7 @@
79
79
  cursor: pointer;
80
80
  background: transparent;
81
81
  border: 0;
82
- border-radius: var(--nk-radius-md);
82
+ border-radius: var(--nk-radius-lg);
83
83
  transition:
84
84
  background-color var(--nk-duration-fast) var(--nk-ease),
85
85
  scale var(--nk-duration-fast) var(--nk-ease);
@@ -542,7 +542,7 @@
542
542
  flex-direction: column;
543
543
  inline-size: min(
544
544
  var(--nk-app-shell-sidebar-width),
545
- calc(100vw - (var(--nk-space) * 12))
545
+ calc(100% - (var(--nk-space) * 12))
546
546
  );
547
547
  max-inline-size: 100%;
548
548
  block-size: 100vh;
@@ -10,16 +10,24 @@
10
10
  [data-nk="appearance-picker"] > [data-slot="appearance-picker-legend"]
11
11
  ) {
12
12
  padding: 0;
13
- font-size: var(--nk-text-sm);
14
- font-weight: var(--nk-font-weight-semibold);
13
+ font-size: var(--nk-title-compact-size);
14
+ font-weight: var(--nk-title-compact-weight);
15
15
  }
16
16
 
17
+ /* The gap below the label exists only when there is a label. */
17
18
  :where(
18
19
  [data-nk="appearance-picker"] > [data-slot="appearance-picker-options"]
19
20
  ) {
20
21
  display: flex;
21
22
  flex-wrap: wrap;
22
23
  gap: calc(var(--nk-space) * 3);
24
+ }
25
+
26
+ :where(
27
+ [data-nk="appearance-picker"]
28
+ > [data-slot="appearance-picker-legend"]
29
+ + [data-slot="appearance-picker-options"]
30
+ ) {
23
31
  margin-block-start: calc(var(--nk-space) * 2);
24
32
  }
25
33
 
@@ -30,15 +38,17 @@
30
38
  display: grid;
31
39
  }
32
40
 
41
+ /* Like every other control, the segmented row fills its container and the
42
+ parent owns the available width; the segments share it equally. */
33
43
  :where(
34
44
  [data-nk="appearance-picker"][data-presentation="segmented"]
35
45
  > [data-slot="appearance-picker-options"]
36
46
  ) {
37
- display: inline-flex;
47
+ display: flex;
38
48
  gap: 0;
39
49
  padding: var(--nk-space);
40
50
  background: var(--nk-color-muted);
41
- border-radius: var(--nk-radius-md);
51
+ border-radius: var(--nk-radius-lg);
42
52
  }
43
53
 
44
54
  :where([data-nk="appearance-picker"] [data-slot="appearance-picker-option"]) {
@@ -53,8 +63,8 @@
53
63
  :where(
54
64
  [data-nk="appearance-picker"] [data-slot="appearance-picker-control"]
55
65
  ) {
56
- inline-size: 1.125rem;
57
- block-size: 1.125rem;
66
+ inline-size: var(--nk-choice-size-md);
67
+ block-size: var(--nk-choice-size-md);
58
68
  margin: 0;
59
69
  accent-color: var(--nk-color-primary);
60
70
  cursor: pointer;
@@ -65,8 +75,10 @@
65
75
  [data-slot="appearance-picker-option"]
66
76
  ) {
67
77
  position: relative;
78
+ flex: 1 1 0;
79
+ justify-content: center;
68
80
  padding-inline: calc(var(--nk-space) * 3);
69
- border-radius: calc(var(--nk-radius-md) - var(--nk-space));
81
+ border-radius: calc(var(--nk-radius-lg) - var(--nk-space));
70
82
  }
71
83
 
72
84
  :where(
@@ -90,7 +102,7 @@
90
102
  font: inherit;
91
103
  background: var(--nk-color-surface);
92
104
  border: var(--nk-border-width) solid var(--nk-color-border);
93
- border-radius: var(--nk-radius-md);
105
+ border-radius: var(--nk-radius-lg);
94
106
  }
95
107
  }
96
108
 
@@ -1,7 +1,7 @@
1
1
  @layer nitro-kit.base {
2
2
  :where([data-nk="auth-shell"]) {
3
- width: 100%;
4
- min-width: 0;
3
+ inline-size: 100%;
4
+ min-inline-size: 0;
5
5
  padding-block: calc(var(--nk-space) * 8);
6
6
  padding-inline: calc(var(--nk-space) * 4);
7
7
  }
@@ -1,11 +1,15 @@
1
1
  @layer nitro-kit.base {
2
2
  :where([data-nk="avatar"]) {
3
3
  position: relative;
4
+ /* The ring below carries a z-index, so without a stacking context it
5
+ would paint above overlapping siblings — in a stack, every ring showed
6
+ through the avatar covering it. Isolation keeps each avatar atomic. */
7
+ isolation: isolate;
4
8
  display: inline-grid;
5
9
  flex: none;
6
10
  place-items: center;
7
- width: var(--_nk-avatar-size);
8
- height: var(--_nk-avatar-size);
11
+ inline-size: var(--_nk-avatar-size);
12
+ block-size: var(--_nk-avatar-size);
9
13
  overflow: hidden;
10
14
  font-size: var(--_nk-avatar-font-size);
11
15
  font-weight: var(--nk-font-weight-medium);
@@ -32,8 +36,8 @@
32
36
  [data-nk="avatar"] > [data-slot="avatar-image"]
33
37
  ) {
34
38
  grid-area: 1 / 1;
35
- width: 100%;
36
- height: 100%;
39
+ inline-size: 100%;
40
+ block-size: 100%;
37
41
  border-radius: inherit;
38
42
  }
39
43
 
@@ -60,22 +64,22 @@
60
64
 
61
65
  @layer nitro-kit.size {
62
66
  :where([data-nk="avatar"][data-size="xs"]) {
63
- --_nk-avatar-size: 1.5rem;
67
+ --_nk-avatar-size: var(--nk-avatar-size-xs);
64
68
  --_nk-avatar-font-size: calc(var(--nk-text-xs) * 0.875);
65
69
  }
66
70
 
67
71
  :where([data-nk="avatar"][data-size="sm"]) {
68
- --_nk-avatar-size: 2rem;
72
+ --_nk-avatar-size: var(--nk-avatar-size-sm);
69
73
  --_nk-avatar-font-size: var(--nk-text-xs);
70
74
  }
71
75
 
72
76
  :where([data-nk="avatar"][data-size="md"]) {
73
- --_nk-avatar-size: 3rem;
77
+ --_nk-avatar-size: var(--nk-avatar-size-md);
74
78
  --_nk-avatar-font-size: var(--nk-text-sm);
75
79
  }
76
80
 
77
81
  :where([data-nk="avatar"][data-size="lg"]) {
78
- --_nk-avatar-size: 4rem;
82
+ --_nk-avatar-size: var(--nk-avatar-size-lg);
79
83
  --_nk-avatar-font-size: var(--nk-text-lg);
80
84
  }
81
85
  }