Tag: Complete

A Complete Guide to Dark Mode on the Web

Dark mode has gained a lot of traction recently. Like Apple, for instance, has added dark mode to its iOS and MacOS operating systems. Windows and Google have done the same. 

DuckDuckGo’s light and dark themes

Let’s get into dark mode in the context of websites. We’ll delve into different options and approaches to implementing a dark mode design and the technical considerations they entail. We’ll also touch upon some design tips along the way.


Toggling Themes

The typical scenario is that you already have a light theme for your site, and you’re interested in making a darker counterpart. Or, even if you’re starting from scratch, you’ll have both themes: light and dark. One theme should be defined as the default that users get on first visit, which is the light theme in most cases (though we can let the user’s browser make that choice for us, as we’ll see). There also should be a way to switch to the other theme (which can be done automatically, as we’ll also see) — as in, the user clicks a button and the color theme changes.

There several approaches to go about doing this:

Using a Body Class

The trick here is to swap out a class that can be a hook for changing a style anywhere on the page.

<body class="dark-theme || light-theme">

Here’s a script for a button that will toggle that class, for example:

// Select the button const btn = document.querySelector('.btn-toggle');  // Listen for a click on the button btn.addEventListener('click', function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle('dark-theme');   })

Here’s how we can use that idea:

<body>   <button class="btn-toggle">Toggle Dark Mode</button>   <h1>Hey there! This is just a title</h2>   <p>I am just a boring text, existing here solely for the purpose of this demo</p>   <p>And I am just another one like the one above me, because two is better than having only one</p>   <a href="#">I am a link, don't click me!</a> </body>

The general idea of this approach is to style things up as we normally would, call that our “default” mode, then create a complete set of color styles using a class set on the <body>  element we can use as a “dark” mode.

Let’s say our default is a light color scheme. All of those “light” styles are written exactly the same way you normally write CSS. Given our HTML, let’s apply some global styling to the body and to links.

