lostcoords-ui 0.4.1 → 0.5.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +303 -89
  3. data/app/assets/tailwind/lostcoords/ui/avatar.css +6 -6
  4. data/app/assets/tailwind/lostcoords/ui/base.css +45 -0
  5. data/app/assets/tailwind/lostcoords/ui/card.css +20 -22
  6. data/app/assets/tailwind/lostcoords/ui/choice.css +39 -0
  7. data/app/assets/tailwind/lostcoords/ui/code-block.css +10 -0
  8. data/app/assets/tailwind/lostcoords/ui/components.css +25 -0
  9. data/app/assets/tailwind/lostcoords/ui/copy-button.css +9 -9
  10. data/app/assets/tailwind/lostcoords/ui/dialog.css +17 -17
  11. data/app/assets/tailwind/lostcoords/ui/disclosure.css +5 -5
  12. data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +43 -43
  13. data/app/assets/tailwind/lostcoords/ui/field.css +2 -2
  14. data/app/assets/tailwind/lostcoords/ui/glass.css +9 -8
  15. data/app/assets/tailwind/lostcoords/ui/layout.css +2 -2
  16. data/app/assets/tailwind/lostcoords/ui/popover.css +14 -14
  17. data/app/assets/tailwind/lostcoords/ui/reading.css +24 -24
  18. data/app/assets/tailwind/lostcoords/ui/sheet.css +60 -60
  19. data/app/assets/tailwind/lostcoords/ui/squircle.css +7 -27
  20. data/app/assets/tailwind/lostcoords/ui/table.css +51 -0
  21. data/app/assets/tailwind/lostcoords/ui/theme.css +194 -225
  22. data/app/assets/tailwind/lostcoords/ui/tooltip.css +50 -0
  23. data/app/assets/tailwind/lostcoords/ui/typography.css +37 -61
  24. data/app/assets/tailwind/lostcoords/ui/ui.css +2 -21
  25. data/app/assets/tailwind/lostcoords/ui/utilities.css +14 -22
  26. data/app/helpers/lostcoords/ui/badge_helper.rb +67 -110
  27. data/app/helpers/lostcoords/ui/button_helper.rb +36 -33
  28. data/app/helpers/lostcoords/ui/card_helper.rb +9 -52
  29. data/app/helpers/lostcoords/ui/choice_helper.rb +83 -0
  30. data/app/helpers/lostcoords/ui/code_block_helper.rb +22 -0
  31. data/app/helpers/lostcoords/ui/copy_button_helper.rb +7 -7
  32. data/app/helpers/lostcoords/ui/description_list_helper.rb +12 -0
  33. data/app/helpers/lostcoords/ui/dialog_helper.rb +6 -13
  34. data/app/helpers/lostcoords/ui/feedback_helper.rb +25 -6
  35. data/app/helpers/lostcoords/ui/form_helper.rb +36 -59
  36. data/app/helpers/lostcoords/ui/glass_helper.rb +7 -7
  37. data/app/helpers/lostcoords/ui/layout_helper.rb +13 -11
  38. data/app/helpers/lostcoords/ui/menu_helper.rb +8 -8
  39. data/app/helpers/lostcoords/ui/navigation_helper.rb +131 -0
  40. data/app/helpers/lostcoords/ui/overlay_helper.rb +17 -0
  41. data/app/helpers/lostcoords/ui/primitives_helper.rb +39 -61
  42. data/app/helpers/lostcoords/ui/reading_helper.rb +2 -2
  43. data/app/helpers/lostcoords/ui/sheet_helper.rb +3 -45
  44. data/app/helpers/lostcoords/ui/structures_helper.rb +55 -64
  45. data/app/helpers/lostcoords/ui/table_helper.rb +11 -0
  46. data/app/helpers/lostcoords/ui/tailwind_helper.rb +13 -4
  47. data/app/helpers/lostcoords/ui/typography_helper.rb +11 -11
  48. data/app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js +23 -0
  49. data/app/javascript/lostcoords/ui/controllers/{disclosure_controller.js → lc_collapsible_controller.js} +3 -3
  50. data/app/javascript/lostcoords/ui/controllers/{confirm_dialog_controller.js → lc_confirm_dialog_controller.js} +4 -4
  51. data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js} +1 -1
  52. data/app/javascript/lostcoords/ui/controllers/{emoji_picker_controller.js → lc_emoji_picker_controller.js} +4 -4
  53. data/app/javascript/lostcoords/ui/controllers/{row_menu_controller.js → lc_menu_controller.js} +3 -3
  54. data/app/javascript/lostcoords/ui/controllers/{ui_popover_controller.js → lc_popover_controller.js} +1 -1
  55. data/app/javascript/lostcoords/ui/controllers/{ui_selection_controller.js → lc_selection_controller.js} +1 -1
  56. data/app/javascript/lostcoords/ui/controllers/{sheet_controller.js → lc_sheet_controller.js} +15 -15
  57. data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +39 -0
  58. data/app/javascript/lostcoords/ui/controllers/{toast_controller.js → lc_toast_controller.js} +2 -2
  59. data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +14 -0
  60. data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +5 -5
  61. data/app/javascript/lostcoords/ui/helpers/context_dialog.js +6 -6
  62. data/app/javascript/lostcoords/ui/helpers/dialog.js +2 -2
  63. data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +7 -7
  64. data/app/javascript/lostcoords/ui/helpers/floating_panel.js +2 -2
  65. data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +4 -4
  66. data/app/javascript/lostcoords/ui/index.js +51 -0
  67. data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +30 -30
  68. data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +18 -0
  69. data/app/views/lostcoords/ui/components/_lc_collapsible.html.erb +5 -0
  70. data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +11 -8
  71. data/app/views/lostcoords/ui/components/_lc_floating_panel.html.erb +8 -0
  72. data/config/helper_renames.json +23 -25
  73. data/config/importmap.rb +10 -2
  74. data/config/locales/en.yml +23 -0
  75. data/config/locales/es.yml +23 -0
  76. data/config/locales/ja.yml +23 -0
  77. data/lib/lostcoords/ui/action_view.rb +5 -3
  78. data/lib/lostcoords/ui/builders.rb +1 -1
  79. data/lib/lostcoords/ui/form_builder.rb +113 -0
  80. data/lib/lostcoords/ui/parts.rb +154 -0
  81. data/lib/lostcoords/ui/tokens.rb +24 -1
  82. data/lib/lostcoords/ui/version.rb +1 -1
  83. metadata +33 -17
  84. data/app/views/lostcoords/ui/components/_form_alert.html.erb +0 -4
  85. data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +0 -5
  86. data/app/views/lostcoords/ui/components/_skeleton.html.erb +0 -4
  87. data/app/views/lostcoords/ui/components/_spinner.html.erb +0 -10
  88. data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +0 -8
  89. /data/app/javascript/lostcoords/ui/controllers/{ui_copy_controller.js → lc_copy_controller.js} +0 -0
  90. /data/app/javascript/lostcoords/ui/controllers/{ui_toggle_controller.js → lc_toggle_controller.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b432f8fc71804447fa32cf3290c7db2556243ef42c6f6849fa7604ed5081870
4
- data.tar.gz: 5cf53096cc4479889f98f4764d6dac7391e787b0a3fe4bb2c21f23e5201d2918
3
+ metadata.gz: 98c4a5462076698b306ba4013842be9e482462b4b1d83a4ecec663eca464b1b8
4
+ data.tar.gz: efd40dc35a5ddac809f39cc56aafb6abb6cae280a5ce72285337563a67083335
5
5
  SHA512:
6
- metadata.gz: aa29b884d7e07cc1204088de38ace739805f0b5d7ff853bdd2196f9787c65ad71c80005e653180840c877e880d21b66159b8c660fcab0846dc9a34715236d8b4
7
- data.tar.gz: 4da2f287536853733eac9ee133fd16b2627d0f54e4c2cfc2bdd2d254588b4ab51f9631191b1558f35ca52b6fbb85ec2caade6674d1987c8eb51cc4ebc07989ec
6
+ metadata.gz: 0c96d71f3a3cf83594571f5e32e3380319533dbaa08f217c1eb483e2a1379f2d7646f9ccad5751eadbfe01171aa86aef943df4ae27888a9741fc4e6d93ac32f0
7
+ data.tar.gz: a3858d8250702539a339c13a7ce6641e43cab5e777bc50a10c9037428f3fd435a9fe2b2ff4b473b60f91dd35f4f78c140c1066518a693f91b2f3007736f60ae5
data/README.md CHANGED
@@ -21,7 +21,7 @@ its gem version, so publishing a library update does not upgrade consumers.
21
21
  ## Install
22
22
 
23
23
  ```ruby
24
- gem "lostcoords-ui", "~> 0.4.0"
24
+ gem "lostcoords-ui", "~> 0.5.0"
25
25
  ```
26
26
 
27
27
  The engine adds its rendering helpers to host views, its JavaScript to the asset paths and its pins to
@@ -49,29 +49,29 @@ Standalone applications compile Tailwind and register Stimulus controllers throu
49
49
  pipeline. `Lostcoords::Ui.root` locates the gem's source assets. Rails applications receive automatic
50
50
  registration; use `require "lostcoords/ui/rails"` explicitly if Rails loads after the components.
51
51
 
52
- ### Stylesheet
52
+ ### Standalone assets
53
53
 
54
- Tailwind CSS v4 is required. Use `tailwindcss-rails` 4.4 or later to compile the component styles and
55
- your app's utilities. Add the gem's entry to `app/assets/tailwind/application.css`:
54
+ Import `app/assets/tailwind/lostcoords/ui/components.css` from the Bundler-resolved gem root
55
+ into your Tailwind build. This entry includes component rules, default tokens and source scanning.
56
+ It does not set body styles, heading defaults, global inline-code styles or document corner radii.
57
+ Map the library's tokens to your app's theme and keep your Tailwind theme declarations after the import.
58
+ The full `ui.css` entry includes those document defaults and remains the Rails default.
56
59
 
57
- ```css
58
- @layer theme, base, layout, components, modules, utilities;
59
-
60
- @import "tailwindcss/theme.css" layer(theme);
61
- @import "tailwindcss/preflight.css" layer(base);
62
- @import "tailwindcss/utilities.css" layer(utilities);
60
+ Start one Stimulus application in the host, then register the controllers you use:
63
61
 
64
- @plugin "@tailwindcss/typography";
62
+ ```javascript
63
+ import { Application } from "@hotwired/stimulus"
64
+ import { registerControllers } from "lostcoords/ui"
65
65
 
66
- @import "../builds/tailwind/lostcoords_ui.css";
66
+ const application = Application.start()
67
+ registerControllers(application, { only: ["lc-copy", "lc-collapsible"] })
67
68
  ```
68
69
 
69
- Tailwind resolves `@import` by path, and Bundler decides where the gem lives. tailwindcss-rails' `tailwindcss:engines`
70
- task, which runs before every `tailwindcss:build` and `tailwindcss:watch` (and so before `assets:precompile`), writes
71
- `app/assets/builds/tailwind/lostcoords_ui.css` with the absolute path to the gem's
72
- `app/assets/tailwind/lostcoords_ui/engine.css`. `app/assets/builds` is already ignored by a Rails app's `.gitignore`
73
- and `.dockerignore`, so there is nothing to commit. The file does not exist until the first Tailwind build, so a lint
74
- that checks imports should skip it.
70
+ Resolve `lostcoords/ui/*` to the gem's `app/javascript/lostcoords/ui/*` and `@hotwired/stimulus` to
71
+ the host's installed package. Build configuration belongs to the host. The entry does not start
72
+ Stimulus or register anything on import. Omit `only` to register all library controllers. Register
73
+ defaults before application overrides. Repeated calls on the same application preserve later
74
+ overrides; unknown controller identifiers raise before registration.
75
75
 
76
76
  The gem's stylesheets write into the `components` and `modules` layers, so the host declares that layer order.
77
77
  The theme defines the `dark:` variant with `html[data-theme="dark"]`. `ui.css` carries its own `@source` lines, so Tailwind
@@ -79,36 +79,83 @@ scans the gem's helpers, views and JavaScript wherever the gem is installed.
79
79
 
80
80
  ### Theming
81
81
 
82
- The engine includes a complete light and dark theme, system fonts, shadows, glass effects and motion
83
- settings. Components work without an app-owned token file. To activate dark mode, set
84
- `data-theme="dark"` on `<html>`. Your app controls the switch and saved preference.
85
-
86
- To customize the theme, add CSS variable overrides after the engine import:
82
+ Colors use [shadcn/ui](https://ui.shadcn.com/docs/theming) names, so a shadcn or tweakcn theme drops in.
83
+ Set `data-theme="dark"` on `<html>` for dark mode; your app owns the switch and saved preference.
84
+ Override any of these after the engine import:
87
85
 
88
86
  ```css
89
87
  :root {
90
- --sky: oklch(52% 0.14 155);
91
- --sky-deep: oklch(46% 0.14 155);
92
- --sky-soft: oklch(96% 0.02 155);
88
+ --primary: oklch(46% 0.14 155);
89
+ --radius: 0.625rem; /* every corner scales from this */
90
+ --lc-density: 0.875; /* semantic spacing and control heights */
93
91
  }
