@expressivecss/expressive 0.7.0 → 0.9.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 (122) hide show
  1. package/CHANGELOG.md +1313 -1
  2. package/README.md +76 -30
  3. package/dist/css/expressive.css +4896 -1777
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/LICENSE-material-symbols +93 -0
  8. package/dist/fonts/LICENSE-noto-sans +93 -0
  9. package/dist/fonts/LICENSE-roboto +93 -0
  10. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  11. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  12. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  13. package/dist/fonts/noto-sans-latin-400.woff2 +0 -0
  14. package/dist/fonts/noto-sans-latin-500.woff2 +0 -0
  15. package/dist/fonts/roboto-latin-400.woff2 +0 -0
  16. package/dist/fonts/roboto-latin-500.woff2 +0 -0
  17. package/dist/js/expressive.cjs +1673 -1338
  18. package/dist/js/expressive.cjs.map +4 -4
  19. package/dist/js/expressive.js +1673 -1338
  20. package/dist/js/expressive.js.map +4 -4
  21. package/dist/js/expressive.min.js +13 -13
  22. package/dist/js/expressive.min.js.map +4 -4
  23. package/dist/js/expressive.mjs +1673 -1338
  24. package/dist/js/expressive.mjs.map +4 -4
  25. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  26. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  27. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  28. package/dist/types/components/appBar.d.ts +26 -0
  29. package/dist/types/components/autocomplete.d.ts +15 -3
  30. package/dist/types/components/buttonGroup.d.ts +39 -0
  31. package/dist/types/components/buttons.d.ts +5 -1
  32. package/dist/types/components/cards.d.ts +11 -13
  33. package/dist/types/components/carousel.d.ts +87 -53
  34. package/dist/types/components/chips.d.ts +21 -1
  35. package/dist/types/components/expandingCard.d.ts +31 -0
  36. package/dist/types/components/index.d.ts +23 -20
  37. package/dist/types/components/lightbox.d.ts +1 -0
  38. package/dist/types/components/menu.d.ts +11 -1
  39. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  40. package/dist/types/components/registry.d.ts +18 -8
  41. package/dist/types/components/slider.d.ts +26 -85
  42. package/dist/types/components/snackbar.d.ts +20 -5
  43. package/dist/types/components/tabs.d.ts +12 -0
  44. package/dist/types/components/timepicker.d.ts +12 -10
  45. package/dist/types/components/tooltip.d.ts +1 -0
  46. package/dist/types/core/utils.d.ts +64 -53
  47. package/dist/types/index.d.ts +8 -9
  48. package/package.json +39 -3
  49. package/src/sass/README.md +61 -11
  50. package/src/sass/abstracts/_breakpoints.scss +17 -19
  51. package/src/sass/abstracts/_mixins.scss +155 -21
  52. package/src/sass/abstracts/_variables.scss +69 -5
  53. package/src/sass/base/_fonts.scss +63 -0
  54. package/src/sass/base/_global.scss +6 -24
  55. package/src/sass/base/_grid.scss +15 -12
  56. package/src/sass/base/_normalize.scss +34 -38
  57. package/src/sass/base/_typography.scss +4 -18
  58. package/src/sass/components/_badges.scss +3 -5
  59. package/src/sass/components/_banners.scss +298 -0
  60. package/src/sass/components/_bottom-app-bar.scss +180 -0
  61. package/src/sass/components/_bottom-sheet.scss +12 -4
  62. package/src/sass/components/_button-groups.scss +358 -0
  63. package/src/sass/components/_buttons.scss +245 -92
  64. package/src/sass/components/_cards.scss +514 -78
  65. package/src/sass/components/_carousel.scss +562 -68
  66. package/src/sass/components/_chips.scss +64 -5
  67. package/src/sass/components/_datepicker.scss +24 -24
  68. package/src/sass/components/_dialog.scss +17 -11
  69. package/src/sass/components/_drag-handle.scss +130 -0
  70. package/src/sass/components/_expanding-card.scss +226 -0
  71. package/src/sass/components/_fab-menu.scss +232 -0
  72. package/src/sass/components/_floating-sheet.scss +86 -0
  73. package/src/sass/components/_icon-buttons.scss +172 -0
  74. package/src/sass/components/_icons-material-design.scss +22 -10
  75. package/src/sass/components/_index.scss +22 -6
  76. package/src/sass/components/_list.scss +16 -13
  77. package/src/sass/components/_loading-indicator.scss +106 -0
  78. package/src/sass/components/_menu.scss +335 -61
  79. package/src/sass/components/_navbar.scss +245 -45
  80. package/src/sass/components/_navigation-bar.scss +22 -11
  81. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  82. package/src/sass/components/_navigation-rail.scss +31 -23
  83. package/src/sass/components/_page-footer.scss +19 -16
  84. package/src/sass/components/_pagination.scss +24 -6
  85. package/src/sass/components/_panes.scss +52 -11
  86. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  87. package/src/sass/components/_scrim.scss +37 -0
  88. package/src/sass/components/_search.scss +259 -0
  89. package/src/sass/components/_segmented-buttons.scss +188 -0
  90. package/src/sass/components/_side-sheet.scss +2 -2
  91. package/src/sass/components/_snackbar.scss +20 -15
  92. package/src/sass/components/_split-button.scss +196 -0
  93. package/src/sass/components/_tabs.scss +7 -8
  94. package/src/sass/components/_timepicker.scss +67 -58
  95. package/src/sass/components/_toolbar.scss +206 -80
  96. package/src/sass/components/_transitions.scss +8 -1
  97. package/src/sass/components/forms/_checkboxes.scss +49 -12
  98. package/src/sass/components/forms/_fieldset.scss +4 -4
  99. package/src/sass/components/forms/_file-input.scss +8 -1
  100. package/src/sass/components/forms/_index.scss +1 -1
  101. package/src/sass/components/forms/_input-fields.scss +50 -44
  102. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  103. package/src/sass/components/forms/_select.scss +38 -1
  104. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  105. package/src/sass/components/forms/_switches.scss +6 -7
  106. package/src/sass/expressive.scss +5 -0
  107. package/src/sass/tokens/_index.scss +4 -1
  108. package/src/sass/tokens/_reference.scss +70 -61
  109. package/src/sass/tokens/_state.scss +24 -0
  110. package/src/sass/tokens/_theme.scss +6 -3
  111. package/src/sass/tokens/_vibrant.scss +76 -0
  112. package/src/sass/utilities/_colors.scss +14 -1
  113. package/src/sass/utilities/_helpers.scss +5 -5
  114. package/src/sass/utilities/_visibility.scss +48 -26
  115. package/dist/types/behaviors/waves.d.ts +0 -15
  116. package/dist/types/components/parallax.d.ts +0 -16
  117. package/dist/types/components/range.d.ts +0 -43
  118. package/dist/types/core/bounding.d.ts +0 -6
  119. package/dist/types/core/edges.d.ts +0 -6
  120. package/src/sass/components/_parallax.scss +0 -104
  121. package/src/sass/components/_pulse.scss +0 -37
  122. package/src/sass/components/_slider.scss +0 -129
