material-inspired-component-library 9.1.0 → 9.1.1

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 (44) hide show
  1. package/README.md +2 -1
  2. package/components/bottomsheet/index.scss +2 -2
  3. package/components/button/_shared.scss +3 -3
  4. package/components/card/README.md +55 -25
  5. package/components/card/index.scss +57 -38
  6. package/components/checkbox/README.md +97 -28
  7. package/components/checkbox/index.scss +29 -19
  8. package/components/chip/index.scss +6 -6
  9. package/components/datepicker/index.scss +1 -1
  10. package/components/iconbutton/index.scss +3 -3
  11. package/components/list/index.scss +9 -5
  12. package/components/radio/README.md +52 -12
  13. package/components/radio/index.scss +53 -35
  14. package/components/stepper/index.scss +1 -1
  15. package/components/switch/README.md +32 -4
  16. package/components/switch/index.scss +45 -40
  17. package/components/timepicker/index.scss +1 -1
  18. package/dist/appbar.css +1 -1
  19. package/dist/bottomsheet.css +1 -1
  20. package/dist/button.css +1 -1
  21. package/dist/card.css +1 -1
  22. package/dist/checkbox.css +1 -1
  23. package/dist/checkbox.js +1 -1
  24. package/dist/chip.css +1 -1
  25. package/dist/components/checkbox/index.d.ts +1 -0
  26. package/dist/datepicker.css +1 -1
  27. package/dist/foundations/form/index.js +1 -1
  28. package/dist/iconbutton.css +1 -1
  29. package/dist/layout.css +1 -1
  30. package/dist/list.css +1 -1
  31. package/dist/micl.css +1 -1
  32. package/dist/micl.js +1 -1
  33. package/dist/navigationbar.css +1 -1
  34. package/dist/navigationrail.css +1 -1
  35. package/dist/radio.css +1 -1
  36. package/dist/sidesheet.css +1 -1
  37. package/dist/snackbar.css +1 -1
  38. package/dist/stepper.css +1 -1
  39. package/dist/stepper.js +1 -1
  40. package/dist/switch.css +1 -1
  41. package/dist/timepicker.css +1 -1
  42. package/foundations/layout/index.scss +6 -3
  43. package/package.json +2 -2
  44. package/{components/checkbox/_label.scss → styles/togglelabel.scss} +11 -3
@@ -22,8 +22,7 @@
22
22
  @use '../../styles/motion';
23
23
  @use '../../styles/shape';
24
24
  @use '../../styles/statelayer';
25
- @use '../../styles/typography';
26
- @use './label';
25
+ @use '../../styles/togglelabel';
27
26
 
28
27
  @include shape.corner('full');
29
28
 
@@ -33,6 +32,7 @@
33
32
  @include statelayer.token('pressed-state-layer-opacity');
34
33
  @include statelayer.token('disabled-state-layer-opacity');
35
34
  @include statelayer.token('focus-indicator-thickness');
35
+ @include statelayer.token('focus-indicator-outer-offset');
36
36
  @include statelayer.token('ripple-opacity-factor');
37
37
  @include statelayer.token('ripple-duration');
38
38
  @include statelayer.property;
@@ -51,28 +51,33 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
51
51
  --_size: var(--md-comp-checkbox-container-size, 18px);
52
52
  --_outline-width: var(--md-comp-checkbox-outline-width, 2px);
53
53
  --_check-thickness: var(--md-comp-checkbox-check-thickness, 2px);
54
+ --_layer: max(var(--md-sys-state-layer-size, 40px), calc(var(--_size) + 22px));
55
+ --_target: max(var(--md-sys-state-target-size, 48px), calc(var(--_layer) + 8px));
54
56
  --statelayer-color: var(--md-sys-color-on-surface);
55
57
 
56
58
  appearance: none;
57
59
  box-sizing: border-box;
58
60
  position: relative;
59
- inline-size: var(--md-sys-target-size, 48px);
60
- min-inline-size: var(--md-sys-target-size, 48px);
61
- block-size: var(--md-sys-target-size, 48px);
62
- min-block-size: var(--md-sys-target-size, 48px);
61
+ inline-size: var(--_target);
62
+ min-inline-size: var(--_target);
63
+ block-size: var(--_target);
64
+ min-block-size: var(--_target);
63
65
  margin: 0;
