material-inspired-component-library 9.1.0 → 9.1.2

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 (73) hide show
  1. package/README.md +2 -1
  2. package/components/appbar/index.scss +1 -1
  3. package/components/badge/README.md +43 -12
  4. package/components/badge/index.scss +15 -21
  5. package/components/bottomsheet/README.md +53 -22
  6. package/components/bottomsheet/index.scss +62 -45
  7. package/components/button/_shared.scss +9 -9
  8. package/components/card/README.md +55 -25
  9. package/components/card/index.scss +57 -38
  10. package/components/checkbox/README.md +97 -28
  11. package/components/checkbox/index.scss +39 -29
  12. package/components/chip/index.scss +6 -6
  13. package/components/datepicker/README.md +184 -59
  14. package/components/datepicker/index.scss +85 -21
  15. package/components/dialog/README.md +151 -56
  16. package/components/dialog/index.scss +50 -62
  17. package/components/iconbutton/index.scss +8 -8
  18. package/components/list/index.scss +15 -11
  19. package/components/menu/index.scss +13 -13
  20. package/components/navigationbar/index.scss +7 -1
  21. package/components/navigationrail/index.scss +27 -22
  22. package/components/radio/README.md +52 -12
  23. package/components/radio/index.scss +59 -41
  24. package/components/select/index.scss +3 -3
  25. package/components/sidesheet/README.md +90 -37
  26. package/components/sidesheet/index.scss +51 -41
  27. package/components/slider/README.md +35 -9
  28. package/components/slider/index.scss +182 -153
  29. package/components/snackbar/README.md +43 -25
  30. package/components/snackbar/index.scss +22 -27
  31. package/components/stepper/index.scss +5 -5
  32. package/components/switch/README.md +32 -4
  33. package/components/switch/index.scss +57 -52
  34. package/components/timepicker/README.md +131 -55
  35. package/components/timepicker/index.scss +47 -26
  36. package/dist/appbar.css +1 -1
  37. package/dist/badge.css +1 -1
  38. package/dist/bottomsheet.css +1 -1
  39. package/dist/bottomsheet.js +1 -1
  40. package/dist/button.css +1 -1
  41. package/dist/card.css +1 -1
  42. package/dist/checkbox.css +1 -1
  43. package/dist/checkbox.js +1 -1
  44. package/dist/chip.css +1 -1
  45. package/dist/components/checkbox/index.d.ts +1 -0
  46. package/dist/datepicker.css +1 -1
  47. package/dist/datepicker.js +1 -1
  48. package/dist/dialog.css +1 -1
  49. package/dist/foundations/form/index.js +1 -1
  50. package/dist/iconbutton.css +1 -1
  51. package/dist/layout.css +1 -1
  52. package/dist/list.css +1 -1
  53. package/dist/menu.css +1 -1
  54. package/dist/micl.css +1 -1
  55. package/dist/micl.js +1 -1
  56. package/dist/navigationbar.css +1 -1
  57. package/dist/navigationrail.css +1 -1
  58. package/dist/radio.css +1 -1
  59. package/dist/select.css +1 -1
  60. package/dist/sidesheet.css +1 -1
  61. package/dist/slider.css +1 -1
  62. package/dist/slider.js +1 -1
  63. package/dist/snackbar.css +1 -1
  64. package/dist/snackbar.js +1 -1
  65. package/dist/stepper.css +1 -1
  66. package/dist/stepper.js +1 -1
  67. package/dist/switch.css +1 -1
  68. package/dist/timepicker.css +1 -1
  69. package/dist/timepicker.js +1 -1
  70. package/foundations/layout/_tokens.scss +55 -0
  71. package/foundations/layout/index.scss +5 -35
  72. package/package.json +2 -2
  73. package/{components/checkbox/_label.scss → styles/togglelabel.scss} +11 -3
