Tag: Between

Nailing the Perfect Contrast Between Light Text and a Background Image

Have you ever come across a site where light text is sitting on a light background image? If you have, you’ll know how difficult that is to read. A popular way to avoid that is to use a transparent overlay. But this leads to an important question: Just how transparent should that overlay be? It’s not like we’re always dealing with the same font sizes, weights, and colors, and, of course, different images will result in different contrasts.

Trying to stamp out poor text contrast on background images is a lot like playing Whac-a-Mole. Instead of guessing, we can solve this problem with HTML <canvas> and a little bit of math.

Like this:

We could say “Problem solved!” and simply end this article here. But where’s the fun in that? What I want to show you is how this tool works so you have a new way to handle this all-too-common problem.

Here’s the plan

First, let’s get specific about our goals. We’ve said we want readable text on top of a background image, but what does “readable” even mean? For our purposes, we’ll use the WCAG definition of AA-level readability, which says text and background colors need enough contrast between them such that that one color is 4.5 times lighter than the other.

Let’s pick a text color, a background image, and an overlay color as a starting point. Given those inputs, we want to find the overlay opacity level that makes the text readable without hiding the image so much that it, too, is difficult to see. To complicate things a bit, we’ll use an image with both dark and light space and make sure the overlay takes that into account.

Our final result will be a value we can apply to the CSS opacity property of the overlay that gives us the right amount of transparency that makes the text 4.5 times lighter than the background.

Optimal overlay opacity: 0.521

To find the optimal overlay opacity we’ll go through four steps:

  1. We’ll put the image in an HTML <canvas>, which will let us read the colors of each pixel in the image.
  2. We’ll find the pixel in the image that has the least contrast with the text.
  3. Next, we’ll prepare a color-mixing formula we can use to test different opacity levels on top of that pixel’s color.
  4. Finally, we’ll adjust the opacity of our overlay until the text contrast hits the readability goal. And these won’t just be random guesses — we’ll use binary search techniques to make this process quick.

Let’s get started!

Step 1: Read image colors from the canvas

Canvas lets us “read” the colors contained in an image. To do that, we need to “draw” the image onto a <canvas> element and then use the canvas context (ctx) getImageData() method to produce a list of the image’s colors.

function getImagePixelColorsUsingCanvas(image, canvas) {   // The canvas's context (often abbreviated as ctx) is an object   // that contains a bunch of functions to control your canvas   const ctx = canvas.getContext('2d'); 
   // The width can be anything, so I picked 500 because it's large   // enough to catch details but small enough to keep the   // calculations quick.   canvas.width = 500; 
   // Make sure the canvas matches proportions of our image   canvas.height = (image.height / image.width) * canvas.width; 
   // Grab the image and canvas measurements so we can use them in the next step   const sourceImageCoordinates = [0, 0, image.width, image.height];   const destinationCanvasCoordinates = [0, 0, canvas.width, canvas.height]; 
   // Canvas's drawImage() works by mapping our image's measurements onto   // the canvas where we want to draw it   ctx.drawImage(     image,     ...sourceImageCoordinates,     ...destinationCanvasCoordinates   ); 
   // Remember that getImageData only works for same-origin or    // cross-origin-enabled images.   // https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image   const imagePixelColors = ctx.getImageData(...destinationCanvasCoordinates);   return imagePixelColors; }

The getImageData() method gives us a list of numbers representing the colors in each pixel. Each pixel is represented by four numbers: red, green, blue, and opacity (also called “alpha”). Knowing this, we can loop through the list of pixels and find whatever info we need. This will be useful in the next step.

Image of a blue and purple rose on a light pink background. A section of the rose is magnified to reveal the RGBA values of a specific pixel.

Step 2: Find the pixel with the least contrast

Before we do this, we need to know how to calculate contrast. We’ll write a function called getContrast() that takes in two colors and spits out a number representing the level of contrast between the two. The higher the number, the better the contrast for legibility.

When I started researching colors for this project, I was expecting to find a simple formula. It turned out there were multiple steps.

To calculate the contrast between two colors, we need to know their luminance levels, which is essentially the brightness (Stacie Arellano does a deep dive on luminance that’s worth checking out.)

Thanks to the W3C, we know the formula for calculating contrast using luminance:

const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05);

Getting the luminance of a color means we have to convert the color from the regular 8-bit RGB value used on the web (where each color is 0-255) to what’s called linear RGB. The reason we need to do this is that brightness doesn’t increase evenly as colors change. We need to convert our colors into a format where the brightness does vary evenly with color changes. That allows us to properly calculate luminance. Again, the W3C is a help here:

const luminance = (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b));

But wait, there’s more! In order to convert 8-bit RGB (0 to 255) to linear RGB, we need to go through what’s called standard RGB (also called sRGB), which is on a scale from 0 to 1.

So the process goes: 

8-bit RGB → standard RGB  → linear RGB → luminance

And once we have the luminance of both colors we want to compare, we can plug in the luminance values to get the contrast between their respective colors.