64
- border: calc((var(--md-sys-target-size, 48px) - var(--md-sys-state-layer-size, 40px)) / 2) solid transparent;
66
+ border: calc((var(--_target) - var(--_layer)) / 2) solid transparent;
65
67
  background-clip: content-box;
66
68
  background-color: transparent;
67
69
  -webkit-tap-highlight-color: transparent;
68
70
  border-radius: var(--md-sys-shape-corner-full);
69
- outline-offset: -4px;
71
+ outline-offset: calc(
72
+ var(--md-sys-state-focus-indicator-outer-offset, 2px)
73
+ - ((var(--_target) - var(--_layer)) / 2)
74
+ );
70
75
 
71
76
  // The check mark: two currentColor borders rotated into a check, revealed by a
72
77
  // combination of block-size and clip-path transitions. currentColor, so that the
73
78
  // error and disabled states recolour it with a single color declaration.
74
79
  &::before {
75
- --_check-width: calc(var(--_size) - 3px);
80
+ --_check-width: calc(var(--_size) * 5 / 6);
76
81
  --_check-height: calc(0.4667 * var(--_check-width));
77
82
 
78
83
  content: "";
@@ -82,14 +87,14 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
82
87
  block-size: 0;
83
88
  inset: 0;
84
89
  inset-block-start: calc(-0.25 * var(--_size));
85
- inset-inline-start: 1px;
90
+ inset-inline-start: calc(var(--_check-thickness) / 2);
86
91
  margin: auto;
87
92
  color: var(--md-comp-checkbox-selected-icon-color, var(--md-sys-color-on-primary));
88
93
  border-block-end: var(--_check-thickness) currentColor solid;
89
94
  border-inline-start: var(--_check-thickness) currentColor solid;
90
95
  transform: rotate(-45deg);
91
96
  transform-origin: center;
92
- clip-path: rect(0 2px 0 0);
97
+ clip-path: rect(0 var(--_check-thickness) 0 0);
93
98
  z-index: 1;
94
99
  transition:
95
100
  block-size var(--md-sys-motion-duration-short3) var(--md-sys-motion-duration-short3) motion.$md-sys-motion-easing-standard-decelerate,
@@ -111,7 +116,7 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
111
116
  inset: 0;
112
117
  margin: auto;
113
118
  border: var(--_outline-width) solid var(--_outline-color);
114
- border-radius: 2px;
119
+ border-radius: var(--md-comp-checkbox-container-shape, 2px);
115
120
  background-color: var(--_container-color);
116
121
  transition:
117
122
  background-color var(--md-comp-checkbox-motion-duration-reverse) motion.$md-sys-motion-easing-emphasized var(--md-comp-checkbox-motion-duration-reverse),
@@ -122,7 +127,7 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
122
127
 
123
128
  &::before {
124
129
  block-size: var(--_check-height);
125
- clip-path: rect(0 calc(var(--_size) - 5px) var(--_size) 0);
130
+ clip-path: rect(0 calc(var(--_check-width) - var(--_check-thickness)) var(--_size) 0);
126
131
  transition:
127
132
  block-size var(--md-sys-motion-duration-short3) var(--md-comp-checkbox-motion-spatial),
128
133
  clip-path var(--md-sys-motion-duration-long4) var(--md-sys-motion-duration-short3) var(--md-comp-checkbox-motion-spatial);
@@ -137,7 +142,7 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
137
142
  }
138
143
  }
139
144
  &:indeterminate::before {
140
- inset-inline-start: 2px;
145
+ inset-inline-start: var(--_check-thickness);
141
146
  border-inline-start-width: 0px;
142
147
  transform: rotate(0deg);
143
148
  }
@@ -241,28 +246,33 @@ $checkbox: 'input[type=checkbox].micl-checkbox';
241
246
  }
242
247
 
243
248
  #{$checkbox}:not(:disabled) + label,
