DEV Community

Cover image for JavaScript equivalent(s) of CSS @supports feature queries for bugfix detection
Ingo Steinke
Ingo Steinke

Posted on • Updated on

JavaScript equivalent(s) of CSS @supports feature queries for bugfix detection

CSS media queries have an equivalent JavaScript function: window.matchMedia. But what about @supports feature queries?

Take this code:

const prefersDarkColorSchemeQuery = window.matchMedia(
  '(prefers-color-scheme: dark)'
);
const prefersDarkMode = (prefersDarkColorSchemeQuery.matches);
Enter fullscreen mode Exit fullscreen mode

A true result tells us that a user prefers dark mode. But false or undefined is ambiguous. It can either mean that no preference has been set or that the query is not supported by the client browser. The latter is true on Safari 12, the latest, but outdated, web browser that you can officially get on iPhone 7.

Use cases for CSS feature queries in JavaScript

We can easily distinguish between media queries and feature queries in CSS, although it might not make much difference at first sight. We could define a grey default theme and switch to a more opinionated light or dark theme if there is an explicit preference. We can do this without JavaScript:

Dark theme, light theme, default theme 🌝🌚🤔🔦💡

body { /* default theme */
    background-color: whitesmoke;
    color: darkslategray;
}
@supports (prefers-color-scheme: light) {
    body { /* light theme  */
        background-color: white;
        color: black;
    }

    @media (prefers-color-scheme: dark) {
        body { /* dark theme */
            background-color: black;
            color: white;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Rotten Apple browsers 🍎🍏🤢🤮

What else? I mentioned outdated Safari versions (also known as "rotten Apple" browsers or the "new Internet Explorer"). Some people like to use their devices for a long time to save money and prevent electronic waste. While most Android users can get up-to-date browsers thanks to Firefox and Chrome, Apple prevents browser updates beyond the official end of support.

Feature detection vs. bug(fix) detection

If you search for best practices for browser detection, everyone will tell you not to do it and use feature detection instead. But how do you detect bugs and bug fixes?

Don't do browser detection ...

The creative (ab)use of feature detection to target specific browser bugs is also known as a "browser hack" or "CSS hack" because it relies on undocumented and unrelated features. Sometimes, this is still the most practical way to apply a workaround only to some specific devices without the risk of unpredictable side effects on tested production code.

... do feature detection instead!

I recently discovered an odd behavior on old iPhones that I could reproduce up to Safari 12, but I wasn't able to find any helpful documentation or even a bug report. I decided to disable several progressive enhancements on Safari 12 by treating it like the user had set a preference for reduced motion.

No problem in CSS.

@media (prefers-reduced-motion) {
  body .decoration__container {
    transform: translateZ(0);
  }
}

@supports (not (prefers-color-scheme: dark)) {
  body .decoration__container {
    transform: translateZ(0);
  }
}
Enter fullscreen mode Exit fullscreen mode

But what if we have some enhancements that are initialized in JavaScript?

const prefersReducedMotionQuery = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
);
const prefersReducedMotion = (prefersReducedMotionQuery.matches);

if (!prefersReducedMotion) {
  // initialize some blinking external social media widget
Enter fullscreen mode Exit fullscreen mode

Now we need the JavaScript equivalent of CSS feature queries. There is no "window.matchSupports", but there is a CSS interface object with a static supports() method.

const supportsReducedMotion = CSS.supports(
  'prefers-reduced-motion: reduce'
);
Enter fullscreen mode Exit fullscreen mode

To target my Safari bug workaround, I will detect browser support for color scheme preference, which I found in Apple's release notes for Safari 13.

const supportsColorScheme = CSS.supports(
  'prefers-color-scheme: dark'
);
Enter fullscreen mode Exit fullscreen mode

CSS feature detection in JavaScript

CSS.supports is only available from Safari 11. If we need to include older browsers, we can check if certain objects and properties are present in the document.body.style object, as described by Lea Verou in her 2009 post Check whether a CSS property is supported.

That works for features like opacity :

if ('opacity' in document.body.style)
Enter fullscreen mode Exit fullscreen mode

Navigator user agent string information

Getting even more specific, I could add a classic navigator user agent string condition. We could use regular expression matching or the more lightweight regex test() method, String.includes() or the classic backwards compatible indexOf idiom.

Deprecated browser detection ⚠️🚫

  • if (!!navigator.platform.match(/iPhone/))
  • if (/iPhone/.test(navigator.userAgent))
  • if (navigator.userAgent.includes('iPhone'))
  • if (navigator.userAgent.indexOf('iPhone') > -1)

While that's more readable than other iOS detection strategies and it perfectly fits my specific use case, as iOS has been removed from the user agent string since Safari 13, this is still a hacky strategy, and if we do it for a legitimate reason, we should document why. I might even add a "TODO" comment, hoping a fellow developer will replace my code with a more elegant fix or workaround!

const supportsReducedMotion = (
  CSS &&
  (typeof(CSS.supports)==='function') &&
  CSS.supports(
    'prefers-reduced-motion: reduce'
  )
);
const prefersReducedMotionQuery = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
);
const prefersReducedMotion = (prefersReducedMotionQuery.matches);

// detect iPhone up to Safari 12 to work around scroll stopper bug:
const isDeprecatedIphone = navigator.userAgent.indexOf('iPhone') > -1);

// TODO replace deprecated iPhone condition with more elegant bugfix
if (
  !prefersReducedMotion ||
  (!supportsReducedMotion && isDeprecatedIphone)
) {
Enter fullscreen mode Exit fullscreen mode

Conclusion – Call to Action

Are you the one who knows the scroll stopper bug and a more elegant workaround (or the proper way to allow for horizontal and vertical scrolling inside a parallax perspective section on iPhones before Safari 13)?

Do you have a better best practice for bug(fix) detection?

Tell me in the comments!

Top comments (0)