Web Standards

A Bare-Bones Approach to Versatile and Reusable Skeleton Loaders

Css Tricks - Wed, 03/03/2021 - 5:51am

UI components like spinners and skeleton loaders make waiting for a page load less frustrating and might even affect how loading times are perceived when used correctly. They won’t completely prevent users from abandoning the website, but they might encourage them to wait a bit longer. Animated spinners are used in most cases since they are easy to implement and they generally do a good enough job. Skeleton loaders have a limited use-case and might be complex to implement and maintain, but they offer an improved loading experience for those specific use-cases.

I’ve noticed that developers are either unsure when to use skeleton loaders to enhance the UX or do not know how to approach the implementation. More common examples of skeleton loaders around the web are not all that reusable or scalable. They are usually tailor-made for a single component and cannot be applied to anything else. That is one of the reasons developers use regular spinners instead and avoid the potential overhead in the code. Surely, there must be a way to implement skeleton loaders in a more simple, reusable, and scalable way.

Spinner elements and skeleton loaders

A spinner (or progress bar) element is the simplest and probably most commonly used element to indicate a loading state. A spinner might look better than a blank page, but it won’t hold user’s attention for long. Spinners tell the user that something will load eventually. Users have to passively wait for content to load, meaning that they are unable to interact with other elements on the page or consume any other content on the page. Spinners take up the entire screen space and no content is available to the user.

The spinner element is displayed and covers the entire screen until all content has finished loading.

However, skeleton loaders (or skeleton screens) tell the user that the content is about to load and they might provide a better loading UX than a simple spinner. Empty boxes (with a solid color or gradient background) are used as a placeholder for the content that is being loaded. In most cases, content is gradually being loaded which allows users to maintain a sense of progress and perception that a page load is faster than it is. Users are actively waiting, meaning that they can interact with the page or consume at least some part of the content while the rest is loading.

Empty boxes (with a solid color or gradient background) are used as a placeholder while content is being gradually loaded. Text content is loaded and displayed first, and images are loaded and displayed after that.

It’s important to note that loading components should not be used to address performance issues. If a website is experiencing performance issues due to the problem that can be addressed (un-optimized assets or code, back-end performance issues, etc.), they should be fixed first. Loading elements won’t prevent users from abandoning websites with poor performance and high loading times. Loading elements should be used as a last resort when waiting is unavoidable and when loading delay is not caused by unaddressed performance issues.

Using skeleton loaders properly

Skeleton loaders shouldn’t be treated as a replacement for full-screen loading elements but instead when specific conditions for content and layout have been met. Let’s take this step-by-step and see how to use loading UI components effectively and how to know when to go with skeleton loaders instead of regular spinners.

Is loading delay avoidable?

The best way to approach loading in terms of UX is to avoid it altogether. We need to make sure that loading delay is unavoidable and is not the result of the aforementioned performance issues that can be fixed. The main priority should always be performance improvements and reducing the time needed to fetch and display the content.

Is loading initiated by the user and is the feedback required?

In some cases, user actions might initiate additional content to load. Some examples include lazy-loading content (e.g. images) in the user’s viewport while scrolling, loading content on a button click, etc. We need to include a loading element for cases where a user needs to get some kind of feedback for their actions that have initiated the loading process.

As seen in the following mockup, without a loading element to provide feedback, a user doesn’t know that their actions have initiated any loading process that is happening in the background.

We are asynchronously loading the content in a modal when the button is clicked. In the first example, no loading element is displayed and users might think that their click hasn’t been registered. In the second example, users get the feedback that their click has been registered and that the content is being loaded. Is the layout consistent and predictable?

If we’ve decided to go with a loader element, we now need to choose what type of loader element best fits our use-case. Skeleton loaders are most effective in cases when we can predict the type and layout of the content that is being loaded in. If the skeleton loader layout doesn’t accurately represent the loaded content’s layout to some degree, the sudden change may cause layout shift and leave the user confused and disoriented. Use skeleton loaders for elements with predictable content for consistent layouts.

The grid layout on the left (taken from discogs.com) represents an ideal use-case for skeleton loaders, while the comments example on the right (taken from CSS-Tricks) is an ideal use-case for spinners. Is there content on the page that is immediately available to the user?

Skeleton loaders are most effective when there are sections or page elements already present on the page while the skeleton loader is active and additional content is actively loading. Gradually loading in content means that static content is available on page load and asynchronously-loaded content is displayed as it becomes available (for example, the first text is loaded and images after that). This approach ensures that the user maintains a sense of progression and is expecting the content to finish loading at any moment. Having the entire screen covered in skeleton loaders without any content present and without gradual content loading is not significantly better than having the screen covered by a full-page spinner or progress bar.

The mockup on the left shows a skeleton loader covering all elements until everything has loaded. The mockup on the right shows a skeleton loader covering only content that is being asynchronously loaded. The page is usable since they have a part of the website’s content displayed and the user maintains a sense of progression. Creating robust skeleton loaders

Now that we know when to use skeleton loaders and how to use them properly, we can finally do some coding! But first, let me tell you how we are going to approach this.

Most skeleton loading examples from around the web are, in my opinion, over-engineered and high-maintenance. You might have seen one of those examples where skeleton screens are created as a separate UI component with separate CSS styles or created with elaborate use of CSS gradients to simulate the final layout. Creating and maintaining a separate skeleton loader or skeleton styles for each UI component can become serious overhead in development with such a highly-specific approach. This is especially true when we look at scalability, as any change to the existing layout also involves updating the skeleton layout or styles.

Let’s try and find a bare-bones approach to implementing skeleton loading that should work for most use-cases and will be easy to implement, reuse and maintain!

Card grid component

We’ll use regular HTML, CSS, and JavaScript for implementation, but the overall approach can be adapted to work with most tech stacks and frameworks.

We are going to create a simple grid of six card elements (three in each row) as an example, and simulate asynchronous content loading with a button click.

We’ll use the following markup for each card. Notice that we are setting width and height on our images and using a 1px transparent image as a placeholder. This will ensure that the image skeleton loader is visible until the image has been loaded.

<div class="card"> <img width="200" height="200" class="card-image" src="..." /> <h3 class="card-title"></h3> <p class="card-description"></p> <button class="card-button">Card button</button> </div>

Here is our card grid example with some layout and presentation styles applied to it. Content nodes are added or removed from the DOM depending on the loading state using JavaScript to simulate asynchronous loading.

CodePen Embed Fallback Skeleton loader styles

Developers usually implement skeleton loaders by creating replacement skeleton components (with dedicated skeleton CSS classes) or by recreating entire layouts with CSS gradients. Those approaches are inflexible and not reusable at all since individual skeleton loaders are tailor-made for each layout. Considering that layout styles (spacings, grid, inline, block and flex elements, etc.) are already present from the main component (card) styles, skeleton loaders just need to replace the content, not the entire component!

With that in mind, let’s create skeleton loader styles that become active only when a parent class is set and use CSS properties that only affect the presentation and content. Notice that these styles are independent from the layout and content of the element they’re being applied to, which should make them highly reusable.

.loading .loading-item { background: #949494 !important; /* Customizable skeleton loader color */ color: rgba(0, 0, 0, 0) !important; border-color: rgba(0, 0, 0, 0) !important; user-select: none; cursor: wait; } .loading .loading-item * { visibility: hidden !important; } .loading .loading-item:empty::after, .loading .loading-item *:empty::after { content: "\00a0"; }

Base parent class .loading is used to activate the skeleton loading styles. The .loading-item class is used to override element’s presentational styles to display a skeleton element. This also ensures that the layout and dimensions of the element are preserved and inherited by the skeleton. Additionally, .loading-item makes sure that all child elements are hidden and have at least an empty space character (\00a0) inside it so that element is displayed and its layout is rendered.

Let’s add skeleton loader CSS classes to our markup. Notice how no additional HTML elements have been added, we are only applying additional CSS classes.

<div class="card loading"> <img width="200" height="200" class="card-image loading-item" src="..." /> <h3 class="card-title loading-item"></h3> <p class="card-description loading-item"></p> <button class="card-button loading-item">Card button</button> </div>

Once the content has loaded, we only need to remove loading CSS class from the parent component to hide the skeleton loader styles.

CodePen Embed Fallback

These few lines should work for most, if not all, use cases depending on your custom CSS since these skeleton loaders inherit the layout from the main (content) styles and create a solid box that replaces the content by filling out the empty space left in the layout. We’re also applying these classes to non-empty elements (button with text) and replacing it with a skeleton. A button might have the text content ready from the start, but it might be missing additional data that is required for it to function correctly, so we should also hide it while that data is loaded in.

This approach can also adapt to most changes in the layout and markup. For example, if we were to remove the description part of the card or decide to move the title above the image, we wouldn’t need to make any changes to the skeleton styles, since skeleton responds to all changes in the markup.

Additional skeleton loading override styles can be applied to a specific element simply by using the .loading .target-element selector.

.loading .button, .loading .link { pointer-events: none; } Multi-line content and layout shifts

As you can see, the previous example works great with cards and the grid layout we’re using, but notice that the page content slightly jumps the moment it is loaded. This is called a layout shift. Our .card-description component has a fixed height with three lines of text, but the skeleton placeholder spans only one line of text. When the extra content is loaded, the container dimensions change and the overall layout is shifted as a result. Layout shift is not bad in this particular case, but might confuse and disorient the user in more severe cases.

This can be easily fixed directly in the placeholder element. Placeholder content is going to get replaced by the content that is being loaded anyway, so we can add anything we need inside it. So, let’s add a few <br /> elements to simulate multiple lines of text.

<div class="card loading"> <img width="200" height="200" class="card-image loading-item" src="..." /> <h3 class="card-title loading-item"></h3> <p class="card-description loading-item"><br/><br/><br/></p> <button class="card-button loading-item">Card button</button> </div>

We’re using basic HTML to shape the skeleton and change the number of lines inside it. Other examples on the web might achieve this using CSS padding or some other way, but this introduces overhead in the code. After all, content can span any number of lines and we would want to cover all those cases.

As an added benefit of using <br /> elements, they inherit the CSS properties that affect the content dimensions (e.g. the line height, font size, etc.). Similarly, &nbsp characters can be used to add additional spacing to the inline placeholder elements.

CodePen Embed Fallback

With a few lines of CSS, we’ve managed to create versatile and extensible skeleton loader styles that can be applied to a wide range of UI components. We’ve also managed to come up with a simple way of vertically extending the skeleton boxes to simulate content that spans multiple lines of text.

To further showcase how versatile this skeleton loader CSS snippet is, I’ve created a simple example where I’ve added the snippet to a page using Bootstrap CSS framework without any additional changes or overrides. Please note that in this example no text content will be displayed or simulated, but it will work as in previous examples. This is just to showcase how styles can be easily integrated with other CSS systems.

CodePen Embed Fallback

Here is an additional example to showcase how these styles can be applied to various elements, including input, label and a elements.

CodePen Embed Fallback Accessibility requirements

We should also take accessibility (a11y) requirements into account and make sure that the content is accessible to all users. Skeleton loaders without a11y features might disorientate and confuse users that have visual disabilities or browse the web using screen readers.

Contrast

You might have noticed that the skeleton loaders in our example have a high contrast and they look more prominent compared to the common low-contrast skeleton loaders in the wild. Some users might experience difficulties perceiving and using low-contrast UI components. That is why Web Content Accessibility Guidelines (WCAG) specify a 3:1 minimum contrast for non-text UI components.

The upcoming “Media queries level 5” draft contains a prefers-contrast media query that will enable us to detect user contrast preferences. This will give us more flexibility by allowing us to assign a high-contrast background color to skeleton loaders for users that request a high-contrast version, and have a subtle low-contrast background color for others. I would suggest implementing high-contrast skeleton loaders by default until the prefers-contrast media query becomes more widely supported.

/* NOTE: as of the time of writing this article, this feature is not supported in browsers, so this code won't work */ .loading .loading-item { /* Default skeleton loader styles */ } @media (prefers-contrast: high) { .loading .loading-item { /* High-contrast skeleton loader styles */ } } Animations

Depending on the design and the implementation of animated skeleton loaders, users suffering from visual disorders might feel overwhelmed by the animations and find the site unusable. It’s always a good idea to prevent animations from firing for users that prefer reduced motion. This media query is widely-supported in modern browsers and can be used without any caveats.

.loading .loading-item { animation-name: skeleton; background: /* animated gradient background */; } @media (prefers-reduced-motion) { .loading .loading-item { animation: none !important; background: /* solid color */; } } Screen readers

To better support screen readers, we need to update our HTML with ARIA (Accessible Rich Internet Applications) markup. This markup won’t affect our content or presentation, but it will allow users using screen readers to better understand and navigate around our website content, including our skeleton loaders.

Adrian Roselli has very detailed research on the topic of accessible skeleton loaders for cases when skeleton loaders are implemented as separate UI components. For our example, I’ll use the aria-hidden attribute in combination with visually hidden text to give screen readers a hint that content is in the process of loading. Screen readers will ignore the content with aria-hidden="true", but they’ll use the visually-hidden element to indicate the loading state to the user.

Let’s update our cards with the ARIA markup and loading indicator element.

<div class="card loading"> <span aria-hidden="false" class="visually-hidden loading-text">Loading... Please wait.</span> <img width="200" height="200" class="card-image loading-item" aria-hidden="true" src="..." /> <h3 class="card-title loading-item" aria-hidden="true"></h3> <p class="card-description loading-item" aria-hidden="true"><br/><br/><br/></p> <button class="card-button loading-item" aria-hidden="true">Card button</button> </div>

We also could have applied aria-hidden to the grid container element and add a single visually hidden element before the container markup, but I wanted to keep the markup examples focused on a single card element rather than on the full grid, so I went with this version.

When the content has finished loading and is displayed in the DOM, we need to toggle aria-hidden to false for content containers and toggle aria-hidden to true on a visually hidden loading text indicator.

Here’s the finished example CodePen Embed Fallback That’s a wrap

Implementing skeleton loaders requires a slightly different approach than implementing regular loading elements, like spinners. I’ve seen numerous examples around the web that implement skeleton loaders in a way that severely limits their reusability. These over-engineered solutions usually involve creating separate skeleton loader UI components with dedicated (narrow-scope) skeleton CSS markup or recreating the layout with CSS gradients and magic numbers. We’ve seen that only the content needs to be replaced with the skeleton loaders, and not the entire component.

We’ve managed to create simple, versatile, and reusable skeleton loaders that inherit the layout from the default styles and replace the content inside the empty containers with solid boxes. With just two CSS classes, these skeleton loaders can easily be added to virtually any HTML element and extended, if needed. We’ve also made sure that this solution is accessible and doesn’t bloat the markup with additional HTML elements or duplicated UI components.

Thank you for taking the time to read this article. Let me know your thoughts on this approach and let me know how did you approach creating skeleton loaders in your projects.

The post A Bare-Bones Approach to Versatile and Reusable Skeleton Loaders appeared first on CSS-Tricks.

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

React Without Build Tools

Css Tricks - Wed, 03/03/2021 - 3:42am

Jim Nielsen:

I think you’ll find it quite refreshing to use React A) with a JSX-like syntax, and B) without any kind of build tooling.