244
- label:has(+ #{$checkbox}:not(:disabled)) {
249
+ label:has(+ #{$checkbox}:not(:disabled)),
250
+ label:has(#{$checkbox}:not(:disabled)) {
245
251
  @extend %micl-toggle-label-pointer;
246
252
  }
247
253
  #{$checkbox} + label,
248
- label:has(+ #{$checkbox}) {
254
+ label:has(+ #{$checkbox}),
255
+ label:has(#{$checkbox}) {
249
256
  @extend %micl-toggle-label;
250
257
  }
251
258
  #{$checkbox}:disabled + label,
252
259
  label:has(+ #{$checkbox}:disabled) {
253
260
  @extend %micl-toggle-label-disabled;
254
261
  }
262
+ label:has(#{$checkbox}:disabled) {
263
+ @extend %micl-toggle-label-disabled-text;
264
+ }
255
265
 
256
266
  #{$checkbox}:dir(rtl) {
257
267
  &::before {
258
- clip-path: rect(0 var(--_size) var(--_size) calc(var(--_size) - 2px));
268
+ clip-path: rect(0 var(--_size) var(--_size) calc(var(--_size) - var(--_check-thickness)));
259
269
  }
260
270
  &:checked::before {
261
- clip-path: rect(0 var(--_size) var(--_size) 2px);
271
+ clip-path: rect(0 var(--_size) var(--_size) var(--_check-thickness));
262
272
  transform: rotate(45deg);
263
273
  }
264
274
  &:indeterminate::before {
265
- clip-path: rect(0 var(--_size) var(--_size) 2px);
275
+ clip-path: rect(0 var(--_size) var(--_size) var(--_check-thickness));
266
276
  transform: rotate(0deg);
267
277
  }
268
278
  }
@@ -176,9 +176,9 @@
176
176
  &::before {
177
177
  content: "";
178
178
  position: absolute;
179
- inset-block-start: calc((100% - var(--md-sys-target-size, 48px)) / 2);
179
+ inset-block-start: calc((100% - var(--md-sys-state-target-size, 48px)) / 2);
180
180
  inset-inline-start: 0;
181
- block-size: var(--md-sys-target-size, 48px);
181
+ block-size: var(--md-sys-state-target-size, 48px);
182
182
  inline-size: 100%;
183
183
  }
184
184
  }
@@ -195,10 +195,10 @@
195
195
  &::before {
196
196
  content: "";
197
197
  position: absolute;
198
- inset-block-start: calc((100% - var(--md-sys-target-size, 48px)) / 2);
199
- inset-inline-start: calc((100% - var(--md-sys-target-size, 48px)) / 2);
200
- block-size: var(--md-sys-target-size, 48px);
201
- inline-size: var(--md-sys-target-size, 48px);
198
+ inset-block-start: calc((100% - var(--md-sys-state-target-size, 48px)) / 2);
199
+ inset-inline-start: calc((100% - var(--md-sys-state-target-size, 48px)) / 2);
200
+ block-size: var(--md-sys-state-target-size, 48px);
201
+ inline-size: var(--md-sys-state-target-size, 48px);
202
202
  }
203
203
  }
204
204
  .micl-chip__avatar {
@@ -86,7 +86,7 @@ dialog.micl-dialog.micl-datepicker {
86
86
  --md-comp-date-picker-button-motion-duration: #{motion.$md-sys-motion-expressive-fast-spatial-duration};
87
87
 
88
88
  // Internal alias: the target size is referenced from many descendants.
89
- --_target: var(--md-sys-target-size, 48px);
89
+ --_target: var(--md-sys-state-target-size, 48px);
90
90
 
91
91
  inline-size: var(--md-comp-date-picker-modal-container-width, 360px);
92
92
 
@@ -56,10 +56,10 @@ $iconbuttons: ':is([class*="micl-iconbutton-standard-"], [class*="micl-iconbutto
56
56
  display: flex;
57
57
  align-items: center;
58
58
  justify-content: center;
59
- block-size: var(--md-sys-target-size, 48px);
59
+ block-size: var(--md-sys-state-target-size, 48px);
60
60
  inline-size: 100%;
61
- min-inline-size: var(--md-sys-target-size, 48px);
62
- inset: calc((var(--micl-height) - var(--md-sys-target-size, 48px)) / 2) 0 0 calc((min(100%, var(--md-sys-target-size, 48px)) - var(--md-sys-target-size, 48px)) / 2);
61
+ min-inline-size: var(--md-sys-state-target-size, 48px);
62
+ inset: calc((var(--micl-height) - var(--md-sys-state-target-size, 48px)) / 2) 0 0 calc((min(100%, var(--md-sys-state-target-size, 48px)) - var(--md-sys-state-target-size, 48px)) / 2);
63
63
  background-color: transparent;
64
64
  }
65
65
  }
@@ -390,11 +390,15 @@ $unselected: ':not(:checked, :has(input[type=checkbox]:checked), #{$open-accordi
390
390
  }
