mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.26k stars 32.12k forks source link

Support for prefers-reduced-motion #16367

Open notsidney opened 5 years ago

notsidney commented 5 years ago

Expected Behavior 🤔

The transition components Collapse, Fade, Grow, Slide, and Zoom (and other MUI components with animations) should have reduced motion in their animations, by either disabling the animations altogether or using a simple fade animation, when the prefers-reduced-motion: reduce media query is true.

Current Behavior 😯

None of the components change behaviour and show the same animation when the media query is true.

Examples 🌈

Here’s the MDN doc for this media query. It’s supported on Chrome, Firefox, and Safari on Windows, Mac, and iOS.

This WebKit blog has several examples of how to support this media query.

Context 🔦

I’m currently adding support for this media query in my web app by disabling page transitions and other custom transitions by using the media query in JSS. But I’m also using MUI’s built-in transition components and there is currently no easy way to disable those animations.

I could use the useMediaQuery hook to modify what my component returns to remove the transition components, but this is quite cumbersome. Supporting this media query directly would make it a lot easier to build more accessible web apps.

oliviertassinari commented 4 years ago

Would the following be an acceptable solution?

import React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { ThemeProvider } from '@material-ui/core/styles';

function App() {
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');

  const theme = React.useMemo(
    () =>
      createMuiTheme({
        ...(prefersReducedMotion
          ? {
              // So we have `transition: none;` everywhere
              transitions: { create: () => 'none' },
            }
          : {}),
      }),
    [prefersReducedMotion],
  );

  return (
    <ThemeProvider theme={theme}>
      <Routes />
    </ThemeProvider>
  );
}
joshwooding commented 4 years ago

@oliviertassinari I'm guessing that doesn't affect the transition components? Fade, Grow, etc.

oliviertassinari commented 4 years ago

@joshwooding It does disable these components too.

oliviertassinari commented 4 years ago

An alternative with global CSS:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-play-state: paused !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

I'm wondering 🤔 I have seen it in https://hankchizljaw.com/wrote/a-modern-css-reset/ (BTW, there are potential good resets we can deploy in CssBasline for v5).

notsidney commented 4 years ago

Hey @oliviertassinari, just to confirm: would your solution be something I’d have to add to each of my React projects using MUI?

oliviertassinari commented 4 years ago

@notseenee Regarding your question. Should we have built-in support for the preferred reduced motion preference? Yes, I think that we should explore this path, it sounds better.

notsidney commented 4 years ago

@oliviertassinari Yep, I think it’s in the interest of users and developers to have built-in support for prefers-reduced-motion as opposed to making devs implement it themselves

colemars commented 4 years ago

Suggested here (https://css-tricks.com/revisiting-prefers-reduced-motion-the-reduced-motion-media-query/) to include something along the lines of:

@media screen and
  (prefers-reduced-motion: reduce), 
  (update: slow) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

Making note that,

Retaining the animation and transition duration also ensures that any functionality that is tied to CSS-based animation will activate successfully (unlike using a declaration of animation: none), while still preventing a disability condition trigger or creating rendering lag

Easy to include at the root of your app:

...

const useStyles = makeStyles(theme => ({
  root: {
    "@media (prefers-reduced-motion: reduce)": {
      "& *": {
        animationDuration: "0.001ms !important",
        animationIterationCount: "1 !important",
        transitionDuration: "0.001ms !important"
      }
    }
  },
}));

....
}

https://codesandbox.io/s/material-ui-prefer-reduced-motion-f1ljg

But I agree having built-in support would be best,

sarahannnicholson commented 2 years ago

Commenting here to resurface this issue. Also, +1 for built in support :)

thany commented 1 year ago

Can we please have this built-in?

Being someone who suffers from motion sensitivity, it immediately stood out to me. And since MUI is almost screaming off the rooftops its support for accessibility features, you can imagine my dampened enthousiasm about a11y when I saw the first transition ever so smoothly giving me my next headache.

I sure hope MUI isn't giving us motion sensitive people an unearned finger, but it looks like that, frankly, seeing how this issue is from 2019. It doesn't take four years to get a fairly simple a11y feature into your framework, does it. If it does, then please explain why it's difficult. Maybe I can help.

IDrumsey commented 1 year ago

Would this apply to the Skeleton component as well? I don't want the loading animation to run when reduced-motion is enabled.

oliviertassinari commented 1 year ago

@IDrumsey Yeah, this would make sense to me. Maybe to make the animation run more slowly rather that entierely disabled?

reduce Indicates that a user has enabled the setting on their device for reduced motion. This keyword value evaluates as true.

https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion

IDrumsey commented 1 year ago

@oliviertassinari I was thinking more along the lines of completely disabling the animation in case people have motion sensitivity. I'm currently just using a hook right now to check if reduced-motion is enabled and the manually setting the animation= based on that. Would be good if this was just built into the component.

thany commented 1 year ago

@IDrumsey Completely disabling animations is the correct way to respond to this setting. I too don't understand why W3C calls it "reduce" (and "prefers" - as it's not a preference, it's accessibility), because there's no way of knowing by how much to reduce. Depends on the user's level of sensitivity. For me personally, only "big" animations are problematic, so a small loader spinner is fine. But a screen-sized menu sliding into the page, or a full page dim for a dialog, those are not okay. There are, however, many people much more sensitive than me, who can't even handle a small spinning loader icon.

It's the reason why Firefox at some point decided to even make the spinning loader in a tab a static icon when the OS reports animations are disabled. Even I wouldn't have thought that to trigger a response in anyone, but it did, and they fixed it.

HOWEVER, please bare in mind that by removing animations, you potentially introduce another problem: flashing images. So a fading animation removed turns into a flashing image. That's great for me, but now you've created a problem for people sensitive to flashing lights/images (epilepsy). This can happen (at least by default) for animationless dialogs that have a dimmed backdrop, for example.

@oliviertassinari Slowing down an animation makes it worse for me.

oliviertassinari commented 1 year ago

Completely disabling animations is the correct way to respond to this setting.

@thany macOS behaves differently, see my recording:

https://github.com/mui/material-ui/assets/3165635/b3f60b09-9eed-4067-9535-99274dd48f76

  1. Switch animation is retained
  2. the window expander animation is replaced with an opacity animation
thany commented 1 year ago

@oliviertassinari It depends on the form of sensitivity one is suffering from. I for one, cannot handle motion, but crossfades are less of a problem. Other people might still have a problem with crossfades.

The safest course of action is to disable all forms of animation, because we've only got the one switch to flip. We haven't got separate switches for motion, flashing lights, and crossfades, have we? 🙂