Refreshing indeed:

CodePen Embed Fallback

It’s not really the React that’s the hard part to pull off without build tools (although I do wonder what we lose from not tree shaking), it’s the JSX. I’m so used to JSX I think it would be hard for me to work on a front-end JavaScript project without it. But I know some people literally prefer a render function instead. If that’s the case, you could use React.createComponent directly and skip the JSX, or in the case of Preact, use h:

CodePen Embed Fallback

I work on a project that uses Mithril for the JavaScript templating which is a bit like that, and it’s not my favorite syntax, but you totally get used to it (and it’s fast):

CodePen Embed Fallback

Direct Link to ArticlePermalink

The post React Without Build Tools appeared first on CSS-Tricks.

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

How to Animate the Details Element

Css Tricks - Tue, 03/02/2021 - 10:58am

Here’s a nice simple demo from Moritz Gießmann on animating the triangle of a <details> element, which is the affordance that tells people this thing can be opened. Animating it, then is another kind of affordance that tells people this thing is opening now.

The tricks?

  1. Turn off the default triangle: details summary::-webkit-details-marker { display:none; }. You can’t animate that one.
  2. Make a replacement triangle with the CSS border trick and a pseudo element.
  3. Animate the new triangle when the state is open: details[open] > summary::before { transform: rotate(90deg); }.
CodePen Embed Fallback

This only animates the triangle. The content inside still “snaps” open. Wanna smooth things out? Louis Hoebregts’ “How to Animate the Details Element Using WAAPI” covers that.

CodePen Embed Fallback

Here’s a fork where I’ll combine them just because:

CodePen Embed Fallback

I see Moritz put the cursor: pointer; on the summary as well like Greg Gibson suggests.

The post How to Animate the Details Element appeared first on CSS-Tricks.

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

The Best Font Loading Strategies and How to Execute Them

Css Tricks - Tue, 03/02/2021 - 6:00am

Zach Leatherman wrote up a comprehensive list of font loading strategies that have been widely shared in the web development field. I took a look at this list before, but got so scared (and confused), that I decided not to do anything at all. I don’t know how to begin loading fonts the best way and I don’t want to feel like an idiot going through this list.

Today, I gave myself time to sit down and figure it out. In this article, I want to share with you the best loading strategies and how to execute all of them.

The best strategies

Zach recommends two strategies in his article:

  1. FOUT with Class – Best approach for most situations. (This works whether we use a font-hosting company or hosting our own fonts.)
  2. Critical FOFT – Most performant approach. (This only works if we host our own fonts.)

Before we dive into these two strategies, we need to clear up the acronyms so you understand what Zach is saying.

FOIT, FOUT, FOFT

The acronyms are as follows:

  • FOIT means flash of invisible text. When web fonts are loading, we hide text so users don’t see anything. We only show the text when web fonts are loaded.
  • FOUT means flash of unstyled text. When web fonts are loading, we show users a system font. When the web font gets loaded, we change the text back to the desired web font.
  • FOFT means flash of faux text. This one is complicated and warrants more explanation. We’ll talk about it in detail when we hit the FOFT section.
Self-hosted fonts vs. cloud-hosted fonts

There are two main ways to host fonts:

  1. Use a cloud provider.
  2. Self-host the fonts.

How we load fonts differs greatly depending on which option you choose.

Loading fonts with cloud-hosted fonts

It’s often easier to cloud-hosted fonts. The provider will give us a link to load the fonts. We can simply plunk this link into our HTML and we’ll get our web font. Here’s an example where we load web fonts from Adobe Fonts now (previously known as Typekit).

<link rel="stylesheet" href="https://use.typekit.net/your-kit-id.css">

Unfortunately, this isn’t the best approach. The href blocks the browser. If loading the web font hangs, users won’t be able to do anything while they wait.

Typekit used to provide JavaScript that loads a font asynchronously. It’s a pity they don’t show this JavaScript version anymore. (The code still works though, but I have no idea when, or if, it will stop working.)

Loading fonts from Google Fonts is slightly better because it provides font-display: swap. Here’s an example where we load Lato from Google Fonts. (The display=swap parameter triggers font-display: swap).

<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" /> Loading fonts with self-hosted fonts

You can only self-host your fonts if you have the license to do so. Since fonts can be expensive, most people choose to use a cloud provider instead.

There’s a cheap way to get fonts though. Design Cuts runs font deals occasionally where you can get insanely high-quality fonts for just $29 per bundle. Each bundle can contain up to 12 fonts. I managed to get classics like Claredon, DIN, Futura, and a whole slew of fonts I can play around by camping on their newsletter for these deals.

If we want to self-host fonts, we need to understand two more concepts: @font-face and font-display: swap.

@font-face

@font-face lets us declare fonts in CSS. If we want to self-host fonts, we need to use @font-face to declare your fonts.

In this declaration, we can specify four things:

  • font-family: This tells CSS (and JavaScript) the name of our font.
  • src: Path to find the font so they can get loaded
  • font-weight: The font weight. Defaults to 400 if omitted.
  • font-style: Whether to italicize the font. Defaults to normal if omitted.

For src, we need to specify several font formats. For a practical level of browser support, we can use woff2 and woff.

Here’s an example where we load Lato via @font-face.

@font-face { font-family: Lato; src: url('path-to-lato.woff2') format('woff2'), url('path-to-lato.woff') format('woff'); } @font-face { font-family: Lato; src: url('path-to-lato-bold.woff2') format('woff2'), url('path-to-lato-bold.woff') format('woff'); font-weight: 700; } @font-face { font-family: Lato; src: url('path-to-lato-italic.woff2') format('woff2'), url('path-to-lato-italic.woff') format('woff'); font-style: italic; } @font-face { font-family: Lato; src: url('path-to-lato-bold-italic.woff2') format('woff2'), url('path-to-lato-bold-italic.woff') format('woff'); font-weight: 700; font-style: italic; }

If you don’t have woff2 or woff files, you can upload your font files (either Opentype or Truetype) into a font-face generator to get them.

Next, we declare the web font in a font-family property.

html { font-family: Lato, sans-serif; }

When browsers parse an element with the web font, they trigger a download for the web font.

font-display: swap

font-display takes one of four possible values: auto, swap, fallback, and optional. swap instructs browsers to display the fallback text before web fonts get loaded. In other words, swap triggers FOUT for self-hosted fonts. Find out about other values from in the CSS-Tricks almanac.

Browser support for font-display: swap is pretty good nowadays so we can use it in our projects.

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

DesktopChromeFirefoxIEEdgeSafari6058No7911.1Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari88868111.3-11.4 FOUT vs. FOUT with Class

FOUT means flash of unstyled text. You always want FOUT over FOIT (flash of invisible text) because it’s better for users to read words written with system fonts compared to words written with invisible ink. We mentioned earlier that font-display: swap gives you the ability to use FOUT natively.

FOUT with Class gives you the same results—FOUT—but it uses JavaScript to load the fonts. The mechanics are as follows:

  • First: Load system fonts.
  • Second: Load web fonts via JavaScript.
  • Third: When web fonts are loaded, add a class to the <html> tag.
  • Fourth: Switch the system font with the loaded web font.

Zach recommends loading fonts via JavaScript even though font-display: swap enjoys good browser support. In other words, Zach recommends FOUT with Class over @font-face + font-display: swap.