// getContrast is the only function we need to interact with directly. // The rest of the functions are intermediate helper steps. function getContrast(color1, color2) {   const color1_luminance = getLuminance(color1);   const color2_luminance = getLuminance(color2);   const lighterColorLuminance = Math.max(color1_luminance, color2_luminance);   const darkerColorLuminance = Math.min(color1_luminance, color2_luminance);   const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05);   return contrast; } 
 function getLuminance({r,g,b}) {   return (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b)); } function getLinearRGB(primaryColor_8bit) {   // First convert from 8-bit rbg (0-255) to standard RGB (0-1)   const primaryColor_sRGB = convert_8bit_RGB_to_standard_RGB(primaryColor_8bit); 
   // Then convert from sRGB to linear RGB so we can use it to calculate luminance   const primaryColor_RGB_linear = convert_standard_RGB_to_linear_RGB(primaryColor_sRGB);   return primaryColor_RGB_linear; } function convert_8bit_RGB_to_standard_RGB(primaryColor_8bit) {   return primaryColor_8bit / 255; } function convert_standard_RGB_to_linear_RGB(primaryColor_sRGB) {   const primaryColor_linear = primaryColor_sRGB < 0.03928 ?     primaryColor_sRGB/12.92 :     Math.pow((primaryColor_sRGB + 0.055) / 1.055, 2.4);   return primaryColor_linear; }

Now that we can calculate contrast, we’ll need to look at our image from the previous step and loop through each pixel, comparing the contrast between that pixel’s color and the foreground text color. As we loop through the image’s pixels, we’ll keep track of the worst (lowest) contrast so far, and when we reach the end of the loop, we’ll know the worst-contrast color in the image.

function getWorstContrastColorInImage(textColor, imagePixelColors) {   let worstContrastColorInImage;   let worstContrast = Infinity; // This guarantees we won't start too low   for (let i = 0; i < imagePixelColors.data.length; i += 4) {     let pixelColor = {       r: imagePixelColors.data[i],       g: imagePixelColors.data[i + 1],       b: imagePixelColors.data[i + 2],     };     let contrast = getContrast(textColor, pixelColor);     if(contrast < worstContrast) {       worstContrast = contrast;       worstContrastColorInImage = pixelColor;     }   }   return worstContrastColorInImage; }

Step 3: Prepare a color-mixing formula to test overlay opacity levels

Now that we know the worst-contrast color in our image, the next step is to establish how transparent the overlay should be and see how that changes the contrast with the text.

When I first implemented this, I used a separate canvas to mix colors and read the results. However, thanks to Ana Tudor’s article about transparency, I now know there’s a convenient formula to calculate the resulting color from mixing a base color with a transparent overlay.

For each color channel (red, green, and blue), we’d apply this formula to get the mixed color:

mixedColor = baseColor + (overlayColor - baseColor) * overlayOpacity

So, in code, that would look like this:

function mixColors(baseColor, overlayColor, overlayOpacity) {   const mixedColor = {     r: baseColor.r + (overlayColor.r - baseColor.r) * overlayOpacity,     g: baseColor.g + (overlayColor.g - baseColor.g) * overlayOpacity,     b: baseColor.b + (overlayColor.b - baseColor.b) * overlayOpacity,   }   return mixedColor; }

Now that we’re able to mix colors, we can test the contrast when the overlay opacity value is applied.

function getTextContrastWithImagePlusOverlay({textColor, overlayColor, imagePixelColor, overlayOpacity}) {   const colorOfImagePixelPlusOverlay = mixColors(imagePixelColor, overlayColor, overlayOpacity);   const contrast = getContrast(this.state.textColor, colorOfImagePixelPlusOverlay);   return contrast; }

With that, we have all the tools we need to find the optimal overlay opacity!

Step 4: Find the overlay opacity that hits our contrast goal

We can test an overlay’s opacity and see how that affects the contrast between the text and image. We’re going to try a bunch of different opacity levels until we find the contrast that hits our mark where the text is 4.5 times lighter than the background. That may sound crazy, but don’t worry; we’re not going to guess randomly. We’ll use a binary search, which is a process that lets us quickly narrow down the possible set of answers until we get a precise result.

Here’s how a binary search works:

  • Guess in the middle.
  • If the guess is too high, we eliminate the top half of the answers. Too low? We eliminate the bottom half instead.
  • Guess in the middle of that new range.
  • Repeat this process until we get a value.

I just so happen to have a tool to show how this works:

In this case, we’re trying to guess an opacity value that’s between 0 and 1. So, we’ll guess in the middle, test whether the resulting contrast is too high or too low, eliminate half the options, and guess again. If we limit the binary search to eight guesses, we’ll get a precise answer in a snap.

Before we start searching, we’ll need a way to check if an overlay is even necessary in the first place. There’s no point optimizing an overlay we don’t even need!

function isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast) {   const contrastWithoutOverlay = getContrast(textColor, worstContrastColorInImage);   return contrastWithoutOverlay < desiredContrast; }

Now we can use our binary search to look for the optimal overlay opacity:

function findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, desiredContrast) {   // If the contrast is already fine, we don't need the overlay,   // so we can skip the rest.   const isOverlayNecessary = isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast);   if (!isOverlayNecessary) {     return 0;   } 
   const opacityGuessRange = {     lowerBound: 0,     midpoint: 0.5,     upperBound: 1,   };   let numberOfGuesses = 0;   const maxGuesses = 8; 
   // If there's no solution, the opacity guesses will approach 1,   // so we can hold onto this as an upper limit to check for the no-solution case.   const opacityLimit = 0.99; 
   // This loop repeatedly narrows down our guesses until we get a result   while (numberOfGuesses < maxGuesses) {     numberOfGuesses++; 
     const currentGuess = opacityGuessRange.midpoint;     const contrastOfGuess = getTextContrastWithImagePlusOverlay({       textColor,       overlayColor,       imagePixelColor: worstContrastColorInImage,       overlayOpacity: currentGuess,     }); 
     const isGuessTooLow = contrastOfGuess < desiredContrast;     const isGuessTooHigh = contrastOfGuess > desiredContrast;     if (isGuessTooLow) {       opacityGuessRange.lowerBound = currentGuess;     }     else if (isGuessTooHigh) {       opacityGuessRange.upperBound = currentGuess;     } 
     const newMidpoint = ((opacityGuessRange.upperBound - opacityGuessRange.lowerBound) / 2) + opacityGuessRange.lowerBound;     opacityGuessRange.midpoint = newMidpoint;   } 
   const optimalOpacity = opacityGuessRange.midpoint;   const hasNoSolution = optimalOpacity > opacityLimit; 
   if (hasNoSolution) {     console.log('No solution'); // Handle the no-solution case however you'd like     return opacityLimit;   }   return optimalOpacity; }

With our experiment complete, we now know exactly how transparent our overlay needs to be to keep our text readable without hiding the background image too much.

We did it!

Improvements and limitations

The methods we’ve covered only work if the text color and the overlay color have enough contrast to begin with. For example, if you were to choose a text color that’s the same as your overlay, there won’t be an optimal solution unless the image doesn’t need an overlay at all.

In addition, even if the contrast is mathematically acceptable, that doesn’t always guarantee it’ll look great. This is especially true for dark text with a light overlay and a busy background image. Various parts of the image may distract from the text, making it difficult to read even when the contrast is numerically fine. That’s why the popular recommendation is to use light text on a dark background.

We also haven’t taken where the pixels are located into account or how many there are of each color. One drawback of that is that a pixel in the corner could possibly exert too much influence on the result. The benefit, however, is that we don’t have to worry about how the image’s colors are distributed or where the text is because, as long as we’ve handled where the least amount of contrast is, we’re safe everywhere else.

I learned a few things along the way

There are some things I walked away with after this experiment, and I’d like to share them with you:

  • Getting specific about a goal really helps! We started with a vague goal of wanting readable text on an image, and we ended up with a specific contrast level we could strive for.
  • It’s so important to be clear about the terms. For example, standard RGB wasn’t what I expected. I learned that what I thought of as “regular” RGB (0 to 255) is formally called 8-bit RGB. Also, I thought the “L” in the equations I researched meant “lightness,” but it actually means “luminance,” which is not to be confused with “luminosity.” Clearing up terms helps how we code as well as how we discuss the end result.
  • Complex doesn’t mean unsolvable. Problems that sound hard can be broken into smaller, more manageable pieces.
  • When you walk the path, you spot the shortcuts. For the common case of white text on a black transparent overlay, you’ll never need an opacity over 0.54 to achieve WCAG AA-level readability.

In summary…

You now have a way to make your text readable on a background image without sacrificing too much of the image. If you’ve gotten this far, I hope I’ve been able to give you a general idea of how it all works.

I originally started this project because I saw (and made) too many website banners where the text was tough to read against a background image or the background image was overly obscured by the overlay. I wanted to do something about it, and I wanted to give others a way to do the same. I wrote this article in hopes that you’d come away with a better understanding of readability on the web. I hope you’ve learned some neat canvas tricks too.

If you’ve done something interesting with readability or canvas, I’d love to hear about it in the comments!


The post Nailing the Perfect Contrast Between Light Text and a Background Image appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , , , ,

Let’s Make a Multi-Thumb Slider That Calculates The Width Between Thumbs

HTML has an <input type="range">, which is, you could argue, the simplest type of proportion slider. Wherever the thumb of that slider ends up could represent a proportion of whatever is before and whatever is after it (using the value and max attributes). Getting fancier, it’s possible to build a multi-thumb slider. But we’ve got another thing in mind today… a proportion slider with multiple thumbs, and sections that cannot overlap.

Here’s what we’ll be building today:

This is a slider, but not just any slider. It’s a proportion slider. Its various sections must all add up to 100% and the user can adjust the different sections in it.

Why would you need such a thing ? 

Maybe you want to build a budget app where a slider like this would consist of your various planned expenses:

I needed something like this to make an anime movie suggestions platform. While researching UX patterns, I noticed that other anime movie suggestion websites have this sort of thing where you select tags to get movie recommendations. That’s cool and all, but how much cooler would it be to add weight to those tags so that recommendations for a tag with a larger weight are prioritized over other tags with lighter weights. I looked around for other ideas, but didn’t find much.

So I built this! And believe it or not, making it is not that complicated. Let me take you through the steps.

The static slider

We’ll be building this in React and TypeScript. That’s not required though. The concepts should port to vanilla JavaScript or any other framework.

Let’s make this slider using dynamic data for the start. We’ll keep the different sections in a variable array with the name and color of each section. 

const _tags = [   {     name: "Action",     color: "red"   },   {     name: "Romance",     color: "purple"   },   {     name: "Comedy",     color: "orange"   },   {     name: "Horror",     color: "black"   } ];

The width of each tag section is controlled by an array of percentages that add up to 100%. This is done by using the Array.Fill() method to initialize the state of each width:

const [widths, setWidths] = useState<number[]>(new Array(_tags.length).fill(100 / _tags.length))

Next, we’ll create a component to render a single tag section:

interface TagSectionProps {   name: string   color: string   width: number } 
 const TagSection = ({ name, color, width }: TagSectionProps) => {   return <div     className='tag'     style={{ ...styles.tag, background: color, width: width + '%' }} >     <span style={styles.tagText}>{name}</span>    <div      style={styles.sliderButton}      className='slider-button'>              <img src={"https://assets.codepen.io/576444/slider-arrows.svg"} height={'30%'} />     </div>   </div > }

Then we’ll render all of the sections by mapping through the _tags array and return the TagSection component we created above:

const TagSlider = () => {   const [widths, setWidths] = useState<number[]>((new Array(_tags.length).fill(100 / _tags.length)))   return <div     style={{       width: '100%',       display: 'flex'     }}>     {     _tags.map((tag, index) => <TagSection       width={widths[index]}       key={index}       name={tag.name}       color={tag.color}     />)     }   </div> }

To make rounded borders and hide the last slider button, let’s the :first-of-type and :last-of-type pseudo-selectors in CSS:

.tag:first-of-type {   border-radius: 50px 0px 0px 50px; } .tag:last-of-type {   border-radius: 0px 50px 50px 0px; } .tag:last-of-type>.slider-button {   display:none !important; }

Here’s where we are so far. Note the slider handles don’t do anything yet! We’ll get to that next.

Adjustable slider sections

We want the slider sections to adjust move when the slider buttons are dragged with either a mouse cursor or touch, We’ll do that by making sure the section width changes correspond to how much the slider buttons have been dragged. That requires us to answer a few questions:

  1. How do we get the cursor position when the slider button is clicked?
  2. How do we get the cursor position while the slider button is being dragged?
  3. How can we make the width of the tag section correspond to how much the tag section button has been dragged ?

One by one…

How do we get the cursor position when the slider button is clicked?

Let’s add an onSliderSelect event handler to the TagSectionProps interface:

interface TagSectionProps {   name: string;   color: string;   width: number;   onSliderSelect: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; }

The onSliderSelect event handler is attached to the onPointerDown event of the TagSection component:

const TagSection = ({   name,   color,   width,   onSliderSelect // Highlight }: TagSectionProps) => {   return (     <div       className="tag"       style={{         ...styles.tag,         background: color,         width: width + "%"         }}     >       <span style={styles.tagText}>{name}</span>       <span style={{ ...styles.tagText, fontSize: 12 }}>{width + "%"}</span>          <div         style={styles.sliderButton}         onPointerDown={onSliderSelect}         className="slider-button"       >       <img src={"https://animesonar.com/slider-arrows.svg"} height={"30%"} />       </div>     </div>   ); };

We use onPointerDown instead of onMouseDown to catch both mouse and touch screen events. Here’s more information about that. 

We’re using e.pageX in the onSliderSelect prop function to get the cursor’s position when the slider’s button has been clicked:

<TagSection   width={widths[index]}   key={index}   name={tag.name}   onSliderSelect={(e) => {     const startDragX = e.pageX;   }} />

One down!

How do we get the cursor position while the slider button is being dragged?

Now we need to add an event listener to listen for the drag events, pointermove and touchmove. We’ll use these events to cover mouse cursor and touch movements. The section widths need to stop updating once the user’s finger raises from the screen (thus ending the drag): 

window.addEventListener("pointermove", resize); window.addEventListener("touchmove", resize); 
 const removeEventListener = () => {   window.removeEventListener("pointermove", resize);   window.removeEventListener("touchmove", resize); } 
 const handleEventUp = (e: Event) => {   e.preventDefault();   document.body.style.cursor = "initial";   removeEventListener(); } 
 window.addEventListener("touchend", handleEventUp); window.addEventListener("pointerup", handleEventUp);

The resize function gives the X coordinate of the cursor while the slider button is being dragged:

const resize = (e: MouseEvent & TouchEvent) => {   e.preventDefault();   const endDragX = e.touches ? e.touches[0].pageX : e.pageX }

When the resize function is triggered by a touch event, e.touches is an array value — otherwise, it’s null, in which case endDragX takes the value of e.pageX

How can we make the width of the tag section correspond to how much the tag section button has been dragged?

To change the width percentages of the various tag sections, let’s get the distance the cursor moves in relation to the entire width of the slider as a percentage. From there, we’ll assign that value to the tag section.

First, we need to get the refof TagSlider using React’s useRef hook:

const TagSlider = () => { const TagSliderRef = useRef<HTMLDivElement>(null);   // TagSlider   return (     <div       ref={TagSliderRef} // ...

Now let’s figure out the width of the slider by using its reference to get the offsetWidth property, which returns the layout width of an element as an integer:

onSliderSelect={(e) => {   e.preventDefault();   document.body.style.cursor = 'ew-resize'; 
   const startDragX = e.pageX;   const sliderWidth = TagSliderRef.current.offsetWidth; }};

Then we calculate the percentage distance the cursor moved relative to the entire slider:

const getPercentage = (containerWidth: number, distanceMoved: number) => {   return (distanceMoved / containerWidth) * 100; }; 
 const resize = (e: MouseEvent & TouchEvent) => {   e.preventDefault();   const endDragX = e.touches ? e.touches[0].pageX : e.pageX;   const distanceMoved = endDragX - startDragX;   const percentageMoved = getPercentage(sliderWidth, distanceMoved); }

Finally, we can assign the newly calculated section width to its index on the _widths state variable:

const percentageMoved = getPercentage(sliderWidth, distanceMoved); const _widths = widths.slice(); const prevPercentage = _widths[index]; const newPercentage = prevPercentage + percentageMoved 
 _widths[index] = newPercentage; setWidths(_widths);

But this isn’t the end! The other sections aren’t changing widths and the percentages can wind up being negative or adding up to more than 100%. Not to mention, the sum of all section widths isn’t always equal to 100% because we haven’t applied a restriction that prevents the overall percentage from changing.

Fixing up the other sections

Let’s make sure the width of one section changes when the section next to it changes.

const nextSectionNewPercentage = percentageMoved < 0    ? _widths[nextSectionIndex] + Math.abs(percentageMoved)   : _widths[nextSectionIndex] - Math.abs(percentageMoved)

This has the effect of reducing the width of the neighboring section if the section increases and vice-versa. We can even shorten it:

const nextSectionNewPercentage = _widths[nextSectionIndex] - percentageMoved

Adjusting a section percentage should only affect its neighbor to the right. This means that the maximum value of a given section maximum percentage should be its width plus the width of its neighbor width when it’s allowed to take up the entire neighbor’s space.

We can make that happen by calculating a maximum percentage value:

const maxPercent = widths[index] + widths[index+1]

To prevent negative width values, let’s restrict the widths to values greater than zero but less than the max percentage: 

const limitNumberWithinRange = (value: number, min: number, max: number):number => {   return Math.min(Math.max(value,min),max) }

The limitNumberWithinRange function both prevents negative values and instances where the sum of sections results ina value higher than the maximum percentage. (Hat tip to this StavkOverflow thread.)

We can use this function for the width of the current section and its neighbor:

const currentSectionWidth = limitNumberWithinRange(newPercentage, 0, maxPercent) _widths[index] = currentSectionWidth 
 const nextSectionWidth = limitNumberWithinRange(nextSectionNewPercentage, 0, maxPercent); _widths[nextSectionIndex] = nextSectionWidth;

Extra touches

Right now, the slider calculates the width of each section as a percentage of the entire container to some crazy decimal. That’s super precise, but not exactly useful for this sort of UI. If we want to work with whole numbers instead of decimals, we can do something like this:

const nearestN = (N: number, number: number) => Math.ceil(number / N) * N; const percentageMoved = nearestN(1, getPercentage(sliderWidth, distanceMoved))

This function approximates the second parameter’s value to the nearest N (specified by the first parameter). Setting  N to 1 like the this example has the effect of making the percentage change in whole numbers instead of tiny incremental decimals.

Another nice to touch is consider additional handling for sections with a zero percentage value. Those should probably be removed from the slider altogether since they no longer take up any proportion of the overall width. We can stop listening for events on those sections:

if (tags.length > 2) {   if (_widths[index] === 0) {      _widths[nextSectionIndex] = maxPercent;     _widths.splice(index, 1);     setTags(tags.filter((t, i) => i !== index));     removeEventListener();   }   if (_widths[nextSectionIndex] === 0) {     _widths[index] = maxPercent;     _widths.splice(nextSectionIndex, 1);     setTags(tags.filter((t, i) => i !== nextSectionIndex));     removeEventListener();   } }

Voila!

Here’s the final slider:

The post Let’s Make a Multi-Thumb Slider That Calculates The Width Between Thumbs appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Striking a Balance Between Native and Custom Select Elements

Here’s the plan! We’re going to build a styled select element. Not just the outside, but the inside too. Total styling control. Plus we’re going to make it accessible. We’re not going to try to replicate everything that the browser does by default with a native <select> element. We’re going to literally use a <select> element when any assistive tech is used. But when a mouse is being used, we’ll show the styled version and make it function as a select element.

That’s what I mean by “hybrid” selects: they are both a native <select> and a styled alternate select in one design pattern.

Custom selects (left) are often used in place of native selects (right) for aesthetics and design consistency.

Select, dropdown, navigation, menu… the name matters

While doing the research for this article, I thought about many names that get tossed around when talking about selects, the most common of which are “dropdown” and “menu.” There are two types of naming mistakes we could make: giving the same name to different things, or giving different names to the same thing. A select can suffer from both mistakes.

Before we move ahead, let me try to add clarity around using “dropdown” as a term. Here’s how I define the meaning of dropdown:

Dropdown: An interactive component that consists of a button that shows and hides a list of items, typically on mouse hover, click or tap. The list is not visible by default until the interaction starts. The list usually displays a block of content (i.e. options) on top of other content.

A lot of interfaces can look like a dropdown. But simply calling an element a “dropdown” is like using “fish” to describe an animal. What type of fish it is? A clownfish is not the same as a shark. The same goes for dropdowns.

Like there are different types of fish in the sea, there are different types of components that we might be talking about when we toss the word “dropdown” around:

  • Menu: A list of commands or actions that the user can perform within the page content.
  • Navigation: A list of links used for navigating through a website.
  • Select: A form control (<select>) that displays a list of options for the user to select within a form.

Deciding what type of dropdown we’re talking about can be a foggy task. Here are some examples from around the web that match how I would classify those three different types. This is based on my research and sometimes, when I can’t find a proper answer, intuition based on my experience.

Dropdown-land: Five scenarios where different dropdowns are used across the internet. Read the table below for a detailed description.
Diagram Label Scenario Dropdown Type
1 The dropdown expects a selected option to be submitted within a form context (e.g. Select Age) Select
2 The dropdown does not need an active option (e.g. A list of actions: copy, paste and cut) Menu
3 The selected option influences the content. (e.g. sorting list) Menu or Select (more about it later)
4 The dropdown contains links to other pages. (e.g. A “meganav” with websites links) Disclosure Navigation
5 The dropdown has content that is not a list. (e.g. a date picker) Something else that should not be called dropdown

Not everyone perceives and interacts with the internet in the same way. Naming user interfaces and defining design patterns is a fundamental process, though one with a lot of room for personal interpretation. All of that variation is what drives the population of dropdown-land. 

There is a dropdown type that is clearly a menu. Its usage is a hot topic in conversations about accessibility. I won’t talk much about it here, but let me just reinforce that the <menu> element is deprecated and no longer recommended. And here’s a detailed explanation about inclusive menus and menus buttons, including why ARIA menu role should not be used for site navigation.

We haven’t even touched on other elements that fall into a rather gray area that makes classifying dropdowns even murkier because of a lack of practical uses cases from the WCAG community.

Uff… that was a lot. Let’s forget about this dropdown-land mess and focus exclusively on the dropdown type that is clearly a <select> element.

Let’s talk about <select>

Styling form controls is an interesting journey. As MDN puts it, there’s the good, the bad, and the ugly. Good is stuff like <form> which is just a block-level element to style. Bad is stuff like checkboxes, which can be done but is somewhat cumbersome. <select> is definitely in ugly terrain.

A lot of articles have been written about it and, even in 2020, it’s still a challenge to create custom selects and some users still prefer the simple native ones

Among developers, the <select> is the most frustrating form control by far, mainly because of its lack of styling support. The UX struggle behind it is so big that we look for other alternatives. Well, I guess the first rule of <select> is similar to ARIA: avoid using it if you can.

I could finish the article right here with “Don’t use <select>, period.” But let’s face reality: a select is still our best solution in a number of circumstances. That might include scenarios where we’re working with a list that contains a lot of options, layouts that are tight on space, or simply a lack of time or budget to design and implement a great custom interactive component from scratch.

Custom <select> requirements

When we make the decision to create a custom select — even if it’s just a “simple” one — these are the requirements we generally have to work with:

  • There is a button that contains the current selected option.
  • Clicking the box toggles the visibility of the options list (also called listbox).
  • Clicking an option in the listbox updates the selected value. The button text changes and the listbox is closed.
  • Clicking outside the component closes the listbox.
  • The trigger contains a small triangle icon pointing downward to indicate there are options.

Something like this:

Some of you may be thinking this works and is good to go. But wait… does it work for everyone?  Not everyone uses a mouse (or touch screen). Plus, a native <select> element comes with more features we get for free and aren’t included in those requirements, such as:

  • The checked option is perceivable for all users regardless of their visual abilities.
  • The component can interact with a keyboard in a predictable way across all browsers (e.g. using arrow keys to navigate, Enter to select, Esc to cancel, etc.).
  • Assistive technologies (e.g. screen readers) announce the element clearly to users, including its role, name and state.
  • The listbox position is adjusted. (i.e. does not get cut off of the screen).
  • The element respects the user’s operating system preferences (e.g high contrast, color scheme, motion, etc.).

This is where the majority of the custom selects fail in some way. Take a look at some of the major UI components libraries. I won’t mention any because the web is ephemeral, but go give it a try. You’ll likely notice that the select component in one framework behaves differently from another. 

Here are additional characteristics to watch for:

  • Is a listbox option immediately activated on focus when navigating with a keyboard?
  • Can you use Enter and/or Space to select an option?
  • Does the Tab key jump go to the next option in the listbox, or jump to the next form control?
  • What happens when you reach the last option in the listbox using arrow keys? Does it simply stay at the last item, does it go back to the first option, or worst of all, does focus move to the next form control? 
  • Is it possible to jump directly to the last item in the listbox using the Page Down key?
  • Is it possible to scroll through the listbox items if there are more than what is currently in view?

This is a small sample of the features included in a native <select> element.

Once we decide to create our own custom select, we are forcing people to use it in a certain way that may not be what they expect.

But it gets worse. Even the native <select> behaves differently across browsers and screen readers. Once we decide to create our own custom select, we are forcing people to use it in a certain way that may not be what they expect. That’s a dangerous decision and it’s in those details where the devil lives.

Building a “hybrid” select

When we build a simple custom select, we are making a trade-off without noticing it. Specifically, we sacrifice functionality to aesthetics. It should be the other way around.

What if we instead deliver a native select by default and replace it with a more aesthetically pleasing one if possible? That’s where the “hybrid” select idea comes into action. It’s “hybrid” because it consists of two selects, showing the appropriate one at the right moment:

  • A native select, visible and accessible by default
  • A custom select, hidden until it’s safe to be interacted with a mouse

Let’s start with markup. First, we’ll add a native <select> with <option> items before the custom selector for this to work. (I’ll explain why in just a bit.)

Any form control must have a descriptive label. We could use <label>, but that would focus the native select when the label is clicked. To prevent that behavior, we’ll use a <span> and connect it to the select using aria-labelledby.

Finally, we need to tell Assistive Technologies to ignore the custom select, using aria-hidden="true". That way, only the native select is announced by them, no matter what.

<span class="selectLabel" id="jobLabel">Main job role</span> <div class="selectWrapper">   <select class="selectNative js-selectNative" aria-labelledby="jobLabel">     <!-- options -->     <option></option>   </select>   <div class="selectCustom js-selectCustom" aria-hidden="true">      <!-- The beautiful custom select -->   </div> </div>

This takes us to styling, where we not only make things look pretty, but where we handle the switch from one select to the other. We need just a few new declarations to make all the magic happen.

First, both native and custom selects must have the same width and height. This ensures people don’t see major differences in the layout when a switch happens.

.selectNative, .selectCustom {   position: relative;   width: 22rem;   height: 4rem; }

There are two selects, but only one can dictate the space that holds them. The other needs to be absolutely positioned to take it out of the document flow. Let’s do that to the custom select because it’s the “replacement” that’s used only if it can be. We’ll hide it by default so it can’t be reached by anyone just yet.

.selectCustom {   position: absolute;   top: 0;   left: 0;   display: none; }

Here comes the “funny” part. We need to detect if someone is using a device where hover is part of the primary input, like a computer with a mouse. While we typically think of media queries for responsive breakpoints or checking feature support, we can use it to detect hover support too using @media query (hover :hover), which is supported by all major browsers. So, let’s use it to show the custom select only on devices that have hover:

@media (hover: hover) {   .selectCustom {     display: block;   } }

Great, but what about people who use a keyboard to navigate even in devices that have hover? What we’ll do is hide the custom select when the native select is in focus. We can reach for an adjacent Sibling combinatioron (+). When the native select is in focus, hide the custom select next to it in the DOM order. (This is why the native select should be placed before the custom one.)

@media (hover: hover) {   .selectNative:focus + .selectCustom {     display: none;   } }

That’s it! The trick to switch between both selects is done! There are other CSS ways to do it, of course, but this works nicely.

Last, we need a sprinkle of JavaScript. Let’s add some event listeners:

  • One for click events that trigger the custom select to open and reveal the options
  • One to sync both selects values. When one select value is changed, the other select value updates as well
  • One for basic keyboard navigation controls, like navigation with Up and Down keys, selecting options with the Enter or Space keys, and closing the select with Esc

Usability testing

I conducted a very small usability test where I asked a few people with disabilities to try the hybrid select component. The following devices and tools were tested using the latest versions of Chrome (81), Firefox (76) and Safari (13):

  • Desktop device using mouse only
  • Desktop device using keyboard only
  • VoiceOver on MacOS using keyboard
  • NVDA on Windows using keyboard
  • VoiceOver on iPhone and iPad using Safari

All these tests worked as expected, but I believe this could have even more usability tests with more diverse people and tools. If you have access to other devices or tools — such as JAWS, Dragon, etc. — please tell me how the test goes.

An issue was found during testing. Specifically, the issue was with the VoiceOver setting “Mouse pointers: Moves Voice Over cursor.” If the user opens the select with a mouse, the custom select will be opened (instead of the native) and the user won’t experience the native select.

What I most like about this approach is how it uses the best of both worlds without compromising the core functionality:

  • Users on mobile and tablets get the native select, which generally offers a better user experience than a custom select, including performance benefits.
  • Keyboard users get to interact with the native select the way they would expect.
  • Assistive Technologies can interact with the native select like normal.
  • Mouse users get to interact with the enhanced custom select.

This approach provides essential native functionality for everyone without the extra huge code effort to implement all the native features.

Don’t get me wrong. This technique is not a one-size-fits-all solution. It may work for simple selects but probably won’t work for cases that involve complex interactions. In those cases, we’d need to use ARIA and JavaScript to complement the gaps and create a truly accessible custom select.

A note about selects that look like menus

Let’s take a look back at the third Dropdown-land scenario. If you recall, it’s  a dropdown that always has a checked option (e.g. sorting some content). I classified it in the gray area, as either a menu or a select. 

Here’s my line of thought: Years ago, this type of dropdown was implemented mostly using a native <select>. Nowadays, it is common to see it implemented from scratch with custom styles (accessible or not). What we end up with is a select element that looks like a menu. 

Three similar dropdowns that always have a selected option.

A <select>  is a type of menu. Both have similar semantics and behavior, especially in a scenario that involves a list of options where one is always checked.  Now, let me mention the WCAG 3.2.2 On Input (Level A) criterion:

Changing the setting of any user interface component should not automatically cause a change of context unless the user has been advised of the behavior before using the component.

Let’s put this in practice. Imagine a sortable list of students. Visually, it may be obvious that sorting is immediate, but that’s not necessarily true for everyone. So, when using <select>, we risk failing the WCAG guideline because the page content changed, and ignificantly re-arranging the content of a page is considered a change of context.

To ensure the criterion success, we must warn the user about the action before they interact with the element, or include a <button> immediately after the select to confirm the change.

<label for="sortStudents">   Sort students   <!-- Warn the user about the change when a confirmation button is not present. -->   <span class="visually-hidden">(Immediate effect upon selection)</span> </label> <select id="sortStudents"> ... </select>

That said, using a <select> or building a custom menu are both good approaches when it comes to simple menus that change the page content. Just remember that your decision will dictate the amount of work required to make the component fully accessible. This is a scenario where the hybrid select approach could be used.

Final words

This whole idea started as an innocent CSS trick but, after all of this research, I was reminded once more that creating unique experiences without compromising accessibility is not an easy task.

Building truly accessible select components (or any kind of dropdown) is harder than it looks. WCAG provides excellent guidance and best practices, but without specific examples and diverse practical uses cases, the guidelines are mostly aspirational. That’s not to mention the fact that ARIA support is tepid and that native <select> elements look and behave differently across browsers.

The “hybrid” select is just another attempt to create a good looking select while getting as many native features as possible. Don’t look at this technique experiment as an excuse to downplay accessibility, but rather as an attempt to serve both worlds. If you have the resources, time and the needed skills, please do it right and make sure to test it with different users before shipping your component to the world.

P.S. Remember to use a proper name when making a “dropdown” component. 😉

The post Striking a Balance Between Native and Custom Select Elements appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

What’s the Difference Between Width/Height in CSS and Width/Height HTML attributes?

Some HTML elements accept width and height as attributes. Some do not. For example:

<!-- valid, works, is a good idea --> <img width="500" height="400" src="..." alt="..."> <iframe width="600" height="400" src="..."></iframe> <svg width="20" height="20"></svg>  <!-- not valid, doesn't work, not a good idea --> <div width="40" height="40"></div> <span width="100" height="10"></span>

Those attributes are sometimes referred to as presentational attributes. The thing to know about them is that they are overridden by any other styling information whatsoever. That makes them ideal as a fallback.

So, if CSS loads and has a declaration like:

img {   width: 400px; }

…that is going to override the width="500" on the <img> tag above. Presentational attributes are the weakest kind of styling, so they are overridden by any CSS, even selectors with very low specificity.

What might be a smidge confusing is that presentational attributes seem like they would have high specificity. These inline styles, for instance, are very strong:

<img style="width: 500px; height: 400px;" src="..." alt="...">

Using an inline style (which works on any element, not a select few), we’ve moved from the weakest way to apply width and height to one of the strongest. Regular CSS will not override this, with a selector of any specificity strength. If we need to override them from CSS, we’ll need !important rules.

img {   width: 400px !important; }

To reiterate, presentational attributes on elements that accept them (e.g. <img>, <iframe>, <canvas>, <svg>, <video>) are a good idea. They are fallback sizing and sizing information as the page is loading. They are particularly useful on <svg>, which may size themselves enormously in an awkward way if they have a viewBox and lack width and height attributes. Browsers even do special magic with images, where the width and height are used to reserve the correct aspect-ratio derived space in a situation with fluid images, which is great for a smooth page loading experience.

But presentational attributes are also weak and are usually overridden in the CSS.

The post What’s the Difference Between Width/Height in CSS and Width/Height HTML attributes? appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Techniques for a Newspaper Layout with CSS Grid and Border Lines Between Elements

I recently had to craft a newspaper-like design that featured multiple row and column spans with divider lines in between them. Take a look at the mockup graphic here and see if it makes you sweat at all. If you’re like me, you have been around a while and know just how difficult this would have been with old layout techniques.

Newspaper design with line dividers between cells

The project came with a few requirements:

  • Show the outlines of the grid
  • Columns can be wider, or longer than others
  • Divider lines must be shown between the various blocks

CSS Grid: Teaching an old layout new tricks

Newspaper layouts can cause headaches because everyday CSS is one-dimensional, meaning that elements flow on either a horizontal or vertical axis. Even modern flexbox layout is still uni-directional.

For a layout like this, we would almost want the properties that good ol’ HTML tables once provided: things like row and column spans to stretch cells in all directions. We would also want the benefits of modern day CSS, with all the responsiveness and flexible boxes that can grow to fill available space.

CSS grid combines the best of tables with the best of flexible boxes. In fact, grid’s even better because it provides the grid-gap property for creating gutters between cells while taking available space into account. Powerful as this may be, how can we create divider-lines exactly in the middle of those gutters?

Let’s look at three techniques to make that happen.

What we’ll create

First, we will build a simplified version of the newspaper design that’ll help illustrate the crux of the three different techniques that we’re going to cover. A deceptively easy design, one would say.

Column and row spans in a CSS grid layout

Technique 1: The faux column

This solution creates “faux” columns that allow us to draw vertical lines, and then place a grid on top. Horizontal dividers are painted if needed. The “faux” columns are created by using pseudo selectors in the grid container.

<div class="frontpage">   <div class="fp-cell fp-cell--1">     <div class="fp-item">1</div>   </div>   <div class="fp-cell fp-cell--2">     <div class="fp-item">2</div>   </div>   <div class="fp-cell fp-cell--3 fp-cell--border-top">     <div class="fp-item">3</div>   </div>   <div class="fp-cell fp-cell--4 fp-cell--border-top">     <div class="fp-item">4</div>   </div> </div>

See the Pen
Newspaper-design, ‘faux-column’ technique
by Marco Troost (@marco-troost)
on CodePen.

Setting up the lines between the columns

Let’s create a three-column container using display: grid and pseudo-selectors (:before and :after) to create two columns that fill 100% of the container’s height.

.frontpage {   position: relative;   display: grid;   /* Three columns */   grid-template-columns: 1fr 1fr 1fr;   grid-column-gap: 32px;   border: 1px solid transparent;   border-top: 1px solid #DADCE0;   border-bottom: 1px solid #DADCE0;   overflow: hidden; }  /* Two faux columns */ .frontpage:before, .frontpage:after {   position: absolute;   top: 0;   height: 100%;   content: '';   width: calc(33.3% - 4px); }  .frontpage:before {   left: 0;   border-right: 1px solid #DADCE0; }  .frontpage:after {   right: 0;   border-left: 1px solid #DADCE0; }

Note: 33% of the container doesn’t take the gutter width into account, so you’ll have to compensate accordingly.

This is calculated as:

33% minus (gutter-width divided by (amount of gutters times amount of gutters)) divided by amount of gutters)

Or, with actual numbers:

33% - (32 / (2* 2)) / 2 = 4

We could use one pseudo-selector instead:

.frontpage {   position: relative;   display: grid;   grid-template-columns: 1fr 1fr 1fr;   grid-column-gap: 32px;   border: 1px solid transparent;   border-top: 1px solid #DADCE0;   border-bottom: 1px solid #DADCE0;   overflow: hidden; }  .frontpage:before {   box-sizing: border-box;   position: absolute;   top: 0;   height: 100%;   content: '';   left: calc(33.3% - 5.3px);   width: calc(33.3% + 10.7px);   border-left: 1px solid #DADCE0;   border-right: 1px solid #DADCE0; }

See the Pen
Newsgrid-layout ‘faux-columns’ (using only :before)
by Marco Troost (@marco-troost)
on CodePen.

Note: A different calculation is needed when using only one pseudo-selector: One for positioning, and one for width.

The width is calculated as:

33% plus (amount of gutters times gutter-width) / (amount of gutters times amount of columns)

Again, with actual numbers:

33% + (2 * 32) / (2 * 3) = 10.7

The position is calculated as:

33% minus (amount of gutters times gutter-width) / (amount of gutters times amount of columns) divided by 2)

Making the grid

The design consists of four blocks of content. We’re going to place them in the container and give them a modifier class for future reference while making sure their z-index is higher than the pseudo-selectors of the grid.

<div class="frontpage">   <div class="fp-cell fp-cell--1"></div>   <div class="fp-cell fp-cell--2"></div>   <div class="fp-cell fp-cell--3"></div>   <div class="fp-cell fp-cell--4"></div> </div>

Now let’s set the background color for the cells (.fp-cell) to white. This way, the vertical lines won’t show through. We can also set the vertical padding for the cell to 16px in order to match half of the gutter.

The first and second content blocks should get their own unique spans as shown in the design. The first block spans all the way down and the second block spans the second and third columns.

.fp-cell {   position: relative;   z-index: 2;   padding: 16px 0;   background-color: #fff; }  /* Span all the way down! */ .fp-cell--1 {   grid-row: 1 / span 2; }  /* Span the second and third columns */ .fp-cell--2 {   grid-column: 2 / span 2; }

Vertical line dividers

If you look at the design, only the last two cells need a horizontal border. We can give ’em a sweet modifier class.

<div class="frontpage">   <div class="fp-cell fp-cell--1"></div>   <div class="fp-cell fp-cell--2"></div>   <div class="fp-cell fp-cell--3 fp-cell--border-top"></div>   <div class="fp-cell fp-cell--4 fp-cell--border-top"></div> </div>
.fp-cell--border-top:before {   content: '';   position: absolute;   top: 0;   left: -16px;   right: -16px;   border-top: 1px solid #DADCE0; }

The negative margins are half of the gutter width.

Technique #2: Using background-color

Another way to create the dividers is to utilize the grid-gap property. This solution doesn’t necessarily create a “real” distance between cells, but rather leaves some blank space where the background-color of the grid can shine through. The gutter width is delegated to padding within the grid cells.

<div class="container">   <div class="frontpage">     <div class="fp-cell fp-cell--1">       <div class="fp-item">1</div>     </div>     <div class="fp-cell fp-cell--2">       <div class="fp-item">2</div>     </div>     <div class="fp-cell fp-cell--3">       <div class="fp-item">3</div>     </div>     <div class="fp-cell fp-cell--4">       <div class="fp-item">4</div>     </div>   </div> </div>
.container {   overflow-x: hidden;   border-top: 1px solid #DADCE0;   border-bottom: 1px solid #DADCE0; }  .frontpage {   position: relative;   display: grid;   grid-template-columns: 1fr 1fr 1fr;   grid-gap: 1px;   margin: 0 -16px;   background-color: #DADCE0; }  .fp-cell {   background-color: #fff;   padding: 16px; }  .fp-cell--1 {   grid-row: 1 / span 2; }  .fp-cell--2 {   grid-column: 2 / span 2; }  .fp-cell--3 {   grid-column: 2; }  .fp-item {   background-color: #efefef;   display: flex;   align-items: center;   justify-content: center;   min-height: 200px;   height: 100%; }

See the Pen
Newspaper-design, background-color technique
by Marco Troost (@marco-troost)
on CodePen.

Since all cells have an extra 16px of horizontal padding, the grid needs to be offset by just as much. A wrapper container will take care of the overflow.

<div class="container">   <div class="frontpage">   <!-- ... -->   </div> </div>
.container {   border-top: 1px solid #DADCE0;   border-bottom: 1px solid #DADCE0;   overflow-x: hidden; }  .frontpage {   position: relative;   display: grid;   grid-template-columns: 1fr 1fr 1fr;   grid-gap: 1px;   background-color: #DADCE0;   margin: 0 -16px; }

Technique #3: Creating a cell border

This solution appends a right and bottom border to each cell. Like the last example, the grid-gap is mimicked by adding padding to the cell content. That means it also needs to be wrapped in an extra container.

<div class="container">   <div class="frontpage">     <div class="fp-cell fp-cell--1">       <div class="fp-item">1</div>     </div>     <div class="fp-cell fp-cell--2">       <div class="fp-item">2</div>     </div>     <div class="fp-cell fp-cell--3">         <div class="fp-item">3</div>     </div>     <div class="fp-cell fp-cell--4">       <div class="fp-item">4</div>     </div>   </div> </div>
.container {   border-top: 1px solid #DADCE0;   overflow-x: hidden; }  .frontpage {   margin: 0 -17px 0 -16px;   position: relative;   display: grid;   grid-template-columns: 1fr 1fr 1fr; }  .fp-cell {   padding: 16px;   background-color: #fff;   border-right: 1px solid #DADCE0;   border-bottom: 1px solid #DADCE0; }  .fp-cell--1 {   grid-row: 1 / span 2; }  .fp-cell--2 {   grid-column: 2 / span 2; }  .fp-cell--3 {   grid-column: 2; }  .fp-item {   background-color: #efefef;   display: flex;   align-items: center;   justify-content: center;   min-height: 200px;   height: 100%; }

See the Pen
Newspaper-design, ‘cell-border’-technique
by Marco Troost (@marco-troost)
on CodePen.

As mentioned, each cell is given a border on the right and on the bottom. The main trick here is the use of the (asymmetrical) negative margin on the grid. This is needed to compensate for the cell’s right border.

.frontpage {   margin: 0 -17px 0 -16px;   position: relative;   display: grid;   grid-template-columns: 1fr 1fr 1fr; }

Conclusion

Occam’s razor stipulates that the simplest solution wins. In our case, that’s technique number two. But then again, the other solutions have plenty of merit and they could prove useful if, for example, access to the DOM is not possible.

All of these techniques will work. Choosing the right one depends on your use case. The first technique uses the actual grid-gap property to create the gaps, but the others are perhaps easier to understand at a glance… and perhaps easier to maintain as well.

The post Techniques for a Newspaper Layout with CSS Grid and Border Lines Between Elements appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

The difference between keyboard and screen reader navigation

There are a few differences between keyboards and screen readers and Léonie Watson highlights of them:

When using the tab key, keyboard focus and screen reader focus are synchronised with each other. The rest of the time, screen reader users have an enormous range of commands at their disposal for reading and navigating content independently of keyboard focus. The commands vary between screen readers, but they all have one thing in common: they’re tied to different HTML elements.

This is also a good reminder that screen readers behave differently from one another. It’s worth doing some research to see how our sites work in all these environments. One thing is clear from this post though: writing semantic and concise HTML is the best way to improve accessibility for both users with keyboards and screen readers. For example, Scott O’Hara has this recent post on best practices using the tabindex attribute to ensure accessible navigation using the keyboard.

Direct Link to ArticlePermalink

The post The difference between keyboard and screen reader navigation appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Quick! What’s the Difference Between Flexbox and Grid?

Let’s go rapid fire and try to answer this question with quick points rather than long explanations. There are a lot of similarities between flexbox and grid, starting with the fact that they are used for layout and much more powerful than any layout technique that came before them. They can stretch and shrink, they can center things, they can re-order things, they can align things… There are plenty of layout situations in which you could use either one to do what we need to do, and plenty of situations where one is more well-suited than the other. Let’s focus on the differences rather than the similarities:


Flexbox can optionally wrap. If we allow a flex container to wrap, they will wrap down onto another row when the flex items fill a row. Where they line up on the next row is independent of what happenned on the first row, allowing for a masonry-like look.

Grid can also optionally wrap (if we allow auto filling) in the sense that items can fill a row and move to the new row (or auto place themselves), but as they do, they will fall along the same grid lines all the other elements do.

Flexbox on top, Grid on bottom

You could think of flexbox as “one dimensional.” While flexbox can make rows and columns in the sense that it allows elements to wrap, there’s no way to declaratively control where elements end up since the elements merely push along a single axis and then wrap or not wrap accordingly. They do as they do, if you will, along a one-dimensional plane and it’s because of that single dimension that we can optionally do things, like align elements along a baseline — which is something grid is unable to do.

.parent {   display: flex;   flex-flow: row wrap; /* OK elements, go as far as you can on one line, then wrap as you see fit */ }

You could think of grid as “two dimensional in that we can (if we want to) declare the sizing of rows and columns and then explicitly place things into both rows and columns as we choose.

.parent {   display: grid;   grid-template-columns: 3fr 1fr; /* Two columns, one three times as wide as the other */   grid-template-rows: 200px auto 100px; /* Three columns, two with explicit widths */   grid-template-areas:     "header header header"     "main . sidebar"     "footer footer footer"; }  /*   Now, we can explicitly place items in the defined rows and columns. */ .child-1 {   grid-area: header; }  .child-2 {   grid-area: main; }  .child-3 {   grid-area: sidebar; }  .child-4 {   grid-area: footer; }
Flexbox on top, Grid on bottom

I’m not the world’s biggest fan of the “1D” vs. “2D” differentiation of grid vs. flexbox, only because I find most of my day-to-day usage of grid is “1D” and it’s great for that. I wouldn’t want someone to think they have to use flexbox and not grid because grid is only when you need 2D. It is a strong distinction though that 2D layout is possible with grid though in ways it is not in flexbox.


Grid is mostly defined on the parent element. In flexbox, most of the layout (beyond the very basics) happen on the children.

/*   The flex children do most of the work */ .flexbox {   display: flex;   > div {     &:nth-child(1) { // logo       flex: 0 0 100px;     }     &:nth-child(2) { // search       flex: 1;       max-width: 500px;     }     &:nth-child(3) { // avatar       flex: 0 0 50px;       margin-left: auto;     }   } }  /*   The grid parent does most of the work */ .grid {   display: grid;   grid-template-columns: 1fr auto minmax(100px, 1fr) 1fr;   grid-template-rows: 100px repeat(3, auto) 100px;   grid-gap: 10px; }

Grid is better at overlapping. Getting elements to overlap in flexbox requires looking at traditional stuff, like negative margins, transforms, or absolute positioning in order to break out of the flex behavior. With grid, we can place items on overlapping grid lines, or even right within the same exact grid cells.

Flexbox on top, Grid on bottom

Grid is sturdier. While the flexing of flexbox is sometimes it’s strength, the way a flex item is sized gets rather complicated. It’s a combination of width, min-width, max-width, flex-basis, flex-grow, and flex-shrink, not to mention the content inside and things like white-space, as well as the other items in the same row. Grid has interesting space-occupying features, like fractional units, and the ability for content to break grids, though, generally speaking, we’re setting up grid lines and placing items within them that plop right into place.


Flexbox can push things away. It’s a rather unique feature of flexbox that you can, for example, put margin-right: auto; on an element and, if there is room, that element will push everything else as far away as it can go can.


Here are some of my favorite tweets on the subject:

The post Quick! What’s the Difference Between Flexbox and Grid? appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Animating Between Views in React

You know how some sites and web apps have that neat native feel when transitioning between two pages or views? Sarah Drasner has shown some good examples and even a Vue library to boot.

These animations are the type of features that can turn a good user experience into a great one. But to achieve this in a React stack, it is necessary to couple crucial parts in your application: the routing logic and the animation tooling.

Let’s start with animations. We’ll be building with React, and there are great options out there for us to leverage. Notably, the react-transition-group is the official package that handles elements entering and leaving the DOM. Let’s explore some relatively straightforward patterns we can apply, even to existing components.

Transitions using react-transition-group

First, let’s get familiar with the react-transition-group library to examine how we can use it for elements entering and leaving the DOM.

Single components transitions

As a simple example of a use case, we can try to animate a modal or dialog — you know, the type of element that benefits from animations that allow it enter and leave smoothly.

A dialog component might look something like this:

import React from "react";  class Dialog extends React.Component {   render() {     const { isOpen, onClose, message } = this.props;     return (       isOpen && (         <div className="dialog--overlay" onClick={onClose}>           <div className="dialog">{message}</div>         </div>       )     );   } }

Notice we are using the isOpen prop to determine whether the component is rendered or not. Thanks to the simplicity of the recently modified API provided by react-transition-group module, we can add a CSS-based transition to this component without much overhead.

First thing we need is to wrap the entire component in another TransitionGroup component. Inside, we keep the prop to mount or unmount the dialog, which we are wrapping in a CSSTransition.

import React from "react"; import { TransitionGroup, CSSTransition } from "react-transition-group";  class Dialog extends React.Component {   render() {     const { isOpen, onClose, message } = this.props;     return (       <TransitionGroup component={null}>         {isOpen && (           <CSSTransition classNames="dialog" timeout={300}>             <div className="dialog--overlay" onClick={onClose}>               <div className="dialog">{message}</div>             </div>           </CSSTransition>         )}       </TransitionGroup>     );   } }

Every time isOpen is modified, a sequence of class names changes will happen in the dialog’s root element.

If we set the classNames prop to "fade", then fade-enter will be added immediately before the element mounts and then fade-enter-active when the transition kicks off. We should see fade-enter-done when the transition finishes, based on the timeout that was set. Exactly the same will happen with the exit class name group at the time the element is about to unmount.

This way, we can simply define a set of CSS rules to declare our transitions.

.dialog-enter {   opacity: 0.01;   transform: scale(1.1); }  .dialog-enter-active {   opacity: 1;   transform: scale(1);   transition: all 300ms; }  .dialog-exit {   opacity: 1;   transform: scale(1); }  .dialog-exit-active {   opacity: 0.01;   transform: scale(1.1);   transition: all 300ms; }

JavaScript Transitions

If we want to orchestrate more complex animations using a JavaScript library, then we can use the Transition component instead.

This component doesn’t do anything for us like the CSSTransition did, but it does expose hooks on each transition cycle. We can pass methods to each hook to run calculations and animations.

<TransitionGroup component={null}>   {isOpen && (     <Transition       onEnter={node => animateOnEnter(node)}       onExit={node => animateOnExit(node)}       timeout={300}     >       <div className="dialog--overlay" onClick={onClose}>         <div className="dialog">{message}</div>       </div>     </Transition>   )} </TransitionGroup>

Each hook passes the node to the callback as a first argument — this gives control for any mutation we want when the element mounts or unmounts.

Routing

The React ecosystem offers plenty of router options. I’m gonna use react-router-dom since it’s the most popular choice and because most React developers are familiar with the syntax.

Let’s start with a basic route definition:

import React, { Component } from 'react' import { BrowserRouter, Switch, Route } from 'react-router-dom' import Home from '../views/Home' import Author from '../views/Author' import About from '../views/About' import Nav from '../components/Nav'  class App extends Component {   render() {     return (       <BrowserRouter>         <div className="app">           <Switch>             <Route exact path="/" component={Home}/>             <Route path="/author" component={Author} />             <Route path="/about" component={About} />           </Switch>         </div>       </BrowserRouter>     )   } }

We want three routes in this application: home, author and about.

The BrowserRouter component handles the browser’s history updates, while Switch decides which Route element to render depending on the path prop. Here’s that without any transitions:

Don’t worry, we’ll be adding in page transitions as we go.

Oil and water

While both react-transition-group and react-router-dom are great and handy packages for their intended uses, mixing them together can break their functionality.

For example, the Switch component in react-router-dom expects direct Route children and the TransitionGroup components in react-transition-group expect CSSTransition or Transition components to be direct children of it too. So, we’re unable to wrap them the way we did earlier.

We also cannot toggle views with the same boolean approach as before since it’s handled internally by the react-router-dom logic.

React keys to the rescue

Although the solution might not be as clean as our previous examples, it is possible to use the libraries together. The first thing we need to do is to move our routes declaration to a render prop.

<BrowserRouter>   <div className="app">     <Route render={(location) => {       return (         <Switch location={location}>           <Route exact path="/" component={Home}/>           <Route path="/author" component={Author} />           <Route path="/about" component={About} />         </Switch>       )}     /> </BrowserRouter>

Nothing has changed as far as functionality. The difference is that we are now in control of what gets rendered every time the location in the browser changes.

Also, react-router-dom provides a unique key in the location object every time this happens.

In case you are not familiar with them, React keys identify elements in the virtual DOM tree. Most times, we don’t need to indicate them since React will detect which part of the DOM should change and then patch it.

<Route render={({ location }) => {   const { pathname, key } = location    return (     <TransitionGroup component={null}>       <Transition         key={key}         appear={true}         onEnter={(node, appears) => play(pathname, node, appears)}         timeout={{enter: 750, exit: 0}}       >         <Switch location={location}>           <Route exact path="/" component={Home}/>           <Route path="/author" component={Author} />           <Route path="/about" component={About} />         </Switch>       </Transition>     </TransitionGroup>   ) }}/>

Constantly changing the key of an element — even when its children or props haven’t been modified — will force React to remove it from the DOM and remount it. This helps us emulate the boolean toggle approach we had before and it’s important for us here because we can place a single Transition element and reuse it for all of our view transitions, allowing us to mix routing and transition components.

Inside the animation function

Once the transition hooks are called on each location change, we can run a method and use any animation library to build more complex scenes for our transitions.

export const play = (pathname, node, appears) => {   const delay = appears ? 0 : 0.5   let timeline    if (pathname === '/')     timeline = getHomeTimeline(node, delay)   else     timeline = getDefaultTimeline(node, delay)    timeline.play() }

Our play function will build a GreenSock timeline here depending on the pathname, and we can set as many transitions as we want for each different routes.

Once the timeline is built for the current pathname, we play it.

const getHomeTimeline = (node, delay) => {   const timeline = new Timeline({ paused: true });   const texts = node.querySelectorAll('h1 > div');    timeline     .from(node, 0, { display: 'none', autoAlpha: 0, delay })     .staggerFrom(texts, 0.375, { autoAlpha: 0, x: -25, ease: Power1.easeOut }, 0.125);    return timeline }

Each timeline method digs into the DOM nodes of the view and animates them. You can use other animation libraries instead of GreenSock, but the important detail is that we build the timeline beforehand so that our main play method can decide which one should run for each route.

Success!

I’ve used this approach on lots of projects, and though it doesn’t present obvious performance issues for inner navigations, I did notice a concurrency issue between the browser’s initial DOM tree build and the first route animation. This caused a visual lag on the animation for the first load of the application.

To make sure animations are smooth in each stage of the application, there’s one last thing we can do.

Profiling the initial load

Here’s what I found when auditing the application in Chrome DevTools after a hard refresh:

You can see two lines: one blue and one red. Blue represents the load event and red the DOMContentLoaded. Both intersect the execution of the initial animations.

These lines are indicating that elements are animating while the browser hasn’t yet finished building the entire DOM tree or it’s parsing resources. Animations account for big performance hits. If we want anything else to happen, we’d have to wait for the browser to be ready with these heavy and important tasks before running our transitions.

After trying a lot of different approaches, the solution that actually worked was to move the animation after these events — simple as that. The issue is that we can’t rely on event listeners.

window.addEventListener(‘DOMContentLoaded’, () => {   timeline.play() })

If for some reason, the event occurs before we declare the listener, the callback we pass will never run and this could lead to our animations never happening and an empty view.

Since this is a concurrency and asynchronous issue, I decided to rely on promises, but then the question became: how can promises and event listeners be used together?

By creating a promise that gets resolved when the event takes place. That’s how.

window.loadPromise = new Promise(resolve => {   window.addEventListener(‘DOMContentLoaded’, resolve) })

We can put this in the document head or just before the script tag that loads the application bundle. This will make sure the event never happens before the Promise is created.

Plus, doing this allows us to use the globally exposed loadPromise to any animation in our application. Let’s say that we don’t only want to animate the entry view but a cookie banner or the header of the application. We can simply call each of these animations after the promise has resolved using then along with our transitions.

window.loadPromise.then(() => timeline.play())

This approach is reusable across the entire codebase, eliminating the issue that would result when an event gets resolved before the animations run. It will defer them until the browser DOMContentLoaded event has passed.

See now that the animation is not kicking off until the red line appears.

The difference is not only on the profiling report — it actually solves an issue we had in a real project.

Wrapping up

In order to act as reminders, I created a list of tips for me that you might find useful as you dig into view transitions in a project:

  • When an animation is happening nothing else should be happening. Run animations after all resources, fetching and business logic have completed.
  • No animation is better than crappy animations If you can’t achieve a good animation, then removing it is a fair sacrifice. The content is more important and showing it is the priority until a good animation solution is in place.
  • Test on slower and older devices. They will make it easier for you to catch spots with weak performance.
  • Profile and base your improvements in metrics. Instead of guessing as you go, like I did, see if you can spot where frames are being dropped or if something looks off and attack that issue first.

That’s it! Best of luck with animating view transitions. Please post a comment if this sparked any questions or if you have used transitions in your app that you’d like to share!

The post Animating Between Views in React appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Bridging the Gap Between CSS and JavaScript: CSS Modules, PostCSS and the Future of CSS

In the previous post in this two-part series, we explored the CSS-in-JS landscape and, we realized not only that CSS-in-JS can produce critical styles, but also that some libraries don’t even have a runtime. We saw that user experience can significantly improve by adding clever optimizations, which is why this series focuses on developer experience (the experience of authoring styles).

In this part, we’ll explore the tools for “plain ol’ CSS” by refactoring the Photo component from our existing example.

Controversy and #hotdrama

One of the most famous CSS debates is whether the language is fine just the way that it is. I think this debate stays alive because there is some truth to both sides. For example, while it’s true that CSS was initially designed to style a document rather than components of an application, it’s also true that upcoming CSS features will dramatically change this, and that many CSS mistakes stem from treating styling as an afterthought instead of taking time to learn it properly or hiring someone who’s good at it.

I don’t think that CSS tools themselves are the source of the controversy; we’ll probably always use them to some extent at the very least. But approaches like CSS-in-JS are different in that they patch up the shortcomings of CSS with client-side JavaScript. However, CSS-in-JS is not the only approach here; it is merely the newest. Remember when we used to have similar debates about preprocessors, like Sass? Sass has features, like mixins, that aren’t based on any CSS proposal (not to mention the entire indented syntax). However, Sass was born in a much different time and has reached a point where it’s no longer fair to include it in the debate because the debate itself has changed — so we started criticizing CSS-in-JS because it’s an easier target.

I think we should use tools that let us use proposed syntax today. Let’s use JavaScript Promises as an analogy. This feature isn’t supported by Internet Explorer, so many people include a polyfill for it. The point of polyfills is to enable us to pretend like the feature is supported everywhere by substituting native browser implementations with a patch. Same goes for transpiling new syntax with tools, like Babel. We can use it today because the code will be compiled to an older, well-supported syntax. This is a good approach because it allows us to use future features today while pushing JavaScript forward the way preprocessing tools, like Sass, have pushed CSS forward.

My take on the CSS controversy is that we should use tools that enable us to use future CSS today.

Preprocessors

We’ve already talked a bit about CSS preprocessors, so it’s worth discussing them in a little more details and how they fit into the CSS-in-JS conversation. We have Sass, Less and PostCSS (among others) that can imbue our CSS code with all kinds of new features.

For our example, we’re only going to be concerned with nesting, one of the most common and powerful features of preprocessors. I suggest using PostCSS because it gives us fine-grained control over the features we’re adding, which is exactly what we need in this case. The PostCSS plugin that we’re going to use is postcss-nesting because it follows the actual proposal for native CSS nesting.

The best way to use PostCSS with our compiling tool, webpack, is to add postcss-loader after css-loader in the configuration. When adding loaders after css-loader, it’s important to account for them in the css-loader options by setting importLoaders to the number of succeeding loaders, which in this case is 1:

{   test: /\.css$  /,   use: [     'style-loader',     {       loader: 'css-loader',       options: {         importLoaders: 1,       },     },     'postcss-loader',   ], }

This ensures that CSS files imported from other CSS files will be processed with postcss-loader as well.

After setting up postcss-loader, we’ll install postcss-nesting and include it in the PostCSS configuration:

yarn add postcss-nesting

There are many ways to configure PostCSS. In this case, we’re going to add a postcss.config.js file at the root of our project:

module.exports = {   plugins: {     "postcss-nesting": {},   }, }

Now, we can write a CSS file for our Photo component. Let’s call it Photo.css:

.photo {   width: 200px;   &.rounded {     border-radius: 1rem;   } }  @media (min-width: 30rem) {   .photo {     width: 400px;   } }

Let’s also add a file called utils.css that contains a class for visually hiding elements, as we covered in the the first part of this series:

.visuallyHidden {   border: 0;   clip: rect(0 0 0 0);   height: 1px;   margin: -1px;   overflow: hidden;   padding: 0;   position: absolute;   width: 1px;   white-space: nowrap; }

Since our component relies on this utility, let’s include utils.css to Photo.css by adding an @import statement to the top:

@import url('utils.css');

This will ensure that webpack requires utils.css, thanks to css-loader. We can place utils.css anywhere we want and adjust the @import path. In this particular case, it’s a sibling of Photo.css.

Next, let’s import Photo.css into our JavaScript file and use the classes to style our component:

import React from 'react' import { getSrc, getSrcSet } from './utils' import './Photo.css'  const Photo = ({ publicId, alt, rounded }) => (   <figure>     <img       className={rounded ? 'photo rounded' : 'photo'}       src={getSrc({ publicId, width: 200 })}       srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}       sizes="(min-width: 30rem) 400px, 200px"     />     <figcaption className="visuallyHidden">{alt}</figcaption>   </figure> )  Photo.defaultProps = {   rounded: false, }  export default Photo

While this will work, our class names are way too simple and they will most certainly clash with others completely unrelated to our .photo class. One of the ways of working around this is using a naming methodology, like BEM, to rename our classes (e.g. photo_rounded and photo__what-is-this--i-cant-even) to help prevent clashes from happening, but components quickly get complex and class names tend to get long, depending on the overall complexity of the project.

Meet CSS Modules.

CSS Modules

Simply put, CSS Modules are CSS files in which all class names and animations are scoped locally by default. They look a lot like regular CSS. For example, we can use our Photo.css and utils.css files as CSS Modules without modifying them at all, simply by passing modules: true to css-loader’s options:

{   loader: 'css-loader',   options: {     importLoaders: 1,     modules: true,   }, }

CSS Modules are an evolving feature and could be discussed at even greater length. Robin’s three-part series on it is a good overview and introduction.

While CSS Modules themselves look very similar to regular CSS, the way we use them is quite different. They are imported into JavaScript as objects where keys correspond to authored class names, and values are unique class names that are auto-generated for us that keep the scope limited to a component:

import React from 'react' import { getSrc, getSrcSet } from './utils' import styles from './Photo.css' import stylesUtils from './utils.css'  const Photo = ({ publicId, alt, rounded }) => (   <figure>     <img       className={rounded         ? `$  {styles.photo} $  {styles.rounded}`         : styles.photo}       src={getSrc({ publicId, width: 200 })}       srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}       sizes="(min-width: 30rem) 400px, 200px"     />     <figcaption className={stylesUtils.visuallyHidden}>{alt}</figcaption>   </figure> )  Photo.defaultProps = {   rounded: false, }  export default Photo

Since we’re using utils.css as a CSS Module, we can remove the @import statement at the top of Photo.css. Also, notice that using camelCase to format class names makes them easier to use in JavaScript. If we had used dashes, we’d have to write things out in full, like stylesUtils['visually-hidden'].

CSS Modules have additional features, like composition. Right now, we’re importing utils.css into Photo.js to apply our component styles, but let’s say that we want to shift the responsibility of styling the caption to Photo.css instead. That way, as far as our JSX code is concerned, styles.caption is just another class name; it just so happens to visually hide the element, but it might be styled differently in the future. Either way, Photo.css will be making those decisions.

So let’s add a caption style to Photo.css to extend the properties of the visuallyHidden utility using composes:

.caption {   composes: visuallyHidden from './utils.css'; }

We could just as well add more rules to that class, but this is all we need in this case. Now, we no longer need to import utils.css into Photo.js; we can simply use styles.caption instead:

<figcaption className={styles.caption}>{alt}</figcaption>

How does this work? Do the styles from visuallyHidden get copied over to caption? Let’s examine the value of styles.caption — whoa, two classes! That’s right: one is from visuallyHidden and the other one will apply any other styles we add to caption. CSS-in-JS makes it too easy to duplicate styles with libraries, like polished, but CSS Modules encourage you to reuse existing styles. No need to create a new VisuallyHidden React component to only apply several CSS rules.

Let’s take it even further by examining this uncomfortable class composition:

rounded   ? `$  {styles.photo} $  {styles.rounded}`   : styles.photo

There are libraries for these situations, like classnames, which are useful for more complex class composition. In our example, though, we can keep on using composes and rename .rounded to .roundedPhoto:

.photo {   width: 200px; }  .roundedPhoto {   composes: photo;   border-radius: 1rem; }  @media (min-width: 30rem) {   .photo {     width: 400px;   } }  .caption {   composes: visuallyHidden from './utils.css'; }

Now we can apply the class names to our component in a much more readable fashion:

rounded ? styles.roundedPhoto : styles.photo

But wait, what if we accidentally place the .roundedPhoto ruleset before .photo and some rules from .photo end up overriding rules from .roundedPhoto due to specificity? Don’t worry, CSS Modules prevent us from composing classes defined after the current class by throwing an error like this:

referenced class name "photo" in composes not found (2:3)    1 | .roundedPhoto { > 2 |   composes: photo;     |   ^   3 |   border-radius: 1rem;   4 | }

Note that it’s generally a good idea to use a file naming convention for CSS Modules, for example using the extension .module.css, because it’s common to want to apply some global styles as well.

Dynamic styles

So far, we’ve been conditionally applying predefined sets of styles, which is called conditional styling. What if we also want to be able to fine-tune the border radius of the rounded photos? This is called dynamic styling because we don’t know what the value is going to be in advance; it can change while the application is running.

There aren’t many use cases for dynamic styling — usually we’re styling conditionally, but in cases when we need this, how would we approach this? While we could get by with inline styles, a native solution for this type of problems is custom properties (a.k.a. CSS variables). A really valuable aspect of this feature is that browsers will update styles using custom properties when JavaScript changes them. We can set a custom property on an element through inline styles, which means that it will be scoped to that element and that element only:

style={typeof borderRadius !== 'undefined' ? {   '--border-radius': borderRadius, } : null}

In Photo.css, we can use this custom property by using var() and passing the default value as the second argument:

.roundedPhoto {   composes: photo;   border-radius: var(--border-radius, 1rem); }

As far as JavaScript is concerned, it’s only passing a dynamic parameter to CSS, then when CSS takes over, it can apply the value as-is, calculate a new value from it using calc(), etc.

Fallback

At the time of this writing, the browser support for custom properties is… well, you decide for yourself. Not supporting these browsers is (probably) out of the question for a real-world application, but keep in mind that some styles are less important than others. In this case, it’s not a big deal if the border radius on IE is always 1rem. The application doesn’t have to look the same way on every browser.

The way we can automatically provide fallbacks for all custom properties is to install postcss-custom-properties and add it to our PostCSS configuration:

yarn add postcss-custom-properties
module.exports = {   plugins: {     'postcss-nesting': {},     'postcss-custom-properties': {},   }, }

This will generate a fallback for our border-radius rule:

.roundedPhoto {   composes: photo;   border-radius: 1rem;   border-radius: var(--border-radius, 1rem); }

Browsers that don’t understand var() will ignore that rule and use the previous one. Don’t let the name of the plugin fool you; it only partially improves the support for custom properties by providing static fallbacks. The dynamic aspect can’t be polyfilled.

Exposing values to JavaScript

In the previous part of this series, we explored how CSS-in-JS allows us to share almost anything between CSS and JavaScript, using media queries as an example. There is no possible way to achieve this here, right?

Thanks to Jonathan Neal, you can!

First, meet postcss-preset-env, the successor to cssnext. It’s a PostCSS plugin that acts as a preset similar to @babel/preset-env. It contains plugins like postcss-nesting, postcss-custom-properties, autoprefixer etc. so we can use future CSS today. It splits the plugins across four stages of standardization. Some of the features I’d like to show you aren’t included in the default range (stage 2+), so we’ll explicitly enable the ones we need:

yarn add postcss-preset-env
module.exports = {   plugins: {     'postcss-preset-env': {       features: {         'nesting-rules': true,         'custom-properties': true, // already included in stage 2+         'custom-media-queries': true, // oooh, what's this? :)       },     },   }, }

Note that we replaced our existing plugins because this postcss-preset-env configuration includes them, meaning our existing code should work the same as before.

Using custom properties in media queries is invalid because that’s not what they were designed for. Instead we’ll use custom media queries:

@custom-media --photo-breakpoint (min-width: 30em);  .photo {   width: 200px; }  @media (--photo-breakpoint) {   .photo {     width: 400px;   } }

Even though this feature is in the experimental stage and therefore not supported in any browser, thanks to postcss-preset-env it just works! One catch is that PostCSS operates on a per-file basis, so this way only Photo.css can use --photo-breakpoint. Let’s do something about that.

Jonathan Neal recently implemented an importFrom option in postcss-preset-env, which is passed to other plugins that support it as well, like postcss-custom-properties and postcss-custom-media. Its value can be many things, but for the purpose of our example, it’s a path to a file that will be imported to the files PostCSS processes. Let’s call this one global.css and move our custom media query there:

@custom-media --photo-breakpoint (min-width: 30em);

…and let’s define importFrom, providing the path to global.css:

module.exports = {   plugins: {     'postcss-preset-env': {       importFrom: 'src/global.css',       features: {         'nesting-rules': true,         'custom-properties': true,         'custom-media-queries': true,       },     },   }, }

Now we can delete the @custom-media line at the top of Photo.css and our --photo-breakpoint value will still work, because postcss-preset-env will use the one from global.css to compile it. Same goes for custom properties and custom selectors.

Now, how to expose it to JavaScript? When experimental features like custom media queries get standardized and implemented in major browsers, we will be able to retrieve them natively from CSS. For example, this is how we would access a custom property called --font-family defined on :root:

const rootStyles = getComputedStyle(document.body) const fontFamily = rootStyles.getPropertyValue('--font-family')

If custom media queries get standardized we will probably be able to access them in a similar way, but in the meantime we have to find an alternative. We could use the exportTo option to generate a JavaScript or JSON file, which we would import into JavaScript. However, that option wasn’t designed for this workflow because webpack would try to require it before it’s generated. Even if we generated it before running webpack, every update to global.css would cause webpack to re-compile twice, once to generate the output file, and once more to import it. I wanted a solution that’s unencumbered by its implementation.

For this series, I’ve created a brand new webpack loader called css-customs-loader just for you! It makes this task easy: all we need to is include it in our webpack configuration before css-loader:

{   test: /\.css$  /,   use: [     'style-loader',     'css-customs-loader',     {       loader: 'css-loader',       options: {         importLoaders: 1,       },     },     'postcss-loader',   ], }

This exposes custom media queries, as well as custom properties, to JavaScript. We can access them simply by importing global.css:

import React from 'react' import { getSrc, getSrcSet } from './utils' import styles from './photo.module.css' import { customMedia } from './global.css'  const Photo = ({ publicId, alt, rounded, borderRadius }) => (   <figure>     <img       className={rounded ? styles.roundedPhoto : styles.photo}       style={         typeof borderRadius !== 'undefined'           ? { ['--border-radius']: borderRadius }           : null       }       src={getSrc({ publicId, width: 200 })}       srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}       sizes={`$  {customMedia['--photo-breakpoint']} 400px, 200px`}     />     <figcaption className={styles.caption}>{alt}</figcaption>   </figure> )  Photo.defaultProps = {   rounded: false, }  export default Photo

That’s it!

I created a repository demonstrating all of the concepts discussed in this series. Its readme also contains some advanced tips about the approach described in this post.

View Repo

Conclusion

It’s safe to say that tools like CSS Modules and PostCSS and upcoming CSS features are up to the task of dealing with many challenges of CSS. Whichever side of the CSS debate you’re on, this approach is worth exploring.

I have a strong CSS-in-JS background, but I’m very susceptible to hype, so keeping up with that world is hard. Which library should I use now, styled-components or emotion? Also, while having styles next to the behavior can be succinct, it’s also mixing two very different languages — CSS is very verbose compared to JavaScript. This incentivized me to write less CSS because I wanted to avoid getting the file too crowded. This may be a matter of personal preference, but I didn’t want that to be an issue. Using a separate file for CSS finally gave my code some air.

While mastering this approach may not be as straightforward as CSS-in-JS, I believe it’s more rewarding in the long run. It will improve your CSS skills and make you better prepared for its future.

Article Series:

  1. CSS-in-JS
  2. CSS Modules, PostCSS and the Future of CSS (This post)

The post Bridging the Gap Between CSS and JavaScript: CSS Modules, PostCSS and the Future of CSS appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Bridging the Gap Between CSS and JavaScript: CSS-in-JS

In this article, we’re going to dig into the concept of CSS-in-JS. If you’re already acquainted with this concept, you might still enjoy a stroll through the philosophy of that approach, and you might be even more interested in the next article (coming tomorrow!).

Web development is very interdisciplinary. We’re used to working closely with multiple languages. And, as developing web applications becomes more commonplace and nuanced, we often look for creative ways to bridge the gaps between those languages to make our development environments and workflows easier and more efficient.

The most common examples are typically when using templating languages. For example, one language might be used to generate the code of a more verbose language (often HTML). This is one of the key aspects of front end frameworks — what does manipulating HTML look like? The most recent twist in this area was JSX because it’s not really a templating language; it’s a syntax extension to JavaScript, and it makes working with HTML really succinct.

Web applications go through many state combinations and it’s often challenging to manage content alone. This is why CSS sometimes falls by the wayside — even though managing styling through different states and media queries is equally important and just as challenging. In this two-part series, I would like to place CSS in the spotlight and explore bridging the gap between it and JavaScript. Throughout this series, I will assume that you’re using a module bundler like webpack. As such, I will use React in my examples, but the same or similar principles are applicable to other JavaScript frameworks, including Vue.

The CSS landscape is evolving in many directions because there are a lot of challenges to solve and there is no “correct” path. I’ve been spending considerable effort experimenting with various approaches, mostly on personal projects, so the intention behind this series is only to inform, not to prescribe.

Challenges of CSS

Before diving into code, it’s worth explaining the most notable challenges of styling web applications. The ones I’ll talk about in this series are scoping, conditional and dynamic styles, and reusability.

Scoping

Scoping is a well-known CSS challenge, it’s the idea of writing styles that don’t leak outside of the component, thus avoid unintended side effects. We would like to achieve it ideally without compromising authoring experience.

Conditional and dynamic styles

While the state in front-end applications started getting more and more advanced, CSS was still static. We were only able to apply sets of styles conditionally — if a button was primary, we would probably apply the class “primary” and define its styles in a separate CSS file to apply how it’s going to look like on the screen. Having a couple of predefined button variations was manageable, but what if we want to have a variety of buttons, like specific ones tailored for Twitter, Facebook, Pinterest and who knows what else? What we really want to do is simply pass a color and define states with CSS like hover, focus, disabled etc. This is called dynamic styling because we’re no longer switching between predefined styles — we don’t know what’s coming next. Inline styles might come to mind for tackling this problem, but they don’t support pseudo-classes, attribute selectors, media queries, or the like.

Reusability

Reusing rulesets, media queries etc. is a topic I rarely see mentioned lately because it’s been solved by preprocessors like Sass and Less. But I’d still like to revisit it in this series.

I will list some techniques for dealing with these challenges along with their limitations in both parts of this series. No technique is superior to the others and they aren’t even mutually exclusive; you can choose one or combine them, depending on what you decide will improve the quality of your project.

Setup

We’ll demonstrate different styling techniques using an example component called Photo. We’ll render a responsive image that may have rounded corners while displaying alternative text as a caption. It will be used like this:

<Photo publicId="balloons" alt="Hot air balloons!" rounded />

Before building the actual component, we’ll abstract away the srcSet attribute to keep the example code brief. So, let’s create a utils.js file with two utilities for generating images of different widths using Cloudinary:

import { Cloudinary } from 'cloudinary-core'  const cl = Cloudinary.new({ cloud_name: 'demo', secure: true })  export const getSrc = ({ publicId, width }) =>   cl.url(publicId, { crop: 'scale', width })  export const getSrcSet = ({ publicId, widths }) => widths   .map(width => `$  {getSrc({ publicId, width })} $  {width}w`)   .join(', ')

We set up our Cloudinary instance to use the name of Cloudinary’s demo cloud, as well as its url method to generate URLs for the image publicId according to the specified options. We’re only interested in modifying the width in this component.

We’ll use these utilities for the src and srcset attributes, respectively:

getSrc({ publicId: 'balloons', width: 200 }) // => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons'  getSrcSet({ publicId: 'balloons', widths: [200, 400] }) // => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons 200w,       https://res.cloudinary.com/demo/image/upload/c_scale,w_400/balloons 400w'

If you’re unfamiliar with srcset and sizes attributes, I suggest reading a bit about responsive images first. That way, you’ll have an easier time following the examples.

CSS-in-JS

CSS-in-JS is a styling approach that abstracts the CSS model to the component level, rather than the document level. This idea is that CSS can be scoped to a specific component — and only that component — to the extent that those specific styles aren’t shared with or leaked to other components, and further, called only when they’re needed. CSS-in-JS libraries create styles at runtime by inserting <style> tags in the <head>.

One of the first libraries to put this concept to use is JSS. Here is and example employing its syntax:

import React from 'react' import injectSheet from 'react-jss' import { getSrc, getSrcSet } from './utils'  const styles = {   photo: {     width: 200,     '@media (min-width: 30rem)': {       width: 400,     },     borderRadius: props => (props.rounded ? '1rem' : 0),   }, }  const Photo = ({ classes, publicId, alt }) => (   <figure>     <img       className={classes.photo}       src={getSrc({ publicId, width: 200 })}       srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}       sizes="(min-width: 30rem) 400px, 200px"     />     <figcaption>{alt}</figcaption>   </figure> ) Photo.defaultProps = {   rounded: false, }  export default injectSheet(styles)(Photo)

At first glance, the styles object looks like CSS written in object notation with additional features, like passing a function to set the value based on props. The generated classes are unique, so you never need to worry about them clashing with other styles. In other words, you get scoping for free! This is how most CSS-in-JS libraries work — of course, with some twists in features and syntax that we’ll cover as we go.

You can see by the attributes that the width of our rendered image starts at 200px, then when the viewport width becomes at least 30rem, the width increases to 400px wide. We generated an extra 800 source to cover even larger screen densities:

  • 1x screens will use 200 and 400
  • 2x screens will use 400 and 800

styled-components is another CSS-in-JS library, but with a much more familiar syntax that cleverly uses tagged template literals instead of objects to look more like CSS:

import React from 'react' import styled, { css } from 'styled-components' import { getSrc, getSrcSet } from './utils'  const mediaQuery = '(min-width: 30rem)'  const roundedStyle = css`   border-radius: 1rem; `  const Image = styled.img`   width: 200px;   @media $  {mediaQuery} {     width: 400px;   }   $  {props => props.rounded && roundedStyle};    const Photo = ({ publicId, alt, rounded }) => (   <figure>     <Image       src={getSrc({ publicId, width: 200 })}       srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}       sizes={`$  {mediaQuery} 400px, 200px`}       rounded={rounded}     />     <figcaption>{alt}</figcaption>   </figure> ) Photo.defaultProps = {   rounded: false, }  export default Photo

We often create semantically-neutral elements like <div> and <span> solely for styling purposes. This library, and many others, allow us to create and style them in a single motion.

My favorite benefit of this syntax is that it’s like regular CSS, minus interpolations. This means that we can migrate our CSS code more easily and we get to use our existing muscle memory instead of having to familiarize ourselves with writing CSS in the object syntax.

Notice that we can interpolate almost anything into our styles. This specific example demonstrates how we can save the media query in the variable and reuse it in multiple places. Responsive images are an excellent use case for this because the sizes attribute contains basically CSS, so we can use JavaScript to make the code more DRY.

Let’s say that we decided we want to visually hide the caption, but still make it accessible for screen readers. I know that a better way of achieving this would be to use an alt attribute instead, but let’s use a different way for the sake of this example. We can use a library of style mixins called polished — it works great with CSS-in-JS libraries making it great for our example. This library includes a mixin called hideVisually which does exactly what we want and we can use it by interpolating its return value:

import { hideVisually } from 'polished'  const Caption = styled.figcaption`   $  {hideVisually()}; `  <Caption>{alt}</Caption>

Even though hideVisually outputs an object, the styled-components library knows how to interpolate it as styles.

CSS-in-JS libraries have many advanced features like theming, vendor prefixing and even inlining critical CSS, which makes it easy to stop writing CSS files entirely. At this point, you can start to see why CSS-in-JS becomes an enticing concept.

Downsides and limitations

The obvious downside to CSS-in-JS is that it introduces a runtime: the styles need to be loaded, parsed and executed via JavaScript. Authors of CSS-in-JS libraries are adding all kinds of smart optimizations, like Babel plugins, but some runtime costs will nevertheless exist.

It’s also important to note that these libraries aren’t being parsed by PostCSS because PostCSS wasn’t designed to be brought into the runtime. Many use stylis instead as a result because it’s much faster. This means that we unfortunately can’t use PostCSS plugins.

The last downside I’ll mention is the tooling. CSS-in-JS is evolving at a really fast rate and text editor extension, linters, code-formatters etc. need to play catch-up with new features to stay on par. For example, people are using the VS Code extension styled-components for similar CSS-in-JS libraries like emotion, even though they don’t all have the same features. I’ve even seen API choices of proposed features being influenced by the goal of retaining syntax highlighting!

The future

There are two new CSS-in-JS libraries, Linaria and astroturf, that have managed zero runtime by extracting CSS into files. Their APIs are similar to styled-components, but they vary in features and goals.

The goal of Linaria is to mimic the API of CSS-in-JS libraries like styled-components by having built-in features like scoping, nesting and vendor prefixing. Conversely, astroturf is built upon CSS Modules, has limited interpolation capabilities, and encourages using a CSS ecosystem instead of deferring to JavaScript.

I built Gatsby plugins for both libraries if you want to play with them:

Two things to have in mind when using these libraries:

  1. having actual CSS files means that we can process them with familiar tools like PostCSS
  2. Linaria uses custom properties (a.k.a. CSS variables) under the hood, be sure to take their browser support into consideration before using this library

Conclusion

CSS-in-JS are all-in-one styling solutions for bridging the gap between CSS and JavaScript. They are easy to use and they contain useful built-in optimizations — but all of that comes at a cost. Most notably, by using CSS-in-JS, we’re essentially ejecting from the CSS ecosystem and deferring to JavaScript to solve our problems.

Zero-runtime solutions mitigate some of the downsides by bringing back the CSS tools, which ascends the CSS-in-JS discussion to a much more interesting level. What are the actual limitations of preprocessing tools compared to CSS-in-JS? This will be covered in the next part of this series.

Article Series:

  1. CSS-in-JS (This post)
  2. CSS Modules, PostCSS and the Future of CSS (Coming tomorrow!)

The post Bridging the Gap Between CSS and JavaScript: CSS-in-JS appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]