391
391
  }
392
392
  #{$items} {
393
- input[type=checkbox].micl-checkbox {
394
- block-size: 24px;
395
- min-block-size: 24px;
396
- inline-size: 24px;
397
- min-inline-size: 24px;
393
+ // The compact list controls. The checkbox and the radio button derive all four of their
394
+ // size properties and their transparent border from these two, so overriding them shrinks
395
+ // the box without leaving the pseudo-elements wider than the padding box that centres
396
+ // them: sizing the element directly left an 18px container inside a 16px padding box,
397
+ // which start-aligned it.
398
+ input[type=checkbox].micl-checkbox,
399
+ input[type=radio].micl-radio {
400
+ --_layer: max(24px, var(--_size));
401
+ --_target: var(--_layer);
398
402
  }
399
403
  }
400
404
 
@@ -11,6 +11,8 @@ To add a basic radio button, use the `<input type="radio">` element with the `mi
11
11
  <label for="myradio">First choice</label>
12
12
  ```
13
13
 
14
+ **Note on labels**: The component automatically applies `cursor: pointer` and the **on-surface** color role to any `<label>` immediately preceding, immediately following, or wrapping a `micl-radio` input. You are encouraged to customize these CSS settings to match your design system.
15
+
14
16
  ### CSS
15
17
  Import the radio button styles into your project:
16
18
 
