|
42 | 42 | var(--shadow-active, 0 6px 18px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2)) |
43 | 43 | ); |
44 | 44 |
|
| 45 | + /* |
| 46 | + * Legacy brightness tokens are preserved, but state rendering now uses |
| 47 | + * a ::before state-layer pseudo-element instead of host-level filters so |
| 48 | + * text and icons are less likely to lose contrast in consumer themes. |
| 49 | + * The state layer is decoupled from box-shadow so shadow tokens set to |
| 50 | + * `none` (e.g. in prefers-contrast: more) cannot invalidate the |
| 51 | + * box-shadow declaration. |
| 52 | + */ |
45 | 53 | --_is-darken-hover: var(--interactive-surface-darken-hover, var(--surface-darken-hover, 0.96)); |
46 | 54 | --_is-darken-active: var(--interactive-surface-darken-active, var(--surface-darken-active, 0.98)); |
| 55 | + --_is-state-layer-hover-opacity: var( |
| 56 | + --interactive-surface-state-layer-hover-opacity, |
| 57 | + calc(1 - var(--_is-darken-hover)) |
| 58 | + ); |
| 59 | + --_is-state-layer-active-opacity: var( |
| 60 | + --interactive-surface-state-layer-active-opacity, |
| 61 | + calc(1 - var(--_is-darken-active)) |
| 62 | + ); |
| 63 | + --_is-state-layer-color: var(--interactive-surface-state-layer-color, rgb(0 0 0)); |
47 | 64 |
|
48 | 65 | --_is-motion-default: var(--interactive-surface-motion-default, var(--motion-default, 140ms)); |
49 | 66 | --_is-motion-press: var(--interactive-surface-motion-press, var(--motion-press, 60ms)); |
|
69 | 86 | --_is-focus-ring-width: var(--interactive-surface-focus-ring-width, 2px); |
70 | 87 | --_is-focus-ring-offset: var(--interactive-surface-focus-ring-offset, 2px); |
71 | 88 | --_is-disabled-opacity: var(--interactive-surface-disabled-opacity, 0.72); |
| 89 | + --_is-tap-highlight: var(--interactive-surface-tap-highlight-color, rgb(11 99 246 / 0.18)); |
72 | 90 |
|
73 | 91 | position: relative; |
| 92 | + isolation: isolate; |
74 | 93 | box-sizing: border-box; |
75 | 94 | border: var(--_is-border-width) solid var(--_is-border-color); |
76 | 95 | border-radius: var(--_is-radius); |
|
82 | 101 | -webkit-appearance: none; |
83 | 102 | appearance: none; |
84 | 103 | touch-action: manipulation; |
85 | | - -webkit-tap-highlight-color: transparent; |
| 104 | + -webkit-tap-highlight-color: var(--_is-tap-highlight); |
| 105 | + background-clip: padding-box; |
86 | 106 | transform: translateY(var(--_is-lift-base)) translateZ(0) !important; |
87 | 107 | box-shadow: var(--_is-shadow-base); |
88 | 108 | transition: |
89 | 109 | transform var(--_is-motion-default) var(--_is-ease-standard), |
90 | 110 | box-shadow var(--_is-motion-default) var(--_is-ease-standard), |
91 | | - filter var(--_is-motion-default) linear, |
92 | 111 | background-color var(--_is-motion-default) linear, |
93 | | - color var(--_is-motion-default) linear; |
| 112 | + border-color var(--_is-motion-default) linear, |
| 113 | + color var(--_is-motion-default) linear, |
| 114 | + outline-color var(--_is-motion-default) linear; |
94 | 115 | will-change: auto; |
95 | 116 | } |
96 | 117 |
|
97 | | -@media (hover: hover) and (pointer: fine) { |
98 | | - .interactive-surface { |
99 | | - will-change: transform, box-shadow; |
100 | | - } |
| 118 | +/* 2a) State-layer pseudo-element */ |
101 | 119 |
|
102 | | - .interactive-surface:hover { |
103 | | - transform: translateY(var(--_is-lift-hover)) translateZ(0) !important; |
104 | | - box-shadow: var(--_is-shadow-hover); |
105 | | - filter: brightness(var(--_is-darken-hover)); |
106 | | - } |
| 120 | +.interactive-surface::before { |
| 121 | + content: ""; |
| 122 | + position: absolute; |
| 123 | + inset: 0; |
| 124 | + border-radius: inherit; |
| 125 | + background-color: var(--_is-state-layer-color); |
| 126 | + opacity: 0; |
| 127 | + pointer-events: none; |
| 128 | + z-index: -1; |
| 129 | + transition: opacity var(--_is-motion-default) var(--_is-ease-standard); |
107 | 130 | } |
108 | 131 |
|
109 | 132 | /* 3) Keyboard focus visibility */ |
|
119 | 142 | } |
120 | 143 | } |
121 | 144 |
|
122 | | -.interactive-surface:focus-visible { |
| 145 | +.interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):focus-visible { |
123 | 146 | transform: translateY(var(--_is-lift-hover)) translateZ(0) !important; |
124 | 147 | box-shadow: var(--_is-shadow-hover); |
125 | | - filter: brightness(var(--_is-darken-hover)); |
| 148 | + outline: var(--_is-focus-ring-width) solid var(--_is-focus-ring-color); |
| 149 | + outline-offset: var(--_is-focus-ring-offset); |
| 150 | +} |
| 151 | + |
| 152 | +.interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):focus-visible::before { |
| 153 | + opacity: var(--_is-state-layer-hover-opacity); |
| 154 | +} |
| 155 | + |
| 156 | +.interactive-surface[aria-disabled="true"]:focus-visible { |
126 | 157 | outline: var(--_is-focus-ring-width) solid var(--_is-focus-ring-color); |
127 | 158 | outline-offset: var(--_is-focus-ring-offset); |
128 | 159 | } |
129 | 160 |
|
130 | 161 | /* 4) Active and toggled states */ |
131 | 162 |
|
132 | | -.interactive-surface.is-active, |
133 | | -.interactive-surface[aria-pressed="true"] { |
| 163 | +.interactive-surface.is-active:not(.is-disabled, [aria-disabled="true"]), |
| 164 | +.interactive-surface[aria-pressed="true"]:not(.is-disabled, :disabled, [aria-disabled="true"]) { |
134 | 165 | transform: translateY(var(--_is-lift-active)) translateZ(0) !important; |
135 | 166 | box-shadow: var(--_is-shadow-active); |
136 | | - filter: brightness(var(--_is-darken-active)); |
137 | 167 | } |
138 | 168 |
|
139 | | -/* 5) Press feedback */ |
| 169 | +.interactive-surface.is-active:not(.is-disabled, [aria-disabled="true"])::before, |
| 170 | +.interactive-surface[aria-pressed="true"]:not(.is-disabled, :disabled, [aria-disabled="true"])::before { |
| 171 | + opacity: var(--_is-state-layer-active-opacity); |
| 172 | +} |
| 173 | + |
| 174 | +/* 5) Pointer hover and press feedback */ |
| 175 | + |
| 176 | +@media (hover: hover) and (pointer: fine) { |
| 177 | + .interactive-surface { |
| 178 | + will-change: transform, box-shadow; |
| 179 | + } |
| 180 | + |
| 181 | + .interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):hover { |
| 182 | + transform: translateY(var(--_is-lift-hover)) translateZ(0) !important; |
| 183 | + box-shadow: var(--_is-shadow-hover); |
| 184 | + } |
| 185 | + |
| 186 | + .interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):hover::before { |
| 187 | + opacity: var(--_is-state-layer-hover-opacity); |
| 188 | + } |
| 189 | +} |
140 | 190 |
|
141 | | -.interactive-surface:active { |
| 191 | +.interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):active { |
142 | 192 | transform: translateY(var(--_is-lift-base)) translateZ(0) !important; |
143 | 193 | box-shadow: var(--_is-shadow-base); |
144 | | - filter: brightness(1); |
145 | 194 | transition-duration: var(--_is-motion-press); |
146 | 195 | transition-timing-function: var(--_is-ease-press); |
147 | 196 | } |
148 | 197 |
|
| 198 | +.interactive-surface:not(.is-disabled, :disabled, [aria-disabled="true"]):active::before { |
| 199 | + opacity: 0; |
| 200 | +} |
| 201 | + |
149 | 202 | /* 6) Size modifiers */ |
150 | 203 |
|
151 | 204 | .interactive-surface.size-sm { |
|
271 | 324 | /* 9) Disabled states */ |
272 | 325 |
|
273 | 326 | .interactive-surface.is-disabled, |
274 | | -.interactive-surface:disabled, |
275 | | -.interactive-surface[aria-disabled="true"] { |
| 327 | +.interactive-surface:disabled { |
276 | 328 | cursor: not-allowed; |
277 | 329 | pointer-events: none; |
278 | 330 | transform: none !important; |
279 | 331 | box-shadow: none; |
280 | | - filter: saturate(0.6) contrast(0.9) brightness(0.92); |
281 | 332 | opacity: var(--_is-disabled-opacity); |
282 | 333 | transition: none; |
283 | 334 | } |
284 | 335 |
|
| 336 | +.interactive-surface[aria-disabled="true"] { |
| 337 | + cursor: not-allowed; |
| 338 | + pointer-events: none; |
| 339 | + transform: none !important; |
| 340 | + box-shadow: none; |
| 341 | + opacity: var(--_is-disabled-opacity); |
| 342 | +} |
| 343 | + |
285 | 344 | /* Guardrail: this class owns motion on the host element. */ |
286 | 345 | .interactive-surface, |
287 | 346 | .interactive-surface:hover, |
|
301 | 360 | transition: none; |
302 | 361 | } |
303 | 362 |
|
| 363 | + .interactive-surface::before { |
| 364 | + transition: none; |
| 365 | + } |
| 366 | + |
304 | 367 | .interactive-surface:hover, |
305 | 368 | .interactive-surface:focus-visible, |
306 | 369 | .interactive-surface:active, |
307 | 370 | .interactive-surface.is-active, |
308 | 371 | .interactive-surface[aria-pressed="true"] { |
309 | 372 | transform: none !important; |
310 | 373 | box-shadow: none; |
311 | | - filter: none; |
| 374 | + } |
| 375 | + |
| 376 | + .interactive-surface:hover::before, |
| 377 | + .interactive-surface:focus-visible::before, |
| 378 | + .interactive-surface:active::before, |
| 379 | + .interactive-surface.is-active::before, |
| 380 | + .interactive-surface[aria-pressed="true"]::before { |
| 381 | + opacity: 0; |
312 | 382 | } |
313 | 383 | } |
314 | 384 |
|
|
319 | 389 | --interactive-surface-darken-hover: 1; |
320 | 390 | --interactive-surface-darken-active: 1; |
321 | 391 | --interactive-surface-focus-ring-width: 3px; |
| 392 | + --interactive-surface-border-width: 2px; |
| 393 | + --interactive-surface-shadow-base: none; |
| 394 | + --interactive-surface-shadow-hover: none; |
| 395 | + --interactive-surface-shadow-active: none; |
322 | 396 | } |
323 | 397 | } |
324 | 398 |
|
|
329 | 403 | color: ButtonText; |
330 | 404 | border-color: ButtonText; |
331 | 405 | box-shadow: none; |
332 | | - filter: none; |
| 406 | + } |
| 407 | + |
| 408 | + .interactive-surface::before { |
| 409 | + display: none; |
333 | 410 | } |
334 | 411 |
|
335 | 412 | .interactive-surface:hover, |
|
339 | 416 | .interactive-surface:active { |
340 | 417 | transform: none !important; |
341 | 418 | box-shadow: none; |
342 | | - filter: none; |
343 | 419 | } |
344 | 420 |
|
345 | 421 | .interactive-surface:focus, |
|
0 commit comments