body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Good good. We have dark text (#222) and dark links (#0033cc) on a light background (#fff). Our “default” theme is off to a solid start.

Now let’s redefine those property values, this time set on a different body class:

body {   color: #222;   background: #fff; } a {   color: #0033cc; } 
 /* Dark Mode styles */ body.dark-theme {   color: #eee;   background: #121212; } body.dark-theme a {   color: #809fff; }

Dark theme styles will be descendants of the same parent class — which is .dark-theme in this example — which we’ve applied to the <body> tag.

How do we “switch” body classes to access the dark styles? We can use JavaScript! We’ll select the button class (.btn-toggle), add a listener for when it’s clicked, then add the dark theme class (.dark-theme) to the body element’s class list. That effectively overrides all of the “light” colors we set, thanks to the cascade and specificity. 

Here’s the complete code working in action. Click the toggle button to toggle in and out of dark mode.

Using Separate Stylesheets

Rather than keeping all the styles together in one stylesheet, we could instead toggle between stylesheets for each theme. This assumes you have full stylesheets ready to go.

For example, a default light theme like light-theme.css:

/* light-theme.css */ 
 body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Then we create styles for the dark theme and save them in a separate stylesheet we’re calling dark-theme.css.

/* dark-theme.css */ 
 body {   color: #eee;   background: #121212; } body a {   color: #809fff; }

This gives us two separate stylesheets — one for each theme — we can link up in the HTML <head> section. Let’s link up the light styles first since we’re calling those the default.

<!DOCTYPE html> <html lang="en"> <head>   <!-- Light theme stylesheet -->   <link href="light-theme.css" rel="stylesheet" id="theme-link"> </head> 
 <!-- etc. --> 
 </html>

We are using a #theme-link ID that we can select with JavaScript to, again, toggle between light and dark mode. Only this time, we’re toggling files instead of classes.

// Select the button const btn = document.querySelector(".btn-toggle"); // Select the stylesheet <link> const theme = document.querySelector("#theme-link");  // Listen for a click on the button btn.addEventListener("click", function() {   // If the current URL contains "ligh-theme.css"   if (theme.getAttribute("href") == "light-theme.css") {     // ... then switch it to "dark-theme.css"     theme.href = "dark-theme.css";   // Otherwise...   } else {     // ... switch it to "light-theme.css"     theme.href = "light-theme.css";   } });

Using Custom Properties

We can also leverage the power of CSS custom properties to create a dark theme! It helps us avoid having to write separate style rulesets for each theme, making it a lot faster to write styles and a lot easier to make changes to a theme if we need to.

We still might choose to swap a body class, and use that class to re-set custom properties:

// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button btn.addEventListener("click", function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle("dark-theme"); });

First, let’s define the default light color values as custom properties on the body element:

body {   --text-color: #222;   --bkg-color: #fff;   --anchor-color: #0033cc; }

Now we can redefine those values on a .dark-theme body class just like we did in the first method:

body.dark-theme {   --text-color: #eee;   --bkg-color: #121212;   --anchor-color: #809fff; }

Here are our rulesets for the body and link elements using custom properties:

body {   color: var(--text-color);   background: var(--bkg-color); } a {   color: var(--anchor-color); }

We could just as well have defined our custom properties inside the document :root. That’s totally legit and even common practice. In that case, all the default theme styles definitions would go inside :root { } and all of the dark theme properties go inside :root.dark-mode { }.

Using Server-Side Scripts

If we’re already working with a server-side language, say PHP, then we can use it instead of JavaScript. This is a great approach if you prefer working directly in the markup.

<?php $ themeClass = ''; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } 
 $ themeToggle = ($ themeClass == 'dark-theme') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>">   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

We can have the user send a GET or POST request. Then, we let our code (PHP in this case) apply the appropriate body class when the page is reloaded. I am using a GET request (URL params) for the purpose of this demonstration.

And, yes, we can swap stylesheets just like we did in the second method.

<?php $ themeStyleSheet = 'light-theme.css'; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } 
 $ themeToggle = ($ themeStyleSheet == 'dark-theme.css') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet"> </head> 
 <body>   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

This method has an obvious downside: the page needs to be refreshed for the toggle to take place. But a server-side solution like this is useful in persisting the user’s theme choice across page reloads, as we will see later.


Which method should you choose?

The “right” method comes down to the requirements of your project. If you are doing a large project, for example, you might go with CSS properties to help wrangle a large codebase. On the other hand, if your project needs to support legacy browsers, then another approach will need to do instead.

Moreover, there’s nothing saying we can only use one method. Sometimes a combination of methods will be the most effective route. There may even be other possible methods than what we have discussed.


Dark Mode at the Operating System Level

So far, we’ve used a button to toggle between light and dark mode but we can simply let the user’s operating system do that lifting for us. For example, many operating systems let users choose between light and dark themes directly in the system settings.

The “General” settings in MacOS System Preferences

Pure CSS

Details

Fortunately, CSS has a prefers-color-scheme media query which can be used to detect user’s system color scheme preferences. It can have three possible values: no preference, light and dark. Read more about it on MDN.

@media (prefers-color-scheme: dark) {   /* Dark theme styles go here */ } 
 @media (prefers-color-scheme: light) {   /* Light theme styles go here */ }

To use it, we can put the dark theme styles inside the media query.

@media (prefers-color-scheme: dark) {   body {     color: #eee;     background: #121212;   } 
   a {     color: #809fff;   } }

Now, if a user has enabled dark mode from the system settings, they will get the dark mode styles by default. We don’t have to resort to JavaScript or server-side scripts to decide which mode to use. Heck, we don’t even need the button anymore!

JavaScript

Details

We can turn to JavaScript to detect the user’s preferred color scheme. This is a lot like the first method we worked with, only we’re using matchedMedia() to detect the user’s preference.

const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');nnif (prefersDarkScheme.matches) {n  document.body.classList.add('dark-theme');n} else {n  document.body.classList.remove('dark-theme');n}

There is a downside to using JavaScript: there will likely be a quick flash of the light theme as JavaScript is executed after the CSS. That could be misconstrued as a bug.

And, of course, we can swap stylesheets instead like we did in the second method. This time, we link up both stylesheets and use the media query to determine which one is applied.

Overriding OS Settings

We just looked at how to account for a user’s system-wide color scheme preferences. But what if users want to override their system preference for a site? Just because a user prefers dark mode for their OS doesn’t always mean they prefer it on a website. That’s why providing a way to manually override dark mode, despite the system settings, is a good idea.

View Code

Let’s use the CSS custom properties approach to demonstrate how to do this. The idea is to define the custom properties for both themes like we did before, wrap dark styles up in the prefers-color-scheme media query, then define a .light-theme class inside of that we can use to override the dark mode properties, should the user want to toggle between the two modes.

/* Default colors */ body {   --text-color: #222;   --bkg-color: #fff; } /* Dark theme colors */ body.dark-theme {   --text-color: #eee;   --bkg-color: #121212; }  /* Styles for users who prefer dark mode at the OS level */ @media (prefers-color-scheme: dark) {   /* defaults to dark theme */   body {      --text-color: #eee;     --bkg-color: #121212;   }   /* Override dark mode with light mode styles if the user decides to swap */   body.light-theme {     --text-color: #222;     --bkg-color: #fff;   } }

Now we can turn back to our trusty button to toggle between light and dark themes. This way, we’re respecting the OS color preference by default and allowing the user to manually switch themes.

// Listen for a click on the button  btn.addEventListener("click", function() {   // If the OS is set to dark mode...   if (prefersDarkScheme.matches) {     // ...then apply the .light-theme class to override those styles     document.body.classList.toggle("light-theme");     // Otherwise...   } else {     // ...apply the .dark-theme class to override the default light styles     document.body.classList.toggle("dark-theme");   } });

Browser Support

The prefers-color-scheme media query feature enjoys support by major browsers, including Chrome 76+, Firefox 67+, Chrome Android 76+ and Safari 12.5+ (13+ on iOS). It doesn’t support IE and Samsung Internet Browser.

That’s a promising amount of support!. Can I Use estimates 80.85% of user coverage.

Operating systems that currently support dark mode include MacOS (Mojave or later), iOS (13.0+), Windows (10+), and Android (10+).


Storing a User’s Preference

What we’ve looked at so far definitely does what it says in the tin: swap themes based on an OS preference or a button click. This is great, but doesn’t carry over when the user either visits another page on the site or reloads the current page.

We need to save the user’s choice so that it will be applied consistently throughout the site and on subsequent visits. To do that, we can save the user’s choice to the localStorage when the theme is toggled. Cookies are also well-suited for the job.

Let’s look at both approaches.

Using localStorage

We have a script that saves the selected theme to localStorage when the toggle takes place. In other words, when the page is reloaded, the script fetches the choice from localStorage and applies it. JavaScript is often executed after CSS, so this approach is prone to a “flash of incorrect theme” (FOIT).

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); // Select the theme preference from localStorage const currentTheme = localStorage.getItem("theme"); 
 // If the current theme in localStorage is "dark"... if (currentTheme == "dark") {   // ...then use the .dark-theme class   document.body.classList.add("dark-theme"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on each click   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in localStorage   localStorage.setItem("theme", theme); });

Using Cookies with PHP

To avoid FLIC, we can use a server-side script like PHP. Instead of saving the user’s theme preference in localStorage, we will create a cookie from JavaScript and save it there. But again, this may only be feasible if you’re already working with a server-side language.

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on the body   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in a cookie   document.cookie = "theme=" + theme; });

We can now check for the existence of that cookie and load the appropriate theme by applying the proper class to the <body> tag.

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

Here is how to do that using the separate stylesheets method:

<?php $ themeStyleSheet = 'light-theme.css'; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } ?> 
 <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet" id="theme-link"> </head> <!-- etc. -->

If your website has user accounts — like a place to log in and manage profile stuff — that’s also a great place to save theme preferences. Send those to the database where user account details are stored. Then, when the user logs in, fetch the theme from the database and apply it to the page using PHP (or whatever server-side script).

There are various ways to do this. In this example, I am fetching the user’s theme preference from the database and saving it in a session variable at the time of login.

<?php // Login action if (!empty($ _POST['login'])) {   // etc. 
   // If the uuser is authenticated...   if ($ loginSuccess) {     // ... save their theme preference to a session variable     $ _SESSION['user_theme'] = $ userData['theme'];   } } 
 // Pick the session variable first if it's set; otherwise pick the cookie $ themeChoice = $ _SESSION['user_theme'] ?? $ _COOKIE['theme'] ?? null; $ themeClass = ''; if ($ themeChoice == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

I am using PHP’s null coalesce operator (??) to decide where to pick the theme preference: from the session or from the cookie. If the user is logged in, the value of the session variable is taken instead that of the cookie. And if the user is not logged in or has logged out, the value of cookie is taken.


Handling User Agent Styles

To inform the browser UA stylesheet about the system color scheme preferences and tell it which color schemes are supported in the page, we can use the color-scheme meta tag.

For example, let’s say the page should support both “dark” and “light” themes. We can put both of them as values in the meta tag, separated by spaces. If we only want to support a “light” theme, then we only need to use “light” as the value. This is discussed in a CSSWG GitHub issue, where it was originally proposed.

<meta name="color-scheme" content="dark light">

When this meta tag is added, the browser takes the user’s color scheme preferences into consideration when rendering UA-controlled elements of the page (like a <button>). It renders colors for the root background, form controls, and spell-check features (as well as any other UA-controlled styles) based on the user’s preference.

Source

Although themes are manually styled for the most part (which overrides the UA styles), informing the browser about the supported themes helps to avoid even the slightest chance of a potential FOIT situation. This is true for those occasions where HTML has rendered but CSS is still waiting to load.

We can also set this in CSS:

:root {   color-scheme: light dark; /* both supported */ }
via Jim Nielsen

At the time of writing, the color-scheme property lacks broad browser support, though Safari and Chrome both support it.


Combining all the things!

Let’s combine everything and create a working demo that:

  1. Automatically loads a dark or light theme based on system preferences
  2. Allows the user to manually override their system preference
  3. Maintains the user’s preferred theme on page reloads

Using JavaScript & Local Storage

// Select the button const btn = document.querySelector(".btn-toggle"); // Check for dark mode preference at the OS level const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); 
 // Get the user's theme preference from local storage, if it's available const currentTheme = localStorage.getItem("theme"); // If the user's preference in localStorage is dark... if (currentTheme == "dark") {   // ...let's toggle the .dark-theme class on the body   document.body.classList.toggle("dark-mode"); // Otherwise, if the user's preference in localStorage is light... } else if (currentTheme == "light") {   // ...let's toggle the .light-theme class on the body   document.body.classList.toggle("light-mode"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // If the user's OS setting is dark and matches our .dark-mode class...   if (prefersDarkScheme.matches) {     // ...then toggle the light mode class     document.body.classList.toggle("light-mode");     // ...but use .dark-mode if the .light-mode class is already on the body,     var theme = document.body.classList.contains("light-mode") ? "light" : "dark";   } else {     // Otherwise, let's do the same thing, but for .dark-mode     document.body.classList.toggle("dark-mode");     var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";   }   // Finally, let's save the current preference to localStorage to keep using it   localStorage.setItem("theme", theme); });

Using PHP & Cookies

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme'])) {   if ($ _COOKIE['theme'] == 'dark') {     $ themeClass = 'dark-theme';   } else if ($ _COOKIE['theme'] == 'light') {     $ themeClass = 'light-theme';   }   } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> <script>   const btn = document.querySelector(".btn-toggle");   const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");      btn.addEventListener("click", function() {     if (prefersDarkScheme.matches) {       document.body.classList.toggle("light-mode");       var theme = document.body.classList.contains("light-mode") ? "light" : "dark";     } else {       document.body.classList.toggle("dark-mode");       var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";     }     document.cookie = "theme=" + theme;   }); </script> </body> </html>


Design Considerations

I often hear that implementing dark mode is easier than designing one. While I’ll refrain from judgement, let’s look at some considerations for designing a dark theme.

You already know the basic task: swap lighter color values for darker ones and vice versa. But there are some UI elements and enhancements that are more nuanced and require more attention. Let’s take a look at those.

Dark Mode Images

A good rule is to decrease the brightness and contrast of images a bit so that it looks comfortable to the eyes when it’s against a dark background. A super bright image on a super dark background can be jarring and dimming the image reduces some of that heavy contrast.

The CSS filter() function is more than capable of handling this for us:

/* Apply the filter directly on the body tag */ body.dark-theme img {   filter: brightness(.8) contrast(1.2); } 
 /* Or apply it via media query */ @media (prefers-color-scheme: dark) {   img {     filter: brightness(.8) contrast(1.2);   } }

We can do the same sort of thing directly in the markup using the <picture> element to load different versions of an image:

<picture>   <!-- Use this image if the user's OS setting is light or unset -->   <source srcset="photo-light.png" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">   <!-- Use this image if the user's OS setting is dark -->   <source srcset="photo-dark.png" media="(prefers-color-scheme: dark)"> </picture>

The downside here is that it requires supplying two files where we only have to deal with one when using CSS. This also doesn’t fully account for the user toggling the color theme on the site.

Dark Mode Shadows

Dark mode shadows are tricky. If we simply invert a dark shadow using light colors, then we get this funky thing with a light shadow on a dark background… and it’s not a good look.

It’s possible to use a dark shadow in dark mode, but the background color has to be “light” enough (like a dark gray) to provide enough contrast to actually see the shadow against it.

Use opacity to convey depth, with high opacity regions having a lower depth. That’s to say, elements that have a higher elevation should have a lower opacity than elements that are “closer” in depth to the background.

Different shades of color create different perceptions of “depth”

Dark Mode Typography

The trick here is a lot like images: we’ve gotta balance the contrast. Use too heavy of a font and we get blaring text that’s makes us want to move away from the screen. Use too light of a font and we’ll strain our eyes while inching toward the screen to get a closer look.

The balance is somewhere in the middle. Robin has a nice write-up where he suggests a small bit of CSS that makes a big difference in legibility.

Dark Mode Icons

Icons fall into this “tricky” category because they’re sort of a cross between text and images. If we’re working with SVG icons, though, we can change the fill with CSS. On the other hand, if we’re using font icons, we can simply change the color property instead.

/* SVG icon */ body.dark-theme svg.icon path {   fill: #efefef; } /* Font icon (using Font Awesome as an example) */ body.dark-theme .fa {   color: #efefef; }

A lot of the same design considerations that are true for text, are also generally applicable to icons. For example, we ought to avoid using full white and heavy outlines.

Dark Mode Colors

Pure white text on a pure black background will look jarring. The trick here is to use an off-white for the text and off-black for the background. Material Design Guidelines for example recommends #121212 for the background.

Dark Mode Color Palettes

We’ve seen the difference using off-white and off-black colors makes for text and images. Let’s expand on that a bit with tips on how to develop a full color palette.

Most things boil down to one thing: contrast. That’s why the first tip before settling on any color is to run ideas through a contrast checker to ensure color ratios conform to WCAG’s guidelines for at least a AA rating, which is a contrast ratio of 4.5:1.

That means desaturated colors are our friends when working with a dark mode design. They help prevent overbearingly bright images and still give us plenty of room to create an effective contrast ratio.

Next, remember that accent colors are meant to be enhancements. They’re likely brighter than the dark theme background color, so using them like a primary color or the background color of a large container is just as jarring and hard on the eyes as a bright image or heavy white text.

If contrast is the balance we’re trying to strike, then remember that dark mode is more than blacks and grays. What about dark blue background with pale yellow text? Or dark brown with tan? There’s an entire (and growing) spectrum of color out there and we can leverage any part of it to fuel creativity.

A few examples of colors that are dark without resorting to full-on black:

#232B32

#152028

#202945

Material Design’s guidelines on dark mode is a handy resource on best practices for dark mode design. It’s definitely worth a read for more tips to keep in mind.

Dark Mode in the Wild

YouTube uses the CSS variables technique. They’ve defined all their colors in variables under the html selector while dark mode colors are defined under html:not(.style-scope)[dark]. When dark mode is enabled, YouTube adds a dark="true" attribute to the <html> tag. This is what they use to override the variables defined in the HTML.

YouTube adds dark=true attribute to the <html> when it switches to the dark mode.

In the wild, the CSS custom properties approach seems to be most popular. It’s being used by Dropbox Paper, Slack, and Facebook.

Simplenote uses the class-swapping method where all light style rules are descendants of a .theme-light parent class and all the dark styles fall under a .theme-dark class. When the theme is toggled, the appropriate class is applied to the <body> tag.

Simplenote uses two classes: .light-theme and .dark-theme to style the themes.

Twitter goes the extra mile and offers several themes to choose from: “Default,” “Dim,” and “Lights out.” The “Dim” option employs dark blue for a background color. Compare that to “Lights out” which uses a stark black.

Twitter offers three themes to choose from.

Dark mode or no dark mode? That is the question.

There are perfectly valid reasons on both sides. Some of those reasons even go beyond the scope of user experience and include things like timing, budget and resources.

While being considerate of why you might not want to implement a dark mode, here are reasons why you might want to have one:

  • It’s cool and trendy (although that’s not a reason alone to do it)
  • It enhances accessibility by supporting users who are sensitive to eye strain in starkly bright themes.
  • It allows users to decide the most comfortable way to consume content while providing us a way to maintain control over the look and feel of things. Remember, we want to beat the Reader Mode button!
  • It helps to preserve battery life for devices with OLED screen where brighter colors consume more energy.
  • It’s extremely popular and appears to be going nowhere. It’s possible that your users who prefer a dark mode (like me!) will expect your site to have one. Might as well be ready for it.

The post A Complete Guide to Dark Mode on the Web appeared first on CSS-Tricks.

CSS-Tricks

, , ,

A Complete Guide to CSS Functions

Introduction

In programming, functions are a named portion of code that performs a specific task. An example of this could be a function written in JavaScript called sayWoof():

function sayWoof() {   console.log("Woof!"); }

We can use this function later in our code, after we have defined our desired behavior. For this example, any time you type sayWoof() in your website or web app’s JavaScript it will print “Woof!” into the browser’s console.

Functions can also use arguments, which are slots for things like numbers or bits of text that you can feed into the function’s logic to have it modify them. It works like this in JavaScript:

function countDogs(amount) {   console.log("There are " + amount + " dogs!"); }

Here, we have a function called countDogs() that has an argument called amount. When a number is provided for amount, it will take that number and add it to a pre-specified sentence. This lets us create sentences that tell us how many dogs we’ve counted.

countDogs(3); // There are 3 dogs! countDogs(276); // There are 276 dogs! countDogs("many"); // There are many dogs!

Some programming languages come with baked-in functions to help prevent you from having to reinvent the wheel for every new project. Typically, these functions are built to help make working with the main strengths and features of the language easier. 

Take libraries, for example. Libraries are collections of opinionated code made to help make development faster and easier, effectively just curated function collections — think FitVids.js for creating flexible video elements.

Like any other programming language, CSS has functions. They can be inserted where you’d place a value, or in some cases, accompanying another value declaration. Some CSS functions even let you nest other functions within them!

Basics of CSS Functions

Anatomy of a CSS declaration. Inside of a selector class called .selector there is a declaration of background-image: url(‘parchment.jpg’); Arrows label the property (background-image), the function (url), and the argument (parchment.jpg).

Unlike other programming languages, we cannot create our own functions in CSS, per se. That kind of logic is reserved for CSS selectors, which allow you to create powerful conditional styling rules

As opposed to other programming languages — where the output of a function typically invisibly affects other logic down the line — the output of CSS functions are visual in nature. This output is used to control both layout and presentation of content. For example: 

.has-orange-glow {   filter: drop-shadow(0.25rem 0 0.75rem #ef9035); }

The CSS filter function drop-shadow() uses the arguments we provide it to create an orange outer glow effect on whatever it is applied to.

In the following demo, I have a JavaScript function named toggleOrangeGlow that toggles the application of the class .has-orange-glow on the CSS-Tricks logo. Combining this with a CSS transition, we’re able to create a cool glowing effect:

You may be familiar with some CSS functions, but the language has a surprisingly expansive list! 

Much like any other technology on the web, different CSS functions have different levels of browser support. Make sure you research and test to ensure your experience works for everyone, and use things like @supports to provide quality alternate experiences.

Common CSS Functions

url()

url() allows you to link to other resources to load them. This can include images, fonts, and even other stylesheets. For performance reasons, it’s good practice to limit the things you load via url(), as each declaration is an additional HTTP request.

attr()

This function allows us to reach into HTML, snag an attribute’s content, and feed it to the CSS content property. You’ll commonly see attr() used in print stylesheets, where it is used to show the URL of a link after its text. Another great application of this function is using it to show the alt description of an image if it fails to load.

calc()

If there’s one function you should spend some time experimenting with, it’s calc(). This function takes two arguments and calculates a result from the operator (+, -, *, /) you supply it, provided those arguments are numbers with or without an accompanying unit.

Unlike CSS preprocessors such as Sass, calc() can mix units, meaning you can do things like subtract 6rem from 100%. calc() is also updated on the fly, so if that 100% represents a width, it’ll still work if that width changes. calc() can also accept CSS Custom Properties as arguments, allowing you an incredible degree of flexibility

lang()

Including a lang attribute in your HTML is a really important thing to do. When present in your HTML, you’re able to use the lang() function to target the presence of the attribute’s value and conditionally apply styling based on it. 

One common use for this selector is to set language-specific quotes, which is great for things like internationalization. 

Clever designers and developers might also use it as a hook for styling translated versions of their sites, where cultural and/or language considerations mean there’s different perceptions about things like negative space.

:not()

This pseudo class selector will select anything that isn’t what you specify. For example, you could target anything that isn’t an image with body:not(img). While this example is dangerously powerful, scoping :not() to more focused selectors such as BEM’s block class can give you a great deal of versatility. 

Currently, :not() supports only one selector for its argument, but support for multiple comma-separated arguments (e.g. div:not(.this, .that)) is being worked on!

CSS Custom Properties

var() is used to reference a custom property declared earlier in the document. It is incredibly powerful when combined with calc().

An example of this is declaring a custom property called --ratio: 1.618; in the root of the document, then invoking it later in our CSS to control line height: line-height: var(--ratio);.

Here, var() is a set of instructions that tells the browser, “Go find the argument called --ratio declared earlier in the document, take its value, and apply it here.” 

Remember! calc() lets us dynamically adjust things on the fly, including the argument you supply via var().

This allows us to create things like modular scale systems directly in CSS with just a few lines of code. If you change the value of --ratio, the whole modular scale system will update to match.

In the following CodePen demo, I’ve done exactly just that. Change the value of --scale in the Pen’s CSS to a different number to see what I mean:

It’s also worth mentioning that JavaScript’s setProperty method can update custom properties in real time. This allows us to quickly and efficiently make dynamic changes to things that previously might have required a lot of complicated code to achieve. 

Color Functions

Another common place you see CSS functions is when working with color.

rgb() and rgba()

These functions allow you to use numbers to describe the red (r), green (g), blue (b), and alpha (a) levels of a color. For example, a red color with a hex value of #fb1010 could also be described as rgba(251, 16, 16, 1). The red value, 251, is far higher than the green and blue values (16 and 16), as the color is mostly comprised of red information. 

The alpha value of 1 means that it is fully opaque, and won’t show anything behind what the color is applied to. If we change the alpha value to be 0.5, the color will be 50% transparent. If you use an rgb() function instead of rgba(), you don’t have to supply an alpha value. This creates terser code, but prevents you from using transparency.

hsl() and hsla()

Similar to rgb() and rgba(), hsl() and hsla() are functions that allow you to describe color. Instead of using red, green, and blue, they use hue (h), saturation (s), and lightness (l). 

I prefer using hsla() over rgba() because its model of describing color works really well with systematized color systems. Each of the color level values for these functions can be CSS Custom Properties, allowing you to create powerful, dynamic code.

Syntax updates

In the upcoming CSS Color Module Level 4 spec, we can ignore the a portion of rgba() and hsla(), as well as the commas. Now, spaces are used to separate the rgb and hsl arguments, with an optional / to indicate an alpha level.

https://twitter.com/argyleink/status/1218305696862588928

Pseudo Class Selectors

These selectors use specialized argument notation that specifies patterns of what to select. This allows you to do things like select every other element, every fifth element, every third element after the seventh element, etc.

Pseudo class selectors are incredibly versatile, yet often overlooked and under-appreciated. Many times, a thoughtful application of a few of these selectors can do the work of one or more node packages. 

:nth-child()

nth-child() allows you to target one or more of the elements present in a group of elements that are on the same level in the Document Object Model (DOM) tree.

In the right hands, :nth-child() is incredibly powerful. You can even solve fizzbuzz with it! If you’re looking for a good way to get started, Chris has a collection useful pseudo selector recipes.

:nth-last-child()

This pseudo class selector targets elements in a group of one or more elements that are on the same level in the DOM. It starts counting from the last element in the group and works backwards through the list of available DOM nodes.

:nth-last-of-type()

This pseudo class selector can target an element in a group of elements of a similar type. Much like :nth-last-child(), it starts counting from the last element in the group. Unlike :nth-last-child, it will skip elements that don’t apply as it works backwards. 

:nth-of-type()

:nth-of-type() matches a specified collection of elements of a given type. For example, a declaration of img:nth-of-type(5) would target the fifth image on a page.

Animation Functions

Animation is an important part of adding that certain je ne sais quoi to your website or web app. Just remember to put your users’ needs first and honor their animation preferences.

Creating animations also requires controlling the state of things over time, so functions are a natural fit for making that happen.

cubic-bezier()

Instead of keyword values like ease, ease-in-out, or linear, you can use cubic-bezier() to create a custom timing function for your animation. While you can read about the math that powers cubic beziers, I think it’s much more fun to play around with making one instead.

A custom cubic bezier curve created on cubic-bezier.com. There are also options to preview and compare your curve with CSS’s ease, linear, ease-in, ease-out, and ease-in-out transitions.
Lea Verou’s cubic-bezier.com.

path()

This function is paired with the offset-path property. It allows you to “draw” a SVG path that other elements can be animated to follow.

Both Michelle Barker and Dan Wilson have published excellent articles that go into more detail about this approach to animation.

steps()

This relatively new function allows you to set the easing timing across an animation, which allows for a greater degree of control over what part of the animation occurs when. Dan Wilson has another excellent writeup of how it fits into the existing animation easing landscape. 

Sizing and Scaling Functions

One common thing we do with animation is stretch and squash stuff. The following functions allow you to do exactly that. There is a catch, however: These CSS functions are a special subset, in that they can only work with the transform property.

scaleX(), scaleY(), scaleZ(), scale3d(), and scale()

Scaling functions let you increase or decrease the size of something along one or more axes. If you use scale3d() you can even do this in three dimensions!

translateX(), translateY(), translateZ(), translate3d(), and translate()

Translate functions let you reposition an element along one or more axes. Much like scale functions, you can also extend this manipulation into three dimensions.

perspective()

This function lets you adjust the appearance of an object to make it look like it is projecting up and out from its background.

rotateX(), rotateY(), rotateZ(), rotate3d(), and rotate()

Rotate functions let you swivel an element along one or more axes, much like grasping a ball and turning it around in your hand.

skewX(), skewY(), and skew()

Skew functions are a little different from scaling and rotation functions in that they apply a distortion effect relative to a single point. The amount of distortion is proportionate to the angle and distance declared, meaning that the further the effect continues in a direction the more pronounced it will be. 

Jorge Moreno also did us all a favor and made a great tool called CSS Transform Functions Visualizer. It allows you to adjust sizing and scaling in real time to better understand how all these functions work together:

As responsible web professionals, we should be mindful of our users and the fact that they may not be using new or powerful hardware to view our content. Large and complicated animations may slow down the experience, or even cause the browser to crash in extreme scenarios.

To prevent this, we can use techniques like will-change to prepare the browser for what’s in store, and the update media feature to remove animation on devices that do not support a fast refresh rate. 

Filter Functions

CSS filter functions are another special subset of CSS functions, in that they can only work with the filter property. Filters are special effects applied to an element, mimicking functionality of graphics editing programs such as Photoshop.

You can do some really wild things with CSS filter functions, stuff like recreating the effects you can apply to your posts on Instagram!

brightness()

This function adjusts how, um, bright something appears. Setting it to a low level will make it appear as if it has had a shadow cast over it. Setting it to a high level will blow it out, like an over-exposed photo.

blur()

If you’re familiar with Photoshop’s Gaussian Blur filter, you know how blur() works. The more of this you apply, the more indistinct the thing you apply it to will look.

contrast()

contrast() will adjust the degree of difference between the lightest and darkest parts of what is applied to.

grayscale()

grayscale() removes the color information from what it is applied to. Remember that this isn’t an all-or-nothing affair! You can apply a partial grayscale effect to make something look weathered or washed out.

An interesting application of grayscale() could be lightly applying it to images when dark mode is enabled, to slightly diminish the overall vibrancy of color in a situation where the user may want less eye strain.

invert()

While invert() can be used to make something look like a photo negative, my favorite technique is to use it in a inverted colors media query to invert inverted images and video:

@media (inverted-colors: inverted) {   img,   video {     filter: invert(100%);   } }

This ensures that image and video content looks the way it should, regardless of a user’s expressed browsing mode preferences. 

opacity()

This function controls how much of the background is visible through the element (and child elements) the function is applied to. 

An element that has 0% opacity will be completely transparent, although it will still be present in the DOM. If you need to remove an object completely, use other techniques such as the hidden attribute.

saturate()

Applying this filter can enhance, or decrease the intensity of the color of what it is applied to. Enhancing an image’s saturation is a common technique photographers use to fix underexposed photos.

sepia()

There are fancier ways to describe this, but realistically it’s a function that makes something look like it’s an old-timey photograph.

drop-shadow()

A drop shadow is a visual effect applied to an object that makes it appear like it is hovering off of the page. There’s a bit of a trick here, in that CSS also allows you to apply drop shadow effects to text and elements. It’s also distinct from the box-shadow property is that it applies drop shadows to the shape of an element rather than the actual box of an element.

Skilled designers and developers can take advantage of this to create complicated visual effects.

hue-rotate()

When a class with a declaration containing hue-rotate() is applied to an element, each pixel used to draw that element will have it’s hue valued shifted by the amount you specify. hue-rotate()‘s effect is applied to each and every pixel it is applied to, so all colors will update relative to their hue value’s starting point.

This can create a really psychedelic effect when applied to things that contain a lot of color information, such as photos.

SVG filters 

filter() also lets us import SVGs filters to use to create specialized visual effects. The topic is too complicated to really do it justice in this article — if you’re looking for a good starting point, I recommend “The Art Of SVG Filters And Why It Is Awesome” by Dirk Weber.

The word “West!” rendered in a Wild West-style font, with layered teal drop shadows giving it a 3D effect. Behind it is a purple starburst pattern. Screenshot.
This effect was created by skillful application of SVG filter effects.

Gradient Functions

Gradients are created when you transition one color to one or more other colors. They are workhorses of modern user interfaces — skilled designers and developers use them to lend an air of polish and sophistication to their work.

Gradient functions allow you to specify a whole range of properties, including:

  • Color values,
  • The position on the gradient area where that color comes in,
  • What angle the gradient is positioned at.

And yes, you guessed it: the colors we use in a gradient can be described using CSS color functions!

linear-gradient() and repeating-linear-gradient()

Linear gradients apply the color transformation in a straight line, from one point to another — this line can be set at an angle as well. In cases where there’s more area than gradient, using repeating-linear-gradient() will, er, repeat the gradient you described until all the available area has been filled.

radial-gradient() and repeating-radial-gradient()

Radial gradients are a lot like linear gradients, only instead of a straight line, color transformations radiate outward from a center point. They’re oftentimes used to create a semitransparent screen to help separate a modal from the background it is placed over.

conic-gradient() and repeating-conical-gradient

Conic gradients are different from radial gradients in that the color rotates around a circle. Because of this, we can do neat things like create donut charts. Unfortunately, support for conic gradients continues to be poor, so use them with caution.

A teal and red donut chart set to 36%. To the right of the chart is a range slider, also set to 36%. Screenshot.
An adjustable conic gradient donut chart made by Ana Tudor

Grid Functions

CSS Grid is a relatively new feature of the language. It allows us to efficiently create adaptive, robust layouts for multiple screen sizes. 

It’s worth acknowledging our roots. Before Grid, layout in CSS was largely a series of codified hacks to work with a language originally designed to format academic documents. Grid’s introduction is further acknowledgement that the language’s intent has changed. 

Modern CSS is an efficient, fault-tolerant language for controlling presentation and layout across a wide range of device form factors. Equipped with Grid and other properties like flexbox, we’re able to create layouts that would have been impossible to create in earlier iterations of CSS. 

Grid introduces the following CSS functions to help you use it.

fit-content()

This function “clamps” the size of grid rows or columns, letting you specify a maximum size a grid track can expand to. fit-content() accepts a range of values, but most notable among them are min-content and max-content. These values allow you to tie your layout to the content it contains. Impressive stuff!

minmax()

minmax() allows you to set the minimum and maximum desired heights and widths of your grid rows and columns. This function can also use min-content and max-content, giving us a great deal of power and flexibility.

repeat()

You can loop through patterns of grid column and rows using repeat(). This is great for two scenarios: 

  1. When you do know how many rows or columns you need, but typing them out would be laborious. A good example of this would be constructing the grid for a calendar.
  2. When you don’t know how many rows or columns you need. Here, you can specify a template that the browser will honor as it propagates content into your layout.

Shape Functions

Like filter() and transform(), shape CSS functions only work with one property: clip-path. This property is used to mask portions of something, allowing you to create all sorts of cool effects.

circle()

This function creates a circular shape for your mask, allowing you to specify its radius and position.

ellipse()

Like circle(), ellipse() will draw a rounded shape, only instead of a perfect circle, ellipse() lets you construct an oblong mask.

inset()

This function will mask out a rectangle inside of the element you apply it to.

polygon()

With polygon(), you are able to specify an arbitrary number of points, allowing you to draw complicated shapes. polygon() also takes an optional fill-rule argument, which specifies which part of the shape is the inside part.

Miscellaneous Functions

These are the un-categorizable CSS functions, things that don’t fit neatly elsewhere.

element()

Ever pointed a camera at its own video feed? That’s sort of what element() does. It allows you to specify the ID of another element to create an “image” of what that element looks like. You can then apply other CSS to that image, including stuff like CSS filters!

It might take a bit to wrap your head around the concept — and it has some support concerns — but element() is a potentially very powerful in the right hands.

Preethi Sam‘s “Using the Little-Known CSS element() Function to Create a Minimap Navigator” demonstrates how to use it to create a code minimap and is an excellent read.

Here, she’s created a minimap for reading through a longform article:

image-set()

This function allows you to specify a list of images for the browser to select for a background image, based on what it knows about the capabilities of its display and its connection speed. It is analogous to what you would do with the srcset property.

::slotted()

This is a pseudo-element selector used to target elements that have been placed into a slot inside a HTML template. ::slotted() is intended to be used when working with Web Components, which are custom, developer-defined HTML elements.

Not Ready for Prime Time

Like any other living programming language, CSS includes features and functionality that are actively being worked on. 

These functions can sometimes be previewed using browsers that have access to the bleeding edge. Firefox Nightly and Chrome Canary are two such browsers. Other features and functionality are so new that they only exist in what is being actively discussed by the W3C.

annotation()

This function enables Alternate Annotation Forms, characters reserved for marking up things like notation and annotation. These characters typically will be outlined with a circle, square, or diamond shape.

Not many typefaces contain Alternate Annotation Forms, so it’s good to check to see if the typeface you’re using includes them before trying to get annotation() to work. Tools such as Wakamai Fondue can help with that.

he numbers 1 and 2 enclosed in hollow and solid-filled circles. Following them are the letters B and R enclosed in hollow and solid-filled squares. Screenshot.Stylistic Alternates.
Examples of annotation glyphs from Jonathan Harrell’s post, “Better Typography with Font Variants”

counter() and counters()

When you create an ordered list in HTML, the browser will automatically generate numbers for you and place them before your list item content. These pieces of browser-generated list content are called counters. 

By using a combination of the ::marker pseudo-element selector, the content property, and the counter() function, we can control the content and presentation of the counters on an ordered list. For browsers that don’t support counter() or counters() yet, you still get a decent experience due to the browser automatically falling back to its generated content:

For situations where you have nested ordered lists, the counters() function allows a child ordered list to access its parent. This allows us to control their content and presentation. If you want to learn more about the power of ::marker, counter(), and counters(), you can read “CSS Lists, Markers, And Counters” by Rachel Andrew.

cross-fade()

This function will allow you to blend one background image into one or more other background images. Its proposed syntax is similar to gradient functions, where you can specify the stops where images start and end.

dir()

This function allows you to flip the orientation of a language’s reading order. For English, that means a left-to-right (ltr) reading order gets turned into right-to-left (rtl). Only Firefox currently has support for dir(), but you can achieve the same effect in Chromium-based browsers by using an attribute selector such as [dir="rtl"].

env()

env(), short for environment, allows you to create conditional logic that is triggered if the device’s User Agent matches up. It was popularized by the iPhone X as a method to work with its notch

That being said, device sniffing is a fallacious affair — you shouldn’t consider env() a way to cheat it. Instead, use it as intended: to make sure your design works for devices that impose unique hardware constraints on the viewport.

has()

has() is a relational pseudo-class that will target an element that contains another element, provided there is at least one match in the HTML source. An example of this is be a:has(> img), which tells the browser to target any link that contains an image. 

A diagram showing how the CSS selector a:has(> img) targets only links that contain images in a collection of links that contain either images or paragraphs.

Interestingly, has() is currently being proposed as CSS you can only write in JavaScript. If I were to wager a guess as to why this is, it is to scope the selector for performance reasons. With this approach has() is triggered only after the browser has been told to process conditional logic, and therefore query the state of things.

image()

This function will let you insert either a static image (referenced with url(), or draw one dynamically via gradients and element()

Trigonometry functions

These functions will allow us to perform more advanced mathematical operations

  • Sine: sin()
  • Cosine: cos()
  • Tangent: tan()
  • Arccosine: acos()
  • Arcsine: asin()
  • Arctangent: atan()
  • Arctangent: atan2()
  • Square root: sqrt()
  • The square root of the sum of squares of its arguments: hypot()
  • Power: pow()

I’m especially excited to see what people who are more clever than I am will do with these functions, especially for things like animation!

clamp()

When providing minimum, maximum, and preferred values as arguments, clamp() will honor the preferred value so long as it does not exceed the minimum and maximum boundaries. 

clamp() will allow us to author things like components whose size will scale along with the size of the viewport, but won’t shrink or grow past a specific size. This will be especially useful for creating CSS locks, where you can ensure a responsive type size will not get so small that it can’t be read.

:host() and :host-context()

To be honest, I’m a little hazy on the specifics of the jargon and mechanics that power the Shadow DOM. Here’s how the MDN describes host():

The :host() CSS pseudo-class function selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function’s parameter matches the shadow host.

And here’s what they have to say about :host-context():

The :host-context() CSS pseudo-class function selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function’s parameter matches the shadow host’s ancestor(s) in the place it sits inside the DOM hierarchy.

:is() and :where()

:is() has had a bit of an identity crisis. Previously referred to as both matches() and vendor prefixed as :-webkit-any/:-moz-any, it now enjoys a standardized, agreed-upon name. It is a pseudo class selector that accepts a range of selectors as its argument. 

This allows an author to group and target a wide range of selectors in an efficient way. :where() is much like :is(), only it has a specificity of zero, while the specificity of :is() is set to the highest specificity in the provided selector list. 

:is() and :where() will allow us a good deal of flexibility about how we select things to style, especially for situations where you may not have as much control over the web site or web app’s stylesheet (e.g. third-party integrations, ads, etc.).

max() and min()

These functions allow you to select either the maximum or minimum value from a range of values you provide. Much like clamp(), these functions allow us to make things responsive up until a certain point. 

:nth-col() and :nth-last-col()

These pseudo-classes will allow you to select one or a specified series columns in a CSS grid to apply styling to them. A good mental model for how these functions will work is how CSS pseudo class selectors operate. Unlike pseudo class selectors, :nth-col() and :nth-last-col() should be able to target implicit grid columns.

symbols()

This function allows you to specify a list of different kinds of characters to use for list bullets. Much like annotation(), you’ll want to make sure the typeface you use contains a glyph you want to use as a symbol before trying to get symbols() to work.

Deprecated Functions

Sometimes things just don’t work out the way you think they will. While deprecated CSS functions may still render in the browser for legacy support reasons, it isn’t recommended you use them going forward.

matrix() and matrix3d()

These functions were turned into more discrete sizing and scaling functions.

rect()

This function was part of the deprecated clip property. Use the clip-path property and its values instead.

target-counter(), target-counters(), and target-text()

These functions were intended to help work with fragment URLs for paged (printed) media. You can read more about them on the W3C’s CSS Generated Content for Paged Media Module documentation

Typography

The web is typography, so it makes sense to give your type the care and attention it deserves. While CSS provides some functions specifically designed to unlock the potential of your website or webapp’s chosen typefaces, it is advised to not use the following functions to access these advanced features. 

Instead, use lower-level syntax via font-feature-settings. You can figure out if the font you’re using supports these features by using a  tool such as Wakamai Fondue.

character-variant(), styleset(), and stylistic()

Many typefaces made by professional foundries include alternate treatments for certain letters, or combinations of letters. One example use case is providing different variations of commonly-used letters for typefaces designed to look like handwriting, to help make it appear more natural-looking.

Two examples of the sentence, “Easy Sunday morning & my fox. The first sentence does not have Stylistic Alternates enabled. The second sentence does, with the alternate characters (a, “un”, “m, “rn” g, &, m, f, and x) highlighted in green. Screenshot.
Stylistic Alternates example by Tunghsiao Liu’s “OpenType Features in CSS”

Utilizing these functions activates these special alternate characters, provided they are present in the font’s glyph set

Unfortunately, it is not a standardized offering. Different typefaces will have different ranges of support, based on what the typographer chose to include. It would be wise to check to see if the font you’re using supports these special features before writing any code.

format()

When you are importing a font via the url() function, the format() function is an optional hint that lets you manually specify the font’s file format. If this hint is provided, the browser won’t download the font if it does not recognize the specified file format.

@font-face {   font-family: 'MyWebFont';   src: url('mywebfont.woff2') format('woff2'), /* Cutting edge browsers */        url('mywebfont.woff') format('woff'), /* Most modern Browsers */        url('mywebfont.ttf') format('truetype'); /* Older Safari, Android, iOS */ }
leader()

You know when you’re reading a menu at a restaurant and there’s a series of periods that help you figure out what price is attached to what menu item? Those are leaders. 

The W3C had plans for them with its CSS Generated Content for Paged Media Module, but it unfortunately seems like leader() never quite managed to take off. Fortunately, the W3C also provides an example of how to accomplish this effect using a clever application of the content property.

local()

local() allows you to specify a font installed locally, meaning it is present on the device. Local fonts either ship with the device, or can be manually installed. 

Betting on someone installing a font so things look the way you want them to is very risky! Because of this, it is recommended you don’t specify a local font that needs to be manually installed. Your site won’t look the way it is intended to, even moreso if you don’t specify a fallback font.

@font-face {   font-family: 'FeltTipPen';   src: local('Felt Tip Pen Web'), /* Full font name */        local('FeltTipPen-Regular'); /* Postscript name */ }
ornaments()

Special dingbat characters can be enabled using this function. Be careful, as not all dingbat characters are properly coded in a way that will work well if a user does something like change the font, or use a specialized browsing mode.

swash()

Swashes are alternate visual treatments for letters that give them an extra-fancy flourish. They’re commonly found in italic and cursive-style typefaces.

An example of a swash being applied to a script-style typeface. There’s two versions of the phrase, “Fred And Ginger”. The first version doesn’t have swashes activated. The second example does. In the second example, the letter F, and, and the letter G are highlighted to demonstrate them being activated. Screenshot.
Swash example by Tunghsiao Liu’s “OpenType Features in CSS”

Why so many?

CSS is maligned as frequently as it is misunderstood. The guiding thought to understanding why all these functions are made available to us is knowing that CSS isn’t prescriptive — not every website has to look like a Microsoft Word document. 

The technologies that power the web are designed in such a way that someone with enough interest can build whatever they want. It’s a powerful, revolutionary concept, a large part of why the web became so ubiquitous.

The post A Complete Guide to CSS Functions appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

A Complete Guide to calc() in CSS

CSS has a special calc() function for doing basic math. Here’s an example:

.main-content {   /* Subtract 80px from 100vh */   height: calc(100vh - 80px); }

In this guide, let’s cover just about everything there is to know about this very useful function.

calc() is for values

The only place you can use the calc() function is in values. See these examples where we’re setting the value for a number of different properties.

.el {   font-size: calc(3vw + 2px);   width:     calc(100% - 20px);   height:    calc(100vh - 20px);   padding:   calc(1vw + 5px); }

It could be used for only part of a property too, for example:

.el {   margin: 10px calc(2vw + 5px);   border-radius: 15px calc(15px / 3) 4px 2px;   transition: transform calc(1s - 120ms); }

It can even be a part of another function that forms a part of a property! For example, here’s calc() used within the color stops of a gradient

.el {   background: #1E88E5 linear-gradient(     to bottom,     #1E88E5,     #1E88E5 calc(50% - 10px),     #3949AB calc(50% + 10px),     #3949AB   ); }

calc() is for lengths

Notice all the examples above are essentially numbers-based. We’ll get to some of the caveats of how the numbers can be used (because sometimes you don’t need a unit), but this is for number math, not strings or anything like that.

.el {   /* Nope! */   counter-reset: calc("My " + "counter"); } .el::before {   /* Nope! */   content: calc("Candyman " * 3); }

There are many lengths of CSS though, and they can all be used with calc():

  • px
  • %
  • em
  • rem
  • in
  • mm
  • cm
  • pt
  • pc
  • ex
  • ch
  • vh
  • vw
  • vmin
  • vmax

You can also not perform any calculation and it is still valid:

.el {   /* Little weird but OK */   width: calc(20px); }

Nope on media queries

When calc() is used correctly (length units used as a value to a property), it sadly calc() won’t work when applied to media queries.

@media (max-width: 40rem) {   /* Narrower or exactly 40rem */ }  /* Nope! */ @media (min-width: calc(40rem + 1px)) {   /* Wider than 40rem */ }

It would be cool someday because you could do mutually exclusive media queries in a fairly logical way (like above).

Mixing units 🎉

This is perhaps the most valuable feature of calc()! Almost every example above has already done this, but just to put a point on it, here it is mixing different units:

/* Percentage units being mixed with pixel units */ width: calc(100% - 20px);

That’s saying: As wide as the element is, minus 20 pixels.

There is literally no way to pre-calculate that value in pixels alone in a fluid width situation. In other words, you can’t preprocess calc() with something like Sass as an attempted complete a polyfill. Not that you need to, as the browser support is fine. But the point is that it has to be done in the browser (at “runtime”) when you mix units in this way, which is most of the value of calc().

Here’s some other examples of mixing units:

transform: rotate(calc(1turn + 45deg));  animation-delay: calc(1s + 15ms);

Those probably could be preprocessed as they mix units that aren’t relative to anything that is determined at runtime.

Comparison to preprocessor math

We just covered that you can’t preprocess the most useful things that calc() can do. But there is a smidge of overlap. For example, Sass has math built into it, so you can do things like:

$ padding: 1rem;  .el[data-padding="extra"] {   padding: $ padding + 2rem; // processes to 3rem;   margin-bottom: $ padding * 2; // processes to 2rem;  }

Even math with units is working there, adding same-unit values together or multiplying by unitless numbers. But you can’t mix units and it has similar limitations to calc() (e.g. like multiplying and dividing must be with unit-less numbers).

Show the math

Even you aren’t using a feature that is uniquely possible only with calc(), it can be used to “show your work” inside CSS. For example, say you need to calculate exactly 17th the width of an element…

.el {   /* This is easier to understand */   width: calc(100% / 7);    /* Than this is */   width: 14.2857142857%; }

That might pan out in some kind of self-created CSS API like:

[data-columns="7"] .col { width: calc(100% / 7); } [data-columns="6"] .col { width: calc(100% / 6); } [data-columns="5"] .col { width: calc(100% / 5); } [data-columns="4"] .col { width: calc(100% / 4); } [data-columns="3"] .col { width: calc(100% / 3); } [data-columns="2"] .col { width: calc(100% / 2); }

The Math operators of calc()

You’ve got +, -, *, and /. But they differ in how you are required to use them.

Addition (+) and subtraction (-) require both numbers to be lengths

.el {   /* Valid 👍 */   margin: calc(10px + 10px);    /* Invalid 👎 */   margin: calc(10px + 5); }

Invalid values invalidate the whole individual declaration.

Division (/) requires the second number to be unitless

.el {   /* Valid 👍 */   margin: calc(30px / 3);    /* Invalid 👎 */   margin: calc(30px / 10px);    /* Invalid 👎 (can't divide by 0) */   margin: calc(30px / 0); }

Multiplication (*) requires one of the numbers to be unitless

.el {   /* Valid 👍 */   margin: calc(10px * 3);    /* Valid 👍 */   margin: calc(3 * 10px);    /* Invalid 👎 */   margin: calc(30px * 3px); }

Whitespace matters

Well, it does for addition and subtraction.

.el {   /* Valid 👍 */   font-size: calc(3vw + 2px);    /* Invalid 👎 */   font-size: calc(3vw+2px);    /* Valid 👍 */   font-size: calc(3vw - 2px);    /* Invalid 👎 */   font-size: calc(3vw-2px); }

I imagine it has to do with negative numbers somehow, as using negative numbers (e.g. calc(5vw - -5px)) is OK. I suppose that makes something like calc(5vw--5px) weird, especially in how custom properties use double-dashes (e.g. var(--padding)).

Multiplication and division do not need the whitespace around the operators. But I’d think good general advice is to include the space for readability and muscle memory for the other operators.

Whitespace around the outsides doesn’t matter. You can even do line breaks if you’d like:

.el {   width: calc(     100%     /   3   ); }

Careful about this, though: no spaces between calc() and the opening paren.

.el {   /* Invalid 👎 */   width: calc (100% / 3); }

Nesting calc(calc());

You can but it’s never necessary. It’s the same as using an extra set of parentheses without the calc() part. For example:

.el {   width: calc(     calc(100% / 3)     -     calc(1rem * 2)   ); }

You don’t need those inside calc() because the parens work alone:

.el {   width: calc(    (100% / 3)     -    (1rem * 2)   ); }

And in this case, the “order of operations” helps us even without the parentheses. That is, division and multiplication happen first (before addition and subtraction), so the parentheses aren’t needed at all. It could be written like this:

.el {   width: calc(100% / 3 - 1rem * 2); }

But feel free to use the parens if you feel like it adds clarity. If the order of operations doesn’t work in your favor (e.g. you really need to do the addition or subtraction first), you’ll need parens.

.el {   /* This */   width: calc(100% + 2rem / 2);    /* Is very different from this */   width: calc((100% + 2rem) / 2); }

CSS custom properties and calc() 🎉

Other than the amazing ability of calc() to mix units, the next most awesome thing about calc() is using it with custom properties. Custom properties can have values that you then use in a calculation:

html {   --spacing: 10px; }  .module {   padding: calc(var(--spacing) * 2); }

I’m sure you can imagine a CSS setup where a ton of configuration happens at the top by setting a bunch of CSS custom properties and then letting the rest of the CSS use them as needed.

Custom properties can also reference each other. Here’s an example where some math is used (note the lack of a calc() function at first) and then later applied. (It ultimately has to be inside of a calc().)

html {   --spacing: 10px;   --spacing-L: var(--spacing) * 2;   --spacing-XL: var(--spacing) * 3; }  .module[data-spacing="XL"] {   padding: calc(var(--spacing-XL)); }

Custom properties can come from the HTML, which is a pretty darn cool and useful thing sometimes. (See how Splitting.js adds indexes to words/characters as an example.)

<div style="--index: 1;"> ... </div> <div style="--index: 2;"> ... </div> <div style="--index: 3;"> ... </div>
div {   /* Index value comes from the HTML (with a fallback) */   animation-delay: calc(var(--index, 1) * 0.2s); }

Adding units later

In case you’re in a situation where it’s easier to store numbers without units, or do math with unit-less numbers ahead of time, you can always wait until you apply the number to add the unit by multiplying by 1 and the unit.

html {   --importantNumber: 2; }  .el {   /* Number stays 2, but it has a unit now */   padding: calc(var(--importantNumber) * 1rem); }

Messing with colors

Color format like RGB and HSL have numbers you can mess with using calc(). For example, setting some base HSL values and then altering them forming a system of your own creation (example):

html {   --H: 100;   --S: 100%;   --L: 50%; }  .el {   background: hsl(     calc(var(--H) + 20),     calc(var(--S) - 10%),     calc(var(--L) + 30%)   ) }

You can’t combine calc() and attr()

The attr() function in CSS looks appealing, like you can yank attribute values out of HTML and use them. But…

<div data-color="red">...</div>
div {   /* Nope */   color: attr(data-color); }

Unfortunately, there are no “types” in play here, so the only thing attr() is for are strings in conjunction with the content property. That means this works:

div::before {   content: attr(data-color); }

I mention this, because it might be tempting to try to pull a number in that way to use in a calculation, like:

<div class="grid" data-columns="7" data-gap="2">...</div>
.grid {   display: grid;    /* Neither of these work */   grid-template-columns: repeat(attr(data-columns), 1fr);   grid-gap: calc(1rem * attr(data-gap)); }

Fortunately, it doesn’t matter much because custom properties in the HTML are just as useful or more!

<div class="grid" style="--columns: 7; --gap: 2rem;">...</div>
.grid {   display: grid;    /* Yep! */   grid-template-columns: repeat(var(--columns), 1fr);   grid-gap: calc(var(--gap)); }

Browser tooling

Browser DevTools will tend you show you the calc() as you authored it in the stylesheet.

Firefox DevTools – Rules

If you need to figure out the computed value, there is a Computed tab (in all browser DevTools, at least that I know about) that will show it to you.

Chrome DevTools – Computed

Browser support

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

Desktop

Chrome Firefox IE Edge Safari
19* 4* 11 12 6*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
80 68 80 6.0-6.1*

If you really needed to support super far back (e.g. IE 8 or Firefox 3.6), the usual trick is to add another property or value before the one that uses calc():

.el {   width: 92%; /* Fallback */   width: calc(100% - 2rem); }

There are quite a few known issues for calc() as well, but they are all for old browsers. Can I use… lists 13 of them, here’s a handful:

  • Firefox <59 does not support calc() on color functions. Example: color: hsl(calc(60 * 2), 100%, 50%).
  • IE 9 – 11 will not render the box-shadow property when calc() is used for any of the values.
  • Neither IE 9 – 11 nor Edge support width: calc() on table cells.

Use-case party

I asked some CSS developers when they last used calc() so we could have a nice taste here for for how others use it in their day-to-day work.

I used it to create a full-bleed utility class: .full-bleed { width: 100vw; margin-left: calc(50% - 50vw); } I’d say calc() is in my top 3 CSS things.

I used it to make space for a sticky footer.

I used it to set some fluid type / dynamic typography… a calculated font-size based on minimums, maxiums, and a rate of change from viewport units. Not just the font-size, but line-height too.

If you’re using calc() as part of a fluid type situation that involves viewport units and such, make sure that you include a unit that uses rem or em so that the user still has some control over bumping that font up or down by zooming in or out as they need to.

One I really like is having a “content width” custom property and then using that to create the spacing that I need, like margins: .margin { width: calc( (100vw - var(--content-width)) / 2); }

I used it to create a cross-browser drop-cap component. Here’s a part of it:

.drop-cap { --drop-cap-lines: 3; font-size: calc(1em * var(--drop-cap-lines) * var(--body-line-height)); }

I used it to make some images overflow their container on an article page.

I used it to place a visualization correctly on the page by combining it with padding and vw/vh units.

I use it to overcome limitations in background-position, but expecially limitations in positioning color stops in gradients. Like “stop 0.75em short of the bottom”.

Other Trickery

The post A Complete Guide to calc() in CSS appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

A Complete Guide to Data Attributes

Introduction

HTML elements can have attributes on them that are used for anything from accessibility information to stylistic control.

<!-- We can use the `class` for styling in CSS, and we've also make this into a landmark region --> <div class="names" role="region" aria-label="Names"></div>

What is discouraged is making up your own attributes, or repurposing existing attributes for unrelated functionality.

<!-- `highlight` is not an HTML attribute --> <div highlight="true"></div>  <!-- `large` is not a valid value of `width` --> <div width="large">

There are a variety of reasons this is bad. Your HTML becomes invalid, which may not have any actual negative consequences, but robs you of that warm fuzzy valid HTML feeling. The most compelling reason is that HTML is a living language and just because attributes and values that don’t do anything today doesn’t mean they never will.

Good news though: you can make up your own attributes. You just need to prefix them with data-* and then you’re free to do what you please!

Syntax

It can be awfully handy to be able to make up your own HTML attributes and put your own information inside them. Fortunately, you can! That’s exactly what data attributes are. They are like this:

<!-- They don't need a value --> <div data-foo></div>  <!-- ...but they can have a value --> <div data-size="large"></div>  <!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside --> <li data-prefix="Careful with HTML in here."><li>  <!-- You can keep dashing if you like --> <aside data-some-long-attribute-name><aside>

Data attributes are often referred to as data-* attributes, as they are always formatted like that. The word data, then a dash -, then other text you can make up.

Can you use the data attribute alone?

<div data=""></div>

It’s probably not going to hurt anything, but you won’t get the JavaScript API we’ll cover later in this guide. You’re essentially making up an attribute for yourself, which as I mentioned in the intro, is discouraged.

What not to do with data attributes

Store content that should be accessible. If the content should be seen or read on a page, don’t only put them in data attributes, but make sure that content is in the HTML content somewhere.

<!-- This isn't accessible content --> <div data-name="Chris Coyier"></div>  <!-- If you need programmatic access to it but shouldn't be seen, there are other ways... --> <div>   <span class="visually-hidden">Chris Coyier</span> </div>

Here’s more about hiding things.

Styling with data attributes

CSS can select HTML elements based on attributes and their values.

/* Select any element with this data attribute and value */ [data-size="large"] {   padding: 2rem;   font-size: 125%; }  /* You can scope it to an element or class or anything else */ button[data-type="download"] { } .card[data-pad="extra"] { }

This can be compelling. The predominant styling hooks in HTML/CSS are classes, and while classes are great (they have medium specificity and nice JavaScript methods via classList) an element either has it or it doesn’t (essentially on or off). With data-* attributes, you get that on/off ability plus the ability to select based on the value it has at the same specificity level.

/* Selects if the attribute is present at all */ [data-size] { }  /* Selects if the attribute has a particular value */ [data-state="open"], [aria-expanded="true"] { }  /* "Starts with" selector, meaning this would match "3" or anything starting with 3, like "3.14" */ [data-version^="3"] { }  /* "Contains" meaning if the value has the string anywhere inside it */ [data-company*="google"] { }

The specificity of attribute selectors

It’s the exact same as a class. We often think of specificity as a four-part value:

inline style, IDs, classes/attributes, tags

So a single attribute selector alone is 0, 0, 1, 0. A selector like this:

div.card[data-foo="bar"] { }

…would be 0, 0, 2, 1. The 2 is because there is one class (.card) and one attribute ([data-foo="bar"]), and the 1 is because there is one tag (div).

Attribute selectors have less specificity than an ID, more than an element/tag, and the same as a class.

Case-insensitive attribute values

In case you’re needing to correct for possible capitalization inconsistencies in your data attributes, the attribute selector has a case-insensitive variant for that.

/* Will match <div data-state="open"></div> <div data-state="Open"></div> <div data-state="OPEN"></div> <div data-state="oPeN"></div> */ [data-state="open" i] { }

It’s the little i within the bracketed selector.

Using data attributes visually

CSS allows you to yank out the data attribute value and display it if you need to.

/* <div data-emoji="✅"> */  [data-emoji]::before {   content: attr(data-emoji); /* Returns '✅' */   margin-right: 5px; }

Example styling use-case

You could use data attributes to specify how many columns you want a grid container to have.

<div data-columns="2"></div> <div data-columns="3"></div> <div data-columns="4"></div>

Accessing data attributes in JavaScript

Like any other attribute, you can access the value with the generic method getAttribute.

let value = el.getAttribute("data-state");  // You can set the value as well. // Returns data-state="collapsed" el.setAttribute("data-state", "collapsed");

But data attributes have their own special API as well. Say you have an element with multiple data attributes (which is totally fine):

<span    data-info="123"    data-index="2"    data-prefix="Dr. "   data-emoji-icon="🏌️‍♀️" ></span>

If you have a reference to that element, you can set and get the attributes like:

// Get span.dataset.info; // 123 span.dataset.index; // 2  // Set span.dataset.prefix = "Mr. "; span.dataset.emojiIcon = "🎪";

Note the camelCase usage on the last line there. It automatically converts kebab-style attributes in HTML, like data-this-little-piggy, to camelCase style in JavaScript, like dataThisLittlePiggy.

This API is arguably not quite as nice as classList with the clear add, remove, toggle, and replace methods, but it’s better than nothing.

You have access to inline datasets as well:

<img src="spaceship.png"   data-ship-id="324" data-shields="72%"   onclick="pewpew(this.dataset.shipId)"> </img>

JSON data inside data attributes

<ul>   <li data-person='     {       "name": "Chris Coyier",       "job": "Web Person"     }   '></li> </ul>

Hey, why not? It’s just a string and it’s possible to format it as valid JSON (mind the quotes and such). You can yank that data and parse it as needed.

const el = document.querySelector("li");  let json = el.dataset.person; let data = JSON.parse(json);  console.log(data.name); // Chris Coyier console.log(data.job); // Web Person

JavaScript use-cases

The concept is that you can use data attributes to put information in HTML that JavaScript may need access to do certain things.

A common one would have to do with database functionality. Say you have a “Like” button:

<button data-id="435432343">♡</button>

That button could have a click handler on it which performs an Ajax request to the server to increment the number of likes in a database on click. It knows which record to update because it gets it from the data attribute.

Specifications

Browser support

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

Desktop

Chrome Firefox IE Edge Safari
7 6 11 12 5.1

Mobile / Tablet

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

The post A Complete Guide to Data Attributes appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

A Complete Guide to Links and Buttons

There is a lot to know about links and buttons in HTML. There is markup implementation and related attributes, styling best practices, things to avoid, and the even-more-nuanced cousins of the link: buttons and button-like inputs.

Let’s take a look at the whole world of links and buttons, and all the considerations at the HTML, CSS, JavaScript, design, and accessibility layers that come with them. There are plenty of pitfalls and bad practices to avoid along the way. By covering it, we’ll have a complete good UX implementation of both elements.

Quick rules of thumb on when to use each:

  • Are you giving a user a way to go to another page or a different part of the same page? Use a link (<a href="/somewhere">link</a>)
  • Are you making a JavaScript-powered clickable action? Use a button (<button type="button">button</button>)
  • Are you submitting a form? Use a submit input (<input type="submit" value="Submit">)

Links

Links are one of the most basic, yet deeply fundamental and foundational building blocks of the web. Click a link, and you move to another page or are moved to another place within the same page.

Table of Contents

A basic link

<a href="https://css-tricks.com">CSS-Tricks</a>

That’s a link to a “fully qualified” or “absolute” URL.

A relative link

You can link “relatively” as well:

<!-- Useful in navigation, but be careful in content that may travel elsewhere (e.g. RSS) --> <a href="/pages/about.html">About</a>

That can be useful, for example, in development where the domain name is likely to be different than the production site, but you still want to be able to click links. Relative URLs are most useful for things like navigation, but be careful of using them within content — like blog posts — where that content may be read off-site, like in an app or RSS feed.

A jump link

Links can also be “hash links” or “jump links” by starting with a #:

<a href="#section-2">Section Two</a> <!-- will jump to... --> <section id="section-2"></section>

Clicking that link will “jump” (scroll) to the first element in the DOM with an ID that matches, like the section element above.

💥 Little trick: Using a hash link (e.g. #0) in development can be useful so you can click the link without being sent back to the top of the page like a click on a # link does. But careful, links that don’t link anywhere should never make it to production.

💥 Little trick: Jump-links can sometimes benefit from smooth scrolling to help people understand that the page is moving from one place to another.

It’s a fairly common UI/UX thing to see a “Back to top” link on sites, particularly where important navigational controls are at the top but there is quite a bit of content to scroll (or otherwise navigate) through. To create a jump link, link to the ID of an element that is at the top of the page where it makes sense to send focus back to.

<a href="#top-of-page">Back to Top</a>

Jump links are sometimes also used to link to other anchor (<a>) elements that have no href attribute. Those are called “placeholder” links:

<a id="section-2"></a> <h3>Section 2</h3>

There are accessibility considerations of these, but overall they are acceptable.

Disabled links

A link without an href attribute is the only practical way to disable a link. Why disable a link? Perhaps it’s a link that only becomes active after logging in or signing up.

a:not[href] {   /* style a "disabled" link */ }

When a link has no href, it has no role, no focusability, and no keyboard events. This is intentional.

Do you need the link to open in a new window or tab?

You can use the target attribute for that, but it is strongly discouraged.

<a href="https://css-tricks.com" target="_blank" rel="noopener noreferrer">   CSS-Tricks </a>

The bit that makes it work is target="_blank", but note the extra rel attribute and values there which make it safer and faster.

Making links open in new tabs is a major UX discussion. We have a whole article about when to use it here. Summarized:

Don’t use it:

  • Because you or your client prefer it personally.
  • Because you’re trying to beef up your time on site metric.
  • Because you’re distinguishing between internal and external links or content types.
  • Because it’s your way out of dealing with infinite scroll trickiness.

Do use it:

  • Because a user is doing something on the current page, like actively playing media or has unsaved work.
  • You have some obscure technical reason where you are forced to (even then you’re still probably the rule, not the exception).

Need the link to trigger a download?

The download attribute on a link will instruct the browser to download the linked file rather than opening it within the current page/tab. It’s a nice UX touch.

<a href="/files/file.pdf" download>Download PDF</a>

The rel attribute

This attribute is for the relationship of the link to the target.

The rel attribute is also commonly used on the <link> element (which is not used for creating hyperlinks, but for things like including CSS and preloading). We’re not including rel values for the <link> element here, just anchor links.

Here are some basic examples:

<a href="/page/3" rel="next">Next</a> <a href="/page/1" rel="prev">Previous</a>  <a href="http://creativecommons.org/licenses/by/2.0/" rel="license">cc by 2.0</a>  <a href="/topics/" rel="directory">All Topics</a>
  • rel="alternate": Alternate version of the document.
  • rel="author": Author of the document.
  • rel="help": A resource for help with the document.
  • rel="license": License and legal information.
  • rel="manifest": Web App Manifest document.
  • rel="next": Next document in the series.
  • rel="prev": Previous document in the series.
  • rel="search": A document meant to perform a search in the current document.

There are also some rel attributes specifically to inform search engines:

  • rel="sponsored": Mark links that are advertisements or paid placements (commonly called paid links) as sponsored.
  • rel="ugc": For not-particularly-trusted user-generated content, like comments and forum posts.
  • rel="nofollow": Tell the search engine to ignore this and not associate this site with where this links to.

And also some rel attributes that are most security-focused:

  • rel="noopener": Prevent a new tab from using the JavaScript window.opener feature, which could potentially access the page containing the link (your site) to perform malicious things, like stealing information or sharing infected code. Using this with target="_blank" is often a good idea.
  • rel="noreferrer": Prevent other sites or tracking services (e.g. Google Analytics) from identifying your page as the source of clicked link.

You can use multiple space-separated values if you need to (e.g. rel="noopener noreferrer")

And finally, some rel attributes come from the microformats standard, like:

  • rel="directory": Indicates that the destination of the hyperlink is a directory listing containing an entry for the current page.
  • rel="tag": Indicates that the destination of that hyperlink is an author-designated “tag” (or keyword/subject) for the current page.
  • rel="payment": Indicates that the destination of that hyperlink provides a way to show or give support for the current page.
  • rel="help": States that the resource linked to is a help file or FAQ for the current document.

ARIA roles

The default role of a link is link, so you do not need to do:

<a role="link" href="/">Link</a>

You’d only need that if you were faking a link, which would be a weird/rare thing to ever need to do, and you’d have to use some JavaScript in addition to this to make it actually follow the link.

<span class="link" tabindex="0" role="link" data-href="/">   Fake accessible link created using a span </span>

Just looking above you can see how much extra work faking a link is, and that is before you consider that is breaks right-clicking, doesn’t allow opening in a new tab, doesn’t work with Windows High Contrast Mode and other reader modes and assistive technology. Pretty bad!

A useful ARIA role to indicate the current page, like:

<a href="/" aria-current="page">Home</a> <a href="/contact">Contact</a> <a href="/about">About/a></a>

Should you use the title attribute?

Probably not. Save this for giving an iframe a short, descriptive title.

<a title="I don't need to be here" href="/">   List of Concerts </a>

title provides a hover-triggered UI popup showing the text you wrote. You can’t style it, and it’s not really that accessible.

Hover-triggered is the key phrase here. It’s unusable on any touch-only device. If a link needs more contextual information, provide that in actual content around the link, or use descriptive text the link itself (as opposed to something like “Click Here”).

Icon-only links

If a link only has an icon inside it, like:

<a href="/">😃</a>  <a href="/">   <svg> ... </svg> </a>

That isn’t enough contextual information about the link, particularly for accessibility reasons, but potentially for anybody. Links with text are almost always more clear. If you absolutely can’t use text, you can use a pattern like:

<a href="/">   <!-- Hide the icon from assistive technology -->   <svg aria-hidden="true" focusable="false"> ... </svg>   <!-- Acts as a label that is hidden from view -->   <span class="visually-hidden">Useful link text</span> </a>

visually-hidden is a class used to visually hide the label text with CSS:

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

Unlike aria-label, visually hidden text can be translated and will hold up better in specialized browsing modes.

Links around images

Images can be links if you wrap them in a link. There is no need to use the alt text to say the image is a link, as assistive technology will do that already.

<a href="/buy/puppies/now">   <img src="puppy.jpg" alt="A happy puppy."> </a>

Links around bigger chunks of content

You can link a whole area of content, like:

<a href="/article/">   <div class="card">     <h2>Card</h2>     <img src="..." alt="...">     <p>Content</p>   </div> </a>

But it’s slightly weird, so consider the UX of it if you ever do it. For example, it can be harder to select the text, and the entire element needs styling to create clear focus and hover states.

Take this example, where the entire element is wrapped in a link, but there are no hover and focus states applied to it.

If you need a link within that card element, well, you can’t nest links. You could get a little tricky if you needed ot, like using a pseudo-element on the link which is absolutely positioned to cover the whole area.

Additionally, this approach can make really long and potentially confusing announcements for screen readers. Even though links around chunks of content is technically possible, it’s best to avoid doing this if you can.

Here’s the default look of a link:

The default User-Agent styling of a link.

It’s likely you’ll be changing the style of your links, and also likely you’ll use CSS to do it. I could make all my links red in CSS by doing:

a {   color: red; }

Sometimes selecting and styling all links on a page is a bit heavy-handed, as links in navigation might be treated entirely differently than links within text. You can always scope selectors to target links within particular areas like:

/* Navigation links */ nav a { }  /* Links in an article */ article a { }  /* Links contained in an element with a "text" class */ .text a { }

Or select the link directly to style.

.link {   /* For styling <a class="link" href="/"> */ }  a[aria-current="page"] {   /* You'll need to apply this attribute yourself, but it's a great pattern to use for active navigation. */ }

Link states

Links are focusable elements. In other words, they can be selected using the Tab key on a keyboard. Links are perhaps the most common element where you’ll very consciously design the different states, including a :focus state.

  • :hover: For styling when a mouse pointer is over the link.
  • :visited: For styling when the link has been followed, as best as the browser can remember. It has limited styling ability due to security.
  • :link: For styling when a link has not been visited.
  • :active: For styling when the link is pressed (e.g. the mouse button is down or the element is being tapped on a touch screen).
  • :focus: Very important! Links should always have a focus style. If you choose to remove the default blue outline that most browsers apply, also use this selector to re-apply a visually obvious focus style.

These are chainable like any pseudo-class, so you could do something like this if it is useful for your design/UX.

/* Style focus and hover states in a single ruleset */ a:focus:hover { }

You can style a link to look button-like

Perhaps some of the confusion between links and buttons is stuff like this:

Very cool “button” style from Katherine Kato.

That certainly looks like a button! Everyone would call that a button. Even a design system would likely call that a button and perhaps have a class like .button { }. But! A thing you can click that says “Learn More” is very much a link, not a button. That’s completely fine, it’s just yet another reminder to use the semantically and functionally correct element.

Color contrast

Since we often style links with a distinct color, it’s important to use a color with sufficient color contrast for accessibility. There is a wide variety of visual impairments (see the tool WhoCanUse for simulating color combinations with different impairments) and high contrast helps nearly all of them.

Perhaps you set a blue color for links:

The blue link is #2196F3.

While that might look OK to you, it’s better to use tools for testing to ensure the color has a strong enough ratio according to researched guidelines. Here, I’ll look at Chrome DevTools and it will tell me this color is not compliant in that it doesn’t have enough contrast with the background color behind it.

Chrome DevTools is telling us this link color does not have enough contrast.

Color contrast is a big consideration with links, not just because they are often colored in a unique color that needs to be checked, but because they have all those different states (hover, focus, active, visited) which also might have different colors. Compound that with the fact that text can be selected and you’ve got a lot of places to consider contrast. Here’s an article about all that.

Styling “types” of links

We can get clever in CSS with attribute selectors and figure out what kind of resource a link is pointing to, assuming the href value has useful stuff in it.

/* Style all links that include .pdf at the end */ a[href$ =".pdf"]::after {   content: " (PDF)"; }  /* Style links that point to Google */ a[href*="google.com"] {   color: purple; }

Styling links for print

CSS has an “at-rule” for declaring styles that only take effect on printed media (e.g. printing out a web page). You can include them in any CSS like this:

@media print {   /* For links in content, visually display the link */    article a::after {      content: " (" attr(href) ")";   } }

Resetting styles

If you needed to take all the styling off a link (or really any other element for that matter), CSS provides a way to remove all the styles using the all property.

.special-area a {   all: unset;   all: revert;      /* Start from scratch */   color: purple; }

You can also remove individual styles with keywords. (Again, this isn’t really unique to links, but is generically useful):

a {   /* Grab color from nearest parent that sets it */   color: inherit;    /* Wipe out style (turn black) */   color: initial;    /* Change back to User Agent style (blue) */   color: revert; }

Say you wanted to stop the clicking of a link from doing what it normally does: go to that link or jump around the page. In JavaScript, you can usepreventDefault to prevent jumping around.

const jumpLinks = document.querySelectorAll("a[href^='#']");  jumpLinks.forEach(link => {  link.addEventListener('click', event => {     event.preventDefault();     // Do something else instead, like handle the navigation behavior yourself   }); });

This kind of thing is at the core of how “Single Page Apps” (SPAs) work. They intercept the clicks so browsers don’t take over and handle the navigation.

SPAs see where you are trying to go (within your own site), load the data they need, replace what they need to on the page, and update the URL. It’s an awful lot of work to replicate what the browser does for free, but you get the ability to do things like animate between pages.

Another JavaScript concern with links is that, when a link to another page is clicked, the page is left and another page loads. That can be problematic for something like a page that contains a form the user is filling out but hasn’t completed. If they click the link and leave the page, they lose their work! Your only opportunity to prevent the user from leaving is by using the beforeunload event.

window.addEventListener("beforeunload", function(event) {   // Remind user to save their work or whatever. });

A link that has had its default behavior removed won’t announce the new destination. This means a person using assistive technology may not know where they wound up. You’ll have to do things like update the page’s title and move focus back up to the top of the document.

JavaScript frameworks

In a JavaScript framework, like React, you might sometimes see links created from something like a <Link /> component rather than a native <a> element. The custom component probably creates a native <a> element, but with extra functionality, like enabling the JavaScript router to work, and adding attributes like aria-current="page" as needed, which is a good thing!

Ultimately, a link is a link. A JavaScript framework might offer or encourage some level of abstraction, but you’re always free to use regular links.

We covered some accessibility in the sections above (it’s all related!), but here are some more things to think about.

  • You don’t need text like “Link” or “Go to” in the link text itself. Make the text meaningful (“documentation” instead of “click here”).
  • Links already have an ARIA role by default (role="link") so there’s no need to explicitly set it.
  • Try not to use the URL itself as the text (<a href="google.com">google.com</a>)
  • Links are generally blue and generally underlined and that’s generally good.
  • All images in content should have alt text anyway, but doubly so when the image is wrapped in a link with otherwise no text.

Unique accessible names

Some assistive technology can create lists of interactive elements on the page. Imagine a group of four article cards that all have a “Read More”, the list of interactive elements will be like:

  • Read More
  • Read More
  • Read More
  • Read More

Not very useful. You could make use of that .visually-hidden class we covered to make the links more like:

<a href="/article">   Read More   <span class="visually-hidden">     of the article "Dancing with Rabbits".   <span> </a>

Now each link is unique and clear. If the design can support it, do it without the visually hidden class to remove the ambiguity for everyone.

Buttons

Buttons are for triggering actions. When do you use the <button> element? A good rule is to use a button when there is “no meaningful href.” Here’s another way to think of that: if clicking it doesn’t do anything without JavaScript, it should be a <button>.

A <button> that is within a <form>, by default, will submit that form. But aside from that, button elements don’t have any default behavior, and you’ll be wiring up that interactivity with JavaScript.

Table of Contents

HTML implementation

<button>Buy Now</button>

Buttons inside of a <form> do something by default: they submit the form! They can also reset it, like their input counterparts. The type attributes matter:

<form action="/" method="POST">   <input type="text" name="name" id="name">   <button>Submit</button>    <!-- If you want to be more explicit... -->   <button type="submit">Submit</button>    <!-- ...or clear the form inputs back to their initial values -->   <button type="reset">Reset</button>    <!-- This prevents a `submit` action from firing which may be useful sometimes inside a form -->   <button type="button">Non-submitting button</button> </form>

Speaking of forms, buttons have some neat tricks up their sleeve where they can override attributes of the <form> itself.

<form action="/" method="get">    <!-- override the action -->   <button formaction="/elsewhere/" type="submit">Submit to elsewhere</button>    <!-- override encytype -->   <button formenctype="multipart/form-data" type="submit"></button>    <!-- override method -->   <button formmethod="post" type="submit"></button>    <!-- do not validate fields -->   <button formnovalidate type="submit"></button>    <!-- override target e.g. open in new tab -->   <button formtarget="_blank" type="submit"></button>  </form>

Autofocus

Since buttons are focusable elements, we can automatically focus on them when the page loads using the autofocus attribute:

<div class="modal">    <h2>Save document?</h2>    <button>Cancel</button>   <button autofocus>OK</button> </div>

Perhaps you’d do that inside of a modal dialog where one of the actions is a default action and it helps the UX (e.g. you can press Enter to dismiss the modal). Autofocusing after a user action is perhaps the only good practice here, moving a user’s focus without their permission, as the autofocus attribute is capable of, can be a problem for screen reader and screen magnifier users.

Note thatautofocus may not work if the element is within an <iframe sandbox> for security reasons.

Disabling buttons

To prevent a button from being interactive, there is a disabled attribute you can use:

<button disabled>Pay Now</button> <p class="error-message">Correct the form above to submit payment.</p>

Note that we’ve included descriptive text alongside the disabled button. It can be very frustrating to find a disabled button and not know why it’s disabled. A better way to do this could be to let someone submit the form, and then explain why it didn’t work in the validation feedback messaging.

Regardless, you could style a disabled button this way:

/* Might be good styles for ANY disabled element! */ button[disabled] {   opacity: 0.5;   pointer-events: none; } 

We’ll cover other states and styling later in this guide.

Buttons can contain child elements

A submit button and a submit input (<input type="submit">) are identical in functionality, but different in the sense that an input is unable to contain child elements while a button can.

<button>    <svg aria-hidden="true" focusable="false">      <path d="..." />    </svg>    <span class="callout">Big</span>    Sale! </button>  <button type="button">   <span role="img" aria-label="Fox">     🦊   </span>   Button </button>

Note the focusable="false" attribute on the SVG element above. In that case, since the icon is decorative, this will help assistive technology only announce the button’s label.

Styling and CSS considerations

Buttons are generally styled to look very button-like. They should look pressable. If you’re looking for inspiration on fancy button styles, you’d do well looking at the CodePen Topic on Buttons.

1, 2, 3, 4, 5, 6

Cross-browser/platform button styles

How buttons look by default varies by browser and platform.

Just on macOS: Chrome, Safari, and Firefox (they look the same)
Add border: 0; to those same buttons as above, and we have different styles entirely.

While there is some UX truth to leaving the defaults of form elements alone so that they match that browser/platform’s style and you get some affordance for free, designers typically don’t like default styles, particularly ones that differ across browsers.

Resetting the default button style

Removing all the styles from a button is easier than you think. You’d think, as a form control, appearance: none; would help, but don’t count on that. Actually all: revert; is a better bet to wipe the slate clean.

You can see how a variety of properties are involved

And that’s not all of them. Here’s a consolidated chunk of what Normalize does to buttons.

button {   font-family: inherit; /* For all browsers */   font-size: 100%; /* For all browsers */   line-height: 1.15; /* For all browsers */   margin: 0; /* Firefox and Safari have margin */   overflow: visible; /* Edge hides overflow */   text-transform: none; /* Firefox inherits text-transform */   -webkit-appearance: button; /* Safari otherwise prevents some styles */ }  button::-moz-focus-inner {   border-style: none;   padding: 0; }  button:-moz-focusring {   outline: 1px dotted ButtonText; }

A consistent .button class

In addition to using reset or baseline CSS, you may want to have a class for buttons that gives you a strong foundation for styling and works across both links and buttons.

.button {   border: 0;   border-radius: 0.25rem;   background: #1E88E5;   color: white;   font-family: -system-ui, sans-serif;   font-size: 1rem;   line-height: 1.2;   white-space: nowrap;   text-decoration: none;   padding: 0.25rem 0.5rem;   margin: 0.25rem;   cursor: pointer; }

Check out this Pen to see why all these properties are needed to make sure it works correctly across elements.

Button states

Just as with links, you’ll want to style the states of buttons.

button:hover { } button:focus { } button:active { } button:visited { } /* Maybe less so */

You may also want to use ARIA attributes for styling, which is a neat way to encourage using them correctly:

button[aria-pressed="true"] { } button[aria-pressed="false"] { }

Link-styled buttons

There are always exceptions. For example, a website in which you need a button-triggered action within a sentence:

<p>You may open your <button>user settings</button> to change this.</p>

We’ve used a button instead of an anchor tag in the above code, as this hypothetical website opens user settings in a modal dialog rather than linking to another page. In this situation, you may want to style the button as if it looks like a link.

This is probably rare enough that you would probably make a class (e.g. .link-looking-button) that incorporates the reset styles from above and otherwise matches what you do for anchor links.

Breakout buttons

Remember earlier when we talked about the possibility of wrapping entire elements in links? If you have a button within another element, but you want that entire outer element to be clickable/tappable as if it’s the button, that’s a “breakout” button. You can use an absolutely-positioned pseudo-element on the button to expand the clickable area to the whole region. Fancy!

JavaScript considerations

Even without JavaScript, button elements can be triggered by the Space and Enter keys on a keyboard. That’s part of what makes them such appealing and useful elements: they are discoverable, focusable, and interactive with assistive technology in a predictable way.

Perhaps any <button> in that situation should be inserted into the DOM by JavaScript. A tall order! Food for thought. 🤔

“Once” handlers

Say a button does something pretty darn important, like submitting a payment. It would be pretty scary if it was programmed such that clicking the button multiple times submitted multiple payment requests. It is situations like this where you would attach a click handler to a button that only runs once. To make that clear to the user, we’ll disable the button on click as well.

document.querySelector("button").addEventListener('click', function(event) {   event.currentTarget.setAttribute("disabled", true); }, {     once: true });

Then you would intentionally un-disable the button and reattach the handler when necessary.

Inline handlers

JavaScript can be executed by activating a button through code on the button itself:

<button onclick="console.log('clicked');">   Log it. </button>  <button onmousedown=""> </button>  <button onmouseup=""> </button>

That practice went from being standard practice to being a faux pas (not abstracting JavaScript functionality away from HTML) to, eh, you need it when you need it. One advantage is that if you’re injecting this HTML into the DOM, you don’t need to bind/re-bind JavaScript event handlers to it because it already has one.

JavaScript frameworks

It’s common in any JavaScript framework to make a component for handling buttons, as buttons typically have lots of variations. Those variations can be turned into an API of sorts. For example, in React:

const Button = ({ className, children }) => {   const [activated, setActivated] = React.useState(false);   return (     <button       className={`button $ {className}`}       aria-pressed={activated ? "true" : "false")       onClick={() => setActivated(!activated)}     >       {children}     </button>   ); };

In that example, the <Button /> component ensures the button will have a button class and handles a toggle-like active class.

Accessibility considerations

The biggest accessibility consideration with buttons is actually using buttons. Don’t try to replicate a button with a <div> or a <span>, which is, unfortunately, more common than you might think. It’s very likely that will cause problems. (Did you deal with focusability? Did you deal with keyboard events? Great. There’s still probably more stuff you’re forgetting.)

Focus styles

Like all focusable elements, browsers apply a default focus style, which is usually a blue outline.

Focus styles on Chrome/macOS

While it’s arguable that you should leave that alone as it’s a very clear and obvious style for people that benefit from focus styles, it’s also not out of the question to change it.

What you should not do is button:focus { outline: 0; } to remove it. If you ever remove a focus style like that, put it back at the same time.

button:focus {   outline: 0; /* Removes the default blue ring */    /* Now, let's create our own focus style */   border-radius: 3px;   box-shadow: 0 0 0 2px red; }
Custom focus style

The fact that a button may become focused when clicked and apply that style at the same time is offputting to some. There is a trick (that has limited, but increasing, browser support) on removing focus styles from clicks and not keyboard events:

:focus:not(:focus-visible) {    outline: 0;  }

ARIA

Buttons already have the role they need (role="button"). But there are some other ARIA attributes that are related to buttons:

  • aria-pressed: Turns a button into a toggle, between aria-pressed="true" and aria-pressed="false". More on button toggles, which can also be done with role="switch" and aria-checked="true".
  • aria-expanded: If the button controls the open/closed state of another element (like a dropdown menu), you apply this attribute to indicate that like aria-expanded="true".
  • aria-label: Overrides the text within the button. This is useful for labeling buttons that otherwise don’t have text, but you’re still probably better off using a visually-hidden class so it can be translated.
  • aria-labelledby: Points to an element that will act as the label for the button.

For that last one:

<button aria-labelledby="buttonText">   Time is running out!    <span id="buttonText">Add to Cart</span> </button>

Deque has a deeper dive blog post into button accessibility that includes much about ARIA.

Dialogs

If a button opens a dialog, your job is to move the focus inside and trap it there. When closing the dialog, you need to return focus back to that button so the user is back exactly where they started. This makes the experience of using a modal the same for someone who relies on assistive technology as for someone who doesn’t.

Focus management isn’t just for dialogs, either. If clicking a button runs a calculation and changes a value on the page, there is no context change there, meaning focus should remain on the button. If the button does something like “move to next page,” the focus should be moved to the start of that next page.

Size

Don’t make buttons too small. That goes for links and any sort of interactive control. People with any sort of reduced dexterity will benefit.

The classic Apple guideline for the minimum size for a touch target (button) is 44x44pt.

Here’s some guidelines from other companies. Fitt’s Law tells us smaller targets have greater error rates. Google even takes button sizes into consideration when evaluating the SEO of a site.

In addition to ample size, don’t place buttons too close each other, whether they’re stacked vertically or together on the same line. Give them some margin because people experiencing motor control issues run the risk of clicking the wrong one.

Activating buttons

Buttons work by being clicked/touched, pressing the Enter key, or pressing the Space key (when focused). Even if you add role="button" to a link or div, you won’t get the spacebar functionality, so at the risk of beating a dead horse, use <button> in those cases.

The post A Complete Guide to Links and Buttons appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Get the Complete Intro to Web Development and Intro to React (with Hooks!) with Brian Holt 🎣

(This is a sponsored post.)

Hey, Marc here from Frontend Masters — excited to support CSS-Tricks ❤️!

Have you checked out Brian Holt’s courses yet? His most popular courses are the “Complete Intro” courses which give you the lay of the land in Web Development as well as the entire React ecosystem.

Complete Intro to Web Development, v2

This Complete Intro to Web Development assumes no prior coding knowledge, taking you from not knowing how websites are made to writing code for your own sites in HTML, CSS, JavaScript…all the way to building a server with Node.js!

You’ll learn to…

  • Setup the tools you need to write code.
  • Write HTML and CSS to put content on a websites and make them look aesthetically pleasing.
  • Make websites interactive with JavaScript, the de facto programming language of the web.
  • Use Git and other modern development tools through your computer’s terminal to save your work and pull in code libraries.
  • Use JavaScript via Node.js to serve your own website from a server.

Complete Intro to React, v5

In the Complete Intro to React course, you’ll start from the ground up, getting all the way to using the latest features in React, including hooks, effects, context, and portals. Throughout the course, you’ll piece together tools from the entire React ecosystem (like Reach Router) to build a full application to browse adoptable pets.

You’ll learn to…


  • Use the new hooks and effects methods to handle state in function components.

  • Understand how React works by writing React with and without JSX.

  • Package client-side applications with Parcel.

  • Leverage Prettier and ESLint to maintain high-quality code.

  • Route to pages and search results with Reach Router.


  • Grab API data and update state asynchronously in an effect.

Intermediate React, v2

The Intermediate React, v2 course is a modular course where you can pick and choose the various pieces of the react ecosystem you want to learn.

You’ll learn to…


  • Learn all the types of hooks, including useReducer, useCallback, useRef and more.

  • Make CSS local to your JavaScript components using the Emotion library.

  • Load your apps fast using code splitting, Suspense React and server-side rendering.

  • Use TypeScript for writing scalable apps with superior developer experience.

  • Redux for state management

  • Test your React applications using Jest.

You’ll love Brian’s awesome courses!

Join Now

Direct Link to ArticlePermalink

The post Get the Complete Intro to Web Development and Intro to React (with Hooks!) with Brian Holt 🎣 appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Frontend Masters: The New, Complete Intro to React Course… Now with Hooks!

, , , , , ,
[Top]