@@ -32,9 +34,49 @@ A live example of the [Radio button component](https://henkpb.github.io/micl/rad
32
34
  ## Variants
33
35
  A radio button can be disabled by adding the `disabled` attribute to the `<input>` element.
34
36
 
35
- The Radio Button component respects the `dir` global attribute, automatically adjusting its layout for right-to-left (RTL) languages when `dir="rtl"` is applied to an ancestor element.
37
+ The Radio button component respects the element's computed direction, automatically adjusting its layout for right-to-left (RTL) languages — whether the `dir` attribute is set on the element itself or inherited from an ancestor.
38
+
39
+ To vertically align a radio button with its label, wrap both in an element with styling similar to the following:
40
+
41
+ ```CSS
42
+ .my-valign-class {
43
+ display: flex;
44
+ flex-direction: row;
45
+ align-items: center;
46
+ }
47
+ ```
48
+
49
+ ```HTML
50
+ <div class="my-valign-class">
51
+ <input type="radio" id="myradio" class="micl-radio" name="foo">
52
+ <label for="myradio">First choice</label>
53
+ </div>
54
+ ```
55
+
56
+ ## Radio group
57
+ Radio buttons are only meaningful as a group. Give every radio button for a given question the same `name`, and the browser makes the choices mutually exclusive for you — selecting one clears the others, and only the selected value is submitted.
58
+
59
+ The group is also a single stop in the tab order: <kbd>Tab</kbd> moves into and out of it, while the arrow keys move the selection between the buttons inside it. This behaviour is native, so it needs no JavaScript and no `role` attributes.
36
60
 
37
- The component applies `cursor: pointer` and the color role **on surface** to the `<label>` element immediately preceding or following an `<input type="radio">` with the `micl-radio` class. You are encouraged to customize these CSS settings to match your design system.
61
+ Wrap the group in a `<fieldset>` with a `<legend>` so that assistive technology announces the question along with each of the options:
62
+
63
+ ```HTML
64
+ <fieldset>
65
+ <legend>Delivery</legend>
66
+ <div class="my-valign-class">
67
+ <input type="radio" id="ship1" class="micl-radio" name="delivery" value="standard" checked>
68
+ <label for="ship1">Standard</label>
69
+ </div>
70
+ <div class="my-valign-class">
71
+ <input type="radio" id="ship2" class="micl-radio" name="delivery" value="express">
72
+ <label for="ship2">Express</label>
73
+ </div>
74
+ </fieldset>
75
+ ```
76
+
77
+ Always mark one option as `checked`, unless leaving the question unanswered is a valid state. A group with no preselected option cannot be returned to its initial state by the user once a choice has been made.
78
+
79
+ Adding the `required` attribute to a radio button makes the whole group mandatory — the browser will not submit the form until one of the buttons carrying that `name` is selected. The [Form foundation](../../foundations/form/README.md) leaves radio buttons to the browser's own reporting, so no MICL-specific error class is involved.
38
80
 
39
81
  ## Theming
40
82
  Each radio button can be themed with CSS custom properties that follow the Material Design 3 component-token naming convention. Set them on any appropriate parent element to affect its child radio buttons.
@@ -50,19 +92,17 @@ Each radio button can be themed with CSS custom properties that follow the Mater
50
92
 
51
93
  ```HTML
52
94
  <div style="--md-comp-radio-button-icon-size:28px">
53
- <input type="radio" id="myradio" class="micl-radio">
95
+ <input type="radio" id="myradio" class="micl-radio" name="foo">
54
96
  <label for="myradio">Large radio button</label>
55
97
  </div>
56
98
  ```
57
99
 
58
- To vertically align a radio button with its label, wrap both in an element that has a styling like suggested below:
100
+ ## Compatibility
101
+ This component relies on a few recent CSS features, which may not be fully supported in your browser:
59
102
 
60
- ```HTML
61
- <div style="display:flex;flex-direction:row;align-items:center">
62
- <input type="radio" id="myradio" class="micl-radio">
63
- <label for="myradio">Large radio button</label>
64
- </div>
65
- ```
103
+ | Feature | Used for |
104
+ |---|---|
105
+ | [Relative RGB color values](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#browser_compatibility) | The state layer and the ripple |
106
+ | [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) | Styling a `<label>` that precedes its radio button |
66
107
 
67
- ## Compatibility
68
- This component utilizes relative RGB color values, which may not be fully supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#browser_compatibility) for details.
108
+ Please check the linked browser-compatibility tables for details.
@@ -22,7 +22,7 @@
22
22
  @use '../../styles/motion';
23
23
  @use '../../styles/shape';
24
24
  @use '../../styles/statelayer';
25
- @use '../checkbox/label';
25
+ @use '../../styles/togglelabel';
26
26
 
27
27
  @include shape.corner('full');
28
28
 
@@ -32,6 +32,7 @@
32
32
  @include statelayer.token('pressed-state-layer-opacity');
33
33
  @include statelayer.token('disabled-state-layer-opacity');
34
34
  @include statelayer.token('focus-indicator-thickness');
35
+ @include statelayer.token('focus-indicator-outer-offset');
35
36
  @include statelayer.token('ripple-opacity-factor');
36
37
  @include statelayer.token('ripple-duration');
37
38
  @include statelayer.property;
@@ -46,26 +47,31 @@ $radio: 'input[type=radio].micl-radio';
46
47
 
47
48
  --_size: var(--md-comp-radio-button-icon-size, 20px);
48
49
  --_outline-width: var(--md-comp-radio-button-outline-width, 2px);
50
+ --_layer: max(var(--md-sys-state-layer-size, 40px), calc(var(--_size) + 20px));
51
+ --_target: max(var(--md-sys-state-target-size, 48px), calc(var(--_layer) + 8px));
52
+ --statelayer-color: var(--md-sys-color-on-surface);
49
53
 
50
54
  appearance: none;
51
55
  box-sizing: border-box;
52
56
  position: relative;
53
- inline-size: var(--md-sys-target-size, 48px);
54
- min-inline-size: var(--md-sys-target-size, 48px);
55
- block-size: var(--md-sys-target-size, 48px);
56
- min-block-size: var(--md-sys-target-size, 48px);
57
+ inline-size: var(--_target);
58
+ min-inline-size: var(--_target);
59
+ block-size: var(--_target);
60
+ min-block-size: var(--_target);
57
61
  margin: 0;
58
- border: calc((var(--md-sys-target-size, 48px) - var(--md-sys-state-layer-size, 40px)) / 2) solid transparent;
62
+ border: calc((var(--_target) - var(--_layer)) / 2) solid transparent;
59
63
  background-clip: content-box;
60
64
  background-color: transparent;
61
65
  -webkit-tap-highlight-color: transparent;
62
66
  border-radius: var(--md-sys-shape-corner-full);
63
- outline-offset: -7px;
67
+ outline-offset: calc(
68
+ var(--md-sys-state-focus-indicator-outer-offset, 2px)
69
+ - ((var(--_target) - var(--_layer)) / 2)
70
+ );
64
71
 
65
- // The ring and the dot in one box: the dot is the primary background clipped to the
66
- // content box, hidden while the padding fills the ring, revealed when the checked
67
- // padding shrinks to reveal a dot of half the icon size.
68
72
  &::after {
73
+ --_outline-color: var(--md-comp-radio-button-unselected-icon-color, var(--md-sys-color-on-surface-variant));
74
+
69
75
  content: "";
70
76
  box-sizing: border-box;
71
77
  position: absolute;
@@ -74,7 +80,7 @@ $radio: 'input[type=radio].micl-radio';
74
80
  inset: 0;
75
81
  margin: auto;
76
82
  padding: calc((var(--_size) / 2) - var(--_outline-width));
77
- border: var(--_outline-width) solid var(--md-comp-radio-button-unselected-icon-color, var(--md-sys-color-on-surface-variant));
83
+ border: var(--_outline-width) solid var(--_outline-color);
78
84
  background-color: var(--md-comp-radio-button-selected-icon-color, var(--md-sys-color-primary));
79
85
  background-clip: content-box;
80
86
  border-radius: var(--md-sys-shape-corner-full);
@@ -82,12 +88,17 @@ $radio: 'input[type=radio].micl-radio';
82
88
  padding var(--md-comp-radio-button-motion-duration-reverse) var(--md-comp-radio-button-motion-effects),
83
89
  border-color var(--md-comp-radio-button-motion-duration-reverse) motion.$md-sys-motion-easing-emphasized;
84
90
  }
85
- &:checked::after {
86
- border-color: var(--md-comp-radio-button-selected-icon-color, var(--md-sys-color-primary));
87
- padding: calc(0.15 * var(--_size));
88
- transition:
89
- padding var(--md-comp-radio-button-motion-duration) var(--md-comp-radio-button-motion-effects),
90
- border-color var(--md-comp-radio-button-motion-duration) motion.$md-sys-motion-easing-emphasized;
91
+ &:checked {
92
+ --statelayer-color: var(--md-sys-color-primary);
93
+
94
+ &::after {
95
+ --_outline-color: var(--md-comp-radio-button-selected-icon-color, var(--md-sys-color-primary));
96
+
97
+ padding: calc((var(--_size) / 4) - var(--_outline-width));
98
+ transition:
99
+ padding var(--md-comp-radio-button-motion-duration) var(--md-comp-radio-button-motion-effects),
100
+ border-color var(--md-comp-radio-button-motion-duration) motion.$md-sys-motion-easing-emphasized;
101
+ }
91
102
  }
92
103
  &:not(:disabled) {
93
104
  --micl-ripple: 1;
@@ -108,31 +119,33 @@ $radio: 'input[type=radio].micl-radio';
108
119
  background-size 0ms,
109
120
  --statelayer-opacity var(--md-comp-radio-button-motion-duration) linear;
110
121
 
111
- &:hover,
112
- &:focus-visible,
113
- &:active {
114
- --statelayer-color: var(--md-sys-color-on-surface);
115
-
116
- &:checked {
117
- --statelayer-color: var(--md-sys-color-primary);
118
- }
119
- &::after {
120
- border-color: var(--md-sys-color-on-surface);
121
- }
122
- &:checked::after {
123
- border-color: var(--md-comp-radio-button-selected-icon-color, var(--md-sys-color-primary));
124
- }
125
- }
126
122
  &:hover {
127
123
  --statelayer-opacity: var(--md-sys-state-hover-state-layer-opacity, 8%);
124
+
125
+ &:not(:checked)::after {
126
+ --_outline-color: var(--md-sys-color-on-surface);
127
+ }
128
128
  }
129
129
  &:focus-visible {
130
130
  --statelayer-opacity: var(--md-sys-state-focus-state-layer-opacity, 10%);
131
131
 
132
132
  outline: var(--md-sys-state-focus-indicator-thickness, 3px) solid var(--md-sys-color-secondary);
133
+
134
+ &:not(:checked)::after {
135
+ --_outline-color: var(--md-sys-color-on-surface);
136
+ }
133
137
  }
134
138
  &:active {
139
+ --statelayer-color: var(--md-sys-color-on-surface);
135
140
  --statelayer-opacity: var(--md-sys-state-pressed-state-layer-opacity, 10%);
141
+
142
+ &:not(:checked) {
143
+ --statelayer-color: var(--md-sys-color-primary);
144
+
145
+ &::after {
146
+ --_outline-color: var(--md-sys-color-on-surface);
147
+ }
148
+ }
136
149
  }
137
150
  &.micl-rippling {
138
151
  animation: micl-ripple var(--md-sys-state-ripple-duration);
@@ -142,7 +155,7 @@ $radio: 'input[type=radio].micl-radio';
142
155
  opacity: var(--md-sys-state-disabled-state-layer-opacity, 38%);
143
156
 
144
157
  &::after {
145
- border-color: var(--md-sys-color-on-surface);
158
+ --_outline-color: var(--md-sys-color-on-surface);
146
159
  }
147
160
  &:checked::after {
148
161
  background-color: var(--md-sys-color-on-surface);
@@ -151,14 +164,19 @@ $radio: 'input[type=radio].micl-radio';
151
164
  }
152
165
 
153
166
  #{$radio}:not(:disabled) + label,
154
- label:has(+ #{$radio}:not(:disabled)) {
167
+ label:has(+ #{$radio}:not(:disabled)),
168
+ label:has(#{$radio}:not(:disabled)) {
155
169
  @extend %micl-toggle-label-pointer;
156
170
  }
157
171
  #{$radio} + label,
158
- label:has(+ #{$radio}) {
172
+ label:has(+ #{$radio}),
173
+ label:has(#{$radio}) {
159
174
  @extend %micl-toggle-label;
160
175
  }
161
176
  #{$radio}:disabled + label,
162
177
  label:has(+ #{$radio}:disabled) {
163
178
  @extend %micl-toggle-label-disabled;
164
179
  }
180
+ label:has(#{$radio}:disabled) {
181
+ @extend %micl-toggle-label-disabled-text;
182
+ }
@@ -61,7 +61,7 @@
61
61
  // re-anchor it to this button's box.
62
62
  &::before {
63
63
  inset: auto;
64
- inset-block-start: calc(anchor(var(--micl-stepper-anchor) center) - var(--md-sys-target-size, 48px) / 2);
64
+ inset-block-start: calc(anchor(var(--micl-stepper-anchor) center) - var(--md-sys-state-target-size, 48px) / 2);
65
65
  inset-inline-start: anchor(var(--micl-stepper-anchor) start);
66
66
  inset-inline-end: anchor(var(--micl-stepper-anchor) end);
67
67
  inline-size: auto;
@@ -11,6 +11,8 @@ To add a basic switch, use the `<input type="checkbox">` element with the `micl-
11
11
  <label for="myswitch">My choice</label>
12
12
  ```
13
13
 
14
+ **Note on labels**: The component applies `cursor: pointer` and the color role **on surface** to the `<label>` element immediately preceding, immediately following, or wrapping the `<input type="checkbox">` with the `micl-switch` class. You are encouraged to customize these CSS settings to match your design system.
15
+
14
16
  ### CSS
15
17
  Import the switch styles into your project:
16
18
 
@@ -30,7 +32,7 @@ No custom JavaScript is required for the core functionality of this component.
30
32
  A live example of the [Switch component](https://henkpb.github.io/micl/switch.html) is available to interact with.
31
33
 
32
34
  ## Variants
33
- By default, the component displays an icon on the switch handle in both the selected and unselected state. To remove the icon in the unselected state, assign an empty string to the following CSS variable:
35
+ By default, the component displays an icon on the switch handle in both the selected and unselected state. To remove the icon in the unselected state, set the corresponding CSS custom property to an empty string:
34
36
 
35
37
  ```CSS
36
38
  #myswitch {
@@ -48,8 +50,6 @@ A switch can be disabled by adding the `disabled` attribute to the `<input>` ele
48
50
 
49
51
  The Switch component respects the element's computed direction, automatically adjusting its layout for right-to-left (RTL) languages — whether the `dir` attribute (including `dir="auto"`) is set on the element itself or inherited from an ancestor.
50
52
 
51
- The component applies `cursor: pointer` and the color role **on surface** to the `<label>` element immediately preceding or following the `<input type="checkbox">` with the `micl-switch` class. You are encouraged to customize these CSS settings to match your design system.
52
-
53
53
  ## Theming
54
54
  Each switch can be themed with CSS custom properties that follow the Material Design 3 component-token naming convention. Set them on any appropriate parent element to affect its child switches.
55
55
 
@@ -68,6 +68,25 @@ Each switch can be themed with CSS custom properties that follow the Material De
68
68
  | `--md-comp-switch-selected-track-color` | The track color when the switch is "on" | `--md-sys-color-primary` |
69
69
  | `--md-comp-switch-selected-handle-color` | The handle color when the switch is "on" | `--md-sys-color-on-primary` |
70
70
  | `--md-comp-switch-selected-icon-color` | The icon color when the switch is "on" | `--md-sys-color-on-primary-container` |
71
+ | `--md-comp-switch-unselected-hover-handle-color` | The handle color when the switch is "off" and hovered | `--md-sys-color-on-surface-variant` |
72
+ | `--md-comp-switch-unselected-focus-handle-color` | The handle color when the switch is "off" and focused | `--md-sys-color-on-surface-variant` |
73
+ | `--md-comp-switch-unselected-pressed-handle-color` | The handle color when the switch is "off" and pressed | `--md-sys-color-on-surface-variant` |
74
+ | `--md-comp-switch-selected-hover-handle-color` | The handle color when the switch is "on" and hovered | `--md-sys-color-primary-container` |
75
+ | `--md-comp-switch-selected-focus-handle-color` | The handle color when the switch is "on" and focused | `--md-sys-color-primary-container` |
76
+ | `--md-comp-switch-selected-pressed-handle-color` | The handle color when the switch is "on" and pressed | `--md-sys-color-primary-container` |
77
+ | `--md-comp-switch-unselected-state-layer-color` | The state layer color around the handle when the switch is "off" | `--md-sys-color-on-surface` |
78
+ | `--md-comp-switch-selected-state-layer-color` | The state layer color around the handle when the switch is "on" | `--md-sys-color-primary` |
79
+ | `--md-comp-switch-focus-indicator-color` | The color of the focus indicator | `--md-sys-color-secondary` |
80
+ | `--md-comp-switch-disabled-unselected-track-color` | The track color when the switch is disabled and "off" | `--md-sys-color-surface-container-highest` |
81
+ | `--md-comp-switch-disabled-unselected-track-outline-color` | The track border color when the switch is disabled and "off" | `--md-sys-color-on-surface` |
82
+ | `--md-comp-switch-disabled-unselected-handle-color` | The handle color when the switch is disabled and "off" | `--md-sys-color-on-surface` |
83
+ | `--md-comp-switch-disabled-unselected-icon-color` | The icon color when the switch is disabled and "off" | `--md-sys-color-surface-container-highest` |
84
+ | `--md-comp-switch-disabled-selected-track-color` | The track color when the switch is disabled and "on" | `--md-sys-color-on-surface` |
85
+ | `--md-comp-switch-disabled-selected-handle-color` | The handle color when the switch is disabled and "on" | `--md-sys-color-surface` |
86
+ | `--md-comp-switch-disabled-selected-icon-color` | The icon color when the switch is disabled and "on" | `--md-sys-color-on-surface` |
87
+ | `--md-comp-switch-disabled-track-opacity` | The opacity of the track when the switch is disabled | `12%` |
88
+
89
+ Because this component follows the Material Design 3 token set, the handle requires distinct colors for each interaction state. Modifying `--md-comp-switch-selected-handle-color` only changes the *resting* state. To ensure a consistent theme, remember to also override the matching `-hover-`, `-focus-`, and `-pressed-` properties.
71
90
 
72
91
  **Example: Changing the width of the switch**
73
92
 
@@ -79,4 +98,13 @@ Each switch can be themed with CSS custom properties that follow the Material De
79
98
  ```
80
99
 
81
100
  ## Compatibility
82
- This component utilizes relative RGB color values, which may not be fully supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#browser_compatibility) for details.
101
+ This component relies on several recent CSS features, which may not be fully supported in your browser:
102
+
103
+ | Feature | Used for |
104
+ |---|---|
105
+ | [Relative RGB color values](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#browser_compatibility) | Dimming the icon of a disabled, selected switch |
106
+ | [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) | The state layer around the handle |
107
+ | [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) | Styling a `<label>` that precedes its switch |
108
+ | [`:dir()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) | Mirroring the icon for right-to-left languages |
109
+
110
+ Please check the linked browser-compatibility tables for details.