94
92
 
95
93
  html[data-theme="dark"] {
96
- --sky: oklch(75% 0.14 155);
97
- --sky-deep: oklch(70% 0.14 155);
98
- --sky-soft: color-mix(in oklab, var(--sky) 18%, var(--paper));
94
+ --primary: oklch(75% 0.14 155);
99
95
  }
100
96
  ```
101
97
 
102
- Override only the values you need. The [theming guide](https://ui.lostcoords.com/theming.html) covers
103
- colors, fonts, corners, shadows and advanced variables. Defaults live in
104
- `app/assets/tailwind/lostcoords/ui/theme.css`. `Lostcoords::Ui::TOKENS` lists the variables in that file
105
- for reference; apps do not have to define them.
98
+ | Setting | Default | Drives |
99
+ | --- | --- | --- |
100
+ | `--background` `--foreground` | white, near black | page and body text |
101
+ | `--card` `--card-foreground` | light gray | cards |
102
+ | `--popover` `--popover-foreground` | page background | menus, popovers, dialogs |
103
+ | `--primary` `--primary-foreground` | near black, white | primary actions, links, selection, focus |
104
+ | `--secondary` `--secondary-foreground` | gray | secondary buttons |
105
+ | `--muted` `--muted-foreground` | light gray, mid gray | subtle fills; secondary text, hints, placeholders |
106
+ | `--accent` `--accent-foreground` | gray | hovered rows and menu items, inline code |
107
+ | `--destructive` `--success` `--warning` | | status; soft fills such as `bg-success-soft` are generated |
108
+ | `--border` `--input` `--ring` | | dividers, input fills, focus ring |
109
+ | `--radius` | `0.5rem` | `rounded-sm` to `rounded-3xl` and `rounded-pill`; the default keeps Tailwind's own sizes |
110
+ | `--lc-density` | `1` | the semantic spacing and control heights below |
111
+ | `--lc-inset` | `1.25rem` | padding of cards, dialogs and popovers |
112
+ | `--font-sans` `--font-display` `--font-mono` | system | font families |
113
+
114
+ Defaults live in `app/assets/tailwind/lostcoords/ui/theme.css`. `Lostcoords::Ui::TOKENS` lists the
115
+ public colors and fonts. Library-internal settings use the `--lc-` prefix.
116
+
117
+ ### Foundations
118
+
119
+ Components never set outer margin. Layout helpers own the space between things, and components pad
120
+ themselves from `--lc-inset`. Every token is a Tailwind utility:
121
+
122
+ | Spacing | Default | Use |
123
+ | --- | --- | --- |
124
+ | `tight` | 4px | icon and label |
125
+ | `related` | 8px | label and control, button groups |
126
+ | `heading` | 12px | a heading and its content |
127
+ | `content` | 16px | blocks and fields |
128
+ | `inset` | `--lc-inset` | container padding and the gaps between its blocks |
129
+ | `section` | 32px | sections on a page |
130
+
131
+ Use them as `gap-related`, `p-inset`, `py-section` and so on. Setting `--lc-inset` on an element
132
+ changes the padding of every card or dialog inside it.
133
+
134
+ | Type role | Size / line height | Use |
135
+ | --- | --- | --- |
136
+ | `text-display` | 36 / 40px, bold | hero |
137
+ | `text-title` | 30 / 36px, bold | page title |
138
+ | `text-heading` | 20 / 28px, bold | section title |
139
+ | `text-subheading` | 18 / 24px, semibold | card and dialog titles |
140
+ | `text-body` | 16 / 24px | text |
141
+ | `text-small` | 14 / 20px | hints and secondary text |
142
+ | `text-label` | 14 / 20px, medium | labels, buttons, menu items |
143
+ | `text-caption` | 12 / 16px | metadata, table headers |
144
+
145
+ Each role sets size, line height, tracking and weight together. `lc-trim` trims the space above the
146
+ capital letters and below the baseline where the browser supports `text-box`.
147
+
148
+ Buttons, inputs, selects, toggles and icon buttons share heights: `h-control-sm` (32px),
149
+ `h-control-md` (40px) and `h-control-lg` (48px). On touch screens the small and medium sizes grow to
150
+ at least 40 and 44px. Containers use `max-w-page`, `max-w-reading` and `max-w-form`, and the page
151
+ container pads with `px-gutter`.
106
152
 
107
153
  ### Importmap
108
154
 
109
- The engine pins its modules under the host's own keys, `controllers/*` and `helpers/*`, so
110
- `eagerLoadControllersFrom("controllers", application)` registers the gem's controllers with no extra wiring.
111
- A host file with the same name, such as `app/javascript/controllers/sheet_controller.js`, shadows the gem's.
155
+ The engine pins its modules under `lostcoords/ui/*`. Controllers are also pinned under the host's
156
+ `controllers/*` key with an `lc_` prefix, so `eagerLoadControllersFrom("controllers", application)`
157
+ registers them as `lc-dialog`, `lc-menu`, `lc-copy` and so on. A host file with the same name, such as
158
+ `app/javascript/controllers/lc_dialog_controller.js`, replaces the gem's.
112
159
 
113
160
  ## Components
114
161
 
@@ -117,7 +164,17 @@ in its code panel. The API reference reads signatures from the helper definition
117
164
  [API JSON](https://ui.lostcoords.com/api.json) and [llms.txt](https://ui.lostcoords.com/llms.txt)
118
165
  include those signatures and the runnable examples.
119
166
 
120
- Buttons use `primary`, `secondary`, `ghost` or `destructive`, with `sm`, `base` or `lg` sizes.
167
+ Buttons use `primary`, `secondary`, `outline`, `ghost`, `glass` or `destructive`, with `sm`, `md` or `lg` sizes.
168
+ Primary, secondary and destructive buttons have visible filled surfaces. Ghost buttons and the
169
+ `lc_icon_button` toolbar action are transparent at rest and show their full shape on hover.
170
+ Primary, secondary and destructive buttons are flat. Secondary buttons use a soft neutral fill distinct from page and card backgrounds.
171
+ Outline buttons have a transparent fill and a visible edge. Glass buttons use the shared translucent material, rim and shadow for floating controls.
172
+ The default light theme uses white pages and softly shaded cards, with no visible card borders or resting shadows.
173
+ Popover triggers accept `trigger_variant` using the same button variants.
174
+ Button hover and press fills mix 94% of the base background with 6% of its foreground in OKLab.
175
+ Ghost and icon buttons stay transparent at rest and use the secondary base for that calculation.
176
+ Apps can override `--secondary` for the neutral base, or `--lc-button-bg` and `--lc-button-ink` on individual buttons.
177
+ Inputs use `--input` and `--lc-input-hover`.
121
178
  `lc_button_to` preserves Rails' form, HTTP method and CSRF behavior. Block helpers capture ERB content.
122
179
  Use `icon: "plus"` for a leading Heroicon or `icon_position: :end` for a trailing one. Custom icons and
123
180
  spinners use `data-icon="inline-start"` or `data-icon="inline-end"`; buttons adjust that edge's padding.
@@ -137,6 +194,161 @@ theme tokens to restyle the library. `lc_field` yields the input's `id` and vali
137
194
  labels, hints and errors stay connected. Linked cards use `lc_card(href: ...)`; cards containing
138
195
  buttons or fields remain ordinary containers.
139
196
 
197
+ `lc_table` renders a native table with a shaded header and subtle row dividers. Supply the caption, header cells and body in its block.
198
+ Wrap wide tables in an app-owned horizontal scroll region with an accessible name and keyboard focus.
199
+ Use `lc_table_class` when an existing table renderer owns the markup.
200
+
201
+ Cards share one API: `lc_card(padding: :md)`, with `:sm` and `:none` for tighter or custom spacing. Padding sizes set `--lc-inset`, so content inside follows the card.
202
+ Inputs keep their `--input` fill inside cards; set `--lc-field-bg` to change it in one context. Cards provide avatar cutouts, a page-colored background for choice tiles, and a validation border when they contain errors.
203
+ Add `class: "@container"` when a card owns its width and its children use container queries.
204
+ Use `--lc-card-bg`, `--lc-card-border`, `--lc-card-radius` and `--lc-card-shadow` for local appearance overrides.
205
+
206
+ `lc_badge` handles labels, counts and status. Use `class: "rounded-full"` for a rounded status label.
207
+ `lc_icon_badge` handles decorative icon containers, with `shape: :square` or `:circle` and `sm`, `md`, `lg` or `xl` sizes.
208
+ Badge, icon badge, Alert and Toast share `neutral`, `primary`, `success`, `warning` and `destructive` tones.
209
+
210
+ `lc_toggle` renders a pressed button. Use `lc_toggle_group` with `lc_toggle_group_item` for mutually exclusive
211
+ choices and a native submitted value. Navigation uses `lc_button_link_to` and `aria-current`, so links keep their native behavior.
212
+ `lc_collapsible` expands content through a native `<details>` element. `lc_alert` renders persistent inline feedback;
213
+ `lc_toast` adds timed dismissal.
214
+
215
+ ### Parts and slots
216
+
217
+ Compound components yield a builder, so their parts are scoped and discoverable. A card stacks its
218
+ parts with the same gap it pads with:
219
+
220
+ ```erb
221
+ <%= lc_card do |card| %>
222
+ <%= card.header title: "Members", description: "People in this workspace" do %>
223
+ <%= lc_button "Invite", size: :sm %>
224
+ <% end %>
225
+ <%= card.content do %>…<% end %>
226
+ <%= card.footer do %><%= lc_button "Save" %><% end %>
227
+ <% end %>
228
+
229
+ <%= lc_alert "Your card was declined.", title: "Payment failed" do |alert| %>
230
+ <%= alert.actions do %><%= lc_button "Update payment", size: :sm %><% end %>
231
+ <% end %>
232
+
233
+ <%= lc_dialog id: "new-project", title: "Create project", description: "Projects group pages." do |dialog| %>
234
+ …
235
+ <%= dialog.footer do %><%= dialog.close %><%= lc_button "Create" %><% end %>
236
+ <% end %>
237
+ ```
238
+
239
+ Every component root and part carries `data-slot`, plus `data-variant`, `data-size` or `data-tone`
240
+ where they apply, so a context can restyle parts without matching utility classes:
241
+ `[data-slot=card-footer] { justify-content: start }`.
242
+
243
+ ### Forms
244
+
245
+ `lc_form_with` is `form_with` with `Lostcoords::Ui::FormBuilder`. Standard helpers keep their Rails
246
+ signatures and render library controls; `field` adds the label, hint and the attribute's errors, and
247
+ sets `aria-invalid` on the control. Rails' `field_with_errors` wrapper does not affect the layout.
248
+
249
+ ```erb
250
+ <%= lc_form_with model: @project, class: "flex flex-col gap-content" do |form| %>
251
+ <%= form.field :name, hint: "Shown to members" %>
252
+ <%= form.field :email, as: :email_field %>
253
+ <%= form.switch :public %>
254
+ <%= form.submit "Save project" %>
255
+ <% end %>
256
+ ```
257
+
258
+ A symbol `size:` such as `size: :sm` picks the control height; an integer still sets the HTML
259
+ attribute. Pass `builder: Lostcoords::Ui::FormBuilder` to `form_with` directly, or call `lc_field`
260
+ with `error: record.errors.full_messages_for(:name)` when you keep your own builder.
261
+
262
+ `lc_checkbox`, `lc_radio` and `lc_switch` render native inputs with a label and optional hint.
263
+ Checkboxes and switches submit an unchecked value like Rails' `check_box`; pass `unchecked_value: nil`
264
+ to omit it. `lc_choice_group` groups them in a fieldset with a legend, hint and error.
265
+
266
+ ### Navigation and overlays
267
+
268
+ ```erb
269
+ <%= lc_breadcrumb [ [ "Projects", projects_path ], @project.name ] %>
270
+
271
+ <%= lc_tabs id: "project-tabs", label: "Project" do |tabs| %>
272
+ <% tabs.panel "Overview" do %>…<% end %>
273
+ <% tabs.panel "Activity", value: :activity do %>…<% end %>
274
+ <% end %>
275
+
276
+ <%= lc_tooltip "Copy a link", id: "copy-tip" do |tip| %>
277
+ <%= lc_icon_button "Copy link", icon: "link", **tip %>
278
+ <% end %>
279
+
280
+ <%= lc_description_list do |list| %>
281
+ <%= list.item "Owner", @project.owner.name %>
282
+ <%= list.item("Plan") { lc_badge @project.plan } %>
283
+ <% end %>
284
+ ```
285
+
286
+ Tabs follow the ARIA tabs pattern with arrow key, Home and End navigation, and dispatch
287
+ `lc-tabs:change`. Tooltips show on hover and keyboard focus, Escape hides them, and they describe
288
+ their trigger; keep essential information visible. Description lists stack on phones and use two
289
+ columns from `sm`.
290
+
291
+ ```erb
292
+ <%= lc_skip_link %>
293
+
294
+ <%= lc_nav_list label: "Settings" do |nav| %>
295
+ <%= nav.group "Workspace" do %>
296
+ <%= nav.link "General", settings_path, current: current_page?(settings_path) %>
297
+ <%= nav.link "Members", members_path %>
298
+ <% end %>
299
+ <% end %>
300
+
301
+ <%= lc_pager previous: [ "Install", install_path ], next: [ "Theming", theming_path ] %>
302
+
303
+ Read the <%= lc_link "guide", guide_path %>.
304
+ ```
305
+
306
+ `lc_nav_list` marks the current link with `aria-current="page"`; `compact: true` is the quieter
307
+ style for an "On this page" list, and `indent: true` nests a link. `lc_pager` takes `[title, url]`
308
+ for each side and `compact: true` for icon buttons. `lc_link` has `inherit`, `muted` and `primary`
309
+ tones; `lc_link_class` styles links you render yourself. `lc_skip_link` targets `#main` by default.
310
+ Pass `card: true` to `lc_radio` or `lc_checkbox` for selectable tiles; a block adds content, such as a
311
+ preview, under the label.
312
+
313
+ This documentation site is built with these components and two copyable blocks: the application
314
+ header and the documentation layout.
315
+
316
+ `lc_dialog` handles modal content and the mobile sheet layout. Set `detents: "medium large"` or `"large"`
317
+ to choose mobile height stops; the default is `"fit"`. Icon buttons, avatar groups, motion and mobile dialogs
318
+ appear with their parent component examples.
319
+
320
+ ### Upgrade to 0.5
321
+
322
+ 0.5 renames the theme to shadcn colors and moves every library name under the `lc-` prefix. Preview
323
+ the changes with `ruby script/migrate_0_5.rb path/to/app`, then add `--write`. The script handles:
324
+
325
+ | 0.4 | 0.5 |
326
+ | --- | --- |
327
+ | `--surface`, `bg-surface` | `--muted`, `bg-muted`; use `bg-card` for card-like surfaces |
328
+ | `--surface-hover` | `--accent` (hover) or `--secondary` (buttons) |
329
+ | `--muted`, `text-muted` | `--muted-foreground`, `text-muted-foreground` |
330
+ | `--on-primary` | `--primary-foreground` |
331
+ | `--danger`, `tone: :danger` | `--destructive`, `tone: :destructive` |
332
+ | `--f-body`, `font-body` | `--font-sans`, `font-sans` |
333
+ | `--control-bg` | `--input` |
334
+ | `--shadow`, `--scrim`, `--glass-*`, `--ui-*`, `--card-*`, `--button-*` | `--lc-` prefixed |
335
+ | `ui-*`, `glass`, `squircle`, `press`, `avatar` classes | `lc-*` |
336
+ | Stimulus `modal`, `row-menu`, `disclosure`, `sheet`, `toast`, `ui-copy` … | `lc-dialog`, `lc-menu`, `lc-collapsible`, `lc-sheet`, `lc-toast`, `lc-copy` … |
337
+ | `size: :base` | `size: :md` |
338
+ | `lc_heading size: :page / :section / :card` | `size: :title / :heading / :subheading` |
339
+
340
+ Review these by hand:
341
+
342
+ - Card `padding: :default` and `:compact` are now `:md` and `:sm`.
343
+ - `--lc-heading-*`, `--lc-text-*` and `--lc-space-*` overrides become the `--text-*` and `--spacing-*` theme
344
+ tokens, or `--lc-density` and `--lc-inset`.
345
+ - Controls are 40px by default instead of 48px.
346
+ - `lc_sheet_row_class`, `lc_sheet_chip_class`, `lc_sheet_datetime_input_class`, `lc_sheet_text_input_class`,
347
+ `lc_dropdown_trigger_class`, `lc_dialog_surface_class` and the `pill-3d` utility are removed.
348
+ - JavaScript imports from `helpers/*` move to `lostcoords/ui/helpers/*`.
349
+
350
+ The 0.4 README covers older migrations.
351
+
140
352
  ## Copy feedback and motion
141
353
 
142
354
  ```erb
@@ -157,17 +369,31 @@ Default labels are translated in English, Japanese and Spanish. Use `label` (the
157
369
  `copied_label:` and `error_label:` to customize visible text, and `aria: { label: ... }` for a contextual name.
158
370
  Normal button variants, sizes, classes, disabled state and attributes are supported.
159
371
 
160
- The component emits bubbling `ui-copy:copied` (`detail.text`) and `ui-copy:error` (`detail.error`) events
161
- from its wrapper. Listen on an ancestor to connect application behavior. The documentation uses the error
162
- event to reveal clipped source code for manual copying.
372
+ The component emits bubbling `lc-copy:copied` (`detail.text`) and `lc-copy:error` (`detail.error`) events
373
+ from its wrapper. Listen on an ancestor to connect application behavior.
374
+
375
+ ```erb
376
+ <%= lc_code_block "puts 'Hello'", id: "ruby-example", language: :ruby %>
377
+ <%= lc_code_block source, id: "file-example", language: :ruby, title: "app/helpers/greeting_helper.rb" %>
378
+ <%= lc_code_block source, id: "long-example", language: :ruby, max_lines: 5 %>
379
+ ```
163
380
 
164
- Buttons include hover and press feedback. Override `--ui-motion-quick` (140ms), `--ui-motion-control` (180ms),
165
- `--ui-motion-feedback` (160ms), `--ui-press-scale` (0.96) and `--ease-out` in your theme or on a container.
381
+ Code blocks escape plain source, preserve whitespace, and include a floating icon-only copy button.
382
+ The copy control appears on hover or keyboard focus and stays visible on touch devices and during copy feedback.
383
+ Snippets have no header by default; use `title:` for a compact filename or heading. `language:` sets code metadata
384
+ without adding a visible label. Snippets longer
385
+ than `max_lines:` (10 by default) can expand and collapse; use `max_lines: nil` to show all lines.
386
+ Copy failures expand clipped source for manual selection. Labels are translated in English, Japanese and Spanish.
387
+ An optional block receives the raw source and can return highlighted markup from the app's syntax highlighter.
388
+ For selective JavaScript registration, include both `lc-code-block` and `lc-copy`.
389
+
390
+ Buttons include hover and press feedback. Override `--lc-motion-quick` (140ms), `--lc-motion-control` (180ms),
391
+ `--lc-motion-feedback` (160ms), `--lc-press-scale` (0.96) and `--lc-ease-out` in your theme or on a container.
166
392
  Reduced motion removes control movement and makes copy feedback immediate. Your application owns the
167
393
  pending, success and failure states of network actions such as saving a form.
168
394
 
169
395
  [Try the copy button](https://ui.lostcoords.com/components/copy_button.html) ·
170
- [Motion reference](https://ui.lostcoords.com/components/motion.html)
396
+ [Motion reference](https://ui.lostcoords.com/components/foundations.html#motion)
171
397
 
172
398
  ### Icons
173
399
 
@@ -185,18 +411,17 @@ Pass SVG attributes through `options:` and existing path attributes through `pat
185
411
 
186
412
  ### Typography and layout
187
413
 
188
- The engine stylesheet includes shared text styles and layout defaults. Heading level controls document
189
- structure independently of its visual size. Layout parents own the spacing between children, including fields.
190
- Inline `<code>` elements get a shared monospace style with a theme-aware background. `<pre><code>`
191
- blocks keep their block styling.
414
+ Heading level controls document structure independently of its visual size. Layout parents own the
415
+ spacing between children, including fields. Inline `<code>` elements get a shared monospace style;
416
+ `<pre><code>` blocks keep their block styling.
192
417
 
193
418
  ```erb
194
419
  <%= lc_stack gap: :section do %>
195
- <%= lc_heading "Workspace", level: 1, size: :page %>
420
+ <%= lc_heading "Workspace", level: 1, size: :title %>
196
421
  <%= lc_section title: "Members" do %>
197
422
  <%= lc_card do %>
198
423
  <%= lc_stack gap: :related do %>
199
- <%= lc_heading "Design team", level: 3, size: :card %>
424
+ <%= lc_heading "Design team", level: 3, size: :subheading %>
200
425
  <%= lc_text "Three people share this workspace.", tone: :muted %>
201
426
  <% end %>
202
427
  <% end %>
@@ -204,16 +429,12 @@ blocks keep their block styling.
204
429
  <% end %>
205
430
  ```
206
431
 
207
- Heading sizes are `page`, `section` and `card`. Text sizes are `body`, `small` and `caption`,
208
- with `default`, `muted` and `subtle` tones. Stacks and inline groups accept `tight` (4px), `related`
209
- (8px), `heading` (12px), `content` (16px), `inset` (20px) and `section` (32px) gaps at the default
210
- browser font size. `lc_section` leaves 12px below its heading and 16px between content blocks.
211
- `lc_container` provides `page`, `reading` and `form` widths. Only the page container adds gutters.
212
-
213
- Fonts and colours come from the shared theme and your app's overrides. Optional custom properties override the shared defaults:
214
- `--ui-space-section`, `--ui-heading-page-size`, `--ui-heading-page-line-height`, `--ui-page-max`,
215
- `--ui-gutter` and `--ui-reading-measure`. Set them in the host theme or on a container. Japanese headings
216
- inherit `lang="ja"` and use natural letter spacing.
432
+ Heading sizes are `display`, `title`, `heading` and `subheading`. Text sizes are `body`, `small`,
433
+ `label` and `caption`, with `default` and `muted` tones. Pass `trim: true` to trim the text box.
434
+ Stacks and inline groups take the spacing names from [Foundations](#foundations). `lc_section` leaves
435
+ `heading` below its title and `content` between blocks. `lc_container` provides `page`, `reading` and
436
+ `form` widths; only the page container adds gutters. Japanese headings inherit `lang="ja"` and use
437
+ natural letter spacing.
217
438
 
218
439
  [Browse the foundations examples](https://ui.lostcoords.com/components/foundations.html).
219
440
 
@@ -237,7 +458,7 @@ Rails' normal escaping rules; sanitise rendered user content before marking it s
237
458
  <% end %>
238
459
  ```
239
460
 
240
- Override `--ui-reading-size`, `--ui-reading-leading`, `--ui-reading-flow` and `--ui-reading-measure`
461
+ Override `--lc-reading-size`, `--lc-reading-leading`, `--lc-reading-flow` and `--lc-reading-measure`
241
462
  to tune a reading context. Use `not-prose` for embedded components.
242
463
 
243
464
  ### Class helpers
@@ -254,23 +475,20 @@ Class helpers remain available for custom markup. The application owns its seman
254
475
  Generated by `bin/rails lostcoords:ui:builders`; `bin/lint` checks this list against the style helpers.
255
476
 
256
477
  <!-- builders -->
257
- - `lc_icon_badge_class(size: :base, extra: nil)` (BadgeHelper)
258
- - `lc_icon_medallion_class(size: :sm, tone: :accent, extra: nil)` (BadgeHelper)
259
- - `lc_avatar_class(size: :base, ink: false, linked: false, extra: nil)` (BadgeHelper)
478
+ - `lc_icon_badge_class(size: :md, shape: :square, tone: :neutral, extra: nil)` (BadgeHelper)
479
+ - `lc_avatar_class(size: :md, ink: false, linked: false, extra: nil)` (BadgeHelper)
260
480
  - `lc_avatar_stack_class(compact: false, extra: nil)` (BadgeHelper)
261
481
  - `lc_avatar_stack_row_class(extra: nil)` (BadgeHelper)
262
- - `lc_eyebrow_class(extra: nil)` (BadgeHelper)
263
482
  - `lc_badge_class(tone: :neutral, extra: nil)` (BadgeHelper)
264
- - `lc_status_pill_class(tone: :accent, extra: nil)` (BadgeHelper)
265
- - `lc_pill_class(active: false, ghost: false, size: :base, extra: nil)` (BadgeHelper)
266
- - `lc_pill_row_class(extra: nil)` (BadgeHelper)
267
- - `lc_button_class(variant: :default, size: :base, block: false, extra: nil)` (ButtonHelper)
268
- - `lc_icon_button_class(shape: :square, size: :base, extra: nil)` (ButtonHelper)
269
- - `lc_card_class(interactive: false, dense: false, extra: nil)` (CardHelper)
270
- - `lc_card_surface_class(extra: nil)` (CardHelper)
271
- - `lc_form_card_class(padding: :default, extra: nil)` (CardHelper)
483
+ - `lc_toggle_class(active: false, ghost: false, size: :md, extra: nil)` (BadgeHelper)
484
+ - `lc_button_class(variant: :primary, size: :md, block: false, extra: nil)` (ButtonHelper)
485
+ - `lc_icon_button_class(shape: :square, size: :md, extra: nil)` (ButtonHelper)
486
+ - `lc_card_class(interactive: false, padding: :md, extra: nil)` (CardHelper)
487
+ - `lc_choice_label_class(card: false, extra: nil)` (ChoiceHelper)
488
+ - `lc_checkbox_class(extra: nil)` (ChoiceHelper)
489
+ - `lc_radio_class(extra: nil)` (ChoiceHelper)
490
+ - `lc_switch_class(extra: nil)` (ChoiceHelper)
272
491
  - `lc_dialog_class(kind = :modal, extra: nil)` (DialogHelper)
273
- - `lc_dialog_surface_class(extra: nil)` (DialogHelper)
274
492
  - `lc_emoji_picker_groups` (EmojiPickerHelper)
275
493
  - `lc_emoji_picker_count` (EmojiPickerHelper)
276
494
  - `lc_emoji_picker_version` (EmojiPickerHelper)
@@ -278,20 +496,18 @@ Generated by `bin/rails lostcoords:ui:builders`; `bin/lint` checks this list aga
278
496
  - `lc_emoji_picker_search_name(emoji)` (EmojiPickerHelper)
279
497
  - `lc_emoji_picker_search_labels(emoji)` (EmojiPickerHelper)
280
498
  - `lc_emoji_picker_toneable?(emoji)` (EmojiPickerHelper)
281
- - `lc_spinner_class(size: :base, extra: nil)` (FeedbackHelper)
499
+ - `lc_alert_class(extra: nil)` (FeedbackHelper)
500
+ - `lc_alert_icon(tone: :destructive)` (FeedbackHelper)
501
+ - `lc_spinner_class(size: :md, extra: nil)` (FeedbackHelper)
282
502
  - `lc_skeleton_class(extra: nil)` (FeedbackHelper)
283
503
  - `lc_empty_state_class(extra: nil)` (FeedbackHelper)
284
- - `lc_segmented_class(extra: nil)` (FormHelper)
285
- - `lc_segmented_tab_class(active: false, extra: nil)` (FormHelper)
286
- - `lc_input_class(size: :base, extra: nil)` (FormHelper)
287
- - `lc_dropdown_trigger_class(size: :base, extra: nil)` (FormHelper)
288
- - `lc_select_class(size: :base, extra: nil)` (FormHelper)
504
+ - `lc_toggle_group_class(extra: nil)` (FormHelper)
505
+ - `lc_toggle_group_item_class(active: false, extra: nil)` (FormHelper)
506
+ - `lc_input_class(size: :md, extra: nil)` (FormHelper)
507
+ - `lc_select_class(size: :md, extra: nil)` (FormHelper)
289
508
  - `lc_textarea_class(extra: nil)` (FormHelper)
290
509
  - `lc_field_class(extra: nil)` (FormHelper)
291
510
  - `lc_field_label_class(extra: nil)` (FormHelper)
292
- - `lc_form_actions_class(extra: nil)` (FormHelper)
293
- - `lc_form_alert_class(extra: nil)` (FormHelper)
294
- - `lc_form_alert_icon(tone: :error)` (FormHelper)
295
511
  - `lc_glass_class(shape: :bar, thick: false, extra: nil)` (GlassHelper)
296
512
  - `lc_stack_class(gap: :content, extra: nil)` (LayoutHelper)
297
513
  - `lc_inline_class(gap: :related, extra: nil)` (LayoutHelper)
@@ -300,16 +516,14 @@ Generated by `bin/rails lostcoords:ui:builders`; `bin/lint` checks this list aga
300
516
  - `lc_popover_class(align: :end, extra: nil)` (MenuHelper)
301
517
  - `lc_menu_popover_class(align: :end, extra: nil)` (MenuHelper)
302
518
  - `lc_menu_option_class(tone: :default, extra: nil)` (MenuHelper)
519
+ - `lc_link_class(tone: :inherit, extra: nil)` (NavigationHelper)
520
+ - `lc_nav_link_class(compact: false, extra: nil)` (NavigationHelper)
303
521
  - `lc_prose_class(extra: nil)` (ReadingHelper)
304
522
  - `lc_sheet_data(data = {}, detents: "fit")` (SheetHelper)
305
- - `lc_sheet_row_class(extra: nil)` (SheetHelper)
306
- - `lc_sheet_row_label_class(extra: nil)` (SheetHelper)
307
- - `lc_sheet_chip_class(active: false, member: false, extra: nil)` (SheetHelper)
308
- - `lc_sheet_datetime_input_class(extra: nil)` (SheetHelper)
309
- - `lc_sheet_text_input_class(extra: nil)` (SheetHelper)
523
+ - `lc_table_class(extra: nil)` (TableHelper)
310
524
  - `lc_tw(*classes)` (TailwindHelper)
311
- - `lc_heading_class(size: :section, extra: nil)` (TypographyHelper)
312
- - `lc_text_class(size: :body, tone: :default, extra: nil)` (TypographyHelper)
525
+ - `lc_heading_class(size: :heading, trim: false, extra: nil)` (TypographyHelper)
526
+ - `lc_text_class(size: :body, tone: :default, trim: false, extra: nil)` (TypographyHelper)
313
527
  <!-- /builders -->
314
528
 
315
529
  ## Blocks
@@ -1,11 +1,11 @@
1
1
  /* A linked avatar's ring draws inside its own footprint, so it takes exactly the space of an
2
2
  unlinked one: the outline sits on the edge, over any cutout border, and the inset shadow
3
3
  paints the gap in the surface colour. Contexts that already cut the avatar out with a
4
- border set --avatar-ring-inset (or --avatar-ring-gap) so the gap doesn't double up. */
5
- @utility avatar-ring {
6
- --avatar-ring: clamp(1.5px, calc(var(--avatar-size, var(--avatar-size-default, --spacing(8))) * 0.06), 3px);
4
+ border set --lc-avatar-ring-inset (or --lc-avatar-ring-gap) so the gap doesn't double up. */
5
+ @utility lc-avatar-ring {
6
+ --lc-avatar-ring: clamp(1.5px, calc(var(--lc-avatar-size, var(--lc-avatar-size-default, --spacing(8))) * 0.06), 3px);
7
7
 
8
- outline: var(--avatar-ring) solid color-mix(in oklab, var(--sky) 74%, var(--paper));
9
- outline-offset: calc(var(--avatar-ring) * -1);
10
- box-shadow: inset 0 0 0 var(--avatar-ring-inset, calc(var(--avatar-ring) * var(--avatar-ring-gap, 2))) var(--avatar-cutout, var(--paper));
8
+ outline: var(--lc-avatar-ring) solid color-mix(in oklab, var(--primary) 74%, var(--background));
9
+ outline-offset: calc(var(--lc-avatar-ring) * -1);
10
+ box-shadow: inset 0 0 0 var(--lc-avatar-ring-inset, calc(var(--lc-avatar-ring) * var(--lc-avatar-ring-gap, 2))) var(--lc-avatar-cutout, var(--background));
11
11
  }
@@ -0,0 +1,45 @@
1
+ /* Document defaults for apps that let the library own the page. components.css
2
+ omits this file, so a host with its own base styles keeps them. */
3
+ @layer base {
4
+ :root { color-scheme: light; }
5
+ html[data-theme="dark"] { color-scheme: dark; }
6
+
7
+ body {
8
+ background-color: var(--background);
9
+ color: var(--foreground);
10
+ font-family: var(--font-sans);
11
+ line-height: 1.5;
12
+ }
13
+
14
+ :is(h1, h2, h3, h4, h5) {
15
+ font-family: var(--font-display);
16
+ line-height: 1.2;
17
+ color: var(--foreground);
18
+ }
19
+
20
+ button { cursor: pointer; background: none; }
21
+
22
+ dialog { background: transparent; color: inherit; }
23
+
24
+ :where([popover]) { color: inherit; }
25
+
26
+ :is(a, button, input, textarea, select, summary, [tabindex]):focus-visible,
27
+ label:has(input.sr-only:focus-visible) {
28
+ outline: calc(var(--spacing) * 0.5) solid var(--ring, color-mix(in oklab, var(--primary) 50%, transparent));
29
+ outline-offset: 0;
30
+ }
31
+
32
+ dialog:focus-visible { outline: none; }
33
+
34
+ :where(code):not(:where(pre code)) {
35
+ @apply lc-inline-code;
36
+ }
37
+
38
+ @media (prefers-reduced-motion: reduce) {
39
+ *, *::before, *::after {
40
+ animation-duration: 0.01ms !important;
41
+ animation-iteration-count: 1 !important;
42
+ transition-duration: 0.01ms !important;
43
+ }
44
+ }
45
+ }
@@ -1,26 +1,24 @@
1
1
  @layer components {
2
- /* Shared paper-card chrome. Used as a bare `.card-surface` hook in ~17
3
- places across many feature partials (group lists, transactions, overview
4
- cards and currency tools. Kept authored — not folded into
5
- card_surface_class — because several call sites override the border
6
- (`border-transparent`), background or radius with utilities that must
7
- out-cascade this base; a utilities-layer helper emitting a `[border:…]`
8
- shorthand would defeat those overrides. Semantic classes set the
9
- --card-surface-* vars for local tone. (Form cards use form_card_class,
10
- which carries this same chrome plus padding/container/error border.) */
11
- .card-surface {
12
- --avatar-cutout: var(--card-surface-bg, var(--paper-2));
13
- background: var(--card-surface-bg, var(--paper-2));
14
- border: var(--card-surface-border, 1px solid transparent);
15
- border-radius: var(--card-surface-radius, var(--radius-3xl));
16
- box-shadow: var(--card-surface-shadow, none);
17
- transition:
18
- background 0.18s var(--ease-out),
19
- border-color 0.18s var(--ease-out),
20
- box-shadow 0.18s var(--ease-out);
21
- }
2
+ /* Padding comes from --lc-inset, so a card that sets --lc-inset passes the
3
+ same rhythm to everything inside it. */
22
4
 
23
- .form-card + .form-card { margin-block-start: 1rem; }
5
+ /* Inputs keep their --input fill on cards. Choice tiles, which use the muted
6
+ fill on the page, switch to the page background so they stand out. */
7
+ .lc-card {
8
+ --lc-tile-bg: var(--background);
9
+ --lc-avatar-cutout: var(--lc-card-bg, var(--card));
10
+ background: var(--lc-card-bg, var(--card));
11
+ color: var(--card-foreground);
12
+ border: var(--lc-card-border, thin solid transparent);
13
+ border-radius: var(--lc-card-radius, var(--radius-3xl));
14
+ box-shadow: var(--lc-card-shadow, none);
15
+ transition:
16
+ background-color var(--lc-motion-control) var(--lc-ease-out),
17
+ border-color var(--lc-motion-control) var(--lc-ease-out),
18
+ box-shadow var(--lc-motion-control) var(--lc-ease-out);
24
19
 
25
- .form-submit { margin-block-start: 2rem; }
20
+ &:has([data-tone="destructive"], [aria-invalid="true"]) {
21
+ border-color: color-mix(in oklab, var(--destructive) 30%, var(--border));
22
+ }
23
+ }
26
24
  }