diff --git a/packages/mdc-ripple/README.md b/packages/mdc-ripple/README.md
index 8de95534b5b..da66e3b8e2a 100644
--- a/packages/mdc-ripple/README.md
+++ b/packages/mdc-ripple/README.md
@@ -62,7 +62,7 @@ General notes:
#### Sass API
-In order to fully style states as well as the ripple effect for pressed state, both `mdc-ripple` mixins below must be included, as well as either the basic `mdc-states` mixin or all of the advanced `mdc-states-...` mixins documented below.
+In order to fully style states as well as the ripple effect for pressed state, both `mdc-ripple` mixins below must be included, as well as either the basic or advanced `mdc-states` mixins documented below.
Once these styles are in place for a component, it is feasible to further override only the parts necessary (e.g. `mdc-states` specifically) for specific variants (e.g. for flat vs. raised buttons).
@@ -75,14 +75,26 @@ Mixin | Description
`mdc-ripple-surface` | Adds base styles for a ripple surface
`mdc-ripple-radius($radius)` | Adds styles for the radius of the ripple effect,
for both bounded and unbounded ripples
-##### Basic States Mixin
+##### Basic States Mixins
+
+When using the basic states mixins, inclusion of the `mdc-states` mixin is mandatory.
+Inclusion of `mdc-states-activated` or `mdc-states-selected` is optional, depending on whether activated or selected
+states are applicable to the component in question.
Mixin | Description
--- | ---
-`mdc-states($color, $has-nested-focusable-element)` | Adds state and ripple styles for the indicated color, deciding opacities based on whether the passed color is light or dark. `$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.
+`mdc-states($color, $has-nested-focusable-element)` | Adds state and ripple styles using the indicated color, deciding opacities based on whether the passed color is light or dark.
`$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.
+`mdc-states-activated($color, $has-nested-focusable-element)` | Adds state and ripple styles for activated states using the indicated color, deciding opacities based on whether the passed color is light or dark.
`$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.
+`mdc-states-selected($color, $has-nested-focusable-element)` | Adds state and ripple styles for selected states using the indicated color, deciding opacities based on whether the passed color is light or dark.
`$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.
##### Advanced States Mixins
+When using the advanced states mixins, every one of the mixins below should be included at least once to establish base
+styles for states.
+
+These mixins can also be used to emit activated or selected styles if applicable, by applying them within a selector for
+`&--activated` or `&--selected` modifier classes.
+
Mixin | Description
--- | ---
`mdc-states-base-color($color)` | Sets up base state styles using the provided color
diff --git a/packages/mdc-ripple/_mixins.scss b/packages/mdc-ripple/_mixins.scss
index 197dcb0f49c..e33b84c1835 100644
--- a/packages/mdc-ripple/_mixins.scss
+++ b/packages/mdc-ripple/_mixins.scss
@@ -61,6 +61,7 @@ $mdc-ripple-common-styles-emitted_: false !default;
&::after {
position: absolute;
border-radius: 50%;
+ opacity: 0;
pointer-events: none;
content: "";
will-change: transform, opacity;
@@ -104,8 +105,6 @@ $mdc-ripple-common-styles-emitted_: false !default;
&::before,
&::after {
@include mdc-theme-prop(background-color, $color, $edgeOptOut: true);
-
- opacity: 0;
}
}
@@ -156,19 +155,43 @@ $mdc-ripple-common-styles-emitted_: false !default;
}
}
-// Simple mixin which automatically selects opacity values based on whether the ink color is light or dark.
+// Simple mixin for base states which automatically selects opacity values based on whether the ink color is
+// light or dark.
@mixin mdc-states($color: black, $has-nested-focusable-element: false) {
- $color-value: mdc-theme-prop-value($color);
- $opacity-map: if(
- mdc-theme-tone($color-value) == "light",
- $mdc-ripple-light-ink-opacities,
- $mdc-ripple-dark-ink-opacities
- );
+ @include mdc-states-interactions_($color, $has-nested-focusable-element);
+}
- @include mdc-states-base-color($color);
- @include mdc-states-hover-opacity(map-get($opacity-map, "hover"));
- @include mdc-states-focus-opacity(map-get($opacity-map, "focus"), $has-nested-focusable-element);
- @include mdc-states-press-opacity(map-get($opacity-map, "press"));
+// Simple mixin for activated states which automatically selects opacity values based on whether the ink color is
+// light or dark.
+@mixin mdc-states-activated($color, $has-nested-focusable-element: false) {
+ $opacity-map: mdc-states-opacities_($color);
+ $activated-opacity: map-get($opacity-map, "activated");
+
+ &--activated {
+ // Stylelint seems to think that '&' qualifies as a type selector here?
+ // stylelint-disable-next-line selector-max-type
+ &::before {
+ opacity: $activated-opacity;
+ }
+
+ @include mdc-states-interactions_($color, $has-nested-focusable-element, $activated-opacity);
+ }
+}
+
+// Simple mixin for selected states which automatically selects opacity values based on whether the ink color is
+// light or dark.
+@mixin mdc-states-selected($color, $has-nested-focusable-element: false) {
+ $opacity-map: mdc-states-opacities_($color);
+ $selected-opacity: map-get($opacity-map, "selected");
+
+ &--selected {
+ // stylelint-disable-next-line selector-max-type
+ &::before {
+ opacity: $selected-opacity;
+ }
+
+ @include mdc-states-interactions_($color, $has-nested-focusable-element, $selected-opacity);
+ }
}
@mixin mdc-ripple-radius($radius: 100%) {
@@ -250,6 +273,27 @@ $mdc-ripple-common-styles-emitted_: false !default;
// Private
//
+@function mdc-states-opacities_($color) {
+ $color-value: mdc-theme-prop-value($color);
+ $opacity-map: if(
+ mdc-theme-tone($color-value) == "light",
+ $mdc-ripple-light-ink-opacities,
+ $mdc-ripple-dark-ink-opacities
+ );
+
+ @return $opacity-map;
+}
+
+@mixin mdc-states-interactions_($color, $has-nested-focusable-element, $opacity-modifier: 0) {
+ $opacity-map: mdc-states-opacities_($color);
+
+ @include mdc-states-base-color($color);
+ @include mdc-states-hover-opacity(map-get($opacity-map, "hover") + $opacity-modifier);
+ @include mdc-states-focus-opacity(map-get($opacity-map, "focus") + $opacity-modifier, $has-nested-focusable-element);
+ @include mdc-states-press-opacity(map-get($opacity-map, "press") + $opacity-modifier);
+}
+
+// Note: This can be removed when we remove the legacy mdc-ripple-color mixin.
@mixin mdc-ripple-color_($color, $opacity) {
// stylelint-disable at-rule-empty-line-before, block-closing-brace-newline-after
@if type-of($color) == "color" {
diff --git a/packages/mdc-ripple/_variables.scss b/packages/mdc-ripple/_variables.scss
index 71904ad718d..b5f11ca8e88 100644
--- a/packages/mdc-ripple/_variables.scss
+++ b/packages/mdc-ripple/_variables.scss
@@ -22,13 +22,17 @@ $mdc-states-wash-duration: 15ms;
$mdc-ripple-dark-ink-opacities: (
hover: .04,
focus: .12,
- press: .16
+ press: .16,
+ selected: .04,
+ activated: .12
) !default;
$mdc-ripple-light-ink-opacities: (
hover: .08,
focus: .24,
- press: .32
+ press: .32,
+ selected: .08,
+ activated: .24
) !default;
// Legacy