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.
- package/README.md +2 -1
- package/components/appbar/index.scss +1 -1
- package/components/badge/README.md +43 -12
- package/components/badge/index.scss +15 -21
- package/components/bottomsheet/README.md +53 -22
- package/components/bottomsheet/index.scss +62 -45
- package/components/button/_shared.scss +9 -9
- package/components/card/README.md +55 -25
- package/components/card/index.scss +57 -38
- package/components/checkbox/README.md +97 -28
- package/components/checkbox/index.scss +39 -29
- package/components/chip/index.scss +6 -6
- package/components/datepicker/README.md +184 -59
- package/components/datepicker/index.scss +85 -21
- package/components/dialog/README.md +151 -56
- package/components/dialog/index.scss +50 -62
- package/components/iconbutton/index.scss +8 -8
- package/components/list/index.scss +15 -11
- package/components/menu/index.scss +13 -13
- package/components/navigationbar/index.scss +7 -1
- package/components/navigationrail/index.scss +27 -22
- package/components/radio/README.md +52 -12
- package/components/radio/index.scss +59 -41
- package/components/select/index.scss +3 -3
- package/components/sidesheet/README.md +90 -37
- package/components/sidesheet/index.scss +51 -41
- package/components/slider/README.md +35 -9
- package/components/slider/index.scss +182 -153
- package/components/snackbar/README.md +43 -25
- package/components/snackbar/index.scss +22 -27
- package/components/stepper/index.scss +5 -5
- package/components/switch/README.md +32 -4
- package/components/switch/index.scss +57 -52
- package/components/timepicker/README.md +131 -55
- package/components/timepicker/index.scss +47 -26
- package/dist/appbar.css +1 -1
- package/dist/badge.css +1 -1
- package/dist/bottomsheet.css +1 -1
- package/dist/bottomsheet.js +1 -1
- package/dist/button.css +1 -1
- package/dist/card.css +1 -1
- package/dist/checkbox.css +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/chip.css +1 -1
- package/dist/components/checkbox/index.d.ts +1 -0
- package/dist/datepicker.css +1 -1
- package/dist/datepicker.js +1 -1
- package/dist/dialog.css +1 -1
- package/dist/foundations/form/index.js +1 -1
- package/dist/iconbutton.css +1 -1
- package/dist/layout.css +1 -1
- package/dist/list.css +1 -1
- package/dist/menu.css +1 -1
- package/dist/micl.css +1 -1
- package/dist/micl.js +1 -1
- package/dist/navigationbar.css +1 -1
- package/dist/navigationrail.css +1 -1
- package/dist/radio.css +1 -1
- package/dist/select.css +1 -1
- package/dist/sidesheet.css +1 -1
- package/dist/slider.css +1 -1
- package/dist/slider.js +1 -1
- package/dist/snackbar.css +1 -1
- package/dist/snackbar.js +1 -1
- package/dist/stepper.css +1 -1
- package/dist/stepper.js +1 -1
- package/dist/switch.css +1 -1
- package/dist/timepicker.css +1 -1
- package/dist/timepicker.js +1 -1
- package/foundations/layout/_tokens.scss +55 -0
- package/foundations/layout/index.scss +5 -35
- package/package.json +2 -2
- 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"
|
|
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
|
|
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
|
|
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"
|
|
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"
|
|
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"
|
|
66
|
-
<span>
|
|
67
|
-
<span
|
|
68
|
-
<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
|
|
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-
|
|
98
|
-
| `--md-comp-snackbar-
|
|
99
|
-
| `--md-comp-snackbar-
|
|
100
|
-
| `--md-comp-snackbar-
|
|
101
|
-
|
|
102
|
-
|
|
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"
|
|
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
|
|
42
|
-
--md-comp-snackbar-motion-duration
|
|
43
|
-
--md-comp-snackbar-motion-duration-reverse
|
|
44
|
-
--micl-duration: var(--
|
|
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:
|
|
57
|
-
max-inline-size:
|
|
58
|
-
min-inline-size:
|
|
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,
|
|
62
|
-
inset-inline:
|
|
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
|
|
78
|
+
overflow: clip;
|
|
80
79
|
|
|
81
80
|
transition:
|
|
82
|
-
block-size var(--micl-duration) var(--
|
|
83
|
-
opacity var(--
|
|
84
|
-
display var(--
|
|
85
|
-
overlay var(--
|
|
86
|
-
--statelayer-opacity var(--
|
|
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-
|
|
90
|
-
--statelayer-opacity: var(--md-comp-snackbar-
|
|
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-
|
|
98
|
-
--statelayer-opacity: var(--md-comp-snackbar-
|
|
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(--
|
|
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:
|
|
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
|
|
29
|
-
--md-comp-stepper-motion-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(--
|
|
109
|
-
transform var(--
|
|
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,
|
|
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
|
|
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/
|
|
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
|
|
42
|
-
--md-comp-switch-motion-effects
|
|
43
|
-
--md-comp-switch-motion-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(--
|
|
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(--
|
|
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(--
|
|
88
|
+
inset-inline-start: calc((var(--_track-height) - var(--_layer)) / 2);
|
|
87
89
|
margin: auto 0;
|
|
88
|
-
font: 300
|
|
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(--
|
|
98
|
-
border-width var(--
|
|
99
|
-
|
|
100
|
-
color var(--
|
|
101
|
-
border-color var(--
|
|
102
|
-
background-color var(--
|
|
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(--
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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
|
-
|
|
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:
|
|
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
|
}
|