@@ -4,15 +4,25 @@ This component implements the [Material Design 3 Expressive Snackbar](https://m3
4
4
  ## Basic Usage
5
5
 
6
6
  ### HTML
7
- To create a simple snackbar, use a `<div>` element with the `micl-snackbar` class. Inside, use a `<span>` element with the `micl-snackbar__supporting-text` class to contain the snackbar's notification.
7
+ To create a simple snackbar, use a `<div>` element with the `micl-snackbar` class and the `popover="manual"` attribute. Inside, use a `<span>` element with the `micl-snackbar__supporting-text` class to contain the snackbar's notification. The `role="status"` attribute on the notification makes screen readers announce it when the snackbar appears.
8
8
 
9
9
  ```HTML
10
- <div class="micl-snackbar" popover="manual" data-micldelay="3000" role="status" aria-atomic="true">
11
- <span class="micl-snackbar__supporting-text">All changes saved</span>
10
+ <div id="mysnackbar" class="micl-snackbar" popover="manual" data-micldelay="3000">
11
+ <span class="micl-snackbar__supporting-text" role="status" aria-atomic="true">All changes saved</span>
12
12
  </div>
13
13
  ```
14
14
 
15
- The `data-micldelay` attribute contains the number of milliseconds the snackbar remains visible.
15
+ The snackbar is a [popover](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API): show it from a button with the `popovertarget` attribute, or from JavaScript with `showPopover()`.
16
+
17
+ ```HTML
18
+ <button type="button" class="micl-button-filled-m" popovertarget="mysnackbar">Save</button>
19
+ ```
20
+
21
+ ```JavaScript
22
+ document.getElementById("mysnackbar").showPopover();
23
+ ```
24
+
25
+ The `data-micldelay` attribute specifies the number of milliseconds the snackbar remains visible before it auto-dismisses. The timer pauses if the pointer rests on the snackbar or if a button inside it receives keyboard focus, and resumes once neither condition is met. Omit the attribute to keep the snackbar open until it is dismissed, for example with a close button (see [Variants](#variants)).
16
26
 
17
27
  ### CSS
18
28
  Import the snackbar styles into your project:
@@ -41,35 +51,39 @@ This will initialize any Snackbar component, including those that will be added
41
51
  A live example of the [Snackbar component](https://henkpb.github.io/micl/snackbar.html) is available to interact with.
42
52
 
43
53
  ## Variants
44
- You can add an action to the snackbar by including a [MICL button](../button/README.md) to the markup:
54
+ You can add an action to the snackbar by adding a [MICL button](../button/README.md) to the markup:
45
55
 
46
56
  ```HTML
47
- <div class="micl-snackbar" popover="manual" data-micldelay="3000" role="status" aria-atomic="true">
48
- <span class="micl-snackbar__supporting-text">Your document has been saved</span>
57
+ <div class="micl-snackbar" popover="manual" data-micldelay="3000">
58
+ <span class="micl-snackbar__supporting-text" role="status" aria-atomic="true">Your document has been saved</span>
49
59
  <button type="button" class="micl-button-text-s">Undo</button>
50
60
  </div>
51
61
  ```
52
62
 
53
- You can also add a close button that dismisses the snackbar when pressed:
63
+ You can also add a close button that dismisses the snackbar when pressed. A snackbar without `data-micldelay` stays open until the close button is pressed:
54
64
 
55
65
  ```HTML
56
- <div id="mysnackbar" class="micl-snackbar" popover="manual" data-micldelay="7000" role="status" aria-atomic="true">
57
- <span class="micl-snackbar__supporting-text">Your document has been saved</span>
58
- <button type="button" class="micl-iconbutton-standard-s material-symbols-outlined" popovertarget="mysnackbar" popovertargetaction="hide">close</button>
66
+ <div id="mysnackbar" class="micl-snackbar" popover="manual">
67
+ <span class="micl-snackbar__supporting-text" role="status" aria-atomic="true">Your document has been saved</span>
68
+ <button type="button" class="micl-iconbutton-standard-s material-symbols-outlined" popovertarget="mysnackbar" popovertargetaction="hide" aria-label="Close">close</button>
59
69
  </div>
60
70
  ```
61
71
 
62
- Add the `micl-snackbar--two` class to the snackbar to increase its height so that it can accommodate two lines of text.
72
+ Add the `micl-snackbar--two` class to the snackbar to increase its height so that it can accommodate two lines of text. Wrap each line in its own `<span>` inside the supporting text:
63
73
 
64
74
  ```HTML
65
- <div class="micl-snackbar micl-snackbar--two" popover="manual" data-micldelay="5000" role="status" aria-atomic="true">
66
- <span>
67
- <span class="micl-snackbar__supporting-text">Message sent</span>
68
- <span class="micl-snackbar__supporting-text">An email has been sent to Alice</span>
75
+ <div class="micl-snackbar micl-snackbar--two" popover="manual" data-micldelay="5000">
76
+ <span class="micl-snackbar__supporting-text" role="status" aria-atomic="true">
77
+ <span>Message sent</span>
78
+ <span>An email has been sent to Alice</span>
69
79
  </span>
70
80
  </div>
71
81
  ```
72
82
 
83
+ Text that does not fit the snackbar is clipped: keep the notification short, or use the two-line variant.
84
+
85
+ With `popover="manual"`, the snackbar is dismissed only by its timer, a close button, or `hidePopover()`. Use the plain `popover` attribute instead for a light-dismissed snackbar that also closes when the user clicks elsewhere or presses <kbd>Escape</kbd>.
86
+
73
87
  The Snackbar 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.
74
88
 
75
89
  ## Theming
@@ -77,8 +91,10 @@ Each snackbar can be themed with CSS custom properties that follow the Material
77
91
 
78
92
  | Custom property | Meaning | Default |
79
93
  |---|---|---|
80
- | `--md-comp-snackbar-margin` | The distance between the snackbar and the bottom of the device | `24px` |
94
+ | `--md-comp-snackbar-margin` | The distance between the snackbar and the bottom edge of the viewport (the safe-area inset is added) | `8px` |
81
95
  | `--md-comp-snackbar-padding` | The inner padding of the snackbar | `16px` |
96
+ | `--md-comp-snackbar-container-min-width` | The minimum width of the snackbar on medium and larger windows | `320px` |
97
+ | `--md-comp-snackbar-container-max-width` | The maximum width of the snackbar | `600px` (the medium breakpoint) |
82
98
  | `--md-comp-snackbar-container-color` | The background color of the snackbar | `--md-sys-color-inverse-surface` |
83
99
  | `--md-comp-snackbar-container-elevation` | The shadow (elevation) of the snackbar | `--md-sys-elevation-level3` |
84
100
  | `--md-comp-snackbar-container-shape` | The corner rounding of the snackbar | `--md-sys-shape-corner-extra-small` |
@@ -94,19 +110,19 @@ Each snackbar can be themed with CSS custom properties that follow the Material
94
110
  | `--md-comp-snackbar-icon-hover-icon-color` | The color of the hovered close icon | `--md-sys-color-inverse-on-surface` |
95
111
  | `--md-comp-snackbar-icon-focus-icon-color` | The color of the focused close icon | `--md-sys-color-inverse-on-surface` |
96
112
  | `--md-comp-snackbar-icon-pressed-icon-color` | The color of the pressed close icon | `--md-sys-color-inverse-on-surface` |
97
- | `--md-comp-snackbar-action-hover-state-layer-color` | The state layer tinting the snackbar while hovered (the auto-dismiss timer pauses) | `--md-sys-color-inverse-primary` |
98
- | `--md-comp-snackbar-action-hover-state-layer-opacity` | The opacity of the hover state layer | `8%` |
99
- | `--md-comp-snackbar-action-focus-state-layer-color` | The state layer tinting the snackbar while focused | `--md-sys-color-inverse-primary` |
100
- | `--md-comp-snackbar-action-focus-state-layer-opacity` | The opacity of the focus state layer | `10%` |
101
- | `--md-comp-snackbar-action-pressed-state-layer-color` | The state layer tinting the snackbar while pressed | `--md-sys-color-inverse-primary` |
102
- | `--md-comp-snackbar-action-pressed-state-layer-opacity` | The opacity of the pressed state layer | `10%` |
113
+ | `--md-comp-snackbar-hover-state-layer-color` | The state layer tinting the snackbar while the pointer rests on it (the auto-dismiss timer pauses) | `--md-sys-color-inverse-primary` |
114
+ | `--md-comp-snackbar-hover-state-layer-opacity` | The opacity of the hover state layer | `8%` |
115
+ | `--md-comp-snackbar-pressed-state-layer-color` | The state layer tinting the snackbar while pressed | `--md-sys-color-inverse-primary` |
116
+ | `--md-comp-snackbar-pressed-state-layer-opacity` | The opacity of the pressed state layer | `10%` |
117
+
118
+ The distance between the snackbar and the side edges of the viewport follows the [layout foundation](../../foundations/layout/README.md)'s `--md-sys-layout-window-margin`. On compact windows, the snackbar spans the full width between the window margins; on larger windows it is centered and sized to its content, within the minimum and maximum container widths above.
103
119
 
104
120
  **Example: Changing the padding of the snackbar**
105
121
 
106
122
  ```HTML
107
123
  <div style="--md-comp-snackbar-padding:24px">
108
- <div class="micl-snackbar" popover="manual" data-micldelay="3000" role="status" aria-atomic="true">
109
- <span class="micl-snackbar__supporting-text">All changes saved</span>
124
+ <div class="micl-snackbar" popover="manual" data-micldelay="3000">
125
+ <span class="micl-snackbar__supporting-text" role="status" aria-atomic="true">All changes saved</span>
110
126
  </div>
111
127
  </div>
112
128
  ```
@@ -121,3 +137,5 @@ To change the amount of rounding of the snackbar's corners, you could for exampl
121
137
 
122
138
  ## Compatibility
123
139
  This component uses the Popover API, which might not be supported in all browsers. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API#api.htmlelement.popover) for details.
140
+
141
+ The entry and exit animations rely on [`@starting-style`](https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style) and [`transition-behavior: allow-discrete`](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior); in browsers without them the snackbar appears and disappears instantly. The styles also use the [relative color syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors) and the [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) pseudo-class.
@@ -19,7 +19,7 @@
19
19
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  // SOFTWARE.
21
21
 
22
- @use '../../foundations/layout';
22
+ @use '../../foundations/layout/tokens' as layout;
23
23
  @use '../../styles/elevation';
24
24
  @use '../../styles/motion';
25
25
  @use '../../styles/shape';
@@ -31,17 +31,16 @@
31
31
  @include shape.corner('extra-small');
32
32
 
33
33
  @include statelayer.token('hover-state-layer-opacity');
34
- @include statelayer.token('focus-state-layer-opacity');
35
34
  @include statelayer.token('pressed-state-layer-opacity');
36
35
  @include statelayer.property;
37
36
 
38
37
  @include typography.scale('body-medium');
39
38
 
40
39
  .micl-snackbar {
41
- --md-comp-snackbar-motion-spatial: #{motion.$md-sys-motion-expressive-default-spatial};
42
- --md-comp-snackbar-motion-duration: #{motion.$md-sys-motion-expressive-default-spatial-duration};
43
- --md-comp-snackbar-motion-duration-reverse: #{motion.$md-sys-motion-expressive-fast-spatial-duration};
44
- --micl-duration: var(--md-comp-snackbar-motion-duration-reverse);
40
+ --_motion-spatial: var(--md-comp-snackbar-motion-spatial, #{motion.$md-sys-motion-expressive-default-spatial});
41
+ --_motion-duration: var(--md-comp-snackbar-motion-duration, #{motion.$md-sys-motion-expressive-default-spatial-duration});
42
+ --_motion-duration-reverse: var(--md-comp-snackbar-motion-duration-reverse, #{motion.$md-sys-motion-expressive-fast-spatial-duration});
43
+ --micl-duration: var(--_motion-duration-reverse);
45
44
  --statelayer-color: var(--md-sys-color-inverse-primary);
46
45
  --statelayer-opacity: 0%;
47
46
 
@@ -53,13 +52,13 @@
53
52
  column-gap: 4px;
54
53
 
55
54
  block-size: 0;
56
- inline-size: max-content;
57
- max-inline-size: min(layout.$md-sys-breakpoint-expanded-max, calc(100vw - (2 * var(--md-sys-layout-window-margin))));
58
- min-inline-size: 300px;
55
+ inline-size: fit-content;
56
+ max-inline-size: var(--md-comp-snackbar-container-max-width, #{layout.$md-sys-breakpoint-medium-min});
57
+ min-inline-size: var(--md-comp-snackbar-container-min-width, 320px);
59
58
 
60
59
  inset: auto;
61
- inset-block-end: calc(var(--md-comp-snackbar-margin, 24px) + env(safe-area-inset-bottom));
62
- inset-inline: 0;
60
+ inset-block-end: calc(var(--md-comp-snackbar-margin, 8px) + env(safe-area-inset-bottom, 0px));
61
+ inset-inline: var(--md-sys-layout-window-margin);
63
62
  margin-block: 0;
64
63
  margin-inline: auto;
65
64
  padding-block: 0;
@@ -76,30 +75,26 @@
76
75
  background-size: 100%;
77
76
 
78
77
  opacity: 0;
79
- overflow-y: hidden;
78
+ overflow: clip;
80
79
 
81
80
  transition:
82
- block-size var(--micl-duration) var(--md-comp-snackbar-motion-spatial),
83
- opacity var(--md-comp-snackbar-motion-duration-reverse) linear,
84
- display var(--md-comp-snackbar-motion-duration-reverse) linear allow-discrete,
85
- overlay var(--md-comp-snackbar-motion-duration-reverse) linear allow-discrete,
86
- --statelayer-opacity var(--md-comp-snackbar-motion-duration) linear;
81
+ block-size var(--micl-duration) var(--_motion-spatial),
82
+ opacity var(--_motion-duration-reverse) linear,
83
+ display var(--_motion-duration-reverse) linear allow-discrete,
84
+ overlay var(--_motion-duration-reverse) linear allow-discrete,
85
+ --statelayer-opacity var(--_motion-duration) linear;
87
86
 
88
87
  &:hover {
89
- --statelayer-color: var(--md-comp-snackbar-action-hover-state-layer-color, var(--md-sys-color-inverse-primary));
90
- --statelayer-opacity: var(--md-comp-snackbar-action-hover-state-layer-opacity, var(--md-sys-state-hover-state-layer-opacity));
91
- }
92
- &:focus-visible {
93
- --statelayer-color: var(--md-comp-snackbar-action-focus-state-layer-color, var(--md-sys-color-inverse-primary));
94
- --statelayer-opacity: var(--md-comp-snackbar-action-focus-state-layer-opacity, var(--md-sys-state-focus-state-layer-opacity));
88
+ --statelayer-color: var(--md-comp-snackbar-hover-state-layer-color, var(--md-sys-color-inverse-primary));
89
+ --statelayer-opacity: var(--md-comp-snackbar-hover-state-layer-opacity, var(--md-sys-state-hover-state-layer-opacity));
95
90
  }
96
91
  &:active {
97
- --statelayer-color: var(--md-comp-snackbar-action-pressed-state-layer-color, var(--md-sys-color-inverse-primary));
98
- --statelayer-opacity: var(--md-comp-snackbar-action-pressed-state-layer-opacity, var(--md-sys-state-pressed-state-layer-opacity));
92
+ --statelayer-color: var(--md-comp-snackbar-pressed-state-layer-color, var(--md-sys-color-inverse-primary));
93
+ --statelayer-opacity: var(--md-comp-snackbar-pressed-state-layer-opacity, var(--md-sys-state-pressed-state-layer-opacity));
99
94
  }
100
95
 
101
96
  &:popover-open {
102
- --micl-duration: var(--md-comp-snackbar-motion-duration);
97
+ --micl-duration: var(--_motion-duration);
103
98
 
104
99
  block-size: var(--md-comp-snackbar-with-single-line-container-height, 48px);
105
100
  opacity: 1;
@@ -157,7 +152,7 @@
157
152
 
158
153
  @media (max-width: layout.$md-sys-breakpoint-compact-max) {
159
154
  .micl-snackbar {
160
- inline-size: calc(100vw - (2 * var(--md-sys-layout-window-margin)));
155
+ inline-size: auto;
161
156
  min-inline-size: auto;
162
157
  }
163
158
  }
@@ -25,8 +25,8 @@
25
25
  @include statelayer.token('disabled-state-layer-opacity');
26
26
 
27
27
  .micl-stepper {
28
- --md-comp-stepper-motion-spatial: #{motion.$md-sys-motion-expressive-slow-spatial};
29
- --md-comp-stepper-motion-duration: #{motion.$md-sys-motion-expressive-slow-spatial-duration};
28
+ --_motion-spatial: var(--md-comp-stepper-motion-spatial, #{motion.$md-sys-motion-expressive-slow-spatial});
29
+ --_motion-duration: var(--md-comp-stepper-motion-duration, #{motion.$md-sys-motion-expressive-slow-spatial-duration});
30
30
 
31
31
  --_dot-size: var(--md-comp-stepper-dot-size, 12px);
32
32
 
@@ -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;
@@ -105,8 +105,8 @@
105
105
  opacity: 0%;
106
106
  transform: translateX(100%);
107
107
  transition:
108
- opacity var(--md-comp-stepper-motion-duration) linear,
109
- transform var(--md-comp-stepper-motion-duration) var(--md-comp-stepper-motion-spatial);
108
+ opacity var(--_motion-duration) linear,
109
+ transform var(--_motion-duration) var(--_motion-spatial);
110
110
 
111
111
  &:has(~ .micl-stepper__step[aria-current=step]) {
112
112
  transform: translateX(-100%);
@@ -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.
@@ -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 '../checkbox/label';
25
+ @use '../../styles/togglelabel';
27
26
 
28
27
  @include shape.corner('full');
29
28
 
@@ -33,14 +32,15 @@
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
 
37
37
  $switch: 'input[type=checkbox].micl-switch';
38
38
 
39
39
  #{$switch} {
40
- --md-comp-switch-unselected-icon: "+";
41
- --md-comp-switch-selected-icon: "\AC";
42
- --md-comp-switch-motion-effects: #{motion.$md-sys-motion-expressive-slow-effects};
43
- --md-comp-switch-motion-duration: #{motion.$md-sys-motion-expressive-slow-effects-duration};
40
+ --_unselected-icon: var(--md-comp-switch-unselected-icon, "+");
41
+ --_selected-icon: var(--md-comp-switch-selected-icon, "\AC");
42
+ --_motion-effects: var(--md-comp-switch-motion-effects, #{motion.$md-sys-motion-expressive-slow-effects});
43
+ --_motion-duration: var(--md-comp-switch-motion-duration, #{motion.$md-sys-motion-expressive-slow-effects-duration});
44
44
 
45
45
  --_track-width: var(--md-comp-switch-track-width, 52px);
46
46
  --_track-height: var(--md-comp-switch-track-height, 32px);
@@ -49,7 +49,9 @@ $switch: 'input[type=checkbox].micl-switch';
49
49
  --_handle-selected-size: var(--md-comp-switch-selected-handle-size, 24px);
50
50
  --_handle-pressed-size: var(--md-comp-switch-pressed-handle-size, 28px);
51
51
  --_layer: var(--md-sys-state-layer-size, 40px);
52
- --_target: var(--md-sys-target-size, 48px);
52
+ --_target: var(--md-sys-state-target-size, 48px);
53
+ --_unselected-layer-color: var(--md-comp-switch-unselected-state-layer-color, var(--md-sys-color-on-surface));
54
+ --_selected-layer-color: var(--md-comp-switch-selected-state-layer-color, var(--md-sys-color-primary));
53
55
 
54
56
  appearance: none;
55
57
  position: relative;
@@ -71,45 +73,45 @@ $switch: 'input[type=checkbox].micl-switch';
71
73
  border: var(--_track-outline-width) solid var(--md-comp-switch-unselected-track-outline-color, var(--md-sys-color-outline));
72
74
  border-radius: inherit;
73
75
  background-color: var(--md-comp-switch-unselected-track-color, var(--md-sys-color-surface-container-highest));
74
- transition: background-color var(--md-comp-switch-motion-duration) motion.$md-sys-motion-easing-emphasized;
76
+ transition: background-color var(--_motion-duration) motion.$md-sys-motion-easing-emphasized;
75
77
  }
76
- // The handle: a state-layer-sized box whose transparent border shrinks the painted
77
- // content box to the handle size; the icon glyphs are rotated typeface characters,
78
- // with their metrics tuned in pixels against a 16px base.
79
78
  &::after {
80
- content: var(--md-comp-switch-unselected-icon);
79
+ content: var(--_unselected-icon);
81
80
  box-sizing: border-box;
82
81
  position: absolute;
82
+ display: grid;
83
+ grid-template-rows: auto 3px;
84
+ place-items: center;
83
85
  inline-size: var(--_layer);
84
86
  block-size: var(--_layer);
85
87
  inset: 0;
86
- inset-inline-start: calc((var(--_target) - var(--_layer) - 16px) / 2);
88
+ inset-inline-start: calc((var(--_track-height) - var(--_layer)) / 2);
87
89
  margin: auto 0;
88
- font: 300 16px / 16px var(--md-ref-typeface-plain);
90
+ font: 300 18px / 0 var(--md-ref-typeface-plain);
89
91
  color: var(--md-comp-switch-unselected-icon-color, var(--md-sys-color-surface-container-highest));
90
- text-align: center;
91
92
  background-color: var(--md-comp-switch-unselected-handle-color, var(--md-sys-color-outline));
92
93
  background-clip: content-box;
93
94
  border: calc((var(--_layer) - var(--_handle-size)) / 2) solid transparent;
94
95
  border-radius: var(--md-sys-shape-corner-full);
95
96
  transform: rotate(135deg);
96
97
  transition:
97
- inset-inline-start var(--md-comp-switch-motion-duration) var(--md-comp-switch-motion-effects),
98
- border-width var(--md-comp-switch-motion-duration) var(--md-comp-switch-motion-effects),
99
- line-height var(--md-comp-switch-motion-duration) var(--md-comp-switch-motion-effects),
100
- color var(--md-comp-switch-motion-duration) motion.$md-sys-motion-easing-emphasized,
101
- border-color var(--md-comp-switch-motion-duration) motion.$md-sys-motion-easing-emphasized,
102
- background-color var(--md-comp-switch-motion-duration) motion.$md-sys-motion-easing-emphasized;
98
+ inset-inline-start var(--_motion-duration) var(--_motion-effects),
99
+ border-width var(--_motion-duration) var(--_motion-effects),
100
+ grid-template-rows var(--_motion-duration) var(--_motion-effects),
101
+ color var(--_motion-duration) motion.$md-sys-motion-easing-emphasized,
102
+ border-color var(--_motion-duration) motion.$md-sys-motion-easing-emphasized,
103
+ background-color var(--_motion-duration) motion.$md-sys-motion-easing-emphasized;
103
104
  }
104
105
  &:checked {
105
106
  &::before {
107
+ border-color: var(--md-comp-switch-selected-track-color, var(--md-sys-color-primary));
106
108
  background-color: var(--md-comp-switch-selected-track-color, var(--md-sys-color-primary));
107
109
  }
108
110
  &::after {
109
- content: var(--md-comp-switch-selected-icon);
110
- inset-inline-start: calc(var(--_track-width) - ((var(--_layer) + var(--_target) - 16px) / 2));
111
- font-size: 25.6px;
112
- line-height: 20.8px;
111
+ content: var(--_selected-icon);
112
+ grid-template-rows: auto 8px;
113
+ inset-inline-start: calc(var(--_track-width) - ((var(--_layer) + var(--_track-height)) / 2));
114
+ font-size: 32px;
113
115
  letter-spacing: 1.6px;
114
116
  border: calc((var(--_layer) - var(--_handle-selected-size)) / 2) solid transparent;
115
117
  color: var(--md-comp-switch-selected-icon-color, var(--md-sys-color-on-primary-container));
@@ -121,82 +123,85 @@ $switch: 'input[type=checkbox].micl-switch';
121
123
 
122
124
  &:hover {
123
125
  &::after {
124
- border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity, 8%), transparent);
125
- background-color: var(--md-sys-color-on-surface-variant);
126
+ border-color: color-mix(in srgb, var(--_unselected-layer-color) var(--md-sys-state-hover-state-layer-opacity, 8%), transparent);
127
+ background-color: var(--md-comp-switch-unselected-hover-handle-color, var(--md-sys-color-on-surface-variant));
126
128
  }
127
129
  &:checked::after {
128
- border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-hover-state-layer-opacity, 8%), transparent);
129
- background-color: var(--md-sys-color-primary-container);
130
+ border-color: color-mix(in srgb, var(--_selected-layer-color) var(--md-sys-state-hover-state-layer-opacity, 8%), transparent);
131
+ background-color: var(--md-comp-switch-selected-hover-handle-color, var(--md-sys-color-primary-container));
130
132
  }
131
133
  }
132
134
  &:focus-visible {
133
135
  &::after {
134
- border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-focus-state-layer-opacity, 10%), transparent);
136
+ border-color: color-mix(in srgb, var(--_unselected-layer-color) var(--md-sys-state-focus-state-layer-opacity, 10%), transparent);
135
137
  }
136
138
  &::before {
137
- outline: var(--md-sys-state-focus-indicator-thickness, 3px) solid var(--md-sys-color-secondary);
138
- outline-offset: 2px;
139
+ outline: var(--md-sys-state-focus-indicator-thickness, 3px) solid var(--md-comp-switch-focus-indicator-color, var(--md-sys-color-secondary));
140
+ outline-offset: var(--md-sys-state-focus-indicator-outer-offset, 2px);
139
141
  }
140
142
  &:checked::after {
141
- border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-focus-state-layer-opacity, 10%), transparent);
142
- background-color: var(--md-sys-color-primary-container);
143
+ border-color: color-mix(in srgb, var(--_selected-layer-color) var(--md-sys-state-focus-state-layer-opacity, 10%), transparent);
144
+ background-color: var(--md-comp-switch-selected-focus-handle-color, var(--md-sys-color-primary-container));
143
145
  }
144
146
  &:not(:checked)::after {
145
- background-color: var(--md-sys-color-on-surface-variant);
147
+ background-color: var(--md-comp-switch-unselected-focus-handle-color, var(--md-sys-color-on-surface-variant));
146
148
  }
147
149
  }
148
150
  &:active {
149
151
  &::after {
150
- line-height: 27.2px;
151
152
  border: calc((var(--_layer) - var(--_handle-pressed-size)) / 2) solid transparent;
152
- background-color: var(--md-sys-color-on-surface-variant);
153
+ background-color: var(--md-comp-switch-unselected-pressed-handle-color, var(--md-sys-color-on-surface-variant));
153
154
  }
154
155
  &:checked::after {
155
- line-height: 25.6px;
156
156
  border-width: calc((var(--_layer) - var(--_handle-pressed-size)) / 2);
157
- border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-pressed-state-layer-opacity, 10%), transparent);
158
- background-color: var(--md-sys-color-primary-container);
157
+ border-color: color-mix(in srgb, var(--_selected-layer-color) var(--md-sys-state-pressed-state-layer-opacity, 10%), transparent);
158
+ background-color: var(--md-comp-switch-selected-pressed-handle-color, var(--md-sys-color-primary-container));
159
159
  }
160
160
  &:not(:checked)::after {
161
- border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-pressed-state-layer-opacity, 10%), transparent);
161
+ border-color: color-mix(in srgb, var(--_unselected-layer-color) var(--md-sys-state-pressed-state-layer-opacity, 10%), transparent);
162
162
  }
163
163
  }
164
164
  }
165
165
  &:disabled {
166
166
  &::before {
167
- border-color: var(--md-sys-color-on-surface);
168
- background-color: var(--md-sys-color-surface-container-highest);
169
- opacity: 12%;
167
+ border-color: var(--md-comp-switch-disabled-unselected-track-outline-color, var(--md-sys-color-on-surface));
168
+ background-color: var(--md-comp-switch-disabled-unselected-track-color, var(--md-sys-color-surface-container-highest));
169
+ opacity: var(--md-comp-switch-disabled-track-opacity, 12%);
170
170
  }
171
171
  &:checked::before {
172
- background-color: var(--md-sys-color-on-surface);
172
+ border-color: var(--md-comp-switch-disabled-selected-track-color, var(--md-sys-color-on-surface));
173
+ background-color: var(--md-comp-switch-disabled-selected-track-color, var(--md-sys-color-on-surface));
173
174
  }
174
175
  &::after {
175
- color: rgb(from var(--md-sys-color-surface-container-highest) r g b / var(--md-sys-state-disabled-state-layer-opacity, 38%));
176
- background-color: var(--md-sys-color-on-surface);
176
+ color: var(--md-comp-switch-disabled-unselected-icon-color, var(--md-sys-color-surface-container-highest));
177
+ background-color: var(--md-comp-switch-disabled-unselected-handle-color, var(--md-sys-color-on-surface));
177
178
  opacity: var(--md-sys-state-disabled-state-layer-opacity, 38%);
178
179
  }
179
180
  &:checked::after {
180
- color: rgb(from var(--md-sys-color-on-surface) r g b / var(--md-sys-state-disabled-state-layer-opacity, 38%));
181
- background-color: var(--md-sys-color-surface);
181
+ color: rgb(from var(--md-comp-switch-disabled-selected-icon-color, var(--md-sys-color-on-surface)) r g b / var(--md-sys-state-disabled-state-layer-opacity, 38%));
182
+ background-color: var(--md-comp-switch-disabled-selected-handle-color, var(--md-sys-color-surface));
182
183
  opacity: 100%;
183
184
  }
184
185
  }
185
186
  }
186
187
 
187
188
  #{$switch}:not(:disabled) + label,
188
- label:has(+ #{$switch}:not(:disabled)) {
189
+ label:has(+ #{$switch}:not(:disabled)),
190
+ label:has(#{$switch}:not(:disabled)) {
189
191
  @extend %micl-toggle-label-pointer;
190
192
  }
191
193
  #{$switch} + label,
192
- label:has(+ #{$switch}) {
194
+ label:has(+ #{$switch}),
195
+ label:has(#{$switch}) {
193
196
  @extend %micl-toggle-label;
194
197
  }
195
198
  #{$switch}:disabled + label,
196
199
  label:has(+ #{$switch}:disabled) {
197
200
  @extend %micl-toggle-label-disabled;
198
201
  }
199
-
202
+ label:has(#{$switch}:disabled) {
203
+ @extend %micl-toggle-label-disabled-text;
204
+ }
200
205
  #{$switch}:dir(rtl)::after {
201
206
  transform: rotate(45deg) scaleY(-1);
202
207
  }