@rcarls/rc-theme-substrate 0.0.0-next-20260921045401

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 (46) hide show
  1. package/CHANGELOG.md +358 -0
  2. package/README.md +79 -0
  3. package/bridge.css +204 -0
  4. package/components/accordion.css +18 -0
  5. package/components/adaptive-menu.css +43 -0
  6. package/components/app-bar.css +21 -0
  7. package/components/bottom-sheet.css +90 -0
  8. package/components/button.css +44 -0
  9. package/components/card.css +51 -0
  10. package/components/carousel.css +31 -0
  11. package/components/chip-group.css +21 -0
  12. package/components/chip.css +20 -0
  13. package/components/combobox.css +55 -0
  14. package/components/dialog.css +43 -0
  15. package/components/disclosure.css +114 -0
  16. package/components/fab-menu.css +36 -0
  17. package/components/fab.css +44 -0
  18. package/components/field.css +136 -0
  19. package/components/icons.css +19 -0
  20. package/components/list-item.css +70 -0
  21. package/components/listbox.css +28 -0
  22. package/components/markdown-editor.css +53 -0
  23. package/components/menu-button.css +18 -0
  24. package/components/menu.css +23 -0
  25. package/components/menubar.css +17 -0
  26. package/components/modes.css +60 -0
  27. package/components/navigation-bar.css +43 -0
  28. package/components/navigation-rail.css +72 -0
  29. package/components/progress.css +11 -0
  30. package/components/range-slider.css +18 -0
  31. package/components/scroller.css +19 -0
  32. package/components/search-bar.css +30 -0
  33. package/components/segmented-button.css +19 -0
  34. package/components/select.css +74 -0
  35. package/components/slider.css +10 -0
  36. package/components/snackbar.css +23 -0
  37. package/components/splitter.css +39 -0
  38. package/components/switch.css +28 -0
  39. package/components/textarea.css +20 -0
  40. package/components/toolbar.css +36 -0
  41. package/components/transfer-list.css +30 -0
  42. package/components/virtual-canvas.css +18 -0
  43. package/components.css +41 -0
  44. package/defaults.css +108 -0
  45. package/package.json +44 -0
  46. package/theme.css +3 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,358 @@
