nitro_kit 2.0.0.alpha.3 → 2.0.0.alpha.4

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +179 -0
  3. data/README.md +22 -26
  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 +63 -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 +27 -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 +11 -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/skills/nitro-kit-rails/SKILL.md +7 -0
  41. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +15 -8
  42. data/src/stylesheets/nitro_kit/components/accordion.css +4 -4
  43. data/src/stylesheets/nitro_kit/components/alert.css +20 -29
  44. data/src/stylesheets/nitro_kit/components/app_navigation.css +74 -2
  45. data/src/stylesheets/nitro_kit/components/app_shell.css +3 -3
  46. data/src/stylesheets/nitro_kit/components/appearance_picker.css +20 -8
  47. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  48. data/src/stylesheets/nitro_kit/components/avatar.css +12 -8
  49. data/src/stylesheets/nitro_kit/components/avatar_stack.css +12 -10
  50. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  51. data/src/stylesheets/nitro_kit/components/button.css +42 -25
  52. data/src/stylesheets/nitro_kit/components/card.css +2 -2
  53. data/src/stylesheets/nitro_kit/components/checkbox.css +29 -46
  54. data/src/stylesheets/nitro_kit/components/checkbox_group.css +3 -3
  55. data/src/stylesheets/nitro_kit/components/combobox.css +6 -6
  56. data/src/stylesheets/nitro_kit/components/command_palette.css +62 -4
  57. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  58. data/src/stylesheets/nitro_kit/components/control_group.css +9 -9
  59. data/src/stylesheets/nitro_kit/components/danger_zone.css +6 -6
  60. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  61. data/src/stylesheets/nitro_kit/components/details_table.css +7 -5
  62. data/src/stylesheets/nitro_kit/components/dialog.css +3 -3
  63. data/src/stylesheets/nitro_kit/components/dropdown.css +7 -7
  64. data/src/stylesheets/nitro_kit/components/dropzone.css +7 -7
  65. data/src/stylesheets/nitro_kit/components/empty_state.css +4 -4
  66. data/src/stylesheets/nitro_kit/components/field.css +2 -2
  67. data/src/stylesheets/nitro_kit/components/fieldset.css +2 -2
  68. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  69. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  70. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  71. data/src/stylesheets/nitro_kit/components/input.css +33 -6
  72. data/src/stylesheets/nitro_kit/components/page_header.css +4 -4
  73. data/src/stylesheets/nitro_kit/components/pagination.css +3 -3
  74. data/src/stylesheets/nitro_kit/components/pagination_bar.css +2 -2
  75. data/src/stylesheets/nitro_kit/components/palette.css +140 -72
  76. data/src/stylesheets/nitro_kit/components/progressive_image.css +1 -1
  77. data/src/stylesheets/nitro_kit/components/radio_button.css +28 -33
  78. data/src/stylesheets/nitro_kit/components/radio_button_group.css +6 -6
  79. data/src/stylesheets/nitro_kit/components/rich_text_area.css +2 -2
  80. data/src/stylesheets/nitro_kit/components/select.css +24 -8
  81. data/src/stylesheets/nitro_kit/components/settings_layout.css +5 -5
  82. data/src/stylesheets/nitro_kit/components/settings_section.css +2 -2
  83. data/src/stylesheets/nitro_kit/components/sheet.css +10 -10
  84. data/src/stylesheets/nitro_kit/components/stat_grid.css +1 -1
  85. data/src/stylesheets/nitro_kit/components/switch.css +34 -40
  86. data/src/stylesheets/nitro_kit/components/table.css +8 -4
  87. data/src/stylesheets/nitro_kit/components/tabs.css +5 -1
  88. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  89. data/src/stylesheets/nitro_kit/components/toast.css +11 -58
  90. data/src/stylesheets/nitro_kit/components/toolbar.css +2 -2
  91. data/src/stylesheets/nitro_kit/components/tooltip.css +4 -0
  92. data/src/stylesheets/nitro_kit/components/typeset.css +4 -4
  93. data/src/stylesheets/nitro_kit/reset.css +6 -6
  94. data/src/stylesheets/nitro_kit/tokens.css +497 -74
  95. metadata +1 -2
  96. data/docs/new_app_strategy.md +0 -22
@@ -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.4"
3
3
  end
