Tag: Working

Working with JavaScript Media Queries

What’s the first thing that comes to mind when you think of media queries? Maybe something in a CSS file that looks like this:

body {   background-color: plum; } 
 @media (min-width: 768px) {   body {     background-color: tomato;   } }

CSS media queries are a core ingredient in any responsive design. They’re a great way to apply different styles to different contexts, whether it’s based on viewport size, motion preference, preferred color scheme, specific interactions and, heck, even certain devices like printers, TVs and projectors, among many others.

But did you know that we have media queries for JavaScript too? It’s true! We may not see them as often in JavaScript, but there definitely are use cases for them I have found helpful over the years for creating responsive plugins, like sliders. For example, at a certain resolution, you may need to re-draw and recalculate the slider items.

Working with media queries in JavaScript is very different than working with them in CSS, even though the concepts are similar: match some conditions and apply some stuff.

Using matchMedia() 

To determine if the document matches the media query string in JavaScript, we use the matchMedia() method. Even though it’s officially part of the CSS Object Model View Module specification which is in Working Draft status, the browser support for it is great going as far back as Internet Explorer 10 with 98.6% global coverage.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
9 6 10 12 5.1

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
84 79 3 5.0-5.1

The usage is nearly identical to CSS media queries. We pass the media query string to matchMedia() and then check the .matches property.

// Define the query const mediaQuery = window.matchMedia('(min-width: 768px)')

The defined media query will return a MediaQueryList object. It is an object that stores information about the media query and the key property we need is .matches. That is a read-only Boolean property that returns true if the document matches the media query.

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 // Check if the media query is true if (mediaQuery.matches) {   // Then trigger an alert   alert('Media Query Matched!') }

That’s the basic usage for matching media conditions in JavaScript. We create a match condition (matchMedia()) that returns an object (MediaQueryList), check against it (.matches), then do stuff if the condition evaluates to true. Not totally unlike CSS!

But there’s more to it. For example, if we were change the window size below our target window size, nothing updates the way it will with CSS right out of the box. That’s because .matches is perfect for one-time instantaneous checks but is unable to continuously check for changes. That means we need to…

Listen for changes

 MediaQueryList has an addListener() (and the subsequent removeListener()) method that accepts a callback function (represented by the .onchange event) that’s invoked when the media query status changes. In other words, we can fire additional functions when the conditions change, allowing us to “respond” to the updated conditions.

// Create a condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 function handleTabletChange(e) {   // Check if the media query is true   if (e.matches) {     // Then log the following message to the console     console.log('Media Query Matched!')   } } 
 // Register event listener mediaQuery.addListener(handleTabletChange)  // Initial check handleTabletChange(mediaQuery)

The one-two punch of matchMedia() and MediaQueryList gives us the same power to not only match media conditions that CSS provides, but to actively respond to updated conditions as well.

When you register an event listener with addListener() it won’t fire initially. We need to call the event handler function manually and pass the media query as the argument.

The old way of doing things

For the sake of context — and a little nostalgia — I would like to cover the old, but still popular, way of doing “media queries” in JavaScript (and, yes, those quotes are important here). The most common approach is binding a resize event listener that checks window.innerWidth or window.innerHeight.

You’ll still see something like this in the wild:

function checkMediaQuery() {   // If the inner width of the window is greater then 768px   if (window.innerWidth > 768) {     // Then log this message to the console     console.log('Media Query Matched!')   } } 
 // Add a listener for when the window resizes window.addEventListener('resize', checkMediaQuery);

Since the resize event is called on each browser resize, this is an expensive operation! Looking at the performance impact of an empty page we can see the difference.

That’s a 157% increase in scripting!

An even simpler way to see the difference is with the help of a console log.

That’s 208 resize events versus six matched media events.

Even if we look past the performance issues, resize is restrictive in the sense that it doesn’t let us write advanced media queries for things like print and orientation. So, while it does mimic “media query” behavior by allowing us to match viewport widths, it’s incapable of matching much of anything else — and we know that true media queries are capable of so much more.

Conclusion

That’s a look at media queries in JavaScript! We explored how matchMedia() allows us to define media conditions and examined the MediaQueryList object that lets us do one-time (.matches) and persistent (addListener()) checks against those conditions so that we can respond to changes (.onchange) by invoking functions.

We also saw the “old” way of doing things by listening for resize events on the window. While it’s still widely used and a totally legit way to respond to changes to the size of the window.innerWidth, it’s unable to perform checks on advanced media conditions.

To finish the article here is a useful example that is not achievable in the old way. Using a media query I will check if the user is in the landscape mode. This approach is common when developing HTML5 games and is best viewed on a mobile device.


The post Working with JavaScript Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,

Working With MDX Custom Elements and Shortcodes

MDX is a killer feature for things like blogs, slide decks and component documentation. It allows you to write Markdown without worrying about HTML elements, their formatting and placement while sprinkling in the magic of custom React components when necessary.

Let’s harness that magic and look at how we can customize MDX by replacing Markdown elements with our own MDX components. In the process, we’ll introduce the concept of “shortcodes” when using those components.

As a heads up, the code snippets here are based on GatsbyJS and React, but MDX can be written with different frameworks as well. If you need a primer on MDX, start here first. This article extends that one with more advanced concepts.

Setting up a layout

We almost always want to render our MDX-based pages in a common layout. That way, they can be arranged with other components on our website. We can specify a default Layout component with the MDX plugin we’re using. For example. we can define a a layout with the gatsby-plugin-mdx plugin like this:

{   resolve: `gatsby-plugin-mdx`,   options: {     defaultLayouts: {       default: path.resolve('./src/templates/blog-post.js'),     },     // ...other options   } }

This would require the src/templates/blog-post.js file to contain a component that would render the children prop it receives.

import { MDXRenderer } from 'gatsby-plugin-mdx'; 
 function BlogPost({ children }) {   return (     <div>{children}</div>   ); } 
 export default BlogPost;

If we are programmatically creating pages, we’d have to use a component named MDXRenderer to achieve the same thing, as specified in the Gatsby docs.

Custom Markdown elements

While MDX is a format where that lets us write custom HTML and React components, its power is rendering Markdown with custom content. But what if we wanted to customize how these Markdown elements render on screen?

We could surely write a remark plugin for it, but MDX provides us with a better, simpler solution. By default, these are some of the elements being rendered by Markdown:

Name HTML Element MDX Syntax
Paragraph <p>
Heading 1 <h1> #
Heading 2 <h2> ##
Heading 3 <h3> ###
Heading 4 <h4> ####
Heading 5 <h5> #####
Heading 6 <h6> ######
Unordered List <ul> -
Ordered List <ol /> 1.
Image <img /> ![alt](https://image-url)
A complete list of components is available in the MDX Docs.

To replace these defaults with our custom React components, MDX ships with a Provider component named  MDXProvider. It relies on the React Context API to inject new custom components and merge them into the defaults provided by MDX.

import React from 'react'; import { MDXProvider } from "@mdx-js/react"; import Image from './image-component'; 
 function Layout({ children }) {   return (     <MDXProvider       components={{         h1: (props) => <h1 {...props} className="text-xl font-light" />         img: Image,       }}      >       {children}     </MDXProvider>   ); } 
 export default Layout;

In this example, any H1 heading (#) in the MDX file will be replaced by the custom implementation specified in the Provider component’s prop while all the other elements will continue to use the defaults. In other words, MDXProvider is able to take our custom markup for a H1 element, merge it with MDX defaults, then apply the custom markup when we write Heading 1 (#) in an MDX file.

MDX and custom components

Customizing MDX elements is great, but what if we want to introduce our own components into the mix?

--- title: Importing Components --- import Playground from './Playground'; 
 Here is a look at the `Playground` component that I have been building: 
 <Playground />

We can import a component into an MDX file and use it the same way we would any React component. And, sure, while this works well for something like a component demo in a blog post, what if we want to use Playground on all blog posts? It would be a pain to import them to all the pages. Instead. MDX presents us with the option to use shortcodes. Here’s how the MDX documentation describes shortcodes:

[A shortcode] allows you to expose components to all of your documents in your app or website. This is a useful feature for common components like YouTube embeds, Twitter cards, or anything else frequently used in your documents.

To include shortcodes in an MDX application, we have to rely on the MDXProvider component again.

import React from 'react'; import { MDXProvider } from "@mdx-js/react"; import Playground from './playground-wrapper'; 
 function Layout({ children }) {   return (     <MDXProvider       components={{         h1: (props) => <h1 {...props} className="text-xl font-light" />         Playground,       }}      >       {children}     </MDXProvider>   ); } 
 export default Layout;

Once we have included custom components into the components object, we can proceed to use them without importing in MDX files.

--- title: Demoing concepts --- 
 Here's the demo for the new concept: 
 <Playground /> 
 > Look ma! No imports

Directly manipulating child components

In React, we get top-level APIs to manipulate children with React.Children. We can use these to pass new props to child components that change their order or determine their visibility. MDX provides us a special wrapper component to access the child components passed in by MDX.

To add a wrapper, we can use the MDXProvider as we did before:

import React from "react"; import { MDXProvider } from "@mdx-js/react"; const components = {   wrapper: ({ children, ...props }) => {     const reversedChildren = React.Children.toArray(children).reverse();     return <>{reversedChildren}</>;   }, }; export default (props) => (   <MDXProvider components={components}>     <main {...props} />   </MDXProvider> );

This example reverses the children so that they appear in reverse order that we wrote it in.

We can even go wild and animate all of MDX children as they come in:

import React from "react"; import { MDXProvider } from "@mdx-js/react"; import { useTrail, animated, config } from "react-spring"; 
 const components = {   wrapper: ({ children, ...props }) => {     const childrenArray = React.Children.toArray(children);     const trail = useTrail(childrenArray.length, {       xy: [0, 0],       opacity: 1,       from: { xy: [30, 50], opacity: 0 },       config: config.gentle,       delay: 200,     });     return (       <section>         {trail.map(({ y, opacity }, index) => (           <animated.div             key={index}             style={{               opacity,               transform: xy.interpolate((x, y) => `translate3d($  {x}px,$  {y}px,0)`),             }}           >             {childrenArray[index]}           </animated.div>         ))}       </section>     );   }, }; 
 export default (props) => (   <MDXProvider components={components}>     <main {...props} />   </MDXProvider> );

Wrapping up

MDX is designed with flexibility out of the box, but extending with a plugin can make it do even more. Here’s what we were just able to do in a short amount of time, thanks to gatsby-plugin-mdx:

  1. Create default Layout components that help format the MDX output.
  2. Replace default HTML elements rendered from Markdown with custom components
  3. Use shortcodes to get rid of us of importing components in every file.
  4. Manipulate children directly to change the MDX output.

Again, this is just another drop in the bucket as far as what MDX does to help make writing content for static sites easier.

More on MDX

The post Working With MDX Custom Elements and Shortcodes appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Working with Fusebox and React

If you are searching for an alternative bundler to webpack, you might want to take a look at FuseBox. It builds on what webpack offers — code-splitting, hot module reloading, dynamic imports, etc. — but code-splitting in FuseBox requires zero configuration by default (although webpack will offer the same as of version 4.0).

Instead, FuseBox is built for simplicity (in the form of less complicated configuration) and performance (by including aggressive caching methods). Plus, it can be extended to use tons of plugins that can handle anything you need above and beyond the defaults.

Oh yeah, and if you are a fan of TypeScript, you might be interested in knowing that FuseBox makes it a first-class citizen. That means you can write an application in Typescript — with no configuration! — and it will use the Typescript transpiler to compile scripts by default. Don’t plan on using Typescript? No worries, the transpiler will handle any JavaScript. Yet another bonus!

To illustrate just how fast it is to to get up and running, let’s build the bones of an application with create-react-app. Everything we’re doing will be on GitHub if you want to follow along.

FuseBox is not the only alternative to webpack, of course. There are plenty and, in fact, Maks Akymenko has a great write-up on Parcel which is another great alternative worth looking into.

The basic setup

Start by creating a new project directory and initializing it with npm:

## Create the directory mkdir csstricks-fusebox-react && $  _ ## Initialize with npm default options npm init -y

Now we can install some dependencies. We’re going to build the app in React, so we’ll need that as well as react-dom.

npm install --save react react-dom

Next, we’ll install FuseBox and Typescript as dependencies. We’ll toss Uglify in there as well for help minifying our scripts and add support for writing styles in Sass.

npm install --save-dev fuse-box typescript uglify-js node-sass

Alright, now let’s create a src folder in the root of the project directory (which can be done manually). Add the following files (`app.js and index.js) in there, including the contents:

// App.js  import * as React from "react"; import * as logo from "./logo.svg";  const App = () => {   return (     <div className="App">       <header className="App-header">         <img src={logo} className="App-logo" alt="logo" />         <h1 className="App-title">Welcome to React</h1>       </header>       <p className="App-intro">         To get started, edit `src/App.js` and save to reload.       </p>     </div>   ) };  export default App;

You may have noticed that we’re importing an SVG file. You can download it directly from the GitHub repo.

// index.js  import * as React from "react"; import * as ReactDOM from "react-dom"; import App from "./App"  ReactDOM.render(   <App />, document.getElementById('root') );

You can see that the way we handle importing files is a little different than a typical React app. That’s because FuseBox does not polyfill imports by default.

So, instead of doing this:

import React from "react";

…we’re doing this:

import * as React from "react";
<!-- ./src/index.html -->  <!DOCTYPE html> <html lang="en">   <head>     <title>CSSTricks Fusebox React</title>     $  css   </head>    <body>     <noscript>       You need to enable JavaScript to run this app.     </noscript>     <div id="root"></div>     $  bundles   </body> </html>

Styling isn’t really the point of this post, but let’s drop some in there to dress things up a bit. We’ll have two stylesheets. The first is for the App component and saved as App.css.

/* App.css */  .App {   text-align: center; }  .App-logo {   animation: App-logo-spin infinite 20s linear;   height: 80px; }  .App-header {   background-color: #222;   height: 150px;   padding: 20px;   color: white; }  .App-intro {   font-size: large; }  @keyframes App-logo-spin {   from {     transform: rotate(0deg);   }   to {     transform:         rotate(360deg);   } }

The second stylesheet is for index.js and should be saved as index.css:

/* index.css */ body {   margin: 0;   padding: 0;   font-family: sans-serif; }

OK, we’re all done with the initial housekeeping. On to extending FuseBox with some goodies!

Plugins and configuration

We said earlier that configuring FuseBox is designed to be way less complex than the likes of webpack — and that’s true! Create a file called fuse.js in the root directory of the application.

We start with importing the plugins we’ll be making use of, all the plugins come from the FuseBox package we installed.

const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");

Next, we’ll initialize a FuseBox instance and tell it what we’re using as the home directory and where to put compiled assets:

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js" });

We’ll let FuzeBox know that we intend to use the TypeScript compiler:

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true, });

We identified plugins in the first line of the configuration file, but now we’ve got to call them. We’re using the plugins pretty much as-is, but definitely check out what the CSSPlugin, SVGPlugin and WebIndexPlugin have to offer if you want more fine-grained control over the options.

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [ // HIGHLIGHT     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] });  const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");  const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] }); fuse.dev(); fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch()  fuse.run();

FuseBox lets us configure a development server. We can define ports, SSL certificates, and even open the application in a browser on build.

We’ll simply use the default environment for this example:

fuse.dev();

It is important to define the development environment *before* the bundle instructions that come next:

fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch().

What the heck is this? When we initialized the FuseBox instance, we specified an output using dist/$ name.js. The value for $ name is provided by the bundle() method. In our case, we set the value as app. That means that when the application is bundled, the output destination will be dist/app.js.

The instructions() method defines how FuseBox should deal with the code. In our case, we’re telling it to start with index.js and to execute it after it’s loaded.

The hmr() method is used for cases where we want to update the user when a file changes, this usually involves updating the browser when a file changes. Meanwhile, watch() re-bundles the bundled code after every saved change.

With that, we’ll cap it off by launching the build process with fuse.run() at the end of the configuration file. Here’s everything we just covered put together:

const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");  const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] }); fuse.dev(); fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch()  fuse.run();

Now we can run the application from the terminal by running node fuse. This will start the build process which creates the dist folder that contains the bundled code and the template we specified in the configuration. After the build process is done, we can point the browser to http://localhost:4444/ to see our app.

Running tasks with Sparky

FuseBox includes a task runner that can be used to automate a build process. It’s called Sparky and you can think of it as sorta like Grunt and Gulp, the difference being that it is built on top of FuseBox with built-in access to FuseBox plugins and the FuseBox API.

We don’t have to use it, but task runners make development a lot easier by automating things we’d otherwise have to do manually and it makes sense to use what’s specifically designed for FuseBox.

To use it, we’ll update the configuration we have in fuse.js, starting with some imports that go at the top of the file:

const { src, task, context } = require("fuse-box/sparky");

Next, we’ll define a context, which will look similar to what we already have. We’re basically wrapping what we did in a context and setConfig(), then initializing FuseBox in the return:

context({   setConfig() {     return FuseBox.init({       homeDir: "src",       output: "dist/$  name.js",       useTypescriptCompiler: true,       plugins: [         CSSPlugin(),         SVGPlugin(),         WebIndexPlugin({           template: "src/index.html"         })       ]     });   },   createBundle(fuse) {     return fuse       .bundle("app")       .instructions(`> index.js`)       .hmr();   } });

It’s possible to pass a class, function or plain object to a context. In the above scenario, we’re passing functions, specifically setConfig() and createBundle(). setConfig() initializes FuseBox and sets up the plugins. createBundle() does what you might expect by the name, which is bundling the code. Again, the difference from what we did before is that we’re embedding both functionalities into different functions which are contained in the context object.

We want our task runner to run tasks, right? Here are a few examples we can define:

task("clean", () => src("dist").clean("dist").exec()); task("default", ["clean"], async (context) => {   const fuse = context.setConfig();   fuse.dev();   context.createBundle(fuse);   await fuse.run() });

The first task will be responsible for cleaning the dist directory. The first argument is the name of the task, while the second is the function that gets called when the task runs.
To call the first task, we can do node fuse clean from the terminal.

When a task is named default (which is the first argument as in the second task), that task will be the one that gets called by default when running node fuse — in this case, that’s the second task in our configuration. Other tasks need to be will need to be called explicitly in terminal, like node fuse <task_name>.

So, our second task is the default and three arguments are passed into it. The first is the name of the task (`default`), the second (["clean"]) is an array of dependencies that should be called before the task itself is executed, and the third is a function (fuse.dev()) that gets the initialized FuseBox instance and begins the bundling and build process.

Now we can run things with node fuse in the terminal. You have the option to add these to your package.json file if that’s more comfortable and familiar to you. The script section would look like this:

"scripts": {   "start": "node fuse",   "clean": "node fuse clean" },

That’s a wrap!

All in all, FuseBox is an interesting alternative to webpack for all your application bundling needs. As we saw, it offers the same sort of power that we all tend to like about webpack, but with a way less complicated configuration process that makes it much easier to get up and running, thanks to built-in Typescript support, performance considerations, and a task runner that’s designed to take advantage of the FuseBox API.

What we look at was a pretty simple example. In practice, you’re likely going to be working with more complex applications, but the concepts and principles are the same. It’s nice to know that FuseBox is capable of handling more than what’s baked into it, but that the initial setup is still super streamlined.

If you’re looking for more information about FuseBox, it’s site and documentation are obviously great starting point. the following links are also super helpful to get more perspective on how others are setting it up and using it on projects.

The post Working with Fusebox and React appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Some Things You Oughta Know When Working with Viewport Units

David Chanin has a quickie article summarizing a problem with setting an element’s height to 100vh in mobile browsers and then also positioning something on the bottom of that.

Summarized in this graphic:

The trouble is that Chrome isn’t taking the address bar (browser chrome) into account when it’s revealed which cuts off the element short, forcing the bottom of the element past the bottom of the actual viewport.

<div class="full-page-element">   <button>Button</button> </div>
.full-page-element {   height: 100vh;   position: relative; }  .full-page-element button {   position: absolute;   bottom: 10px;   left: 10px; }

You’d expect that button in the graphic to be visible (assuming this element is at the top of the page and you haven’t scrolled) since it’s along the bottom edge of a 100vh element. But it’s actually hidden behind the browser chrome in mobile browsers, including iOS Safari or Android Chrome.

I use this a lot:

body {   height: 100vh; /* Nice to not have to think about the HTML element parent   margin: 0; }

It’s just a quick way to make sure the body is full height without involving any other elements. I’m usually doing that on pretty low-stakes demo type stuff, but I’m told even that is a little problematic because you might experience jumpiness as browser chrome appears and disappears, or things may not be quite as centered as you’d expect.

You’d think you could do body { height: 100% }, but there’s a gotcha there as well. The body is a child of <html> which is only as tall as the content it contains, just like any other element.

If you need the body to be full height, you have to deal with the HTML element as well:

html, body {    height: 100%; }

…which isn’t that big of a deal and has reliable cross-browser consistency.

It’s the positioning things along the bottom edge that is tricky. It is problematic because of position: absolute; within the “full height” (often taller-than-visible) container.

If you are trying to place something like a fixed navigation bar at the bottom of the screen, you’d probably do that with position: fixed; bottom: 0; and that seems to work fine. The browser chrome pushes it up and down as you’d expect (video).

Horizontal viewport units are just as weird and problematic due to another bit of browser UI: scrollbars. If a browser window has a visible scrollbar, that scrollbar will usually eat into the visual space although a value of 100vw is calculated as if the scrollbar wasn’t there. In other words, 100vw will cause horizontal scrolling in a way you probably wouldn’t expect.

See the Pen
CSS Vars for viewport width minus scrollbar
by Shaw (@shshaw)
on CodePen.

Our last CSS wishlist roundup mentioned better viewport unit handling a number of times, so developers are clearly pretty interested in having better solutions for these things. I’m not sure what that would mean for web compatibility though, because changing the way they work might break all the workarounds we’ve used that are surely still out in the wild.

The post Some Things You Oughta Know When Working with Viewport Units appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

A Quick Look at the First Public Working Draft for Color Adjust Module 1

We’ve been talking a lot about Dark Mode around here ever since Apple released it as a system setting in MacOS 10.14 and subsequently as part of Safari. It’s interesting because of both what it opens up as as far as design opportunities as well as tailoring user experience based on actual user preferences.

This week, we got an Editor’s Draft for the Color Adjust Module Level 1 specification and the First Public Working Draft of it. All of this is a work-in-progress, but the progression of it has been interesting to track. The spec introduces three new CSS properties that help inform how much control the user agent should have when determining the visual appearance of a rendered page based on user preferences.

color-scheme is the first property defined in the spec and perhaps the centerpiece of it. It accepts light and dark values which — as you may have guessed — correspond to Light Mode and Dark Mode preferences for operating systems that support them. And, for what it’s worth, we could be dealing with labels other than “Light” and “Dark” (e.g. “Day” and “Night”) but what we’re dealing with boils down to a light color scheme versus a dark one.

Source: developer.apple.com

This single property carries some important implications. For one, the idea is that it allows us to set styles based on a user’s system preferences which gives us fine-grained control over that experience.

Another possible implication is that declaring the property at all enables the user agent to take some responsibility for determining an element’s colors, where declaring light or dark informs the user agent that an element is “aware” of color schemes and should be styled according to a preference setting matching the value. On the other hand, we can give the browser full control to determine what color scheme to use based on the user’s system preferences by using the auto value. That tells the browser that an element is “unaware” of color schemes and that the browser can determine how to proceed using the user preferences and a systems’s default styling as a guide.

It’s worth noting at this point that we may also have a prefers-color-scheme media feature (currently in the Editor’s Draft for the Media Queries Level 5 specification) that also serves to let us detect a user’s preference and help gives us greater control of the user experience based on system preferences. Robin has a nice overview of it. The Color Adjust Module Level 1 Working Draft also makes mention of possibly using a color scheme value in a <meta> element to indicate color scheme support.

There’s more to the property, of course, including an only keyword, chaining values to indicate an order of preference, and even an open-ended custom ident keyword. So definitely dig in there because there’s a lot to take in.

Pretty interesting, right? Hopefully you’re starting to see how this draft could open up new possibilities and even impacts how we make design decisions. And that’s only the start because there are two more properties!

  • forced-color-adjust: This is used when we want to support color schemes but override the user agent’s default stylesheet with our own CSS. This includes a note about possibly merging this into color-adjust.
  • color-adjust: Unlike forcing CSS overrides onto the user agent, this property provides a hint to browsers that they can change color values based on the both the user’s preferences and other factors, such as screen quality, bandwidth, or whatever is “deem[ed] necessary and prudent for the output device.” Eric Bailey wrote up the possibilities this property could open up as far as use cases, enhanced accessibility, and general implementations.

The current draft is sure to expand but, hey, this is where we get to be aware of the awesome work that W3C authors are doing, gain context for the challenges they face, and even contribute to the work. (See Rachel Andrew’s advice on making contributions.)

The post A Quick Look at the First Public Working Draft for Color Adjust Module 1 appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]