He recommends FOUT with Class because of these three reasons:

  1. We can group repaints.
  2. We can adapt to user preferences.
  3. We can skip font-loading altogether if users have a slow connection.

I’ll let you dive deeper into the reasons in another article. While writing this article, I found a fourth reason to prefer FOUT with Class: We can skip font-loading when users already have the font loaded in their system. (We do this with sessionStorage as we’ll see below.)

FOUT with Class (for cloud-hosted fonts)

First, we want to load our fonts as usual from your cloud-hosting company. Here’s an example where I loaded Lato from Google Fonts:

<head> <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" /> </head>

Next, we want to load fonts via JavaScript. We’ll inject a script into the <head> section since the code footprint is small, and it’s going to be asynchronous anyway.

<head> <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" /> <script src="js/load-fonts.js"></script> </head>

In load-fonts.js, we want to use the CSS Font Loading API to load the font. Here, we can use Promise.all to load all fonts simultaneously. When we do this, we’re grouping repaints.

The code looks like this:

if ('fonts' in document) { Promise.all([ document.fonts.load('1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => () { document.documentElement.classList.add('fonts-loaded') }) }

When fonts are loaded, we want to change the body text to the web font. We can do this via CSS by using the .fonts-loaded class.

/* System font before [[webfont]]s get loaded */ body { font-family: sans-serif; } /* Use [[webfont]] when [[webfont]] gets loaded*/ .fonts-loaded body { font-family: Lato, sans-serif; }

Pay attention here: We need to use the .fonts-loaded class with this approach.

We cannot write the web font directly in the <body>‘s font-family; doing this will trigger fonts to download, which means you’re using @font-face + font-display: swap. It also means the JavaScript is redundant.

/* DO NOT DO THIS */ body { font-family: Lato, sans-serif; }

If users visit additional pages on the site, they already have the fonts loaded in their browser. We can skip the font-loading process (to speed things up) by using sessionStorage.

if (sessionStorage.fontsLoaded) { document.documentElement.classList.add('fonts-loaded') } else { if ('fonts' in document) { Promise.all([ document.fonts.load('1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => () { document.documentElement.classList.add('fonts-loaded') }) } }

If we want to optimize the JavaScript for readability, we can use an early return pattern to reduce indentation.

function loadFonts () { if (sessionStorage.fontsLoaded) { document.documentElement.classList.add('fonts-loaded') return } if ('fonts' in document) { Promise.all([ document.fonts.load('1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => () { document.documentElement.classList.add('fonts-loaded') }) } loadFonts() FOUT with Class (for self-hosted fonts)

First, we want to load our fonts as usual via @font-face. The font-display: swap property is optional since we’re loading fonts via JavaScript.

@font-face { font-family: Lato; src: url('path-to-lato.woff2') format('woff2'), url('path-to-lato.woff') format('woff'); } /* Other @font-face declarations */

Next, we load the web fonts via JavaScript.

if ('fonts' in document) { Promise.all([ document.fonts.load('1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => () { document.documentElement.classList.add('fonts-loaded') }) }

Then we want to change the body text to the web font via CSS:

/* System font before webfont is loaded */ body { font-family: sans-serif; } /* Use webfont when it loads */ .fonts-loaded body { font-family: Lato, sans-serif; }

Finally, we skip font loading for repeat visits.

if ('fonts' in document) { Promise.all([ document.fonts.load('1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => () { document.documentElement.classList.add('fonts-loaded') }) } CSS Font Loader API vs. FontFaceObserver

The CSS Font Loader API has pretty good browser support, but it’s a pretty cranky API.

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

DesktopChromeFirefoxIEEdgeSafari3541No7910Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari88868110.0-10.2

So, if you need to support older browsers (like IE 11 and below), or if you find the API weird and unwieldy, you might want to use Bramstein’s FontFaceObserver. It’s super lightweight so there’s not much harm.

The code looks like this. (It’s much nicer compared to the CSS Font Loader API).

new FontFaceObserver('lato') .load() .then(_ => { document.documentElement.classList.add('fonts-loaded') })

Make sure to use fontfaceobserver.standalone.js if you intend to it to load fonts on browsers that don’t support Promises.

FOFT

FOFT means flash of faux text. The idea here is we split font loading into three stages:

  • Step 1: Use fallback font when web fonts aren’t loaded yet.
  • Step 2: Load the Roman (also called “book” or “regular”) version of the font first. This replaces most of the text. Bold and italics will be faked by the browser (hence “faux text”).
  • Step 3: Load the rest of the fonts

Note: Zach also calls this Standard FOFT.

Using Standard FOFT

First, we load the Roman font.

@font-face { font-family: LatoInitial; src: url('path-to-lato.woff2') format('woff2'), url('path-to-lato.woff') format('woff'); unicode-range: U+65-90, U+97-122; } .fonts-loaded-1 body { font-family: LatoInitial; } if('fonts' in document) { document.fonts.load("1em LatoInitial") .then(_ => { document.documentElement.classList.add('fonts-loaded-1') }) }

Then, we load other fonts.

Pay attention here: we’re loading Lato again, but this time, we set font-family to Lato instead of LatoInitial.

/* Load this first */ @font-face { font-family: LatoInitial; src: url('path-to-lato.woff2') format('woff2'), url('path-to-lato.woff') format('woff'); unicode-range: U+65-90, U+97-122; } /* Load these afterwards */ @font-face { font-family: Lato; src: url('path-to-lato.woff2') format('woff2'), url('path-to-lato.woff') format('woff'); unicode-range: U+65-90, U+97-122; } /* Other @font-face for different weights and styles*/ .fonts-loaded-1 body { font-family: LatoInitial; } .fonts-loaded-2 body { font-family: Lato; } if ('fonts' in document) { document.fonts.load('1em LatoInitial') .then(_ => { document.documentElement.classList.add('fonts-loaded-1') Promise.all([ document.fonts.load('400 1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => { document.documentElement.classList.add('fonts-loaded-2') }) }) }

Again, we can optimize it for repeat views.

Here, we can add fonts-loaded-2 to the <html> straightaway since fonts are already loaded.

function loadFonts () { if (sessionStorage.fontsLoaded) { document.documentElement.classList.add('fonts-loaded-2') return } if ('fonts' in document) { document.fonts.load('1em Lato') .then(_ => { document.documentElement.classList.add('fonts-loaded-1') Promise.all([ document.fonts.load('400 1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => { document.documentElement.classList.add('fonts-loaded-2') // Optimization for Repeat Views sessionStorage.fontsLoaded = true }) }) } } Critical FOFT

The “critical” part comes from ‘critical css” (I believe) – where we load only essential CSS before loading the rest. We do this because it improves performance.
When it comes to typography, the only critical things are letters A to Z (both capitals and small letters). We can create a subset of these fonts with unicode-range.

When we create this subset, we can also create a separate font file with the necessary characters.

Here’s what @font-face declaration looks like:

@font-face { font-family: LatoSubset; src: url('path-to-optimized-lato.woff2') format('woff2'), url('path-to-optimized-lato.woff') format('woff'); unicode-range: U+65-90, U+97-122; }

We load this subset first.

.fonts-loaded-1 body { font-family: LatoSubset; } if('fonts' in document) { document.fonts.load('1em LatoSubset') .then(_ => { document.documentElement.classList.add('fonts-loaded-1') }) }

And we load other font files later.

.fonts-loaded-2 body { font-family: Lato; } if ('fonts' in document) { document.fonts.load('1em LatoSubset') .then(_ => { document.documentElement.classList.add('fonts-loaded-1') Promise.all([ document.fonts.load('400 1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => { document.documentElement.classList.add('fonts-loaded-2') }) }) }

Again, we can optimize it for repeat views:

function loadFonts () { if (sessionStorage.fontsLoaded) { document.documentElement.classList.add('fonts-loaded-2') return } if ('fonts' in document) { document.fonts.load('1em LatoSubset') .then(_ => { document.documentElement.classList.add('fonts-loaded-1') Promise.all([ document.fonts.load('400 1em Lato'), document.fonts.load('700 1em Lato'), document.fonts.load('italic 1em Lato'), document.fonts.load('italic 700 1em Lato') ]).then(_ => { document.documentElement.classList.add('fonts-loaded-2') // Optimization for Repeat Views sessionStorage.fontsLoaded = true }) }) } } Critical FOFT variants

Zach proposed two additional Critical FOFT variants:

  • Critical FOFT with Data URI
  • Critical FOFT with Preload
Critical FOFT with data URIs

In this variant, we load the critical font (the subsetted roman font) with data URIs instead of a normal link. Here’s what it looks like. (And it’s scary.)

@font-face { font-family: LatoSubset; src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); /* ... */ }

This code looks scary, but there’s no need to be scared. The hardest part here is generating the data URI, and CSS-Tricks has us covered here.

Critical FOFT with preload

In this variant, we add a link with the preload tag to the critical font file. Here’s what it looks like:

<link rel="preload" href="path-to-roman-subset.woff2" as="font" type="font/woff2" crossorigin>

We should only preload one font here. If we load more than one, we can adversely affect the initial load time.

In the strategy list, Zach also mentioned he prefers using a data URI over the preload variant. He only prefers it because browser support for preload used to be bad. Today, I feel that browser support is decent enough to choose preloading over a data URI.

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

DesktopChromeFirefoxIEEdgeSafari5085No7911.1Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari88868111.3-11.4 Final note from Zach

Chris ran this article via Zach and Zach wished he prioritized a JavaScript-free approach in his original article.

I think the article is good but I think my article that its based off of is probably a little dated in a few ways. I wish it prioritized no-JS approaches more when you’re only using one or two font files (or more but only 1 or 2 of each typeface). The JS approaches are kind of the exception nowadays I think (unless you’re using a lot of font files or a cloud provider that doesn’t support font-display: swap)

This switches the verdict a tiny bit, which I’m going to summarize in the next section.

Which font loading strategy to use?

If you use a cloud-hosted provider:

  • Use font-display: swap if the host provides it.
  • Otherwise, use FOUT with class

If you host your web fonts, you have a few choices:

  1. @font-face + font-display: swap
  2. FOUT with Class
  3. Standard FOFT
  4. Critical FOFT

Here’s how to choose between them:

  • Choose @font-face + font-display: swap if you’re starting out and don’t want to mess with JavaScript. It’s the simplest of them all. Also choose this option if you use only few font files (fewer than two files) for each typeface.
  • Choose Standard FOFT if you’re ready to use JavaScript, but don’t want to do the extra work of subsetting the Roman font.
  • Choose a Critical FOFT variant if you want to go all the way for performance.

That’s it! I hope you found all of this useful!

If you loved this article, you may like other articles I wrote. Consider subscribing to my newsletter &#x1f609;. Also, feel free to reach out to me if you have questions. I’ll try my best to help!

The post The Best Font Loading Strategies and How to Execute Them appeared first on CSS-Tricks.

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

The “Gray Dead Zone” of Gradients

Css Tricks - Mon, 03/01/2021 - 9:16am

Erik D. Kennedy notes an interesting phenomenon of color gradients. If you have a gradient between two colors where the line between them in the color space goes through the zero-saturation middle, you get this “gray dead zone” in the middle.

It’s a real thing. See the gray middle here:

CodePen Embed Fallback

You can also see how colors might not do that, like red and blue here shooting right through purple instead, which you can visualize on that color circle above.

CodePen Embed Fallback

Erik says one solution can be taking a little detour instead of going straight through the gray zone:

His updated gradient tool deals with this by using different “interpolation modes” and easing the gradient with a choice of precision stops. Don’t miss the radial and conic options as well, with the ability to place the centers “offscreen” which can yield pretty cool looks you can’t do any other way.

Oh and speaking of conic gradients, Adam Argyle has a little gallery of possibilities that is pretty unique.

The post The “Gray Dead Zone” of Gradients appeared first on CSS-Tricks.

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

How to Map Mouse Position in CSS

Css Tricks - Mon, 03/01/2021 - 5:45am

Let’s look at how to get the user’s mouse position and map it into CSS custom properties: --positionX and --positionY.

We could do this in JavaScript. If we did, we could do things like make make an element draggable or move a background. But actually, we can still do similar things, but not use any JavaScript!

I’ve used this method as a part of a demo I’ve made for getting a ‘Click and drag’ effect with pure CSS. I used the perspective tips from my pervious article. It’s a pretty neat effect to get this done entirely in CSS, which might have wider usefulness than my demo, so let’s take a look.

The setup

Our first demo is going to use --positionX and --positionY custom properties to set the width and height of an element.

CodePen Embed Fallback

Heads up that we’re only using SCSS here for brevity, but all of this can be done in pure CSS.

This is our initial state. We have here a ‘wrapper’ <div> with a .content class of that is spread to the width and height of the body. This <div> will host the content of our project, and the elements we want to control using the mouse position – in this case, the .square element.

We’ve also added the two custom-properties to the content. we will use the mouse position to set the value of these properties, and then use them set the .square element’s width and height accordingly.

Once we have mapped the custom properties for mouse position, we can use them to do pretty much anything we want . For example, we could use them to set the top / left properties of an absolute positioned element, control a transform property, set the background-position, manipulate colors, or even set the content of a pseudo-element. We’ll see some of these bonus demos at the end of the article.

The grid

The goal is to create an invisible grid on the screen, and use the :hover pseudo-class to map each ‘cell’ to a set of values that we will allocate to the custom properties. So, when the mouse cursor moves to the right side of the screen, the value of the --positionX will be higher; and when it moves to left, it gets lower. We’ll do the same with --positionY: the value will be lower as the cursor moves to the top, and higher when it moves to the bottom.

A few words about the grid size we’re using: We can actually make the grid whatever size we want. The larger it is, the more accurate our custom property values will be. But that also means we will have more cells, which can lead to performance issues. It’s important to maintain proper balance and adjust the grid size to the specific needs of each project.

For now, lets say that we want a 10×10 grid for a total of 100 cells in our markup. (And yes, it’s OK to use Pug for that, even though I won’t in this example.)

<div class="cell"></div> <div class="cell"></div> <div class="cell"></div> <!-- 97 more cells --> <div class="content"> <div class="square"></div> </div>

You’re probably wondering why the .cell elements come before the .content. That’s because of the cascade.

We want to use the .cell class to control the .square, and the way the cascade works (for now) is that an element can only control its children (or descendants) and its siblings (or their descendants) — but only as long as the sibling is after the controlling element.

That means two things:

  1. Each .cell must come before the element we want to control (in this case, the .square).
  2. We can’t put those .cells in a container, because if we do, the .content is no longer their sibling.
Positioning the cells

There are a few ways to position the .cells. we can position: absolute them and offset them with the top and left properties. Or we can translate them into position using transform. But the easiest option is likely using display: grid.

body { background-color: #000; height: 100vh; display: grid; grid-template: repeat(10, 1fr) / repeat(10, 1fr); } .cell { width: 100%; height: 100%; border: 1px solid gray; z-index: 2; }
  • The border is just temporary, so we could see the cells on the screen. we’ll remove it later on.
  • the z-index is important, because we want the cells to be in front of the content.

Here’s what we have so far:

CodePen Embed Fallback Adding values

We want to use .cell to set the --positionX and --positionY values.

When we hover over a .cell that is in the first (left) column, the value of --positionX should be 0. When we hover over a .cell in the second column, the value should be 1. It should be 2 in the third column, and so on.

The same goes for the y-axis. When we hover over a .cell that is in the first (top) row, --positionY should be 0, and when we hover over a cell in the second row, the value should be 1, and so on.

The numbers in this image represent the numbers of the cell elements in the grid. If we take just a single .cell as an example — let’s say cell 42 — we can select it using :nth-child():

.cell:nth-child(42) { }

But we need to remember a couple of things:

  1. We only want this selector to work when we hover over the cell, so we will attach :hover to it.
  2. We want to select the .content instead of the cell itself, so we’ll use the General Sibling Combinator (~) to do that.

So now, to set --positionX to 1 and --positionY to 3 for .content when the 42nd cell is hovered, we need to do something like this:

.cell:nth-child(42):hover ~ .content { --positionX: 1; --positionY: 3; }

But who wants to do that 100 times!? There are a few approaches to make things easier:

  1. Use a Sass @for loop to go through all 100 cells, and do some math to set the proper --positionX and --positionY values for each iteration.
  2. Separate the x- and y-axes to select each row and each column individually with :nth-child’s functional notation.
  3. Combine those two approaches and use a Sass @for loop and :nth-child functional notation.

I thought long and hard about what would be the best and easiest approach to take, and while all of then have pros and cons, I landed on the third approach. The amount of code to write, the quality of the compiled code, and the math complexity all went into my thinking. Don’t agree? Tell my why in the comments!

Setting values with a @for loop @for $i from 0 to 10 { .cell:nth-child(???):hover ~ .content { --positionX: #{$i}; } .cell:nth-child(???):hover ~ .content { --positionY: #{$i}; } }

This is the basic loop. We’re going over it 10 times since we have 10 rows and 10 columns. And we’ve separated the x- and y-axes to set --positionX for each column, and the --positionY for each row. All we need to do now is to replace those ??? things with the proper notation to select each row and column.

Let’s start with the x-axis

Going back to our grid image (the one with numbers),We can see that the numbers of all the cells in the second column are multiples of 10, plus 2. The cells in the third column are multiples of 10 plus 3. And so on.

Now let’s ‘translate’ it into the :nth-child functional notation. Here’s how that looks for the second column:

:nth-child(10n + 2)
  • 10n selects every multiple of 10.
  • 2 is the column number.

And for our loop, we will replace the column number with #{$i + 1} to iterate sequentially:

.cell:nth-child(10n + #{$i + 1}):hover ~ .content { --positionX: #{$i}; } Now lets deal with the y-axis

Look again at the grid image and focus on the fourth row. The cell numbers are somewhere between 41 and 50. The cells in the fifth row are between 51 to 60, and so on. To select each row, we’ll need to define its range. For example, the range for the fourth row is:

.cell:nth-child(n + 41):nth-child(-n + 50)
  • (n + 41) is the start of the range.
  • (-n + 50) is the end of the range.

Now we’ll replace the numbers with some math on the $i value. For the start of the range, we get (n + #{10 * $i + 1}), and for the end of the range we get (-n + #{10 * ($i + 1)}).

So the final @for loop is:

@for $i from 0 to 10 { .cell:nth-child(10n + #{$i + 1}):hover ~ .content { --positionX: #{$i}; } .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content { --positionY: #{$i}; } }

The mapping is done! When we hover over elements, --positionX and --positionY change according to the mouse position. That means we can use them to control the elements inside the .content.

Handling the custom properties

OK, so now we have the mouse position mapped to two custom-properties, The next thing is to use them to control the .square element’s width and height values.

Let’s start with the width and say that we want the minimum width of the .square to be 100px (i.e. when the mouse cursor is at the left side of the screen), and we want it to grow 20px for each step the mouse cursor moves to the right.

Using calc(), we can do that:

.square { width: calc(100px + var(--positionX) * 20px); }

And of course, we’ll do the same for the height, but with --positionY instead:

.square { width: calc(100px + var(--positionX) * 20px); height: calc(100px + var(--positionY) * 20px); }

That’s it! Now we have a simple .square element, with a width and height that is controlled by the mouse position. Move your mouse cursor over the window, and see how the square changes its width and height accordingly.

CodePen Embed Fallback

I added a small transition to make the effect smoother. That’s not required, of course. And I’ve also commented out on the .cell border.

Let’s try an alternative method

There might be a case where you want to ‘bypass’ --positionX and --positionY, and set the end value directly inside the @for loop. So, for our example it would look like this:

@for $i from 0 to 10 { .cell:nth-child(10n + #{$i + 1}):hover ~ .content { --squareWidth: #{100 + $i * 20}px; } .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content { --squareHeight: #{100 + $i * 20}px;: #{$i}; } }

Then the .square would use the custom properties like this:

.square { width: var(--squareWidth); height: var(--squareHeight); }

This method is a little more flexible because it allows for more advanced Sass math (and string) functions. That said, the general principle is absolutely the same as what we already covered.

What’s next?

Well, the rest is up to you — and the possibilities are endless! How do you think you’d use it? Can you take it further? Try using this trick in your CSS, and share your work in the comments or let me know about it on Twitter. It will be great to see a collection of these come together.

Here are a few examples to get your hamster wheel turning:

CodePen Embed Fallback CodePen Embed Fallback CodePen Embed Fallback CodePen Embed Fallback

The post How to Map Mouse Position in CSS appeared first on CSS-Tricks.

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

CSS Border Font

Css Tricks - Sun, 02/28/2021 - 6:11am

Every letter in this “font” by Davor Suljic is a single div and drawn only with border. That means employing some trickery like border-radius with exotic syntax like border-radius: 100% 100% 0 0 / 37.5% 37.5% 0 0; which rounds just the top of an element with a certain chillness that works here. Plus, using pseudo-elements. I love all the wacky variations with colors, shadows, and border styles, leaning into the limits of CSS.

Drawing things with CSS has long fascinated people. Icons are a popular choice (famously, Nicolas Gallagher’s Pure CSS GUI icons from 2010), since we can draw so many shapes with CSS without even needing to lean on the all-powerful clip-path.

But as Lynn Fisher has taught us, a single div is barely a limitation at all.

Direct Link to ArticlePermalink

The post CSS Border Font appeared first on CSS-Tricks.

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

Next.js on Netlify

Css Tricks - Sun, 02/28/2021 - 6:11am

(This is a sponsored post.)

If you want to put Next.js on Netlify, here’s a 5 minute tutorial¹. One of the many strengths of Next.js is that it can do server-side rendering (SSR) with a Node server behind it. But Netlify does static hosting not Node hosting, right? Well Netlify has functions, and those functions can handle the SSR. But you don’t even really need to know that, you can just use the plugin.

Need a little bit more hand-holding than that? You got it, Cassidy is doing a free Webinar about all the next Thursday (March 4th, 2021) at 9am Pacific. That way you can watch live and ask questions and stuff. Netlify has a bunch of webinars they have now smartly archived on a new resources site.

  1. I’ve also mentioned this before if it sounds familiar, the fact that it supports the best of the entire rendering spectrum is very good.

Direct Link to ArticlePermalink

The post Next.js on Netlify appeared first on CSS-Tricks.

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

Weekly Platform News: Reduced Motion, CORS, WhiteHouse.gov, popups, and 100vw

Css Tricks - Fri, 02/26/2021 - 7:00am

In this week’s roundup, we highlight a proposal for a new <popup> element, check the use of prefers-reduced-motion on award-winning sites, learn how to opt into cross-origin isolation, see how WhiteHouse.gov approaches accessibility, and warn the dangers of 100vh.

Let’s get into the news!

The new HTML <popup> element is in development

On January 21, Melanie Richards from the Microsoft Edge web platform team posted an explainer for a proposed HTML <popup> element (the name is not final). A few hours later, Mason Freed from Google announced an intent to prototype this element in the Blink browser engine. Work on the implementation is taking place in Chromium issue #1168738.

A popup is a temporary (transient) and “light-dismissable” UI element that is displayed on the the “top layer” of all other elements. The goal for the <popup> element is to be fully stylable and accessible by default. A <popup> can be anchored to an activating element, such as a button, but it can also be a standalone element that is displayed on page load (e.g., a teaching UI).

A <popup> is automatically hidden when the user presses the Esc key or moves focus to a different element (this is called a light dismissal). Unlike the <dialog> element, only one <popup> can be shown at a time, and unlike the deprecated <menu> element, a <popup> can contain arbitrary content, including interactive elements:

We imagine <popup> as being useful for various different types of popover UI. We’ve chosen to use an action menu as a primary example, but folks use popup-esque patterns for many different types of content.

Award-winning websites should honor the “reduce motion” preference

Earlier this week, AWWWARDS announced the winners of their annual awards for the best websites of 2020. The following websites were awarded:

All these websites are highly dynamic and show full-screen motion on load and during scroll. Unfortunately, such animations can cause dizziness and nausea in people with vestibular disorders. Websites are therefore advised to reduce or turn off non-essential animations via the prefers-reduced-motion media query, which evaluates to true for people who have expressed their preference for reduced motion (e.g., the “Reduce motion” option on Apple’s platforms). None of the winning websites do this.

/* (code from animal-crossing.com) */ @media (prefers-reduced-motion: reduce) { .main-header__scene { animation: none; } }

An example of a website that does this correctly is the official site of last year’s Animal Crossing game. Not only does the website honor the user’s preference via prefers-reduced-motion, but it also provides its own “Reduce motion” toggle button at the very top of the page.

(via Eric Bailey)

Websites can now opt into cross-origin isolation

Cross-origin isolation is part of a “long-term security improvement.” Websites can opt into cross-origin isolation by adding the following two HTTP response headers, which are already supported in Chrome and Firefox:

Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin

A cross-origin-isolated page relinquishes its ability to load content from other origins without their explicit opt-in (via CORS headers), and in return, the page gains access to some powerful APIs, such as SharedArrayBuffer.

if (crossOriginIsolated) { // post SharedArrayBuffer } else { // do something else } The White House recommits to accessibility

The new WhiteHouse.gov website of the Biden administration was built from scratch in just six weeks with accessibility as a top priority (“accessibility was top of mind”). Microsoft’s chief accessibility officer reviewed the site and gave it the thumbs up.

The website’s accessibility statement (linked from the site’s footer) declares a “commitment to accessibility” and directly references the latest version of the Web Content Accessibility Guidelines, WCAG 2.1 (2018). This is notable because federal agencies in the USA are only required to comply with WCAG 2.0 (2008).

The Web Content Accessibility Guidelines are the most widely accepted standards for internet accessibility. … By referencing WCAG 2.1 (the latest version of the guidelines), the Biden administration may be indicating a broader acceptance of the WCAG model.

The CSS 100vw value can cause a useless horizontal scrollbar

On Windows, when a web page has a vertical scrollbar, that scrollbar consumes space and reduces the width of the page’s <html> element; this is called a classic scrollbar. The same is not the case on macOS, which uses overlay scrollbars instead of classic scrollbars; a vertical overlay scrollbar does not affect the width of the <html> element.

macOS users can switch from overlay scrollbars to classic scrollbars by setting “Show scroll bars” to ”Always” in System preferences > General.

The CSS length value 100vw is equal to the width of the <html> element. However, if a classic vertical scrollbar is added to the page, the <html> element becomes narrower (as explained above), but 100vw stays the same. In other words, 100vw is wider than the page when a classic vertical scrollbar is present.

This can be a problem for web developers on macOS who use 100vw but are unaware of its peculiarity. For example, a website might set width: 100vw on its article header to make it full-width, but this will cause a useless horizontal scrollbar on Windows that some of the site’s visitors may find annoying.

Web developers on macOS can switch to classic scrollbars to make sure that overflow bugs caused by 100vw don’t slip under their radar. In the meantime, I have asked the CSS Working Group for comment.

The post Weekly Platform News: Reduced Motion, CORS, WhiteHouse.gov, popups, and 100vw appeared first on CSS-Tricks.

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

An Interactive Guide to CSS Transitions

Css Tricks - Thu, 02/25/2021 - 1:30pm

A wonderful post by Josh that both introduces CSS transitions and covers the nuances for using them effectively. I like the advice about transitioning the position of an element, leaving the original space it occupied alone so it doesn’t result in what he calls “doom flicker.” Six hundred and fifty years ago I created CSS Jitter Man to attempt to explain that idea.

The interactive stuff is really neat and helps explain the concepts. I’m a little jealous that Josh writes in MDX — meaning he’s got Markdown and JSX at his disposal. That means these demos can be little one-off React components. Here’s a thread that Josh did showing off how valuable that can be.

Direct Link to ArticlePermalink

The post An Interactive Guide to CSS Transitions appeared first on CSS-Tricks.

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

Ensuring the correct vertical position of large text

Css Tricks - Thu, 02/25/2021 - 1:30pm

Tobi Reif notes how the position of custom fonts set at very large font sizes can be super different, even in the same browser across operating systems. The solution? Well, you know how there are certain CSS properties that only work within @font-face blocks? They are called “descriptors” and font-display is a popular example. There are more that are less-supported, like ascent-override, descent-override, and line-gap-override. Chrome supports them, and lo-and-behold, they can be used to fix this issue.

I really like the idea that these can be used to override the “metrics” of local (fallback) fonts to match a custom font you will load, so that, when it does, there’s little-to-no-movement. I detest FOUT (I know it’s theoretically good for performance), but I can swallow it if the text swap doesn’t move crap around so much.

Direct Link to ArticlePermalink

The post Ensuring the correct vertical position of large text appeared first on CSS-Tricks.

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

How We Improved the Accessibility of Our Single Page App Menu

Css Tricks - Thu, 02/25/2021 - 5:43am

I recently started working on a Progressive Web App (PWA) for a client with my team. We’re using React with client-side routing via React Router, and one of the first elements that we made was the main menu. Menus are a key component of any site or app. That’s really how folks get around, so making it accessible was a super high priority for the team.

But in the process, we learned that making an accessible main menu in a PWA isn’t as obvious as it might sound. I thought I’d share some of those lessons with you and how we overcame them.

As far as requirements go, we wanted a menu that users could not only navigate using a mouse, but using a keyboard as well, the acceptance criteria being that a user should be able to tab through the top-level menu items, and the sub-menu items that would otherwise only be visible if a user with a mouse hovered over a top-level menu item. And, of course, we wanted a focus ring to follow the elements that have focus.

The first thing we had to do was update the existing CSS that was set up to reveal a sub-menu when a top-level menu item is hovered. We were previously using the visibility property, changing between visible and hidden on the parent container’s hovered state. This works fine for mouse users, but for keyboard users, focus doesn’t automatically move to an element that is set to visibility: hidden (the same applies for elements that are given display: none). So we removed the visibility property, and instead used a very large negative position value:

.menu-item { position: relative; } .sub-menu { position: absolute left: -100000px; /* Kicking off the page instead of hiding visiblity */ } .menu-item:hover .sub-menu { left: 0; }

This works perfectly fine for mouse users. But for keyboard users, the sub menu still wasn’t visible even though focus was within that sub menu! In order to make the sub-menu visible when an element within it has focus, we needed to make use of :focus and :focus-within on the parent container:

.menu-item { position: relative; } .sub-menu { position: absolute left: -100000px; } .menu-item:hover .sub-menu, .menu-item:focus .sub-menu, .menu-item:focus-within .sub-menu { left: 0; }

This updated code allows the the sub-menus to appear as each of the links within that menu gets focus. As soon as focus moves to the next sub menu, the first one hides, and the second becomes visible. Perfect! We considered this task complete, so a pull request was created and it was merged into the main branch.

But then we used the menu ourselves the next day in staging to create another page and ran into a problem. Upon selecting a menu item—regardless of whether it’s a click or a tab—the menu itself wouldn’t hide. Mouse users would have to click off to the side in some white space to clear the focus, and keyboard users were completely stuck! They couldn’t hit the esc key to clear focus, nor any other key combination. Instead, keyboard users would have to press the tab key enough times to move the focus through the menu and onto another element that didn’t cause a large drop down to obscure their view.

The reason the menu would stay visible is because the selected menu item retained focus. Client-side routing in a Single Page Application (SPA) means that only a part of the page will update; there isn’t a full page reload.

There was another issue we noticed: it was difficult for a keyboard user to use our “Jump to Content” link. Web users typically expect that pressing the tab key once will highlight a “Jump to Content” link, but our menu implementation broke that. We had to come up with a pattern to effectively replicate the “focus clearing” that browsers would otherwise give us for free on a full page reload.

The first option we tried was the easiest: Add an onClick prop to React Router’s Link component, calling document.activeElement.blur() when a link in the menu is selected:

const Menu = () => { const clearFocus = () => { document.activeElement.blur(); } return ( <ul className="menu"> <li className="menu-item"> <Link to="/" onClick={clearFocus}>Home</Link> </li> <li className="menu-item"> <Link to="/products" onClick={clearFocus}>Products</Link> <ul className="sub-menu"> <li> <Link to="/products/tops" onClick={clearFocus}>Tops</Link> </li> <li> <Link to="/products/bottoms" onClick={clearFocus}>Bottoms</Link> </li> <li> <Link to="/products/accessories" onClick={clearFocus}>Accessories</Link> </li> </ul> </li> </ul> ); }

This approach worked well for “closing” the menu after an item is clicked. However, if a keyboard user pressed the tab key after selecting one of the menu links, then the next link would become focused. As mentioned earlier, pressing the tab key after a navigation event would ideally focus on the “Jump to Content” link first.

At this point, we knew we were going to have to programmatically force focus to another element, preferably one that’s high up in the DOM. That way, when a user starts tabbing after a navigation event, they’ll arrive at or near the top of the page, similiar to a full page reload, making it much easier to access the jump link.

We initially tried to force focus on the <body> element itself, but this didn’t work as the body isn’t something the user can interact with. There wasn’t a way for it to receive focus.

The next idea was to force focus on the logo in the header, as this itself is just a link back to the home page and can receive focus. However, in this particular case, the logo was below the “Jump To Content” link in the DOM, which means that a user would have to shift + tab to get to it. No good.

We finally decided that we had to render an interact-able element, for example, an anchor element, in the DOM, at a point that’s above than the “Jump to Content” link. This new anchor element would be styled so that it’s invisible and that users are unable to focus on it using “normal” web interactions (i.e. it’s taken out of the normal tab flow). When a user selects a menu item, focus would be programmatically forced to this new anchor element, which means that pressing tab again would focus directly on the “Jump to Content” link. It also meant that the sub-menu would immediately hide itself once a menu item is selected.

const App = () => { const focusResetRef = React.useRef(); const handleResetFocus = () => { focusResetRef.current.focus(); }; return ( <Fragment> <a ref={focusResetRef} href="javascript:void(0)" tabIndex="-1" style={{ position: "fixed", top: "-10000px" }} aria-hidden >Focus Reset</a> <a href="#main" className="jump-to-content-a11y-styles">Jump To Content</a> <Menu onSelectMenuItem={handleResetFocus} /> ... </Fragment> ) }

Some notes of this new “Focus Reset” anchor element:

  • href is set to javascript:void(0) so that if a user manages to interact with the element, nothing actually happens. For example, if a user presses the return key immediately after selecting a menu item, that will trigger the interaction. In that instance, we don’t want the page to do anything, or the URL to change.
  • tabIndex is set to -1 so that a user can’t “normally” move focus to this element. It also means that the first time a user presses the tab key upon loading a page, this element won’t be focused, but the “Jump To Content” link instead.
  • style simply moves the element out of the viewport. Setting to position: fixed ensures it’s taken out of the document flow, so there isn’t any vertical space allocated to the element
  • aria-hidden tells screen readers that this element isn’t important, so don’t announce it to users

But we figured we could improve this even further! Let’s imagine we have a mega menu, and the menu doesn’t hide automatically when a mouse user clicks a link. That’s going to cause frustration. A user will have to precisely move their mouse to a section of the page that doesn’t contain the menu in order to clear the :hover state, and therefore allow the menu to close.

What we need is to “force clear” the hover state. We can do that with the help of React and a clearHover class:

// Menu.jsx const Menu = (props) => { const { onSelectMenuItem } = props; const [clearHover, setClearHover] = React.useState(false); const closeMenu= () => { onSelectMenuItem(); setClearHover(true); } React.useEffect(() => { let timeout; if (clearHover) { timeout = setTimeout(() => { setClearHover(false); }, 0); // Adjust this timeout to suit the applications' needs } return () => clearTimeout(timeout); }, [clearHover]); return ( <ul className={`menu ${clearHover ? "clearHover" : ""}`}> <li className="menu-item"> <Link to="/" onClick={closeMenu}>Home</Link> </li> <li className="menu-item"> <Link to="/products" onClick={closeMenu}>Products</Link> <ul className="sub-menu"> {/* Sub Menu Items */} </ul> </li> </ul> ); }

This updated code hides the menu immediately when a menu item is clicked. It also hides immediately when a keyboard user selects a menu item. Pressing the tab key after selecting a navigation link moves the focus to the “Jump to Content” link.

At this point, our team had updated the menu component to a point where we were super happy. Both keyboard and mouse users get a consistent experience, and that experience follows what a browser does by default for a full page reload.

Our actual implementation is slightly different than the example here so we could use the pattern on other projects. We put it into a React Context, with the Provider set to wrap the Header component, and the Focus Reset element being automatically added just before the Provider’s children. That way, the element is placed before the “Jump to Content” link in the DOM hierarchy. It also allows us to access the focus reset function with a simple hook, instead of having to prop drill it.

We have created a Code Sandbox that allows you to play with the three different solutions we covered here. You’ll definitely see the pain points of the earlier implementation, and then see how much better the end result feels!

We would love to hear feedback on this implementation! We think it’s going to work well, but it hasn’t been released to in the wild yet, so we don’t have definitive data or user feedback. We’re certainly not a11y experts, just doing our best with what we do know, and are very open and willing to learn more on the topic.

The post How We Improved the Accessibility of Our Single Page App Menu appeared first on CSS-Tricks.

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

Boost app engagement with chat, voice, and video APIs

Css Tricks - Thu, 02/25/2021 - 5:42am

Sendbird is a service for helping you add social features to your app. Wanna add in-app chat? Sendbird does that. Wanna add in-app voice or video calls? Sendbird does that.

Here’s how I always think about stuff like this. Whatever the thing you are building is, you should specialize in the core of it, and not get bogged down in building essentially another product as you’re doing it. Say you want to build an app for dentists to do bookings and customer management. Please do, by the way, my dentist could really use it. You’ve got a lot of work ahead of you, the core of which is building a thing that is perfect for actual dentists and getting the UX right. In-app chat might be an important part of that, but you aren’t specifically building chat software, you’re building dentist software. Lean on Sendbird for the chat (and everything else).

To elaborate on the dentist software example for a bit, I can tell you more about my dentist. They are so eager to text me, email me, call me, even use social media, to remind me about everything constantly. But for me to communicate with them, I have to call. It’s the only way to talk to them about anything—and it’s obnoxious. If there was a dentist in town where I knew I could fire up a quick digital chat with them to book things, I’d literally switch dentists. Even better if I could click a button in a browser to call them or do a video consult. That’s just good business.

You know what else? Your new app for dentists (seriously, you should do this) is going to have to be compliant on a million standards for any dentist to buy it. This means any software you buy will need to be too. Good thing Sendbird is all set with HIPPA/HITECH, SOC 2, GDPR, and more, not to mention being hugely security-focused.

Sendbird aren’t spring chickens either, they are already trusted by Reddit, Virgin UAE, Yahoo, Meetup, and tons more.

Chat is tricky stuff, too. It’s not just a simple as shuffling a message off to another user and displaying it. Modern chat offers things like reactions, replies, and notifications. Chat needs to be friendly to poor networks and offline situations. Harder still, moderating chat activity and social control like blocking and reporting. Not only does Sendbird help with all that, their UIKit will help you build the interface as well.

Build it with Sendbird, and it’ll scale forever.

Sendbird’s client base gave us confidence that they would be able to handle our traffic and projected growth. ”

Ben Celibicic, CTO

Modern apps have modern users that expect these sort of features.

Try Sendbird

The post Boost app engagement with chat, voice, and video APIs appeared first on CSS-Tricks.

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

Teaching Web Dev for Free is Good Business

Css Tricks - Wed, 02/24/2021 - 12:32pm

It feels like a trend (and a smart one) for tech platforms to invest in really high-quality learning material for their platform. Let’s have a gander.

Webflow University

Surely Webflow is thinking: if people invest in learning Webflow, they’ll be able to build what they need for themselves and their clients in Weblow, and they’ll stick around and be a good customer.

Jamstack Explorers

Surely Netlify is thinking: if people really grok Jamstack, they’ll build their existing and future sites using it. They’ll get comfortable using Netlify’s features to solve their problems, and they’ll stick around and be a good customer.

Salesforce Trailhead

Surely Salesforce is thinking: if we train people to learn Salesforce and build Salesforce-specific software, not only will they be a good customer, but they’ll bring more customers to us and help big companies stay good customers.

Figma Crash Course

This is not created by Figma themselves, but by Pablo Stanley, who must be thinking: I can teach people to use Figma, and along the way show them how cool and powerful Blush is, which will gain Blush good customers.

Apollo Odyssey

Surely Apollo is thinking: if y’all know GraphQL, and learned it in the context of Apollo, you probably continue using Apollo and bring it to the teams you’re on, which might make for great customers.

WP Courses 

This one is an outlier because these are paid courses, but my guess is that Automattic is thinking: there is already a ton of WordPress learning material out there, why not leverage our brand and deep expertise to make content people are willing to pay for.

Git Tutorials & Training

Surely Atlassian is thinking: if we are the place where people literally learned Git, we can sprinkle in our tooling into those lessons, and you’ll use those tools for your Git workflow, which will follow you through your entire developer career. Not to mention this is good SEO juice.

GitHub does the same thing.

Helping your customers learn your platform is certainly not a new concept. The word “webinar” exists after all. It’s a funny word, but effective. For example, AWS Marketplace sponsors CodePen emails sometimes with the idea of getting people to attend webinars about certain technologies. Wanna learn about Apache Kafka? You can do that for free coming up Thursday, February 25th. Surely AWS is thinking if people learn how this technology works, they’ll use AWS and AWS Marketplace partners to spin it up when they get to using it.

Cypress publishes their webinars. Appcues publishes their webinars. It’s not rare.

What feels a new here is the idea of packaging up learning material on a microsite with a fancy design and making it feel in-line with modern learning platforms. Like you are going to some expensive code school, except you’re getting it for free.

I’m a fan of all this. It’s good marketing for companies. It’s a good learning opportunity for everyone else. It’s also very biased. Learning materials you get directly from companies is going to tell you all about how great the technology of that company is. You should know that going in, if it’s isn’t obvious.

I’m also a fan—perhaps an even bigger fan—of paying for high-quality learning material. Our learning partner, Frontend Masters, has no particular bias to technology because you’re their customer. If they help you, they succeed and you succeed as well.

The post Teaching Web Dev for Free is Good Business appeared first on CSS-Tricks.

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

A DRY Approach to Color Themes in CSS

Css Tricks - Wed, 02/24/2021 - 6:21am

The other day, Florens Verschelde asked about defining dark mode styles for both a class and a media query, without repeat CSS custom properties declarations. I had run into this issue in the past but hadn’t come up with a proper solution.

What we want is to avoid redefining—and thus repeating—custom properties when switching between light and dark modes. That’s the goal of DRY (Don’t Repeat Yourself) programming, but the typical pattern for switching themes is usually something like this:

:root { --background: #fff; --text-color: #0f1031; /* etc. */ } @media (prefers-color-scheme: dark) { :root { --background: #0f1031; --text-color: #fff; /* etc. */ } }

See what I mean? Sure, it might not seem like a big deal in an abbreviated example like this, but imagine juggling dozens of custom properties at a time—that’s a lot of duplication!

Then I remembered Lea Verou’s trick using --var: ;, and while it didn’t hit me at first, I found a way to make it work: not with var(--light-value, var(--dark-value)) or some nested combination like that, but by using both side by side!

Certainly, someone smarter must have discovered this before me, but I haven‘t heard of leveraging (or rather abusing) CSS custom properties to achieve this. Without further ado, here’s the idea:

--color: var(--light, orchid) var(--dark, rebeccapurple);

If the --light value is set to initial, the fallback will be used (orchid), which means --dark should be set to a whitespace character (which is a valid value), making the final computed value look like this:

--color: orchid ; /* Note the additional whitespace */

Conversely, if --light is set to a whitespace and --dark to initial, we end up with a computed value of:

--color: rebeccapurple; /* Again, note the whitespace */

Now, this is great but we do need to define the --light and --dark custom properties, based on the context. The user can have a system preference in place (either light or dark), or can have toggled the website‘s theme with some UI element. Just like Florens‘s example, we’ll define these three cases, with some minor readability enhancement that Lea proposed using “on” and “off” constants to make it easier to understand at a glance:

:root { /* Thanks Lea Verou! */ --ON: initial; --OFF: ; } /* Light theme is on by default */ .theme-default, .theme-light { --light: var(--ON); --dark: var(--OFF); } /* Dark theme is off by default */ .theme-dark { --light: var(--OFF); --dark: var(--ON); } /* If user prefers dark, then that's what they'll get */ @media (prefers-color-scheme: dark) { .theme-default { --light: var(--OFF); --dark: var(--ON); } }

We can then set up all of our theme variables in a single declaration, without repetition. In this example, the theme-* classes are set to the html element, so we can use :root as a selector, as many people like to do, but you could set them on the body, if the cascading nature of the custom properties makes more sense that way:

:root { --text: var(--light, black) var(--dark, white); --bg: var(--light, orchid) var(--dark, rebeccapurple); }

And to use them, we use var() with built-in fallbacks, because we like being careful:

body { color: var(--text, navy); background-color: var(--bg, lightgray); }

Hopefully you’re already starting to see the benefit here. Instead of defining and switching armloads of custom properties, we’re dealing with two and setting all the others just once on :root. That’s a huge improvement from where we started.

Even DRYer with pre-processors

If you were to show me this following line of code out of context, I’d certainly be confused because a color is a single value, not two!

--text: var(--light, black) var(--dark, white);

That’s why I prefer to abstract things a bit. We can set up a function with our favorite pre-processor, which is Sass in my case. If we keep our code above defining our --light and --dark values in various contexts, we need to make a change only on the actual custom property declaration. Let’s create a light-dark function that returns the CSS syntax for us:

@function light-dark($light, $dark) { @return var(--light, #{ $light }) var(--dark, #{ $dark }); }

And we’d use it like this:

:root { --text: #{ light-dark(black, white) }; --bg: #{ light-dark(orchid, rebeccapurple) }; --accent: #{ light-dark(#6d386b, #b399cc) }; } CodePen Embed Fallback

You’ll notice there are interpolation delimiters #{ … } around the function call. Without these, Sass would output the code as is (like a vanilla CSS function). You can play around with various implementations of this but the syntax complexity is up to your tastes.

How’s that for a much DRYer codebase?

More than one theme? No problem!

You could potentially do this with more than two modes. The more themes you add, the more complex it becomes to manage, but the point is that it is possible! We add another theme set of ON or OFF variables, and set an extra variable in the list of values.

.theme-pride { --light: var(--OFF); --dark: var(--OFF); --pride: var(--ON); } :root { --text: var(--light, black) var(--dark, white) var(--pride, #ff8c00) ; /* Line breaks are absolutely valid */ /* Other variables to declare… */ }

Is this hacky? Yes, it absolutely is. Is this a great use case for potential, not-yet-existing CSS booleans? Well, that’s the dream.

How about you? Is this something you’ve figured out with a different approach? Share it in the comments!

The post A DRY Approach to Color Themes in CSS appeared first on CSS-Tricks.

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

SmolCSS

Css Tricks - Wed, 02/24/2021 - 5:54am

A wonderful collection of little layout-related CSS snippets from Stephanie Eckles that serves both as a quick reference and a reminder of how straightforward and powerful CSS has become.

Random things to note!

  • The resizeable containers aren’t some JavaScript library. They are just <div>s with resize: horizontal; and overflow: auto; (although there is a nice little lib for that that displays current width output).
  • Each demo can be opened on CodePen, which is the prefill API at work.
  • CSS custom properties are tastefully sprinkled throughout in a way that makes it more understandable instead of less understandable.
  • The dark mode doesn’t go super duper dark, but fairly dark blues and purples. That’s a good reminder that dark mode isn’t gray/black mode. It remembers your setting, but does have flash-of-light-mode, which is the boss-mode problem with color preferences. I think you need server-side tech to really get it perfect.
  • The whole site is open source. Go Eleventy!

Direct Link to ArticlePermalink

The post SmolCSS appeared first on CSS-Tricks.

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

Hiding Content Responsibly

Css Tricks - Tue, 02/23/2021 - 2:35pm

We’ve covered the idea of hiding things in CSS many times here, the most recent post being Marko Ilic’s “Comparing Various Ways to Hide Things in CSS” which did a nice job of comparing different techniques which you’d use in different situations. Hugo “Kitty” Giraudel has done something similar in “Hiding Content Responsibly” which looks at 10 methods—and even those, you could say, aren’t totally comprehensive.

Does this mean CSS is messy and incomprehensible? Nah. I feel like all the methods are logical, have good use cases, and result in good outcomes. Allow me to do a have a pretend conversation walking through my thought process here.

I need to hide this thing completely. For everyone.

No problem, use display: none;.

I need to hide this thing, but only hide it for screen readers, not visually. (For example, an icon that has no additional meaning for screen readers, as there is an accessible label nearby.)

No problem, that’s what the aria-hidden attribute is for.

I need to hide this thing, but only visually, not for screen readers. (For example, the contents of non-active tabs.)

No problem, use a .sr-only class. That leaves it accessible but hides it visually until you remove that class.

Oops, I actually want to hide this thing visually, but I still want it to take up physical space, not collapse. (For example, say a button has a loading spinner icon that is only needed when performing an action. The size of the button should factor in the size of that icon all the time, not just when the spinner is visible. That way, there’s no layout shifting when that icon comes and goes.)

No problem, use transform: scale(0) which will visually collapse it, but the original space will remain, and will leave it accessible to screen readers.

Oh nice, I could transition the transform, too, I suppose. But actually, that transition doesn’t fit my site well. I just want something I can fade out and fade in.

The opacity property is transitional, so transition that between 0 and 1 for fades. The good news is that visibility is also transitional. When fading out, use visibility: hidden, and when fading in, use visibility: visible to hide and unhide the thing from screen readers.

That’s not entirely comprehensive, but I find that covers 95% of hiding cases.

The post Hiding Content Responsibly appeared first on CSS-Tricks.

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

What’s the Backup Plan for Your WordPress Site?

Css Tricks - Tue, 02/23/2021 - 7:08am

Of all the reasons we love and use Jetpack for CSS-Tricks—a poster child WordPress site—is that we can sleep easy at night knowing we have real-time backups running with Jetpack Backup. That way, no matter what, everything that makes this site tick, from all the template files to every single word we’ve ever typed, is always a click away from being restored if we need it for any reason at all.

There’s really no question whether or not you should be backing up your WordPress site. You absolutely should. It’s sort of like being prepared for an earthquake: you know it could happen at any time, so you want to make sure you’ve got all the tooling in place to keep things safe, not if, but when it happens.

What’s your backup plan? For us, it’s logging into WordPress.com, locating which backup to use, and clicking a button to restore things to that point in time. That’s all the files of course, like WordPress itself, the theme, and plugins, but also the entire database and all the media files.

Another reason we love Jetpack Backup? It provides a complete activity log of all the changes that happen on the site. It’s one thing to have your site backed up and be able to restore it. It’s another to know what caused the issue in the first place.

Jetpack Backup offers two plans one for daily backups and the other for real-time backups. We’ve got real-time running around here and that’s a great option for large sites that are updated often, like e-commerce. Most sites can probably get away with daily backups instead.

That leads to another wonderful thing: Jetpack Backup is sold à la carte. That means you can just get backups if that’s all you want from Jetpack. And, hey, if you find yourself needing more from Jetpack, like all the stuff we use it for here, then that rich feature set is just a couple of clicks away.

Not sure what your WordPress backup plan is? You really ought to check out Jetpack Backup. It works extremely well for us and we can’t recommend it enough.

Get Started True Story

While working on a bit of content for @css across team members, I noticed some of what I had written had disappeared. It got saved over by accident. I forgot to turn on “Revisions” for this Custom Post Type (it was a newsletter, which we write in WordPress).

It was tempting to be like, “Oh well, I’m a dummy, I’ll just have to remember and re-write it.” But no! I have Jetpack real-time backups on this site. I was able to find the exact moment I made my changes and download a copy of the site at that moment.

I didn’t need to restore the site to that point, just what I had written. So, I loaded up the wp_posts table from the SQL dump in that backup, plucked out my writing, and put it back in place.

And of course, I enabled revisions for that Custom Post Type so it won’t happen again.

Not only is that a true story, but this is a Jetpack double-whammy, because I “unrolled” that Twitter thread right here in WordPress via a Jetpack feature.

The post What’s the Backup Plan for Your WordPress Site? appeared first on CSS-Tricks.

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

Getting Deep into Shadows

Css Tricks - Mon, 02/22/2021 - 6:00am

Let’s talk shadows in web design. Shadows add texture, perspective, and emphasize the dimensions of objects. In web design, using light and shadow can add physical realism and can be used to make rich, tactile interfaces.

Take the landing page below. It is for cycling tours in Iceland. Notice the embellished drop shadow of the cyclist and how it creates the perception that they are flying above not only the content on the page, but the page itself, as though they are “popping” over the screen. It feels dynamic and immediate, which is perfect for the theme of adventure.

Credit: Kate Hahu

Compare that with this next example. It’s a “flat” design, sans shadows. In this case, the bike itself is the focal point. The absence of depth and realism allows the bike to stand out on its own.

Credit: saravana

You can appreciate the differences between these approaches. Using shadows and depth is a design choice; they should support the theme and the message you want the content to convey.

Light and shadows

As we just saw, depth can enhance content. And what exactly makes a shadow? Light!

It’s impossible to talk about shadow without getting into light. It controls the direction of a shadow as well as how deep or shallow the shadow appears. You can’t have one without the other.

Google’s Material Design design system is a good example of employing light and shadows effectively. You’ve certainly encountered Material Design’s aesthetics because Google employs it on nearly all of its products.

The design system takes cues from the physical world and expresses interfaces in three-dimensional space using light, surfaces, and cast shadows. Their guidelines on using light and shadows covers this in great detail.

In the Material Design environment, virtual lights illuminate the UI. Key lights create sharper, directional shadows, called key shadows. Ambient light appears from all angles to create diffused, soft shadows, called ambient shadows.

Shadows are a core component of Material Design. Compare that with Apple’s Human Interface Guidelines for macOS, where translucency and blurring is more of a driving factor for evoking depth.

In this case, light is still an influential factor, as it allows elements to either blend into the desktop, or even into other panels in the UI. Again, it’s is a design choice to employ this in your interface. Either way, you can see how light influences the visual perception of depth.

Light sources and color

Now that we understand the relationship between light and shadows, we ought to dig in a little deeper to see how light affects shadows. We’ve already seen how the strength of light produces shadows at different depths. But there’s a lot to say about the way light affects the direction and color of shadows.

There are two kinds of shadows that occur when a light shines on an object, a drop shadow and a form shadow.

Drop shadows

A drop shadow is cast when an object blocks a light source. A drop shadow can vary in tone and value. Color terminology can be dense and confusing, so let’s talk about tone and value for a moment.

Tone is a hue blended with grey. Value describes the overall lightness or darkness of a color. Value is a big deal in painting as it is how the artist translates light and object relationships to color.

In the web design world, these facets of color are intrinsic to the color picker UI.

Form shadows

A form shadow, on the other hand, is the side of an object facing away from the light source. A form shadow has softer, less defined edges than a drop shadow. Form shadows illustrate the volume and depth of an object.

The appearance of a shadow depends on the direction of light, the intensity of light, and the distance between the object and the surface where the shadow is cast. The stronger the light, the darker and sharper the shadow is. The softer the light, the fainter and softer the shadow is. In some cases, we get two distinct shadows for directional light. The umbra is where light is obstructed and penumbra is where light is cast off.

If a surface is close to an object, the shadow will be sharper. If a surface is further away, the shadow will be fainter. This is not some abstract scientific stuff. This is stuff we encounter every day, whether you realize it or not.

Light may also be reflected from sides of an object or another surface. Bright surfaces reflect light, dark surfaces absorb light.

These are the most valuable facets of light to understand for web design. The physics behind light is a complex topic, I have just lightly touched on some of it here. If you’d like to see explicit examples of what shadows are cast based on different light sources, this guide to drawing shadows for comics is instructive.

Positioning light sources

Remember, shadows go hand-in-hand with light, so defining a light source — even though there technically isn’t one — is the way to create impressive shadow effects. The trick is to consistently add shadows relative to the light source. A light source positioned above an element will cast a shadow below the element. Placing a light source to the left of an element will cast a shadow to the right. Placing multiple light sources to the top, bottom, left and right of an element actually casts no shadow at all!

A light source can be projected in any direction you choose. Just make sure it’s used consistently in your design, so the shadow on one element matches other shadows on the page.

Elevation

Shadows can also convey elevation. Once again, Material Design is a good example because it demonstrates how shadows are used to create perceived separation between elements.

Credit: Nate Wilson Inner shadows

Speaking of elevation, the box-shadow property is the only property that can create inner shadows for a sunken effect. So, instead of elevating up, the element appears to be pressed in. That’s thanks to the inset keyword.

That good for something like an effect where clicking a button appears to physically press it.

CodePen Embed Fallback

It’s also possible to “fake” an inner text shadow with a little trickery that’s mostly supported across browsers:

CodePen Embed Fallback Layering shadows

We’re not limited to a single shadow per element! For example, we can provide a comma-separated list of shadows on the box-shadow property. Why would we want to do that? Smoother shadows, for one.

CodePen Embed Fallback

Interesting effects is another.

CodePen Embed Fallback

Layering shadows can even enhance typography using the text-shadow property.

CodePen Embed Fallback

Just know that layering shadows is a little different for filter: drop-shadow() It’s syntax also takes a list, but it’s space-separated instead of comma-separated.

.box { box-shadow: 0 2px 2px #555, /* commas */ 0 6px 5px #777, 0 12px 10px #999 ; } .box { filter: drop-shadow(0 2px 2px #555) /* spaces */ drop-shadow(0 6px 5px #777) drop-shadow(0 12px 10px #999); }

Another thing? Shadows stack on top of one another, in the order they are declared where the top shadow is the first one in the list.

You may have guessed that drop-shadow() works a little differently here. Shadows are added exponentially, i.e. 2^number of shadows - 1.

Here’s how that works:

  • 1 shadow = (2^1 – 1). One shadow is rendered.
  • 2 shadows = (2^2 – 1). Three shadows are rendered.
  • 3 shadows = (2^3 – 1). Seven shadows are rendered.

Or, in code:

.one-shadow { filter: drop-shadow(20px 20px 0 grey); } .three-shadows { filter: drop-shadow(20px 20px 0 grey) drop-shadow(40px 0 0 yellow); } .seven-shadows { filter: drop-shadow(20px 20px 0 grey) drop-shadow(40px 0 0 yellow); drop-shadow(80px 0 0 red); }

The <feDropShadow> element works the exact same way for SVGs.

Shadows and accessibility

Here’s something for you to chew on: shadows can help improve accessibility.

Google conducted a study with low-vision participants to better understand how shadows and outlines impact an individual’s ability to identify and interact with a component. They found that using shadows and outlines:

  • increases the ease and speed of finding a component when scanning pages, and
  • improves one’s ability to determine whether or not a component is interactive.

That wasn’t a wide-ranging scientific study or anything, so let’s turn around and see what the W3C says in it’s guidelines for WCAG 2.0 standards:

[…] the designer might darken the background behind the letter, or add a thin black outline (at least one pixel wide) around the letter in order to keep the contrast ratio between the letter and the background above 4.5:1.

That’s talking about light text on a light background. WCAG recommends a contrast ratio that’s at least 4.5:1 between text and images. You can use text shadows to add a stronger contrast between them.

Photo credit: Woody Van der Straeten Shadows and performance

Before diving into shadows and adding them on all the things, it’s worth calling out that they do affect performance.

For example, filter: drop-shadow is hardware-accelerated by some browsers. A new compositor layer may be created for that element, and offloaded to the GPU. You don’t want to have too many layers, as it takes up limited GPU memory, and will eventually degrade performance. You can assess this in your browser’s DevTools.

Blurring is an expensive operation, so use it sparingly. When you blur something, it mixes the colors from pixels all around the output pixel to generate a blurred result. For example, if your <blur-radius> parameter is 2px, then the filter needs to look at two pixels in every direction around each output pixel to generate the mixed color. This happens for each output pixel, so that means a lot of calculations that grow exponentially. So, shadows with a large blur radius are generally slower to render than other shadows.

Did you know?

Did you know that shadows don’t influence the document layout?

A shadow is the same size as the element it targets. You can modify the size of a box-shadow (through the spread radius parameter), but other properties cannot modify the shadow size.

And did you know that a shadow implicitly has a lower z-index than elements? That’s why shadows sit below other elements.

And what about clipping and masking? If an element with a box-shadow is clipped (with clip-path) or uses a mask (with mask), the shadow isn’t shown. Conversely, if an element with text-shadow or filter: drop-shadow() is clipped, a shadow is shown, as long as it is within the clip region.

Here’s another: We can’t create oblique shadows (with diagonal lines) with shadow properties. That requires creating a shadow element and use a transform:skew() on it.

CodePen Embed Fallback

Oh, and one more: box-shadow follows border-radius. If an element has rounded corners, the shadow is rounded as well. In other words, the shadow mirrors the shape of the box. On the other hand, filter: drop-shadow() can create an irregular shape because it respects transparency and follows the shape of the content.

Best use cases for different types of shadows

Practically anything on the web can have a shadow and there are multiple CSS properties and functions that create shadows. But choosing the right type of shadow is what makes a shadow effective.

Let’s evaluate the options:

  • box-shadow: This CSS property creates shadows that conform to the elements bounding box. It’s versatile and can be used on anything from cards to buttons to just about anything where the shadow simply needs to follow the element’s box.
  • text-shadow: This is a CSS property that creates shadows specifically for text elements.
  • filter: drop-shadow(): The CSS property here is filter, but what create the shadow is the drop-shadow function it accepts. What makes this type of shadow different from, say box-shadow, is that it follows the rendered shape of any element (including pseudos).
  • <feDropShadow>: This is actually an SVG element, whereas the rest are CSS properties. So, you would use this to create drop shadows directly in SVG markup.

Once you get the hang of the different types of shadows and each one’s unique shadow-creating powers, the possibilities for shadow effects feels endless. From simple drop shadows to floating elements, and even inner shadows, we can create interesting visuals that add extra meaning or value to UI.

CodePen Embed Fallback

The same goes for text shadows.

CodePen Embed Fallback Shadows in the wild

Shadows are ubiquitous. We’re seeing them used in new and interesting ways all the time.

Have you heard the buzzword “neumorphism” floating around lately? That’s all about shadows. Here’s an implementation by Maria Muñoz:

CodePen Embed Fallback

Yuan Chuan, who makes amazing generative art, calls shadows a secret weapon in UI design:

CSS relies on existing DOM structure in the browser. It’s not possible to generate new elements other than ::before and ::after. Sometimes I really wish CSS had the ability to do so straightforwardly.

Yet, we can partially make up for this by creating various shadows and gradients entirely in CSS.

That’s why having drop-shadow is so exciting. Together with text-shadow and box-shadow we can do a lot more.

Just check out how he uses drop shadows to create intricate patterns.

CodePen Embed Fallback

Yes, that’s pretty crazy. And speaking of crazy, it’s worth mentioning that going too crazy can result in poor performance, so tread carefully.

What about pseudo-elements?

Oh yes, shadow properties are supported by the ::before and ::after pseudo-elements.

Other pseudos that respect shadows? The ::first-letter pseudo-element accepts box-shadow and text-shadow. The ::first-line pseudo-element accepts text-shadow.

Look at how Jhey Tompkins got all creative using box-shadow on pseudo elements to create animated loaders.

CodePen Embed Fallback Animating shadows

Yes, we can make them move! The properties and function we’ve covered here are totally compatible with CSS animations and transitions. That means we can move shadows, blur shadows, expand/shrink shadows (with box-shadow), and alter the color.

Animating a shadow can provide a user with a cue that an element is interactive, or that an action has taken place. We saw earlier with our button example that an inset shadow showed that the button had been pressed. Another common animation pattern is elevating a card on hover.

CodePen Embed Fallback

If you want to optimize the animation performance, avoid animating box-shadow! It is more performant to animate drop-shadow(). But if you want the smoothest animation, a hack is the best option! Add an ::after pseudo-element with a bigger box-shadow, and animate its opacity instead.

CodePen Embed Fallback

Of course, there is a lot more you can animate. I will leave that exploration up to you!

Wrapping up

Phew, who knew there was so much to something as seemingly “simple” as CSS shadows! There’s the light source and how shadows are cast. The different types of shadows and their color. There’s using shadows for evoking depth, elevating elements and insetting them. There’s the fact that we can layer shadows on top of other shadows. And that we have a selection of CSS properties that we can use for different use cases. Then, there are the accessibility and performance implications that come with them. And, hey, animation is thing! That’s a heckuva lot!

Anyway, hopefully this broad overview gave you something new to chew on, or at the very least, helped you brush up on some concepts.

The post Getting Deep into Shadows appeared first on CSS-Tricks.

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

To the brain, reading computer code is not the same as reading language

Css Tricks - Mon, 02/22/2021 - 5:56am

One of the things I do when teaching beginning front-end development is ask students to describe what it’s like to read HTML. I give them pretty basic markup for a long-form article, and ask them to read it twice: first in the code, then on the front end.

The #1 common response I hear? It’s like learning a new language.

Of course it is, I tell them. It’s in the name: Hypertext Markup Language. So, I advise them to start treating the materials in the course like they’re learning French, Spanish, or any other language.

Then I wake up this morning and see this MIT study that reading computer code is not the same as reading language, even though they share similarities.

In spite of those similarities, MIT neuroscientists have found that reading computer code does not activate the regions of the brain that are involved in language processing. Instead, it activates a distributed network called the multiple demand network, which is also recruited for complex cognitive tasks such as solving math problems or crossword puzzles.

Duh, you might say. But wait, reading code actually appears to activate additional parts of the multiple demand network that make the task more or a near-match to mathematical reasoning than the exact same thing.

The MIT team found that reading computer code appears to activate both the left and right sides of the multiple demand network […]. This finding goes against the hypothesis that math and coding rely on the same brain mechanisms.

So, back to my HTML reading assignment. Is it better to teach code as a language for recognizing symbols that communicate to the browser what to do, or as a math skill that’s based on solving problems?

The answer is &#x1f937;‍♂️.

The most interesting thing about the study to me is not how to teach code, but rather how how I work with it. Chris always says a front-end developer is aware, and the fact that reading code taps on a region of the brain that’s responsible for handling multi-tasking and holding lots of information only supports that. It also explains why I personally get annoyed when I’m pulled away from my code or distracted from it—it’s like my brain has to drop all the plates it was balancing to pay attention to something else, then pick up and reassemble all the pieces before I can jump back in to what I was doing.

Direct Link to ArticlePermalink

The post To the brain, reading computer code is not the same as reading language appeared first on CSS-Tricks.

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

Syndicate content
©2003 - Present Akamai Design & Development.