@@ -29,6 +29,13 @@ contract as a substitute for the installed API.
29
29
 
30
30
  ## Follow the application grammar
31
31
 
32
+ - In a greenfield application, run `bin/rails generate phlex:install` and use
33
+ Phlex for the application layout, route-level views, and reusable UI. Render
34
+ `Views::*` objects from controllers instead of creating ERB wrappers around
35
+ Phlex. In an established application, preserve its view architecture and
36
+ introduce Phlex only at the requested boundary unless an application-wide
37
+ migration is explicitly authorized. Judge this from existing view code, not
38
+ the Rails version or apparent age.
32
39
  - Scope tenant data through `Current.team` or `Current.account`; record
33
40
  `Current.user` as the actor.
34
41
  - Put domain behavior on the model before introducing another abstraction.
@@ -31,19 +31,26 @@ If the gem is not installed, say that the skill requires Nitro Kit and follow th
31
31
 
32
32
  ## Build the interface
33
33
 
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
34
+ 1. In a greenfield application, run `bin/rails generate phlex:install` and use
35
+ Phlex for the application layout, route-level `Views::*`, and reusable UI.
36
+ Do not add ERB wrappers whose only purpose is to render Phlex. In an
37
+ established application with meaningful view conventions, preserve them
38
+ and introduce Phlex and Nitro Kit only at the requested boundary unless an
39
+ application-wide migration is explicitly authorized. Determine this from
40
+ the existing view architecture, not the Rails version or apparent age.
41
+ 2. Reuse the highest-level Nitro block that matches the page region, then compose components inside it.
42
+ 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::*`.
43
+ 4. Use `NitroKit::FormBuilder` explicitly with Rails `form_with` for model-backed forms.
44
+ 5. Keep routes, authorization, records, query policy, DOM IDs, Turbo boundaries, and response semantics in the application.
45
+ 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.
46
+ 7. Verify closed options and required compound declarations before rendering.
47
+ 8. For authenticated CRUD, prefer a hybrid `AppShell` with a `Toolbar` that
41
48
  owns the route's single `h1` and basic actions. The shell main region owns
42
49
  one content gutter. Do not repeat that heading in `PageHeader`, or wrap each
43
50
  table, form, and detail region in another Card. At narrow widths, preserve
44
51
  the full title and persistent actions by stacking the trailing actions below
45
52
  the title rather than clipping either region.
46
- 8. For team administration and account settings, read
53
+ 9. For team administration and account settings, read
47
54
  `docs/patterns/application_foundation.md`. Put Settings after an
48
55
  `AppNavigation` spacer and use `SettingsLayout` with plain `SettingsSection`
49
56
  regions instead of a stack of Cards.
@@ -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
  }
@@ -45,9 +45,9 @@
45
45
  margin-inline-start: 0;
46
46
  }
47
47
 
48
+ /* The chip is one of the heads: same fill as the avatar fallbacks and the
49
+ same hairline ring, drawn as a real border since nothing overlaps it. */
48
50
  :where([data-nk="avatar-stack"] > [data-slot="avatar-stack-overflow"]) {
49
- --_nk-avatar-stack-fill: var(--nk-color-elevated);
50
-
51
51
  /* Preceding avatars are positioned, so the overflow chip must be too or
52
52
  their paint order covers its leading "+". */
53
53
  position: relative;
@@ -56,6 +56,8 @@
56
56
  font-weight: var(--nk-font-weight-semibold);
57
57
  line-height: 1;
58
58
  color: var(--nk-color-muted-foreground);
59
+ border: var(--nk-border-width) solid
60
+ color-mix(in oklab, var(--nk-color-foreground) 10%, transparent);
59
61
  border-radius: var(--nk-radius-full);
60
62
  }
61
63
 
@@ -63,8 +65,8 @@
63
65
  [data-nk="avatar-stack"][data-size="xs"]
64
66
  > [data-slot="avatar-stack-overflow"]
65
67
  ) {
66
- width: 1.5rem;
67
- height: 1.5rem;
68
+ inline-size: var(--nk-avatar-size-xs);
69
+ block-size: var(--nk-avatar-size-xs);
68
70
  font-size: calc(var(--nk-text-xs) * 0.875);
69
71
  }
70
72
 
@@ -72,8 +74,8 @@
72
74
  [data-nk="avatar-stack"][data-size="sm"]
73
75
  > [data-slot="avatar-stack-overflow"]
74
76
  ) {
75
- width: 2rem;
76
- height: 2rem;
77
+ inline-size: var(--nk-avatar-size-sm);
78
+ block-size: var(--nk-avatar-size-sm);
77
79
  font-size: var(--nk-text-xs);
78
80
  }
79
81
 
@@ -81,8 +83,8 @@
81
83
  [data-nk="avatar-stack"][data-size="md"]
82
84
  > [data-slot="avatar-stack-overflow"]
83
85
  ) {
84
- width: 3rem;
85
- height: 3rem;
86
+ inline-size: var(--nk-avatar-size-md);
87
+ block-size: var(--nk-avatar-size-md);
86
88
  font-size: var(--nk-text-sm);
87
89
  }
88
90
 
@@ -90,8 +92,8 @@
90
92
  [data-nk="avatar-stack"][data-size="lg"]
91
93
  > [data-slot="avatar-stack-overflow"]
92
94
  ) {
93
- width: 4rem;
94
- height: 4rem;
95
+ inline-size: var(--nk-avatar-size-lg);
96
+ block-size: var(--nk-avatar-size-lg);
95
97
  font-size: var(--nk-text-base);
96
98
  }
97
99
  }
@@ -1,19 +1,18 @@
1
1
  @layer nitro-kit.base {
2
2
  :where([data-nk="badge"]) {
3
- --_nk-badge-color: var(--_nk-palette-light);
4
3
  --_nk-badge-tint-strength: 20%;
5
4
 
6
5
  display: inline-flex;
7
6
  align-items: center;
8
- max-width: 100%;
7
+ max-inline-size: 100%;
9
8
  font-weight: var(--nk-font-weight-medium);
10
9
  line-height: var(--nk-leading-tight);
11
- color: var(--_nk-badge-color);
10
+ color: var(--_nk-semantic-content);
12
11
  white-space: nowrap;
13
12
  vertical-align: middle;
14
13
  background-color: color-mix(
15
14
  in oklab,
16
- var(--_nk-palette-tint) var(--_nk-badge-tint-strength),
15
+ var(--_nk-semantic-accent) var(--_nk-badge-tint-strength),
17
16
  transparent
18
17
  );
19
18
  border: var(--nk-border-width) solid transparent;
@@ -21,7 +20,7 @@
21
20
  }
22
21
 
23
22
  :where([data-nk="badge"] > [data-slot="badge-label"]) {
24
- min-width: 0;
23
+ min-inline-size: 0;
25
24
  overflow: hidden;
26
25
  white-space: nowrap;
27
26
  text-overflow: ellipsis;
@@ -31,17 +30,22 @@
31
30
  @layer nitro-kit.variant {
32
31
  :where([data-nk="badge"][data-variant="outline"]) {
33
32
  background-color: transparent;
34
- border-color: color-mix(in oklab, var(--_nk-palette-tint) 50%, transparent);
33
+ border-color: color-mix(
34
+ in oklab,
35
+ var(--_nk-semantic-accent) 50%,
36
+ transparent
37
+ );
35
38
  }
36
39
 
40
+ /* Only the tint strength changes with appearance. The colours themselves come
41
+ from the `--nk-palette-*` roles, whose `-content` half already resolves per
42
+ appearance. */
37
43
  :where([data-theme="dark"] [data-nk="badge"]) {
38
- --_nk-badge-color: var(--_nk-palette-dark);
39
44
  --_nk-badge-tint-strength: 40%;
40
45
  }
41
46
 
42
47
  @media (prefers-color-scheme: dark) {
43
48
  :where(:root:not([data-theme]) [data-nk="badge"]) {
44
- --_nk-badge-color: var(--_nk-palette-dark);
45
49
  --_nk-badge-tint-strength: 40%;
46
50
  }
47
51
  }
@@ -60,7 +64,7 @@
60
64
 
61
65
  @layer nitro-kit.size {
62
66
  :where([data-nk="badge"][data-size="xs"]) {
63
- padding: 0 calc(var(--nk-space) * 1.25);
67
+ padding: 0 var(--nk-space);
64
68
  font-size: var(--nk-text-xs);
65
69
  }
66
70