@@ -34,9 +34,19 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
34
34
  height: 56px;
35
35
  background-color: transparent;
36
36
  @include typescale("body-large");
37
- font-family: $font-stack;
38
37
  }
39
38
 
39
+ // A .field is full of spans that are not the floating label: supporting text,
40
+ // the character counter, the file path, the select caret, and - since field
41
+ // icons stopped being <i> - the leading and trailing icons. Every rule that
42
+ // styles "the label, or the span standing in for it" has to exclude all of
43
+ // them, and there are five such rules. They were five hand-copied lists that
44
+ // had already drifted (.file-path was in two of them), and adding .prefix /
45
+ // .suffix to four of five is exactly the bug this variable prevents: the
46
+ // leading icon inherited the label's position and sat at 16px with
47
+ // pointer-events: none.
48
+ $_not-label: ".supporting-text, .character-counter, .file-path, .caret, .prefix, .suffix";
49
+
40
50
  .field {
41
51
  --md-comp-filled-text-field-container-color: var(--md-sys-color-surface-container-highest);
42
52
  --md-comp-filled-text-field-container-shape: 1rem;
@@ -72,16 +82,16 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
72
82
  }
73
83
 
74
84
  &:focus:not([readonly]) + label,
75
- &:focus:not([readonly]) + span:not(.supporting-text, .character-counter, .caret) {
85
+ &:focus:not([readonly]) + span:not(#{$_not-label}) {
76
86
  color: var(--input-color);
77
87
  }
78
88
 
79
89
  &:focus:not([readonly]) + label,
80
90
  &:not([placeholder=" "]) + label,
81
91
  &:not(:placeholder-shown) + label,
82
- &:focus:not([readonly]) + span:not(.supporting-text, .character-counter, .caret),
83
- &:not([placeholder=" "]) + span:not(.supporting-text, .character-counter, .caret),
84
- &:not(:placeholder-shown) + span:not(.supporting-text, .character-counter, .caret) {
92
+ &:focus:not([readonly]) + span:not(#{$_not-label}),
93
+ &:not([placeholder=" "]) + span:not(#{$_not-label}),
94
+ &:not(:placeholder-shown) + span:not(#{$_not-label}) {
85
95
  transform: scale(calc(12 / 16));
86
96
  top: 8px;
87
97
  }
@@ -123,8 +133,10 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
123
133
  user-select: none;
124
134
  }
125
135
 
126
- > label,
127
- > span:not(.supporting-text, .character-counter, .file-path, .caret) {
136
+ // :not(.button, .icon-button) - the file input's trigger is a <label> and is
137
+ // a button, not this field's floating label.
138
+ > label:not(.button, .icon-button),
139
+ > span:not(#{$_not-label}) {
128
140
  color: var(--md-sys-color-on-surface-variant);
129
141
  user-select: none;
130
142
  position: absolute;
@@ -134,7 +146,6 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
134
146
  transform-origin: top left;
135
147
  pointer-events: none;
136
148
  @include typescale("body-large");
137
- font-family: $font-stack;
138
149
  transition: left 0.2s ease-out, top 0.2s ease-out, transform 0.2s ease-out, color 0.2s ease-out;
139
150
  }
140
151
 
@@ -145,7 +156,6 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
145
156
  padding: 0 16px;
146
157
  color: var(--md-sys-color-on-surface-variant);
147
158
  @include typescale("body-small");
148
- font-family: $font-stack;
149
159
  }
150
160
 
151
161
  > .character-counter {
@@ -154,13 +164,20 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
154
164
  padding: 0 16px;
155
165
  color: var(--md-sys-color-on-surface-variant);
156
166
  @include typescale("body-small");
157
- font-family: $font-stack;
158
167
  }
159
168
 
160
169
  // Shared icon box. Position is set on the leading / trailing rules;
161
170
  // a lone <i> matches several of the :is() items and must not pick
162
171
  // a side here.
163
- > i:is(.prefix, .suffix, :first-of-type, :last-of-type, :last-child) {
172
+ //
173
+ // The `i` rules are the legacy path and still work. They cannot be widened
174
+ // to the canonical <span class="material-symbols"> form, because they lean
175
+ // on :first-of-type / :last-of-type - which count elements of the same
176
+ // *type*, and a field is full of other spans (.supporting-text, .caret,
177
+ // .character-counter, .file-path). So the documented markup names the side
178
+ // explicitly with .prefix / .suffix, and those rules key on nothing else.
179
+ > i:is(.prefix, .suffix, :first-of-type, :last-of-type, :last-child),
180
+ > :is(.prefix, .suffix) {
164
181
  position: absolute;
165
182
  top: 16px;
166
183
  display: flex;
@@ -174,28 +191,41 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
174
191
  }
175
192
 
176
193
  > i:first-of-type:not(.suffix):not(:last-child),
177
- > i.prefix {
194
+ > i.prefix,
195
+ > .prefix {
178
196
  left: 12px;
179
197
  pointer-events: none;
180
198
  }
181
199
 
182
200
  > i.suffix,
183
201
  > i:last-of-type:not(:first-of-type),
184
- > i:last-child:not(:first-child):not(.prefix) {
202
+ > i:last-child:not(:first-child):not(.prefix),
203
+ > .suffix {
185
204
  right: 12px;
205
+ // Both icons inherited this from the floating-label rule until they were
206
+ // excluded from it. .prefix restates it below; .suffix has to as well, or
207
+ // the trailing icon swallows the click that should focus the input.
208
+ pointer-events: none;
186
209
  }
187
210
 
188
- &:has(> i:first-of-type:not(.suffix):not(:last-child), > i.prefix) {
211
+ &:has(> i:first-of-type:not(.suffix):not(:last-child), > i.prefix, > .prefix) {
189
212
  > :is(input, textarea) {
190
213
  padding-left: 52px;
191
214
  }
192
215
 
193
- > :is(label, span:not(.supporting-text, .character-counter, .file-path, .caret)) {
216
+ // The exclusion list is what stops this indenting things that are not the
217
+ // floating label. .prefix / .suffix joined it when field icons stopped
218
+ // being <i> and became spans - without them the leading icon was pushed
219
+ // to 52px, i.e. indented past its own padding.
220
+ > :is(
221
+ label,
222
+ span:not(#{$_not-label})
223
+ ) {
194
224
  left: 52px;
195
225
  }
196
226
  }
197
227
 
198
- &:has(> i.suffix, > i:last-of-type:not(:first-of-type), > i:last-child:not(:first-child):not(.prefix)) {
228
+ &:has(> i.suffix, > i:last-of-type:not(:first-of-type), > i:last-child:not(:first-child):not(.prefix), > .suffix) {
199
229
  > :is(input, textarea) {
200
230
  padding-right: 52px;
201
231
  }
@@ -220,9 +250,9 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
220
250
  &:focus:not([readonly]) + label,
221
251
  &:not([placeholder=" "]) + label,
222
252
  &:not(:placeholder-shown) + label,
223
- &:focus:not([readonly]) + span:not(.supporting-text, .character-counter, .caret),
224
- &:not([placeholder=" "]) + span:not(.supporting-text, .character-counter, .caret),
225
- &:not(:placeholder-shown) + span:not(.supporting-text, .character-counter, .caret) {
253
+ &:focus:not([readonly]) + span:not(#{$_not-label}),
254
+ &:not([placeholder=" "]) + span:not(#{$_not-label}),
255
+ &:not(:placeholder-shown) + span:not(#{$_not-label}) {
226
256
  top: -8px;
227
257
  left: 16px;
228
258
  margin-left: -4px;
@@ -249,30 +279,6 @@ $_text-inputs: "input:not([type]):not(.browser-default), input[type=text]:not(.b
249
279
  display: inline-block;
250
280
  }
251
281
 
252
- .searchbar {
253
- > i:first-child {
254
- position: absolute;
255
- padding-left: 1rem;
256
- top: 0;
257
- user-select: none;
258
- display: flex;
259
- align-self: center;
260
- }
261
-
262
- > input {
263
- border-width: 0;
264
- background-color: transparent;
265
- padding-left: 3rem;
266
- }
267
- }
268
-
269
- .searchbar.has-sidebar {
270
- margin-left: 0;
271
- @include bp-up("large") {
272
- margin-left: 300px;
273
- }
274
- }
275
-
276
282
  textarea {
277
283
  width: 100%;
278
284
  height: 3rem;
@@ -300,7 +306,7 @@ textarea {
300
306
  }
301
307
  }
302
308
 
303
- .datepicker-date-input {
309
+ :is(.datepicker-date-input, .date-picker-date-input) {
304
310
  position: relative;
305
311
  text-indent: -9999px;
306
312
 
@@ -30,18 +30,20 @@ label:has(> [type="radio"]),
30
30
  user-select: none;
31
31
  color: var(--md-sys-color-on-surface);
32
32
  @include typescale("body-large");
33
- font-family: $font-stack;
34
33
  }
35
34
 
36
- nav:has(> label > [type="radio"]) {
35
+ // A row of radios is a group of controls, not navigation - see rule 4 in
36
+ // SEMANTICS.md. `.inline` is the documented hook; the bare <nav> is the older
37
+ // markup and still works.
38
+ :is(nav, .inline):has(> label > [type="radio"]) {
37
39
  display: flex;
38
40
  flex-wrap: wrap;
39
41
  align-items: center;
40
42
  gap: 8px 24px;
41
43
  }
42
44
 
43
- nav > label:has(> [type="radio"]),
44
- nav > .radio {
45
+ :is(nav, .inline) > label:has(> [type="radio"]),
46
+ :is(nav, .inline) > .radio {
45
47
  display: inline-flex;
46
48
  }
47
49
 
@@ -73,16 +75,31 @@ label:has(> [type="radio"]) > [type="radio"],
73
75
  }
74
76
 
75
77
  &:hover {
76
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
78
+ @include state-layer-ring(
79
+ var(--md-comp-radio-button-state-layer-size),
80
+ var(--md-comp-radio-button-icon-size),
81
+ var(--md-sys-color-on-surface),
82
+ "hover"
83
+ );
77
84
  }
78
85
 
79
86
  &:checked:hover {
80
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
87
+ @include state-layer-ring(
88
+ var(--md-comp-radio-button-state-layer-size),
89
+ var(--md-comp-radio-button-icon-size),
90
+ var(--md-sys-color-primary),
91
+ "hover"
92
+ );
81
93
  }
82
94
 
83
95
  &:focus-visible,
84
96
  &.tabbed:focus {
85
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
97
+ @include state-layer-ring(
98
+ var(--md-comp-radio-button-state-layer-size),
99
+ var(--md-comp-radio-button-icon-size),
100
+ var(--md-sys-color-primary),
101
+ "focus"
102
+ );
86
103
  outline: none;
87
104
  }
88
105
 
@@ -163,16 +180,31 @@ label:has(> [type="radio"]:disabled),
163
180
  }
164
181
 
165
182
  [type="radio"]:hover + span::before {
166
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
183
+ @include state-layer-ring(
184
+ var(--md-comp-radio-button-state-layer-size),
185
+ var(--md-comp-radio-button-icon-size),
186
+ var(--md-sys-color-on-surface),
187
+ "hover"
188
+ );
167
189
  }
168
190
 
169
191
  [type="radio"]:checked:hover + span::before {
170
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
192
+ @include state-layer-ring(
193
+ var(--md-comp-radio-button-state-layer-size),
194
+ var(--md-comp-radio-button-icon-size),
195
+ var(--md-sys-color-primary),
196
+ "hover"
197
+ );
171
198
  }
172
199
 
173
200
  [type="radio"]:focus-visible + span::before,
174
201
  [type="radio"].tabbed:focus + span::before {
175
- box-shadow: 0 0 0 10px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
202
+ @include state-layer-ring(
203
+ var(--md-comp-radio-button-state-layer-size),
204
+ var(--md-comp-radio-button-icon-size),
205
+ var(--md-sys-color-primary),
206
+ "focus"
207
+ );
176
208
  }
177
209
 
178
210
  [type="radio"]:disabled + span {
@@ -34,7 +34,6 @@ select {
34
34
  background-color: var(--md-comp-filled-select-container-color);
35
35
  color: var(--md-comp-filled-select-input-text-color);
36
36
  @include typescale("body-large");
37
- font-family: $font-stack;
38
37
 
39
38
  &:focus {
40
39
  outline: 2px solid var(--md-sys-color-primary);
@@ -133,6 +132,44 @@ select.browser-default {
133
132
  }
134
133
  }
135
134
 
135
+ // Filled selects always have a floated label. Put that label in a real row
136
+ // so translated wrapping and enlarged text reserve space above the value.
137
+ .field:has(> .hide-select):has(> label):not(.outlined) {
138
+ display: grid;
139
+
140
+ > label {
141
+ grid-row: 1;
142
+ background-color: var(--md-comp-filled-text-field-container-color);
143
+ border-start-start-radius: inherit;
144
+ border-start-end-radius: inherit;
145
+ position: static;
146
+ transform: none;
147
+ margin: 0;
148
+ padding: 8px 16px 0;
149
+ padding-inline-end: 52px;
150
+ overflow-wrap: anywhere;
151
+ pointer-events: auto;
152
+ @include typescale("body-small");
153
+ }
154
+
155
+ &:has(> i:first-of-type:not(.suffix):not(:last-child), > i.prefix, > .prefix) > label {
156
+ padding-left: 52px;
157
+ }
158
+
159
+ &.disabled > label {
160
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 4%, transparent);
161
+ }
162
+
163
+ > input.menu-trigger {
164
+ grid-row: 2;
165
+ height: auto;
166
+ min-height: calc(var(--md-sys-typescale-body-large-line-height) + 8px);
167
+ padding-block: 0 7px;
168
+ border-start-start-radius: 0;
169
+ border-start-end-radius: 0;
170
+ }
171
+ }
172
+
136
173
  .field:has(> .hide-select).disabled {
137
174
  .caret {
138
175
  background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
@@ -3,9 +3,8 @@
3
3
  // Sliders.
4
4
  //
5
5
  // Anatomy is the HTML. An <input type="range"> is the control. A wrapping
6
- // .range (or label / .range-field) is the host. .slider is the carousel —
7
- // do not put it on a range. The IIFE plugin is Expressive.Range because
8
- // Slider is the carousel.
6
+ // .slider is the host (.range and .range-field are the older names).
7
+ // The plugin is Expressive.Slider; Expressive.Range is kept as an alias.
9
8
  //
10
9
  // Variants (M3 Expressive):
11
10
  // standard default. Active from the start to the handle.
@@ -30,7 +29,10 @@
30
29
  // stop 4dp primary dot
31
30
  // label 40dp, inverse-surface / inverse-on-surface, body-small
32
31
 
33
- $_host: ".range, .range-field, label:has(> [type='range'])";
32
+ // M3 calls this component "Slider", so `.slider` is the name. It was the image
33
+ // slideshow's host before 0.8.0. The slideshow is gone in 0.8.0, so the class
34
+ // belongs to this component and nothing else.
35
+ $_host: ".slider, .range, .range-field, label:has(> [type='range'])";
34
36
 
35
37
  :is(#{$_host}),
36
38
  input[type="range"] {
@@ -61,7 +63,6 @@ input[type="range"] {
61
63
  overflow: visible;
62
64
  color: var(--md-sys-color-on-surface);
63
65
  @include typescale("body-large");
64
- font-family: $font-stack;
65
66
  }
66
67
 
67
68
  // Sizes. Default is XS. .small / .large keep the old names, now S / L.
@@ -119,7 +120,7 @@ input[type="range"] {
119
120
  }
120
121
 
121
122
  // Inset icon. Sits in the active track; M / L / XL are the intended sizes.
122
- :is(#{$_host}) > i:first-child {
123
+ :is(#{$_host}) > #{$icon}:first-child {
123
124
  position: absolute;
124
125
  z-index: 1;
125
126
  left: var(--md-comp-slider-stop-inset);
@@ -133,7 +134,7 @@ input[type="range"] {
133
134
  pointer-events: none;
134
135
  }
135
136
 
136
- :is(#{$_host}).vertical > i:first-child {
137
+ :is(#{$_host}).vertical > #{$icon}:first-child {
137
138
  left: 50%;
138
139
  top: auto;
139
140
  bottom: var(--md-comp-slider-stop-inset);
@@ -427,7 +428,6 @@ input[type="range"]:hover:not(:disabled)::-webkit-slider-thumb {
427
428
  box-shadow: 0 0 0 18px color-mix(in oklab, var(--md-comp-slider-handle-color) 8%, transparent);
428
429
  }
429
430
 
430
- .keyboard-focused input[type="range"]:focus:not(:disabled)::-webkit-slider-thumb,
431
431
  input[type="range"]:focus-visible:not(:disabled)::-webkit-slider-thumb {
432
432
  box-shadow: 0 0 0 18px color-mix(in oklab, var(--md-comp-slider-handle-color) 10%, transparent);
433
433
  }
@@ -469,7 +469,6 @@ input[type="range"]:hover:not(:disabled)::-moz-range-thumb {
469
469
  box-shadow: 0 0 0 18px color-mix(in oklab, var(--md-comp-slider-handle-color) 8%, transparent);
470
470
  }
471
471
 
472
- .keyboard-focused input[type="range"]:focus:not(:disabled)::-moz-range-thumb,
473
472
  input[type="range"]:focus-visible:not(:disabled)::-moz-range-thumb {
474
473
  box-shadow: 0 0 0 18px color-mix(in oklab, var(--md-comp-slider-handle-color) 10%, transparent);
475
474
  }
@@ -32,7 +32,6 @@
32
32
  -webkit-tap-highlight-color: transparent;
33
33
  color: var(--md-sys-color-on-surface);
34
34
  @include typescale("body-large");
35
- font-family: $font-stack;
36
35
  }
37
36
 
38
37
  .switch label {
@@ -104,16 +103,16 @@
104
103
  }
105
104
 
106
105
  &:hover {
107
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
106
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
108
107
  }
109
108
 
110
109
  &:checked:hover {
111
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
110
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) state-opacity("hover"), transparent);
112
111
  }
113
112
 
114
113
  &:focus-visible,
115
114
  &.tabbed:focus {
116
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
115
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) state-opacity("focus"), transparent);
117
116
  outline: none;
118
117
  }
119
118
 
@@ -186,19 +185,19 @@
186
185
 
187
186
  .switch [type="checkbox"]:hover + .lever,
188
187
  .switch [type="checkbox"]:hover + span {
189
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
188
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
190
189
  }
191
190
 
192
191
  .switch [type="checkbox"]:checked:hover + .lever,
193
192
  .switch [type="checkbox"]:checked:hover + span {
194
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
193
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) state-opacity("hover"), transparent);
195
194
  }
196
195
 
197
196
  .switch [type="checkbox"]:focus-visible + .lever,
198
197
  .switch [type="checkbox"].tabbed:focus + .lever,
199
198
  .switch [type="checkbox"]:focus-visible + span,
200
199
  .switch [type="checkbox"].tabbed:focus + span {
201
- box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
200
+ box-shadow: 0 0 0 8px color-mix(in oklab, var(--md-sys-color-primary) state-opacity("focus"), transparent);
202
201
  }
203
202
 
204
203
  .switch [type="checkbox"]:disabled + .lever,
@@ -41,6 +41,11 @@
41
41
 
42
42
  @forward "abstracts";
43
43
 
44
+ // @font-face must stay unlayered. A face declared inside @layer base can
45
+ // lose to any unlayered consumer face, or be ignored by engines that still
46
+ // treat layered faces as experimental.
47
+ @include meta.load-css("base/fonts");
48
+
44
49
  @layer tokens, base, components, utilities;
45
50
 
46
51
  @layer tokens {
@@ -1,4 +1,7 @@
1
- // Material Design 3 design tokens. Emits :root custom properties only.
1
+ // Material Design 3 design tokens. Emits custom properties only - on :root,
2
+ // and on [vibrant] for the emphasis axis.
2
3
 
3
4
  @forward "reference"; // raw ramps + resolved light/dark pairs
4
5
  @forward "theme"; // maps the pairs onto the live --md-sys-color-* names
6
+ @forward "vibrant"; // [vibrant] remaps the surface family onto an accent
7
+ @forward "state"; // md.sys.state.* state layer opacities
@@ -129,7 +129,7 @@ $sys-dark: (
129
129
  "surface-variant": "neutral-variant30", "surface-tint": "primary80"
130
130
  ) !default;
131
131
 
132
- :root {
132
+ :root, :host {
133
133
  --md-source: #{$md-source};
134
134
 
135
135
  /* primary, secondary, tertiary, neutral, neutral-variant - all generated */
@@ -181,109 +181,118 @@ $sys-dark: (
181
181
  --md-sys-color-#{$role}-dark: var(--md-ref-palette-#{$ref});
182
182
  }
183
183
 
184
+ /* typefaces
185
+ M3 separates expressive large type (brand) from readable small type
186
+ (plain). Roboto is the default for both. Noto Sans follows it as the
187
+ language fallback recommended by Material. Override either brand/plain
188
+ token to customize the scale without rewriting every role. */
189
+ --md-ref-typeface-brand: "Roboto";
190
+ --md-ref-typeface-plain: "Roboto";
191
+ --md-ref-typeface-fallback: "Noto Sans", sans-serif;
192
+
184
193
  /* display - large */
185
- --md-sys-typescale-display-large-font-family-name: Roboto;
194
+ --md-sys-typescale-display-large-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
186
195
  --md-sys-typescale-display-large-font-family-style: Regular;
187
196
  --md-sys-typescale-display-large-font-weight: 400;
188
- --md-sys-typescale-display-large-font-size: 57px;
189
- --md-sys-typescale-display-large-line-height: 64px;
190
- --md-sys-typescale-display-large-letter-spacing: -0.25px;
197
+ --md-sys-typescale-display-large-font-size: 3.5625rem;
198
+ --md-sys-typescale-display-large-line-height: 4rem;
199
+ --md-sys-typescale-display-large-letter-spacing: -0.015625rem;
191
200
  /* display - medium */
192
- --md-sys-typescale-display-medium-font-family-name: Roboto;
201
+ --md-sys-typescale-display-medium-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
193
202
  --md-sys-typescale-display-medium-font-family-style: Regular;
194
203
  --md-sys-typescale-display-medium-font-weight: 400;
195
- --md-sys-typescale-display-medium-font-size: 45px;
196
- --md-sys-typescale-display-medium-line-height: 52px;
197
- --md-sys-typescale-display-medium-letter-spacing: 0px;
204
+ --md-sys-typescale-display-medium-font-size: 2.8125rem;
205
+ --md-sys-typescale-display-medium-line-height: 3.25rem;
206
+ --md-sys-typescale-display-medium-letter-spacing: 0rem;
198
207
  /* display - small */
199
- --md-sys-typescale-display-small-font-family-name: Roboto;
208
+ --md-sys-typescale-display-small-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
200
209
  --md-sys-typescale-display-small-font-family-style: Regular;
201
210
  --md-sys-typescale-display-small-font-weight: 400;
202
- --md-sys-typescale-display-small-font-size: 36px;
203
- --md-sys-typescale-display-small-line-height: 44px;
204
- --md-sys-typescale-display-small-letter-spacing: 0px;
211
+ --md-sys-typescale-display-small-font-size: 2.25rem;
212
+ --md-sys-typescale-display-small-line-height: 2.75rem;
213
+ --md-sys-typescale-display-small-letter-spacing: 0rem;
205
214
  /* headline - large */
206
- --md-sys-typescale-headline-large-font-family-name: Roboto;
215
+ --md-sys-typescale-headline-large-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
207
216
  --md-sys-typescale-headline-large-font-family-style: Regular;
208
217
  --md-sys-typescale-headline-large-font-weight: 400;
209
- --md-sys-typescale-headline-large-font-size: 32px;
210
- --md-sys-typescale-headline-large-line-height: 40px;
211
- --md-sys-typescale-headline-large-letter-spacing: 0px;
218
+ --md-sys-typescale-headline-large-font-size: 2rem;
219
+ --md-sys-typescale-headline-large-line-height: 2.5rem;
220
+ --md-sys-typescale-headline-large-letter-spacing: 0rem;
212
221
  /* headline - medium */
213
- --md-sys-typescale-headline-medium-font-family-name: Roboto;
222
+ --md-sys-typescale-headline-medium-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
214
223
  --md-sys-typescale-headline-medium-font-family-style: Regular;
215
224
  --md-sys-typescale-headline-medium-font-weight: 400;
216
- --md-sys-typescale-headline-medium-font-size: 28px;
217
- --md-sys-typescale-headline-medium-line-height: 36px;
218
- --md-sys-typescale-headline-medium-letter-spacing: 0px;
225
+ --md-sys-typescale-headline-medium-font-size: 1.75rem;
226
+ --md-sys-typescale-headline-medium-line-height: 2.25rem;
227
+ --md-sys-typescale-headline-medium-letter-spacing: 0rem;
219
228
  /* headline - small */
220
- --md-sys-typescale-headline-small-font-family-name: Roboto;
229
+ --md-sys-typescale-headline-small-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
221
230
  --md-sys-typescale-headline-small-font-family-style: Regular;
222
231
  --md-sys-typescale-headline-small-font-weight: 400;
223
- --md-sys-typescale-headline-small-font-size: 24px;
224
- --md-sys-typescale-headline-small-line-height: 32px;
225
- --md-sys-typescale-headline-small-letter-spacing: 0px;
232
+ --md-sys-typescale-headline-small-font-size: 1.5rem;
233
+ --md-sys-typescale-headline-small-line-height: 2rem;
234
+ --md-sys-typescale-headline-small-letter-spacing: 0rem;
226
235
  /* body - large */
227
- --md-sys-typescale-body-large-font-family-name: Roboto;
236
+ --md-sys-typescale-body-large-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
228
237
  --md-sys-typescale-body-large-font-family-style: Regular;
229
238
  --md-sys-typescale-body-large-font-weight: 400;
230
- --md-sys-typescale-body-large-font-size: 16px;
231
- --md-sys-typescale-body-large-line-height: 24px;
232
- --md-sys-typescale-body-large-letter-spacing: 0.50px;
239
+ --md-sys-typescale-body-large-font-size: 1rem;
240
+ --md-sys-typescale-body-large-line-height: 1.5rem;
241
+ --md-sys-typescale-body-large-letter-spacing: 0.03125rem;
233
242
  /* body - medium */
234
- --md-sys-typescale-body-medium-font-family-name: Roboto;
243
+ --md-sys-typescale-body-medium-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
235
244
  --md-sys-typescale-body-medium-font-family-style: Regular;
236
245
  --md-sys-typescale-body-medium-font-weight: 400;
237
- --md-sys-typescale-body-medium-font-size: 14px;
238
- --md-sys-typescale-body-medium-line-height: 20px;
239
- --md-sys-typescale-body-medium-letter-spacing: 0.25px;
246
+ --md-sys-typescale-body-medium-font-size: 0.875rem;
247
+ --md-sys-typescale-body-medium-line-height: 1.25rem;
248
+ --md-sys-typescale-body-medium-letter-spacing: 0.015625rem;
240
249
  /* body - small */
241
- --md-sys-typescale-body-small-font-family-name: Roboto;
250
+ --md-sys-typescale-body-small-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
242
251
  --md-sys-typescale-body-small-font-family-style: Regular;
243
252
  --md-sys-typescale-body-small-font-weight: 400;
244
- --md-sys-typescale-body-small-font-size: 12px;
245
- --md-sys-typescale-body-small-line-height: 16px;
246
- --md-sys-typescale-body-small-letter-spacing: 0.40px;
253
+ --md-sys-typescale-body-small-font-size: 0.75rem;
254
+ --md-sys-typescale-body-small-line-height: 1rem;
255
+ --md-sys-typescale-body-small-letter-spacing: 0.025rem;
247
256
  /* label - large */
248
- --md-sys-typescale-label-large-font-family-name: Roboto;
257
+ --md-sys-typescale-label-large-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
249
258
  --md-sys-typescale-label-large-font-family-style: Medium;
250
259
  --md-sys-typescale-label-large-font-weight: 500;
251
- --md-sys-typescale-label-large-font-size: 14px;
252
- --md-sys-typescale-label-large-line-height: 20px;
253
- --md-sys-typescale-label-large-letter-spacing: 0.10px;
260
+ --md-sys-typescale-label-large-font-size: 0.875rem;
261
+ --md-sys-typescale-label-large-line-height: 1.25rem;
262
+ --md-sys-typescale-label-large-letter-spacing: 0.00625rem;
254
263
  /* label - medium */
255
- --md-sys-typescale-label-medium-font-family-name: Roboto;
264
+ --md-sys-typescale-label-medium-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
256
265
  --md-sys-typescale-label-medium-font-family-style: Medium;
257
266
  --md-sys-typescale-label-medium-font-weight: 500;
258
- --md-sys-typescale-label-medium-font-size: 12px;
259
- --md-sys-typescale-label-medium-line-height: 16px;
260
- --md-sys-typescale-label-medium-letter-spacing: 0.50px;
267
+ --md-sys-typescale-label-medium-font-size: 0.75rem;
268
+ --md-sys-typescale-label-medium-line-height: 1rem;
269
+ --md-sys-typescale-label-medium-letter-spacing: 0.03125rem;
261
270
  /* label - small */
262
- --md-sys-typescale-label-small-font-family-name: Roboto;
271
+ --md-sys-typescale-label-small-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
263
272
  --md-sys-typescale-label-small-font-family-style: Medium;
264
273
  --md-sys-typescale-label-small-font-weight: 500;
265
- --md-sys-typescale-label-small-font-size: 11px;
266
- --md-sys-typescale-label-small-line-height: 16px;
267
- --md-sys-typescale-label-small-letter-spacing: 0.50px;
274
+ --md-sys-typescale-label-small-font-size: 0.6875rem;
275
+ --md-sys-typescale-label-small-line-height: 1rem;
276
+ --md-sys-typescale-label-small-letter-spacing: 0.03125rem;
268
277
  /* title - large */
269
- --md-sys-typescale-title-large-font-family-name: Roboto;
278
+ --md-sys-typescale-title-large-font-family-name: var(--md-ref-typeface-brand), var(--md-ref-typeface-fallback);
270
279
  --md-sys-typescale-title-large-font-family-style: Regular;
271
280
  --md-sys-typescale-title-large-font-weight: 400;
272
- --md-sys-typescale-title-large-font-size: 22px;
273
- --md-sys-typescale-title-large-line-height: 28px;
274
- --md-sys-typescale-title-large-letter-spacing: 0px;
281
+ --md-sys-typescale-title-large-font-size: 1.375rem;
282
+ --md-sys-typescale-title-large-line-height: 1.75rem;
283
+ --md-sys-typescale-title-large-letter-spacing: 0rem;
275
284
  /* title - medium */
276
- --md-sys-typescale-title-medium-font-family-name: Roboto;
285
+ --md-sys-typescale-title-medium-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
277
286
  --md-sys-typescale-title-medium-font-family-style: Medium;
278
287
  --md-sys-typescale-title-medium-font-weight: 500;
279
- --md-sys-typescale-title-medium-font-size: 16px;
280
- --md-sys-typescale-title-medium-line-height: 24px;
281
- --md-sys-typescale-title-medium-letter-spacing: 0.15px;
288
+ --md-sys-typescale-title-medium-font-size: 1rem;
289
+ --md-sys-typescale-title-medium-line-height: 1.5rem;
290
+ --md-sys-typescale-title-medium-letter-spacing: 0.009375rem;
282
291
  /* title - small */
283
- --md-sys-typescale-title-small-font-family-name: Roboto;
292
+ --md-sys-typescale-title-small-font-family-name: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback);
284
293
  --md-sys-typescale-title-small-font-family-style: Medium;
285
294
  --md-sys-typescale-title-small-font-weight: 500;
286
- --md-sys-typescale-title-small-font-size: 14px;
287
- --md-sys-typescale-title-small-line-height: 20px;
288
- --md-sys-typescale-title-small-letter-spacing: 0.10px;
295
+ --md-sys-typescale-title-small-font-size: 0.875rem;
296
+ --md-sys-typescale-title-small-line-height: 1.25rem;
297
+ --md-sys-typescale-title-small-letter-spacing: 0.00625rem;
289
298
  }