1
+ # @rcarls/rc-theme-substrate
2
+
3
+ ## 0.0.0-next-20260921045401
4
+
5
+ ### Minor Changes
6
+
7
+ - 079e025: Standardize component styling contracts for the breaking 0.7 release.
8
+
9
+ BREAKING CHANGE: Rename the Markdown editor's `--rme-*` properties to
10
+ `--rc-markdown-editor-*`, `--rc-app-bar-background` to `--rc-app-bar-bg`,
11
+ `--rc-textarea-radius` to `--rc-textarea-border-radius`, the transfer-list
12
+ `--rc-transfer-list-panel-*` properties to `--rc-transfer-list-listbox-*`,
13
+ `--rc-chip-group-webkit-scrollbar-display` to
14
+ `--rc-chip-group-scrollbar-display`, and `--rc-text` to `--rc-field-text`.
15
+ Compatibility aliases are intentionally not provided.
16
+
17
+ BREAKING CHANGE: Replace `--rc-select-chip-padding-inline` and
18
+ `--rc-combobox-chip-padding-inline` with explicit `*-padding-inline-start` and
19
+ `*-padding-inline-end` tokens. Generated removable chips now reserve the full
20
+ remove target and its edge offset, so their indicator cannot overlap label text.
21
+
22
+ Add canonical app-bar elevation, transfer-list listbox radius, and textarea
23
+ line-action tokens. Add logical `rc-chip` inline-padding properties so themes
24
+ can implement Material's icon-aware spacing without overriding authored child
25
+ styles. Make `rc-field` consume the shared semantic token layer,
26
+ and place the `rc-chip` and `rc-search-bar` light-DOM structural styles in the
27
+ `rc-base` cascade layer so normal consumer rules override them.
28
+
29
+ Prevent decorative, `aria-hidden` trailing list content from intercepting a
30
+ whole-row action target, and keep segmented-list item geometry from leaking
31
+ into nested standard lists.
32
+
33
+ Remove unused Material navigation-rail toggle tokens and obsolete list-item
34
+ theme tokens. Register the segmented-button private theme seam and enforce
35
+ token ownership, documentation, removed-name, selector-budget, and cascade
36
+ layer contracts through the component architecture audit.
37
+
38
+ - 4341311: Add reusable fullscreen dialogs and declarative dialog popup mode to `rc-select` and
39
+ `rc-combobox`. Dialog selection and creation are staged until Done, while close and Escape
40
+ discard the draft. Fullscreen surfaces follow the visual viewport under browser zoom and
41
+ software keyboards, and anchored popups now clamp to the visual viewport.
42
+ - 2c7c80a: Give `rc-dialog` a surface styling contract.
43
+
44
+ It exposed only `--rc-dialog-scrim`, so both themes drew the surface by selecting the native
45
+ `<dialog>` directly. It now installs a light-DOM base stylesheet, the same pattern as `rc-button`
46
+ and `rc-disclosure`, and exposes minimum and maximum inline and block bounds, padding, border, radius, background,
47
+ foreground, and shadow, plus fullscreen-specific padding and background for the two values that
48
+ variant genuinely needs of its own.
49
+
50
+ Fullscreen geometry, meaning position, size, margin, radius, and shadow, stays structural rather
51
+ than something a theme sets: filling the visual viewport is what keeps the surface correct under browser zoom
52
+ and a software keyboard.
53
+
54
+ Material's explicit block bound with `overflow: hidden` moved into the base layer rather than
55
+ staying a theme value. It exists because Chromium does not reliably clip a scrolling element's
56
+ own border radius at the scroll boundary, which is a correctness fix every theme needs.
57
+
58
+ `--rc-dialog-visual-viewport-left`, `-top`, `-width`, and `-height` are no longer documented as
59
+ theme properties. They are geometry the component writes while a fullscreen dialog is open:
60
+ readable if something inside the dialog needs the same measurements, but setting them does not
61
+ move the dialog. They are candidates for private `--_rc-dialog-*` names in a future revision.
62
+
63
+ - 4b9bfe6: Give `rc-disclosure` and `rc-accordion` a public styling contract.
64
+
65
+ Both were styled by targeting the native `<details>` element directly, so neither exposed a
66
+ single `--rc-*` property and no theme could change them without rewriting theme CSS. They now
67
+ follow the light-DOM base pattern `rc-button` and `rc-listbox` already use: `rc-disclosure`
68
+ installs one structural stylesheet per containing `Document` or `ShadowRoot`, in `@layer rc-base`
69
+ behind a `data-rc-light-dom-base` sentinel, reading documented properties with UA-like defaults.
70
+
71
+ One recipe covers both supported child forms, a bare `<details>` inside `rc-accordion` and one
72
+ wrapped in `rc-disclosure`, so neither theme repeats the panel rules. `rc-accordion` owns only
73
+ `--rc-accordion-gap`.
74
+
75
+ Motion is opt-in. `--rc-disclosure-duration` defaults to `0ms`, so a disclosure with no theme keeps
76
+ the platform's snap-open behavior, and the height animation runs on `::details-content` behind a
77
+ `@supports selector()` check. This replaces Material's `60rem` `max-block-size` ceiling, which
78
+ clipped content taller than the guess and made every non-summary child its own animation unit.
79
+
80
+ **Fixes a missing expand affordance in the Material theme.** It set `display: grid` on the
81
+ `<summary>` and then styled `summary::marker`. Any `display` other than `list-item` drops the
82
+ marker box, so that rule styled nothing and the theme drew no replacement: a Material disclosure
83
+ had no disclosure triangle at all. The base layer never sets `display` on a summary, so the
84
+ marker survives. A theme may still change it, as Substrate does, but only alongside a
85
+ replacement affordance.
86
+
87
+ - 873592e: Add four directional motion tokens to the shared base contract:
88
+ `--rc-motion-effects-easing-enter`/`-exit` and
89
+ `--rc-motion-spatial-easing-enter`/`-exit`. An opening element decelerates
90
+ into place; a closing one accelerates out. There are no matching enter/exit
91
+ duration tokens; pair `-easing-exit` with an existing `-duration-fast` token
92
+ instead.
93
+
94
+ All three themes map the new tokens: Material pairs them with its
95
+ standard/emphasized decelerate and accelerate curves, Substrate keeps its one
96
+ curve as the enter curve and adds a generic accelerate shape for exit
97
+ (refined further once Substrate gets its own full spatial/effects scale), and
98
+ `rc-theme-win31` declares all four as `step-end`, matching its zero-motion
99
+ contract, so an application that loads the shared base tokens alongside
100
+ `rc-theme-win31` does not inherit the base defaults' real easing curves for
101
+ these two directions.
102
+
103
+ - 7c952e2: `rc-theme-material` and `rc-theme-substrate` refine `prefers-reduced-motion:
104
+ reduce` from removing every transition and animation to removing only
105
+ spatial motion; effects motion (color and opacity) is kept, shortened.
106
+ Vestibular harm, the reason for the preference, comes from movement across
107
+ the visual field; a color or opacity change doesn't trigger it, and removing
108
+ it too was also removing motion that aids comprehension.
109
+
110
+ Material's effects duration shortens to 50/70/100&nbsp;ms (from its full
111
+ 150/200/300&nbsp;ms scale); Substrate's to 40/55/70&nbsp;ms, scaled down from
112
+ its own faster 100/160/240&nbsp;ms base. `rc-theme-win31` needs no change: it
113
+ already declares every duration at zero unconditionally.
114
+
115
+ Several component recipes that bypassed the shared motion scale entirely,
116
+ referencing a raw duration primitive directly, are rewired to the
117
+ appropriate effects or spatial token so this refinement actually reaches
118
+ them: Material's `rc-disclosure`, `rc-list-item`, `rc-bottom-sheet`'s snap
119
+ duration, `rc-button`'s ripple, and its shared state-layer opacity fade;
120
+ Substrate's `rc-listbox`, `rc-splitter`, and `rc-disclosure`. Most land on
121
+ their existing value exactly; `rc-button`'s ripple duration normalizes from
122
+ 450&nbsp;ms to 500&nbsp;ms, the nearest real spatial tier.
123
+
124
+ - 7549dde: Theme the nine components Substrate left without a token contract.
125
+
126
+ `rc-adaptive-menu`, `rc-carousel`, `rc-chip-group`, and `rc-scroller` had no component CSS at
127
+ all. `rc-toolbar` and `rc-virtual-canvas` had a file that styled the element directly without
128
+ declaring a single `--rc-*` property. `rc-splitter` declared two colors that mixed against the
129
+ bare `Canvas` and `ButtonBorder` system colors, and the dialog scrim mixed against `CanvasText`,
130
+ so none of the three had a value that could leave CSS.
131
+
132
+ All are themed now, and the mixes point at the theme's own surface, border, and inverse surface
133
+ instead of at system colors. Every new color clears WCAG AA against the surface it is drawn on,
134
+ most of them AAA.
135
+
136
+ The splitter keeps its existing division of labor: this theme paints it and leaves its geometry
137
+ to the component's own defaults.
138
+
139
+ `rc-accordion` and `rc-disclosure` are unchanged. They read no `--rc-*` property anywhere in
140
+ their own styles, so there is nothing for a theme to set; giving them a contract is a change to
141
+ those components.
142
+
143
+ - 77f9c63: Fix three theme gaps where a token fell through to a system color.
144
+
145
+ **Substrate had no error color.** `--rc-field-error-color` fell through to `rc-field`'s own
146
+ default of `Mark`, the system color for the background of highlighted text, which renders as
147
+ pure yellow and measured 1.07:1 against the field. Validation worked, the message was in the
148
+ accessibility tree, and it could not be read. Substrate now ships `--substrate-error`, held
149
+ at hue 25 so it reads as red rather than as a darker shade of the orange primary at hue 48,
150
+ and the bridge maps it. Both modes clear 7:1.
151
+
152
+ Every remaining error fallback of `Mark`, in `rc-field` and in the Material field styles, now
153
+ falls back to the validation red a user agent uses for an invalid control. The `Mark` usages
154
+ inside `forced-colors` blocks are unchanged, where a system color is correct.
155
+
156
+ **Material never set `--rc-button-text`.** `rc-select`, `rc-combobox`, and `rc-menu-button`
157
+ read it directly for text drawn over `--rc-button-bg`, which Material sets to the primary
158
+ fill, so the `ButtonText` fallback gave 3.26:1 in light and 1.70:1 in dark. It now follows
159
+ `--md-sys-color-on-primary` alongside `--rc-button-color`, giving 6.44:1 and 7.71:1.
160
+
161
+ **Substrate referenced three tokens it never declared.** `--substrate-inverse-surface`,
162
+ `--substrate-inverse-on-surface`, and `--substrate-radius-pill` were used by the bridge and
163
+ component styles but missing from `defaults.css`, leaving `rc-snackbar` on system colors and
164
+ the bottom sheet handle on a literal. All three are declared, and a `defaults` test now
165
+ asserts that every referenced token exists.
166
+
167
+ - 1207f3c: Theme `rc-field`, the last component Substrate left without coverage.
168
+
169
+ The field takes Substrate's palette and the hover, focus, and invalid treatment
170
+ its other form controls already share, so it reads as one of them. `rc-field`
171
+ keeps owning its layout, invalid border, disabled opacity, focus outline, and
172
+ forced-colors fallbacks; the theme only supplies values.
173
+
174
+ An `rc-select`, `rc-combobox`, or `rc-textarea` acting as the field's control
175
+ provider now stops drawing its own border, background, and focus ring, which
176
+ otherwise nested visibly inside the field's. A standalone control outside a
177
+ field is unaffected.
178
+
179
+ The field also fills its container up to a readable measure instead of
180
+ shrink-wrapping, so a provider-backed field with no value no longer collapses
181
+ to its toggle.
182
+
183
+ - 58f4e71: `rc-theme-substrate` and `rc-theme-win31` now map the full shared
184
+ spatial/effects motion scale that `rc-theme-material` already implements,
185
+ closing a gap where each declared only its own single compatibility duration.
186
+
187
+ Substrate adds a small, quicker scale of its own: 100/160/240&nbsp;ms for
188
+ effects and 160/240/320&nbsp;ms for spatial, with its existing single easing
189
+ curve as the spatial curve (it is already decelerate-shaped) and `ease-out`
190
+ for effects. The compatibility token now follows the effects default rather
191
+ than its own literal, matching the shared base contract's own convention.
192
+ The one remaining hardcoded duration, `rc-list-item`'s 100&nbsp;ms row
193
+ transition, now reads from the effects-fast token. Substrate's
194
+ `prefers-reduced-motion` override moves from the bridge layer into
195
+ `components/modes.css`, in the components layer, matching where the other
196
+ two themes keep their mode overrides; a cascade layer test now proves the
197
+ later layer's override wins rather than assuming it.
198
+
199
+ win31 declares all thirteen shared tokens, plus the four directional ones
200
+ added last release, at zero duration with `step-end` easing. Previously only
201
+ the single compatibility token was set, so an application loading the shared
202
+ base tokens alongside this theme inherited base's real 180/300/450&nbsp;ms
203
+ spatial motion for every other token in the scale, silently contradicting the
204
+ theme's own zero-motion contract. `rc-chip`'s existing zero-duration override
205
+ gets its matching `step-end` easing token.
206
+
207
+ Verifying that claim surfaced a limit worth knowing about even after this
208
+ fix: it holds only when the theme class is applied to a container, the
209
+ documented pattern, not to `<html>`. The shared `base.css` declares no cascade
210
+ layer, so its `:root` values outrank any theme layer whenever both target the
211
+ same element, which happens only when the theme class sits on `<html>` itself.
212
+ No theme package can fix this by declaring more tokens; see the styling guide
213
+ for details.
214
+
215
+ ### Patch Changes
216
+
217
+ - 114e38a: `rc-progress`'s determinate fill transition is now themeable through two new
218
+ CSS custom properties, `--rc-progress-fill-transition-duration` (default
219
+ `150ms`) and `--rc-progress-fill-transition-easing` (default `ease-out`),
220
+ replacing a hardcoded `0.15s ease-out` no theme could reach.
221
+
222
+ The indeterminate loop's easing changes from `ease-in-out` to `linear`. A
223
+ loop has no beginning or end to decelerate into or accelerate out of, so an
224
+ eased loop visibly pulses at each cycle boundary; Material 3 specifies linear
225
+ easing for indeterminate loops for the same reason.
226
+
227
+ `rc-theme-material` and `rc-theme-substrate` map the new duration/easing
228
+ properties to their effects-fast motion tokens; `rc-theme-win31` already sets
229
+ `transition: none` on the fill part directly and needs no change.
230
+
231
+ - fef89dc: Move the segmented appearance into the component behind one switch.
232
+
233
+ Turning the native fieldset into a segmented control took 27 `--_rc-*`
234
+ declarations that every theme had to restate, 23 of them byte-identical. Two of
235
+ those blocks were accessibility recipes rather than taste: the visually hidden
236
+ legend that keeps the group's accessible name, and the hidden but focusable
237
+ radio. A theme that got either wrong removed the accessible name or made the
238
+ control unreachable by keyboard.
239
+
240
+ The component now owns the recipe and a theme sets
241
+ `--rc-segmented-button-appearance: segmented`. Four genuine theme choices become
242
+ public properties: `--rc-segmented-button-cursor`,
243
+ `--rc-segmented-button-disabled-cursor`,
244
+ `--rc-segmented-button-vertical-divider`, and
245
+ `--rc-segmented-button-selected-icon-display`.
246
+
247
+ Unset, the fieldset, legend, and radios keep their browser appearance, as
248
+ before. The switch is read with a style container query (Chrome 111, Safari 18,
249
+ Firefox 128); where that is unsupported the recipe does not apply and the
250
+ control falls back to the same native appearance.
251
+
252
+ To migrate a theme, replace its `--_rc-segmented-button-*` block with the single
253
+ `--rc-segmented-button-appearance: segmented` declaration, plus any of the four
254
+ public properties whose value differs from the default.
255
+
256
+ - 40f132d: `rc-theme-material` now declares the Material 3 motion system's seven named
257
+ easing curves (`--md-sys-motion-easing-standard`, `-emphasized`, and their
258
+ decelerate/accelerate variants, plus `-linear`) and sixteen named duration
259
+ bands (`--md-sys-motion-duration-short1` through `-extra-long4`), composed
260
+ from the control points and millisecond primitives already vendored. Several
261
+ component recipes were already reaching for these names with an inline
262
+ fallback (`var(--md-sys-motion-easing-standard, cubic-bezier(0.2, 0, 0, 1))`)
263
+ that silently won every time because nothing declared the token; those
264
+ references now resolve for real, and the fallback silently carrying a
265
+ Material 2 curve (`cubic-bezier(0.4, 0, 0.2, 1)`, in `rc-state-layer`'s
266
+ transition) is fixed to the correct Material 3 curve. Two more vendored-token
267
+ gaps of the same shape are fixed alongside: Material's disabled state-layer
268
+ and container opacity tokens, referenced across a dozen component recipes
269
+ with no local declaration, and two Substrate token names
270
+ (`--substrate-border`, `--substrate-radius-medium`) that never matched their
271
+ real declarations (`--substrate-border-color`, `--substrate-radius-md`) and
272
+ so always fell back to a hardcoded literal instead of the theme's actual
273
+ values; `rc-fab-menu`'s Substrate shadow tokens are corrected the same way.
274
+
275
+ ## 0.6.0
276
+
277
+ ### Minor Changes
278
+
279
+ - a108336: Add shared-column `rc-list` and `rc-list-item` elements with standard and
280
+ segmented appearances, default content truncation, and native-backed single or
281
+ multiple selection coordination. Restore author-owned item state when native
282
+ selection coordination ends.
283
+ - 509ee64: Add `rc-progress`, a native `<progress>` enhancer with a formatted value
284
+ display, a track and fill you can restyle with CSS custom properties, and a
285
+ built-in fix for the indeterminate/undefined-binding footgun.
286
+
287
+ Keep the native element as the uncontrolled source of truth, support releasing
288
+ a controlled value, and react to live native `value`/`max` changes. Preserve a
289
+ native determinate value while `indeterminate` temporarily removes its
290
+ attribute.
291
+
292
+ The bar renders at its intended height with no `display` attribute set,
293
+ matching its own documented default: previously the track/fill/native
294
+ `<progress>` are all absolutely positioned, contributing nothing to the
295
+ control's normal-flow height, so with no value-display text sharing its grid
296
+ row to give it one, the whole bar collapsed to 0px.
297
+
298
+ ## 0.5.0
299
+
300
+ ## 0.4.2
301
+
302
+ ## 0.4.1
303
+
304
+ ## 0.4.0
305
+
306
+ ### Minor Changes
307
+
308
+ - e57277f: Add Material 3 search-view, fixed-pane splitter, dialog scrim, and resize enhancements.
309
+
310
+ `rc-search-bar` now supports `variant="view"`, controlled/default open state, search-view methods, a rich `suggestions` slot, and datalist-derived text suggestions. `rc-splitter` adds `mode="fixed"` for clampable pixel primary panes. `rc-common` now honors CSS max-size constraints during resize gestures, and `rc-dialog` exposes a scrim token plus resize handle/origin hooks used by themed surfaces.
311
+
312
+ ### Patch Changes
313
+
314
+ - 9c7476d: Add theme-specific rc-button progress treatments, including compact Material spinners and
315
+ Substrate percentage indicators.
316
+ - ebec800: Dock bottom sheets without a theme to the viewport block-end by default and
317
+ support absolute positioning for non-modal sheets contained by a positioned ancestor.
318
+ Center authored drag handles with a splitter-aligned pill treatment. Align the
319
+ Material theme with its surface, elevation, scrim, drag-handle, and contextual
320
+ button treatment.
321
+ - e88eff8: Refine semantic card-title typography and icon-button alignment across both
322
+ themes. Simplify Substrate navigation selection, match its bottom-sheet handle
323
+ to the splitter grip, improve inverse notification action contrast, and theme the
324
+ Markdown editor's composed controls.
325
+ - 6f91e7a: Fix collapsed `rc-navigation-rail` items so the whole item's box wraps icon and
326
+ label, not just the icon. The shared component pulled the label out of flow
327
+ (`position: absolute`), so a theme's whole-item background or focus ring only
328
+ covered the icon; Material's detached-pill layout now lives in its own theme
329
+ stylesheet instead of the shared base. Substrate's collapsed rail also grows
330
+ from `5rem` to `6rem` so single-word labels like "Settings" stop wrapping.
331
+ - dce5749: Replace the navigation rail's shadow-DOM fallback toggle with a consumer-authored light-DOM
332
+ button, and align collapsed and expanded item layouts across the default and reference themes.
333
+ - 55e8ab5: Preserve UA-like control and surface styling until an optional theme supplies decorative tokens.
334
+ Restore themed button hover and pressed state layers, including a pointer-origin Material ripple.
335
+ - 4d118ff: Share icon-font size and line-height tokens across themed component icon markers.
336
+ - 3e89095: Replace rc-menu's generated submenu glyph with an author-supplied `indicator`
337
+ slot on rc-menu-button. Keep the decorative indicator inside the trigger bounds,
338
+ reserve label space for it, and expose theme-neutral size, color, and inset
339
+ tokens for Material, Substrate, and application themes.
340
+ - c96f40c: Use a native system font stack by default.
341
+ - 979b989: Use a zero default gap for tighter chip content layout and a smaller default
342
+ font size for slotted remove icons. Derive removable content padding from the
343
+ remove target width and chip gap so the label and trailing icon stay aligned.
344
+
345
+ ## 0.3.2
346
+
347
+ ## 0.3.1
348
+
349
+ ## 0.3.0
350
+
351
+ ### Added
352
+
353
+ - Add Substrate as a lightweight CSS reference theme for branded app foundations.
354
+ - Add scoped defaults, token bridges, component polish, and docs preview integration.
355
+
356
+ ### Changed
357
+
358
+ - Update package metadata, README intro, and docs links.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # `@rcarls/rc-theme-substrate`
2
+
3
+ Lightweight CSS reference theme for app-oriented `rc-webcomponents` layouts.
4
+
5
+ Docs: [https://richardcarls.github.io/rc-webcomponents/guide/theme-previews](https://richardcarls.github.io/rc-webcomponents/guide/theme-previews).
6
+
7
+ It is a CSS-only, app-oriented foundation theme: orange primary color,
8
+ neutral surfaces, compact controls, system-color fallbacks, and a small set of
9
+ component polish rules that demonstrate how to build a branded theme without
10
+ copying the heavier Material package.
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ yarn add @rcarls/rc-theme-substrate
16
+ ```
17
+
18
+ ## Full reference theme
19
+
20
+ Import the full theme bundle:
21
+
22
+ ```css
23
+ @import '@rcarls/rc-theme-substrate/theme.css';
24
+ ```
25
+
26
+ Apply the theme class to your app shell or any subtree:
27
+
28
+ ```html
29
+ <main class="rc-theme-substrate">
30
+ <rc-select>...</rc-select>
31
+ <rc-dialog>...</rc-dialog>
32
+ </main>
33
+ ```
34
+
35
+ ## Build your own theme from it
36
+
37
+ Substrate is intentionally token-first. To use it as a branded foundation,
38
+ copy the `bridge.css` pattern and override the public semantic tokens at your
39
+ theme boundary.
40
+
41
+ ```css
42
+ @import '@rcarls/rc-theme-substrate/bridge.css';
43
+ @import '@rcarls/rc-theme-substrate/components.css';
44
+
45
+ .my-product-theme {
46
+ --substrate-primary: oklch(58% 0.19 275);
47
+ --substrate-radius-md: 0.5rem;
48
+ --substrate-font-family: Inter, system-ui, sans-serif;
49
+ }
50
+ ```
51
+
52
+ Selective component styles are exported for apps that want only part of the
53
+ reference layer:
54
+
55
+ ```css
56
+ @import '@rcarls/rc-theme-substrate/bridge.css';
57
+ @import '@rcarls/rc-theme-substrate/components/select.css';
58
+ @import '@rcarls/rc-theme-substrate/components/dialog.css';
59
+ ```
60
+
61
+ Tag general icon-font markup with `data-rc-icon`; component-specific markers
62
+ such as `data-rc-button-icon` and `data-rc-navigation-icon` opt in
63
+ automatically. The shared `--rc-icon-font-size` and
64
+ `--rc-icon-font-line-height` tokens keep glyph geometry consistent and can be
65
+ overridden at the theme boundary or on one component.
66
+
67
+ ## View transitions
68
+
69
+ Substrate includes stable CSS hooks for apps that opt into View Transitions,
70
+ but it does not start transitions or require browser support:
71
+
72
+ ```css
73
+ .rc-theme-substrate [data-rc-view-transition='panel'] {
74
+ --rc-view-transition-name: rc-panel;
75
+ }
76
+ ```
77
+
78
+ Application code remains responsible for deciding when to call
79
+ `document.startViewTransition()`.
package/bridge.css ADDED
@@ -0,0 +1,204 @@
1
+ @layer rc-theme-substrate.defaults, rc-theme-substrate.bridge, rc-theme-substrate.components;
2
+
3
+ @layer rc-theme-substrate.bridge {
4
+ /*
5
+ * Maps Substrate's small brand token set to the rc-webcomponents public
6
+ * styling contract. Consumers can replace --substrate-* values and keep this
7
+ * file as their bridge.
8
+ */
9
+ .rc-theme-substrate,
10
+ :host(.rc-theme-substrate) {
11
+ --rc-surface: var(--substrate-surface, Canvas);
12
+ --rc-field: var(--substrate-field, Field);
13
+ --rc-button-bg: var(--substrate-surface-raised, ButtonFace);
14
+ --rc-field-text: var(--substrate-on-surface, FieldText);
15
+ --rc-field-error-color: var(--substrate-error, red);
16
+ --rc-button-text: var(--substrate-on-surface, ButtonText);
17
+ --rc-highlight: var(--substrate-primary-container, Highlight);
18
+ --rc-highlight-text: var(--substrate-on-primary-container, HighlightText);
19
+ --rc-text-disabled: var(--substrate-on-surface-muted, GrayText);
20
+ --rc-accent: var(--substrate-primary, Highlight);
21
+ --rc-border-color: var(--substrate-border-color, ButtonBorder);
22
+ --rc-border-width: 1px;
23
+ --rc-border-style: solid;
24
+ --rc-border: var(--rc-border-width) var(--rc-border-style) var(--rc-border-color);
25
+ --rc-shadow: var(--substrate-shadow-md, 0 2px 8px rgb(0 0 0 / 15%));
26
+ --rc-font-family: var(--substrate-font-family, inherit);
27
+ --rc-font-size: var(--substrate-font-size, 1em);
28
+ --rc-font-weight: var(--substrate-font-weight, 400);
29
+ --rc-line-height: var(--substrate-line-height, normal);
30
+ --rc-icon-font-size: 1.5rem;
31
+ --rc-icon-font-line-height: var(--rc-icon-font-size);
32
+ --rc-control-block-size: var(--substrate-control-block-size, 2.5rem);
33
+ --rc-control-padding-block: var(--substrate-control-padding-block, 0.5rem);
34
+ --rc-control-padding-inline: var(--substrate-control-padding-inline, 0.75rem);
35
+ --rc-control-gap: var(--substrate-control-gap, 0.5rem);
36
+ --rc-button-color: var(--rc-button-text, ButtonText);
37
+ --rc-button-block-size: var(--rc-control-block-size);
38
+ --rc-button-radius: var(--rc-control-radius);
39
+ --rc-button-border: var(--rc-border);
40
+ --rc-button-padding-block: var(--rc-control-padding-block);
41
+ --rc-button-padding-inline: var(--rc-control-padding-inline);
42
+ --rc-button-gap: var(--rc-control-gap);
43
+ --rc-button-font: var(--rc-font-weight) var(--rc-font-size) / var(--rc-line-height)
44
+ var(--rc-font-family);
45
+ --rc-button-hover-state-layer-opacity: 0.08;
46
+ --rc-button-focus-state-layer-opacity: 0.12;
47
+ --rc-button-pressed-state-layer-opacity: 0.12;
48
+ --rc-card-bg: var(--substrate-surface-raised, Canvas);
49
+ --rc-card-color: var(--substrate-on-surface, CanvasText);
50
+ --rc-card-radius: var(--substrate-radius-lg, 0.75rem);
51
+ --rc-card-padding-block: 0.75rem;
52
+ --rc-card-padding-inline: 0.75rem;
53
+ --rc-card-actions-gap: 0.5rem;
54
+ --rc-card-subtitle-color: color-mix(in srgb, currentColor 72%, transparent);
55
+ --rc-card-hover-state-layer-opacity: 0.08;
56
+ --rc-card-pressed-state-layer-opacity: 0.12;
57
+ --rc-chip-block-size: 2rem;
58
+ --rc-chip-gap: 0px;
59
+ --rc-chip-padding-inline: 0.75rem;
60
+ --rc-chip-border: var(--rc-border);
61
+ --rc-chip-radius: var(--substrate-radius-md, 0.5rem);
62
+ --rc-chip-bg: var(--substrate-surface-raised, ButtonFace);
63
+ --rc-chip-color: var(--substrate-on-surface, ButtonText);
64
+ --rc-chip-font: inherit;
65
+ --rc-chip-text-decoration: none;
66
+ --rc-chip-selected-bg: var(--rc-highlight, Highlight);
67
+ --rc-switch-native-appearance: none;
68
+ --rc-switch-native-inline-size: 100%;
69
+ --rc-switch-native-block-size: 100%;
70
+ --rc-switch-native-margin: 0;
71
+ --rc-switch-native-opacity: 0;
72
+ --rc-switch-decoration-display: block;
73
+ --rc-switch-icon-display: grid;
74
+ --rc-switch-track-inline-size: 3.25rem;
75
+ --rc-switch-track-block-size: 2rem;
76
+ --rc-switch-selected-track-bg: var(--rc-accent);
77
+ --rc-switch-thumb-translate: 1.125rem;
78
+ --rc-segmented-button-segment-min-block-size: var(--rc-control-block-size);
79
+ --rc-segmented-button-selected-bg: var(--rc-highlight, Highlight);
80
+ --rc-snackbar-bg: var(--substrate-inverse-surface, CanvasText);
81
+ --rc-snackbar-color: var(--substrate-inverse-on-surface, Canvas);
82
+ --rc-snackbar-border: 0;
83
+ --rc-snackbar-action-color: var(--substrate-inverse-on-surface, Canvas);
84
+ --rc-snackbar-action-border: 0;
85
+ --rc-snackbar-action-radius: 9999px;
86
+ --rc-snackbar-action-padding-block: 0.5rem;
87
+ --rc-snackbar-action-padding-inline: 0.75rem;
88
+ --rc-snackbar-action-bg: transparent;
89
+ --rc-snackbar-action-font: 650 0.875rem / 1.25 var(--rc-font-family, inherit);
90
+ --rc-item-padding-block: var(--substrate-item-padding-block, 0.55rem);
91
+ --rc-item-padding-inline: var(--substrate-item-padding-inline, 0.75rem);
92
+ --rc-item-gap: var(--substrate-item-gap, 0.5rem);
93
+ --rc-list-item-gap: var(--rc-item-gap);
94
+ --rc-list-item-min-block-size: 2.25rem;
95
+ --rc-list-item-padding-block: var(--rc-item-padding-block);
96
+ --rc-list-item-padding-inline: var(--rc-item-padding-inline);
97
+ --rc-list-item-selected-bg: var(--rc-highlight, Highlight);
98
+ --rc-list-item-selected-color: var(--rc-highlight-text, HighlightText);
99
+ --rc-radius-sm: var(--substrate-radius-sm, 0.25rem);
100
+ --rc-radius-md: var(--substrate-radius-md, 0.5rem);
101
+ --rc-control-radius: var(--substrate-radius-md, 0.5rem);
102
+ --rc-focus-ring: var(--substrate-focus-ring, 2px solid Highlight);
103
+ --rc-focus-ring-offset: var(--substrate-focus-ring-offset, 2px);
104
+ --rc-disabled-opacity: var(--substrate-disabled-opacity, 0.52);
105
+ --rc-motion-effects-duration-fast: var(--substrate-motion-effects-duration-fast, 100ms);
106
+ --rc-motion-effects-easing-fast: ease-out;
107
+ --rc-motion-effects-duration-default: var(--substrate-motion-effects-duration-default, 160ms);
108
+ --rc-motion-effects-easing-default: var(--rc-motion-effects-easing-fast);
109
+ --rc-motion-effects-duration-slow: var(--substrate-motion-effects-duration-slow, 240ms);
110
+ --rc-motion-effects-easing-slow: var(--rc-motion-effects-easing-fast);
111
+ --rc-motion-spatial-duration-fast: var(--substrate-motion-spatial-duration-fast, 160ms);
112
+ --rc-motion-spatial-easing-fast: var(--substrate-motion-easing, cubic-bezier(0.2, 0, 0, 1));
113
+ --rc-motion-spatial-duration-default: var(--substrate-motion-spatial-duration-default, 240ms);
114
+ --rc-motion-spatial-easing-default: var(--rc-motion-spatial-easing-fast);
115
+ --rc-motion-spatial-duration-slow: var(--substrate-motion-spatial-duration-slow, 320ms);
116
+ --rc-motion-spatial-easing-slow: var(--rc-motion-spatial-easing-fast);
117
+ /* Substrate's one curve is decelerate-shaped, so it is the enter curve. */
118
+ --rc-motion-effects-easing-enter: ease-out;
119
+ --rc-motion-effects-easing-exit: ease-in;
120
+ --rc-motion-spatial-easing-enter: var(--rc-motion-spatial-easing-fast);
121
+ --rc-motion-spatial-easing-exit: cubic-bezier(0.3, 0, 1, 1);
122
+ /* Matches base.css: the compatibility token follows the effects default. */
123
+ --rc-motion-duration: var(--rc-motion-effects-duration-default);
124
+ /*
125
+ * Mixed against the theme's own inverse surface rather than `CanvasText`,
126
+ * so the scrim carries the palette's warmth and resolves to a real color
127
+ * rather than to a system one that cannot leave CSS.
128
+ */
129
+ --rc-dialog-scrim: color-mix(
130
+ in srgb,
131
+ var(--substrate-inverse-surface, CanvasText) 42%,
132
+ transparent
133
+ );
134
+ }
135
+
136
+ .rc-theme-substrate rc-listbox,
137
+ :host(.rc-theme-substrate) rc-listbox,
138
+ .rc-theme-substrate rc-menu,
139
+ :host(.rc-theme-substrate) rc-menu {
140
+ --rc-listbox-option-gap: var(--rc-list-item-gap, 0.5rem);
141
+ --rc-listbox-option-min-block-size: var(--rc-list-item-min-block-size, 2.25rem);
142
+ --rc-listbox-option-padding-block: var(--rc-list-item-padding-block, 0.55rem);
143
+ --rc-listbox-option-padding-inline: var(--rc-list-item-padding-inline, 0.75rem);
144
+ --rc-listbox-hover-bg: color-mix(in srgb, var(--rc-accent, Highlight) 10%, transparent);
145
+ --rc-listbox-hover-color: var(--rc-field-text, FieldText);
146
+ --rc-listbox-active-bg: color-mix(in srgb, var(--rc-accent, Highlight) 16%, transparent);
147
+ --rc-listbox-active-color: var(--rc-field-text, FieldText);
148
+ --rc-listbox-selected-bg: var(--rc-list-item-selected-bg, Highlight);
149
+ --rc-listbox-selected-color: var(--rc-list-item-selected-color, HighlightText);
150
+ }
151
+
152
+ .rc-theme-substrate rc-select,
153
+ :host(.rc-theme-substrate) rc-select,
154
+ .rc-theme-substrate rc-combobox,
155
+ :host(.rc-theme-substrate) rc-combobox {
156
+ --rc-select-control-block-size: var(--rc-control-block-size);
157
+ --rc-select-radius: var(--rc-control-radius);
158
+ --rc-select-shadow: var(--rc-shadow);
159
+ --rc-select-listbox-radius: var(--substrate-radius-lg, 0.75rem);
160
+ --rc-select-listbox-border: var(--rc-border);
161
+ --rc-select-chip-radius: var(--substrate-radius-sm, 0.25rem);
162
+ --rc-combobox-control-block-size: var(--rc-control-block-size);
163
+ --rc-combobox-radius: var(--rc-control-radius);
164
+ --rc-combobox-shadow: var(--rc-shadow);
165
+ --rc-combobox-listbox-radius: var(--substrate-radius-lg, 0.75rem);
166
+ --rc-combobox-listbox-border: var(--rc-border);
167
+ --rc-combobox-chip-radius: var(--substrate-radius-sm, 0.25rem);
168
+ }
169
+
170
+ .rc-theme-substrate rc-slider,
171
+ :host(.rc-theme-substrate) rc-slider,
172
+ .rc-theme-substrate rc-range-slider,
173
+ :host(.rc-theme-substrate) rc-range-slider {
174
+ --rc-slider-track-size: 0.375rem;
175
+ --rc-slider-track-background: color-mix(in srgb, var(--rc-border-color) 70%, transparent);
176
+ --rc-slider-progress-background: var(--rc-accent);
177
+ --rc-slider-track-radius: 999px;
178
+ --rc-range-slider-accent: var(--rc-accent);
179
+ --rc-range-slider-track-background: color-mix(in srgb, var(--rc-border-color) 70%, transparent);
180
+ --rc-range-slider-thumb-background: var(--substrate-surface, Canvas);
181
+ --rc-range-slider-thumb-border: var(--rc-accent);
182
+ --rc-thumb-radius: 0.5625rem;
183
+ }
184
+
185
+ .rc-theme-substrate rc-progress,
186
+ :host(.rc-theme-substrate) rc-progress {
187
+ --rc-progress-track-background: color-mix(in srgb, var(--rc-border-color) 70%, transparent);
188
+ --rc-progress-track-opacity: 1;
189
+ --rc-progress-track-radius: 999px;
190
+ --rc-progress-fill-background: var(--rc-accent);
191
+ --rc-progress-value-color: var(--substrate-on-surface-muted, GrayText);
192
+ }
193
+
194
+ .rc-theme-substrate rc-bottom-sheet,
195
+ :host(.rc-theme-substrate) rc-bottom-sheet {
196
+ --rc-bottom-sheet-bg: var(--substrate-surface-raised, Canvas);
197
+ --rc-bottom-sheet-color: var(--rc-field-text, CanvasText);
198
+ --rc-bottom-sheet-radius: var(--substrate-radius-xl, 1rem) var(--substrate-radius-xl, 1rem) 0 0;
199
+ --rc-bottom-sheet-shadow: var(--substrate-shadow-lg, none);
200
+ --rc-bottom-sheet-scrim: var(--rc-dialog-scrim);
201
+ --rc-bottom-sheet-handle-color: var(--rc-border-color, GrayText);
202
+ --rc-bottom-sheet-handle-radius: var(--substrate-radius-pill, 999px);
203
+ }
204
+ }
@@ -0,0 +1,18 @@
1
+ @layer rc-theme-substrate.components {
2
+ /*
3
+ * Only the group box. Panels inside an accordion take the same
4
+ * `--rc-disclosure-*` mapping as a standalone disclosure, which disclosure.css
5
+ * sets for `rc-accordion` as well, so the panel recipe is not repeated here.
6
+ */
7
+ :where(.rc-theme-substrate, :host(.rc-theme-substrate)) rc-accordion {
8
+ --rc-accordion-gap: 0.5rem;
9
+
10
+ /* Laid out before the element upgrades; see disclosure.css. */
11
+ display: grid;
12
+ gap: var(--rc-accordion-gap, 0.5rem);
13
+ }
14
+
15
+ :where(.rc-theme-substrate, :host(.rc-theme-substrate)) rc-accordion > rc-disclosure {
16
+ display: block;
17
+ }
18
+ }