Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies

Wednesday, June 29, 2022

Lesser-Known And Underused CSS Features In 2022

 CSS is constantly evolving, and some cool and useful properties either go completely unnoticed or are not talked about as much as others for some reason or another. In this article, we’ll cover a fraction of those CSS properties and selectors.

I’ve asked myself which properties and selectors are lesser-known or should be used more often. Some answers from the community surprised me, as they’ve included some very useful and often-requested CSS features which were made available in the past year or two.

The following list is created with community requests and my personal picks. So, let’s get started!

all Property

This is a shorthand property which is often used for resetting all properties to their respective initial value by effectively stopping inheritance, or to enforce inheritance for all properties.

  • initial
    Sets all properties to their respective initial values.
  • inherit
    Sets all properties to their inherited values.
  • unset
    Changes all values to their respective default value which is either inherit or initial.
  • revert
    Resulting values depend on the stylesheet origin where this property is located.
  • revert-layer
    Resulting values will match a previous cascade layer or the next matching rule.

This property can be used effectively for resetting styles or when refactoring CSS to stop inheritance and prevent unwanted styles for leaking in.

h2 {
   color: var(--color-primary);
   font-size: var(--font-size-large);
   line-height: 1.5;
   text-decoration: underline;
   margin-bottom: 2rem;
}

.article h2 {
  padding: 2em;
  border-bottom: 2px solid currentColor;
}

.article__title {
  /* We don't want styles from previous selector. We only need a margin and a font size. */
  all: unset;
  margin-bottom: 2rem;
  font-size: var(--font-size-medium);
}

With revert-layer we can skip to a next cascade layer, to inherit styles from another selector but to prevent styles from the closest selector in the cascade from leaking in.

While playing around with this property, I’ve discovered an interesting behavior — underline color won’t update to a currently assigned color unless text-decoration: underline; is applied again to the .title selector which contains the all property.

currentColor

Often referred to as “the first CSS variable”, currentColor is a value equal to the element’s color property. It can be used to assign a value equal to the value of the color property to any CSS property which accepts a color value. It forces a CSS property to inherit the value of the color property.

This value can be very useful to avoid assigning the same value to multiple CSS properties which accept color like border-color, background, box-shadow, etc. within the same selector.

See the Pen curentColor alerts by Adrian Bece.

In my opinion, one of the best use-cases for currentColor is styling inlined SVG elements. Whenever we export an icon from a design tool, it comes with a specific fill and other color values defined in the design. We can manually replace all those color values with currentColor, and that way we can easily customize the color of SVG without having to go into the SVG markup and override the fill or other color-based attributes for an individual path, or other SVG elements, and make our CSS selectors complex and convoluted.

<!-- Before -->
<path fill="#bbdb44" d="..."/>

<!-- After -->
<path fill="currentColor" d="..."/>
/* Before */
.icon:hover path {
  fill: #112244;
}

/* After */
.icon {
  color: #bbdb44;
}

.icon:hover {
  color: #112244;
}
See the Pen currentColor svg by Adrian Bece.

Custom Property Fallback Value #

Custom properties brought significant improvements to CSS by allowing developers to create reusable values in their stylesheet without the need for CSS preprocessor like SASS. Custom properties were instantly adopted and are widely used today to great effect, especially in theming and interaction with JavaScript.

However, I feel like the fallback value was somewhat ignored. If you are unfamiliar with the fallback value, it’s the second value that can be assigned to var function which is applied if the first value is not set.

color: var(--color-icon, #9eeb34);

We can also set another variable as a fallback.

color: var(--color-icon-primary, var(--color-icon-default));

You can probably already see how this value can be used to provide a reliable fallback to default styles while allowing for customization.

This secondary value also allows for an elegant way to override theme colors without increasing specificity.

We can easily change custom variable values by overriding them.

:root {
  --theme-color-background: #f5f5f5;
  --theme-color-text: #111111;
}

/* Global override on a parent class on <body> or <html> element */
.theme--dark {
  --theme-color-background: #111111;
  --theme-color-text: #f5f5f5;
}

But what about the cases where this global override is not ideal for all components, and we want to fine-tune the properties for individual components? In such cases, we would have to override the styles.

.box {
    color: var(--color-theme-default);
}

.theme--dark .box {
  color: var(--color-component-override);
}

We have increased specificity as a result which is not ideal and can cause issues in more complex cases or in cases where specificity is left unmanaged. What we can do instead is use the fallback value to apply theming without increasing specificity inside the component. This makes the component more themable and portable, as it doesn’t introduce any parent class names for the component and other similar dependencies.

:root {
--theme-color-default: darkgoldenrod;
--color-some-other-color: cyan;
}

.theme--dark {
/* Dark theme */
  --color-component-override: var(--color-some-other-color);
}

.box {
  color: var(--color-component-override, var(--theme-color-default));
}

CSS allows developers to define named counters that can be incremented, decremented, and displayed using CSS content property.

  • counter-reset
    This property is used for initializing single or multiple counters. A default starting value can also be assigned.
  • reversed
    Function used when defining a counter with counter-reset to make the counter count down instead of up.
  • counter-increment
    Specify a counter to increment (or decrements if counter is defined as reversed or if a negative value is passed to counter-increment). Default increment value is 1, but a custom value value can also be passed to this property.
  • counter
    Used for accessing counter value. Usually used in content property.

In the following example, we are initializing two counters articles which keeps count of the main sections and notes which keeps count of the notes on the page. A single section section can have multiple notes.

See the Pen counters articles and notes by Adrian Bece.

What if we want to easily identify which note belongs to which article on a page? We need to add an article number to each note. For example, a second note of the third article — “Note 3.2.”.

We can easily adjust how notes counters are initialized and displayed. We can use multiple counter values in a single content property.

See the Pen counters articles and notes - nested by Adrian Bece.

Using CSS counters, allows us to easily add, remove, and rearrange these elements without having to worry about updating the counter values manually and without the need for using JavaScript.

Interaction Media Queries

Cristian Díaz covered this topic in his recent article. When creating responsive websites, we often make assumptions about input mechanisms based on their screen size. We assume that the screen size of 1920px belongs to a desktop computer or laptop and the user is interacting with the website using a mouse and keyboard, but what about laptops with touchscreen or smart TV screens?

This is where Interaction Media Features come in and allow us to fine-tune the usability of our components that users can interact with (inputs, offcanvas menus, dropdowns, modals, etc.) depending on the primary input mechanism — touch, stylus, mouse pointer, etc.

@media (pointer: fine) {
  /* using a mouse or stylus */
}
@media (pointer: coarse) {
  /* using touch */
}
@media (hover: hover) {
  /* can be hovered */
}
@media (hover: none) {
  /* can't be hovered */
}

aspect-ratio for Sizing Control

When aspect-ratio was initially released, I thought I won’t use it outside image and video elements and in very narrow use-cases. I was surprised to find myself using it in a similar way I would use currentColor — to avoid unnecessarily setting multiple properties with the same value.

With aspect-ratio, we can easily control size of an element. For example, equal width and height buttons will have an aspect ratio of 1. That way, we can easily create buttons that adapt to their content and varying icon sizes, while maintaining the required shape.

See the Pen aspect-radio buttons by Adrian Bece.

Better Gradients

We’ve been using gradients on the Web for a while, and they’ve become a staple in design. However, as Josh W. Comeau points out, the middle part of the gradient can sometimes look gray and washed out, depending on the colors you are using.

In the following example, we are setting two gradients between the same two values (green and red). Notice in the first example how the colors in the middle part look muddy and washed out, because the browser is using RGB color interpolation by default. We cannot change that at the moment, but we might in the future with new CSS features. However, we can fix that by adding some midpoints to the gradient.

The second example uses an interpolation technique with multiple midpoints, which is generated using Josh W. Comeau’s Gradient generator tool. Notice how the middle part is now darker yellow and orange, and it looks much more vibrant and beautiful than in the first example.

See the Pen Gradients by Adrian Bece.

:where and :is Pseudo-selectors

These two pseudo-selectors gained wider browser support last year, and although there was much talk around them, I haven’t seen all those many uses around the Web. Stephanie Eckles has talked in-depth about these two pseudo-selectors in her article.

Both of these selectors deal with grouping and specificity, so let’s start with :is pseudo-selector.

Let’s take a look at the following example. We want to set the following default styles for list items and nested lists. We need to cover both ordered and unordered lists and their combinations.

ol li,
ul li {
  margin-bottom: 0.25em;
}

ol ol,
ul ul,
ol ul,
ul ol {
  margin: 0.25em 0 1em;
}

With :is pseudo-selector, we can easily turn these selectors into a single expression.

:is(ol,ul) li {
  margin-bottom: 0.25em;
}

:is(ol,ul) :is(ol,ul) {
  margin: 0.25em 0 1em;
}
See the Pen Nested lists by Adrian Bece.

:where works the same as :is, but it reduces the specificity of the expression to zero. Now, why is this important? Let’s go back to our example and change the markup a bit. Let’s add a .list selector, so we can add styles to list by assigning a class. Let’s add an additional class for a nested list .list-highlight which adds a background color and adjusts paddings and margins, so the nested list looks more prominent.

/* Default styles for nested lists */
.list :is(ol,ul) {
  margin: 0.25em 0 1em;
}

/* Utility class for a nested list */
.list-highlight  {
  background: #eeeeee;
  padding: 1em 1em 1em 2em;
  margin: 0.5em 0;
}

However, when we apply list-highlight class to any of the nested lists, the margins look off, because that style doesn’t apply. What is going on?

Resulting specificity for :is selector matches the highest one in the list. So, margin styles from our .list-highlight util class will never win against it.

We want to avoid increasing specificity and adding dependencies for our utility classes, so let’s switch :is with :where and see what happens.

.list :where(ol,ul) {
  /* ... */
}
See the Pen Nested lists - :where by Adrian Bece.

Our utility class works without the need for a higher specificity or other overrides! :where sets the specificity of the selectors in the list to zero and allows us to override the default styles.

We can use :is and :where to group multiple selectors into a single expression. With :where, we can set safe default styles with complex selectors which can be easily overridden with simple utility classes without needlessly increasing specificity.

scroll-padding

One of my pet-peeves, when implementing a fixed page header, used to be how the on-page scroll links cause fixed page header to cover part of the content. We had to use JavaScript to fix this issue and implement custom scroll logic to take into account the fixed header offset. And things would only become more complicated if the header height changed on breakpoints, so we needed to cover those cases with JavaScript, too.

Luckily, we don’t have to rely on JavaScript for that anymore. We can specify scroll-padding-top and change its value using standard CSS media queries.

html {
  scroll-padding-top: 6rem;
  scroll-behavior: smooth;
}
See the Pen Scroll offset by Adrian Bece.

We can also set other directions or use a longhand scroll-padding.

scroll-padding: /* ... */;

scroll-padding-top: /* ... */;
scroll-padding-right: /* ... */;
scroll-padding-bottom: /* ... */;
scroll-padding-left: /* ... */;

Font Rendering Options #

I’ve recently worked on animating numeric values on a project where a value would increment from zero to a final value. I’ve noticed that the text kept jumping left and right during the animation due to individual characters having different widths.

I assumed that this issue cannot be fixed, and I moved on. One of the tweets from the community poll suggested that I should look into font-variant-numeric: tabular-nums, and I was surprised to find a plethora of options that affect font rendering.

For example, tabular-nums fixed the aforementioned issue by setting the equal width for all numeric characters.

See the Pen font-variant-numeric by Adrian Bece.

Please note that available features depend on the font itself, and some features might not be supported. For a complete list of options, consult the documentation. There is also a font-variant CSS property that allows us to activate even more features for all characters, not just the numeric.

Here are a few more examples of font-variant-numeric that are available in the font Source Sans 3.

See the Pen font-variant by Adrian Bece.

Creating Stacking Context with isolate

This property may be confusing to developers, and I wasn’t aware of it until I read Josh W. Comeau’s awesome article on the topic of z-index and stacking contexts. In short, it allows us to compartmentalize our z-index stacks.

You probably ran into a case where you, for example, added a reusable tooltip component to your page, only to find out that the tooltip element has a z-index lower than some other adjacent element on the page, causing the tooltip to display below it. We would usually solve it by increasing the z-index value of the tooltip, but that could potentially cause regressions and similar issues somewhere else in the projects.

This is exactly what happens in the example below. The tooltip is locked in a hovered state for demo purposes.

See the Pen stacking context - no isolate by Adrian Bece.

Let’s see what is going on here. A developer made a styled title component that has a decorative element behind it, as defined in a design. But they went overboard with z-index values:

  • title text has z-index: 2;
  • decorative background element has a z-index: 1.

This component works as expected and was merged with a main codebase. After some time had passed, someone else made a tooltip component with a z-index: 1. There is no reason to assign a higher value to z-index: 1, as the tooltip needs to be just above the text. After a while, an edge case happened where title text ended up above the tooltip.

We could mess around with z-index values for title component and tooltip component or assign a z-index to their respective parent elements with position: relative to create a new stacking context, but we are relying on magic numbers!

Let’s think about the issue differently — what if we could create a new stacking context without relying on z-index magic numbers? This is exactly what isolation: isolate does! It creates a new stacking context or a group. It tells the browser not to mix these two stacking groups, not even if we increase title z-index value to highest possible value. So, we can keep the z-index values low and not worry if value should be 2, 10, 50, 100, 999999, etc.

Let’s create a new stacking context at the root of our title component and at the root of our tooltip component and see what happens.

.title {
  isolation: isolate;
  /* ... */
}

.tooltip-root {
  isolation: isolate;
  /* ... */
}
See the Pen stacking context - isolate by Adrian Bece.

And we fixed the issue by isolating the stacking contexts for our two conflicting components without messing around with magic numbers for z-index values.

Render Performance Optimization

When it comes to rendering performance, it’s very rare to run into these issues when working on regular projects. However, in the case of large DOM trees with several thousands of elements or other similar edge cases, we can run into some performance issues related to CSS and rendering. Luckily, we have a direct way of dealing with these performance issues that cause lag, unresponsiveness to user inputs, low FPS, etc.

This is where contain property comes in. It tells the browser what won’t change in the render cycle, so the browser can safely skip it. This can have consequences on the layout and style, so make sure to test if this property doesn’t introduce any visual bugs.

.container {
  /* child elements won't display outside of this container so only the contents of this container should be rendered*/
  contain: paint;
{

This property is quite complex, and Rachel Andrew has covered it in great detail in her article. This property is somewhat difficult to demonstrate, as it is most useful in those very specific edge cases. For example, Johan Isaksson covered one of those examples in his article, where he noticed a major scroll lag on Google Search Console. It was caused by having over 38 000 elements on a page and was fixed by containing property!

As you can see, contain relies on the developer knowing exactly which properties won’t change and knowing how to avoid potential regressions. So, it’s a bit difficult to use this property safely.

However, there is an option where we can signal the browser to apply the required contain value automatically. We can use the content-visibility property. With this property, we can defer the rendering of off-screen and below-the-fold content. Some even refer to this as “lazy-rendering”.

Una Kravets and Vladimir Levin covered this property in their travel blog example. They apply the following class name to the below-the-fold blog sections.

.story {
  content-visibility: auto; /* Behaves like overflow: hidden; */
  contain-intrinsic-size: 100px 1000px;
}

With contain-intrinsic-size, we can estimate the size of the section that is going to be rendered. Without this property, the size of the content would be 0, and page dimensions would keep increasing, as content is loaded.

Going back to Una Kravets and Vladimir Levin’s travel blog example. Notice how the scrollbar jumps around, as you scroll or drag it. This is because of the difference between the placeholder (estimated) size set with contain-intrinsic-size and the actual render size. If we omit this property, the scroll jumps would be even more jarring.

See the Pen Content-visibility Demo: Base (With Content Visibility) by Vladimir Levin.

Thijs Terluin covers several ways of calculating this value including PHP and JavaScript. Server-side calculation using PHP is especially impressive, as it can automate the value estimation on larger set of various pages and make it more accurate for a subset of screen sizes.

Keep in mind that these properties should be used to fix issues once they happen, so it’s safe to omit them until you encounter render performance issues.

Conclusion

CSS evolves constantly, with more features being added each year. It’s important to keep up with the latest features and best practices, but also keep an eye out on browser support and use progressive enhancement.

I’m sure there are more CSS properties and selectors that aren’t included here. Feel free to let us know in the comments which properties or selectors are less known or should be used more often, but may be a bit convoluted or there is not enough buzz around them.

 

Monday, June 27, 2022

Understanding Weak Reference In JavaScript

Memory and performance management are important aspects of software development and ones that every software developer should pay attention to. Though useful, weak references are not often used in JavaScript. WeakSet and WeakMap were introduced to JavaScript in the ES6 version.

Weak Reference

To clarify, unlike strong reference, weak reference doesn’t prevent the referenced object from being reclaimed or collected by the garbage collector, even if it is the only reference to the object in memory.

Before getting into strong reference, WeakSet, Set, WeakMap, and Map, let’s illustrate weak reference with the following snippet:

// Create an instance of the WeakMap object.
let human = new WeakMap():

// Create an object, and assign it to a variable called man.
let man = { name: "Joe Doe" };

// Call the set method on human, and pass two arguments (key and value) to it.
human.set(man, "done")

console.log(human)

The output of the code above would be the following:

WeakMap {{} => 'done'}

man = null;
console.log(human)

The man argument is now set to the WeakMap object. At the point when we reassigned the man variable to null, the only reference to the original object in memory was the weak reference, and it came from the WeakMap that we created earlier. When the JavaScript engine runs a garbage-collection process, the man object will be removed from memory and from the WeakMap that we assigned it to. This is because it is a weak reference, and it doesn’t prevent garbage collection.

It looks like we are making progress. Let’s talk about strong reference, and then we’ll tie everything together.

Strong Reference #

A strong reference in JavaScript is a reference that prevents an object from being garbage-collected. It keeps the object in memory.

The following code snippets illustrate the concept of strong reference:

let man = {name: "Joe Doe"};

let human = [man];

man =  null;
console.log(human);

The result of the code above would be this:

// An array of objects of length 1. 
[{}]

The object cannot be accessed via the dog variable anymore due to the strong reference that exists between the human array and object. The object is retained in memory and can be accessed with the following code:

console.log(human[0])

The important point to note here is that a weak reference doesn’t prevent an object from being garbage-collected, whereas a strong reference does prevent an object from being garbage-collected.

Garbage Collection in JavaScript

As in every programming language, memory management is a key factor to consider when writing JavaScript. Unlike C, JavaScript is a high-level programming language that automatically allocates memory when objects are created and that clears memory automatically when the objects are no longer needed. The process of clearing memory when objects are no longer being used is referred to as garbage collection. It is almost impossible to talk about garbage collection in JavaScript without touching on the concept of reachability.

Reachability #

All values that are within a specific scope or that are in use within a scope are said to be “reachable” within that scope and are referred to as “reachable values”. Reachable values are always stored in memory.

Values are considered reachable if they are:

  • values in the root of the program or referenced from the root, such as global variables or the currently executing function, its context, and callback;
  • values accessible from the root by a reference or chain of references (for example, an object in the global variable referencing another object, which also references another object — these are all considered reachable values).

The code snippets below illustrate the concept of reachability:

let languages = {name: “JavaScript”};

Here we have an object with a key-value pair (with the name JavaScript) referencing the global variable languages. If we overwrite the value of languages by assigning null to it…

languages = null;

… then the object will be garbage-collected, and the value JavaScript cannot be accessed again. Here is another example:

let languages = {name: “JavaScript”};

let programmer = languages;

From the code snippets above, we can access the object property from both the languages variable and the programmer variable. However, if we set languages to null

languages = null;

… then the object will still be in memory because it can be accessed via the programmer variable. This is how garbage collection works in a nutshell.

Note: By default, JavaScript uses strong reference for its references. To implement weak reference in JavaScript, you would use WeakMap, WeakSet, or WeakRef.

Comparing Set and WeakSet #

A set object is a collection of unique values with a single occurrence. A set, like an array, does not have a key-value pair. We can iterate through a set of arrays with the array methods for… of and .forEach.

Let’s illustrate this with the following snippets:

let setArray = new Set(["Joseph", "Frank", "John", "Davies"]);
for (let names of setArray){
  console.log(names)
}// Joseph Frank John Davies

We can use the .forEach iterator as well:

 setArray.forEach((name, nameAgain, setArray) =>{
   console.log(names);
 });

A WeakSet is a collection of unique objects. As the name applies, WeakSets use weak reference. The following are properties of WeakSet():

  • It may only contain objects.
  • Objects within the set can be reachable somewhere else.
  • It cannot be looped through.
  • Like Set(), WeakSet() has the methods add, has, and delete.

The code below illustrates how to use WeakSet() and some of the methods available:

const human = new WeakSet();

let paul = {name: "Paul"};
let mary = {gender: "Mary"};

// Add the human with the name paul to the classroom. 
const classroom = human.add(paul);

console.log(classroom.has(paul)); // true

paul = null;

// The classroom will be cleaned automatically of the human paul.

console.log(classroom.has(paul)); // false

On line 1, we’ve created an instance of WeakSet(). On lines 3 and 4, we created objects and assigned them to their respective variables. On line 7, we added paul to the WeakSet() and assigned it to the classroom variable. On line 11, we made the paul reference null. The code on line 15 returns false because WeakSet() will be automatically cleaned; so, WeakSet() doesn’t prevent garbage collection.

Comparing Map and WeakMap

As we know from the section on garbage collection above, the JavaScript engine keeps a value in memory as long as it is reachable. Let’s illustrate this with some snippets:

let smashing = {name: "magazine"};
// The object can be accessed from the reference.

// Overwrite the reference smashing.
smashing = null;
// The object can no longer be accessed.

Properties of a data structure are considered reachable while the data structure is in memory, and they are usually kept in memory. If we store an object in an array, then as long as the array is in memory, the object can still be accessed even if it has no other references.

let smashing = {name: "magazine"};

let arr = [smashing];

// Overwrite the reference.
smashing = null;
console.log(array[0]) // {name: 'magazine'}

We’re still able to access this object even if the reference has been overwritten because the object was saved in the array; hence, it was saved in memory as long the array is still in memory. Therefore, it was not garbage-collected. As we’ve used an array in the example above, we can use map too. While the map still exists, the values stored in it won’t be garbage-collected.

let map = new Map();

let smashing {name: "magazine"};

map.set(smashing, "blog");

// Overwrite the reference.
smashing = null;

// To access the object.
console.log(map.keys());

Like an object, maps can hold key-value pairs, and we can access the value through the key. But with maps, we must use the .get() method to access the values.

According to Mozilla Developer Network, the Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either key or value.

Unlike a map, WeakMap holds a weak reference; hence, it doesn’t prevent garbage collection from removing values that it references if those values are not strongly referenced elsewhere. Apart from this, WeakMap is the same as map. WeakMaps are not enumerable due to weak references.

With WeakMap, the keys must be objects, and the values may be a number or a string.

The snippets below illustrate how WeakMap works and the methods in it:

// Create a weakMap.
let weakMap = new WeakMap();

let weakMap2 = new WeakMap();

// Create an object.
let ob = {};

// Use the set method.
weakMap.set(ob, "Done");

// You can set the value to be an object or even a function.
weakMap.set(ob, ob)

// You can set the value to undefined.
weakMap.set(ob, undefined);

// WeakMap can also be the value and the key.
weakMap.set(weakMap2, weakMap)

// To get values, use the get method.
weakMap.get(ob) // Done

// Use the has method.
weakMap.has(ob) // true

weakMap.delete(ob)

weakMap.has(ob) // false

One major side effect of using objects as keys in a WeakMap with no other references to it is that they will be automatically removed from memory during garbage collection.

Areas of Application of WeakMap

WeakMap can be used in two areas of web development: caching and additional data storage.

Caching

This a web technique that involves saving (i.e. storing) a copy of a given resource and serving it back when requested. The result from a function can be cached so that whenever the function is called, the cached result can be reused.

Let’s see this in action. Create a file, name it cachedResult.js, and write the following in it:

 let cachedResult = new WeakMap();
 // A function that stores a result.
function keep(obj){
if(!cachedResult.has(obj){
  let result = obj;
  cachedResult.set(obj, result);
  }
return cachedResult.get(obj);
}


let obj = {name: "Frank"};

let resultSaved = keep(obj)

obj = null;

// console.log(cachedResult.size); Possible with map, not with WeakMap

If we had used Map() instead of WeakMap() in the code above, and there were multiple invocations on the function keep(), then it would only calculate the result the first time it was called, and it would retrieve it from cachedResult the other times. The side effect is that we’ll need to clean cachedResult whenever the object is not needed. With WeakMap(), the cached result will be automatically removed from memory as soon as the object is garbage-collected. Caching is a great means of improving software performance — it could save the costs of database usage, third-party API calls, and server-to-server requests. With caching, a copy of the result from a request is saved locally.

Additional Data #

Another important use of WeakMap() is additional data storage. Imagine we are building an e-commerce platform, and we have a program that counts visitors, and we want to be able to reduce the count when visitors leave. This task would be very demanding with Map, but quite easy to implement with WeakMap():

let visitorCount = new WeakMap();
function countCustomer(customer){
   let count = visitorCount.get(customer) || 0;
    visitorCount.set(customer, count + 1);
}

Let’s create client code for this:

let person = {name: "Frank"};

// Taking count of person visit.
countCustomer(person)

// Person leaves.
person = null;

With Map(), we will have to clean visitorCount whenever a customer leaves; otherwise, it will grow in memory indefinitely, taking up space. But with WeakMap(), we do not need to clean visitorCount; as soon as a person (object) becomes unreachable, it will be garbage-collected automatically.

Conclusion #

In this article, we learned about weak reference, strong reference, and the concept of reachability, and we tried to connect them to memory management as best we could. I hope you found this article valuable. Feel free to drop a comment.

Manage Accessible Design System Themes With CSS Color-Contrast()

 Developing accessible products can be challenging, especially when some of the requirements are beyond the scope of development. It’s one thing to enforce alt text for images and labels for form fields, but another to define an accessible color palette. From working with design handoffs to supporting custom themes in a design system, the CSS color-contrast() function can become a cornerstone for developers in enforcing sufficiently contrasting and accessible UIs.

There’s certainly no shortage of design systems available to use when building your next project. Between IBM’s Carbon, Wanda and Nord, there are plenty of terrific design systems to choose from. Yet, while each one contains its own nuances and opinions, most share a similar goal — simplifying the development process of creating beautifully accessible user interfaces.

It’s an admirable goal and, honestly, one that has led me to shift my own career into design systems. But a core feature at the foundation of many design systems is the extensibility for theming. And why wouldn’t it be? Without some flexibility for branding, every product using a particular system would look the same, à la Bootstrap around 2012.

While providing support for custom themes is vital, it also leaves the most well-intentioned system’s accessibility at the mercy of the implementation. Some teams may spend weeks, if not months, defining their ideal color palette for a rebranding. They’ll labor over each shade and color combination to ensure everything is reliable, informative, and accessible.

Others simply can’t and/or won’t do that.

It’s one thing to require alt text on an img element or a label for an input element, but enforcing accessible color palettes is an entirely different beast. It’s a beast with jagged yellow teeth, fiery-red eyes, and green scales covering its body like sheets of crocodile armor.

At least you think it is. For all you know, it could be a beast of nothing more than indistinct shades of black and slightly darker black.

And therein lies the problem.

The CSS Color-Contrast() Function

Building inclusive products doesn’t mean supporting devices but supporting the people using them.

The CSS color-contrast() function is an experimental feature which is currently a part of Color Module 5. Its purpose — and the reason for the excitement of this article — is to select the greatest contrasting color from a list when compared against a base color.

For the sake of this article, we will refer to the first parameter as the “base color” and the second as the “color list.” These parameters can accept any combination of browser-supported CSS color formats, but be weary of opacities. There’s an optional third parameter, but let’s look at that later. First, let’s define what we mean by this being an experimental feature.

At the time of writing, the color-contrast() feature is only available in the Safari Technology Preview browser. The feature can be toggled through the Develop and Experimental Features menus. The following demos will only work if the feature is enabled in that browser. So, if you’d like to switch, now wouldn’t be the worst time to do so.

Now, with the base syntax, terminology, and support out of the way, let’s dive in. 🤿

More after jump! Continue reading below ↓

Color Me Intrigued

It was Rachel Andrew’s talk at AxeCon 2022, “New CSS With Accessibility in Mind”, where I was introduced to color-contrast(). I scribbled the function down into my notebook and circled it multiple times to make it pop. Because my mind has been entirely in the world of design systems as of late, I wondered how big of an impact this little CSS feature could have in that context.

In her presentation, Rachel demoed the new feature by dynamically defining text colors based on a background. So, let’s start there as well, by setting background and text colors on an article.

article {
  --article-bg: #222;

  background: var(--article-bg);
  color: color-contrast(var(--article-bg) vs #FFF, #000);
}

We start by defining the --article-bg custom property as a dark grey, #222. That property is then used as the base color in the color-contrast() function and compared against each item in the color list to find the highest contrasting value.

Base ColorColor ListContrast Ratio
#222#FFF15.9
#222#0001.31

As a result, the article’s color will be set to white, #FFF.

But this can be taken further.

We can effectively chain color-contrast() functions by using the result of one as the base color of another. Let’s extend the article example by defining the ::selection color relative to its text.

article {
  --article-bg: #222;
  --article-color: color-contrast(var(--article-bg) vs #FFF, #000);

  background: var(--article-bg);
  color: var(--article-color);

  ::selection {
    background: color-contrast(var(--article-color) vs #FFF, #000);
  }
}

Now, as the text color is defined, so will its selection background.

See the Pen CSS Color-Contrast() - Text and ::selection Demo [forked] by Daniel Yuschick.
Results of using color-contrast() for text and ::selection colors

The color-contrast() function isn’t limited to only comparing HEX codes either. In fact, it can compare multiple color types at once. The previous example can be modified to use different color types while returning the same results.

article {
  --article-bg: rgb(34, 34, 34);
  --article-color: color-contrast(var(--article-bg) vs hsl(0,0%,100%), black);

  background: var(--article-bg);
  color: var(--article-color);

  ::selection {
    background: color-contrast(var(--article-color) vs hsl(0,0%,100%), black);
  }
}

From Pseudo-Elements To Pseudo-Classes #

Setting text and ::selection colors dynamically can be intriguing, but it’s not exactly like being in a high-speed car chase with Burt Reynolds either — at least, I wouldn’t think. Text and background colors tend to be quite static. Once they’re rendered, they don’t often change.

So, let’s shift gears and focus 🥁 on interactive elements and their pseudo-classes.

It’s essential that all interactive elements have compliant focus indicators, but it’s rarely as straight forward as creating a single, universal style.

When navigating a page by keyboard, there tends to be quite a variety of tab stops along the way — links inside of body text, buttons, and inputs, maybe even a card or a linked image. While it’s essential that each of these elements have a compliant focus indicator, it’s rarely as straightforward as creating a single, universal style. Using color-contrast() can help.

:root {
  --body-bg: #131e25;
}

button {
  --btn-bg: #ffba76;
  --btn-color: color-contrast(var(--btn-bg) vs #fff, #000);

  background: var(--btn-bg);
  color: var(--btn-color);

  &:hover {
    --btn-bg: #b15900;
  }

  &:focus {
    --color-list: var(--btn-bg), var(--btn-color), #bbb, #555;
    box-shadow: 0 0 1px 3px
      color-contrast(var(--body-bg) vs var(--color-list));
  }
}

There’s a lot going on in this snippet demonstrating the potential of color-contrast(), so let’s go through it.

The --btn-bg custom property is used as the base color in selecting the --btn-color value. Anytime --btn-bg changes, --btn-color will be redefined as well. This is leveraged in the :hover state, doing away with pairing button colors manually and letting color-contrast() do it automatically.

The :focus styles is where this approach can be expanded by using the --body-bg custom property as the base color. It’s compared to the current button styles. What this provides is the ability to have contextually-aware focus styles. Should the default focus styles be too low contrast given the element’s background placement, a color matching that element can be used. Of course, the color list can also contain safe fallbacks, just in case.

See the Pen CSS Color-Contrast() - Button and :focus Demo [forked] by Daniel Yuschick.
Results of color-contrast() on button :hover and :focus pseudo-classes

The requirements for compliant focus indicators stretch beyond the scope of this article, but Stephanie Eckles’ presentation, “Modern CSS Upgrades To Improve Accessibility” covers them in great detail and with clear examples.

Define A Target Contrast Ratio

Earlier, I may have been a touch blasé about the optional third parameter for color-contrast(). When in reality, this is where the feature showcases its potential.

The optional third parameter for color-contrast() defines a target contrast ratio. The parameter accepts either a keyword — AA, AA-large, AAA, and AAA-large — or a number. When a target contrast is defined, the first color from the color list that meets or exceeds it is selected.

See the Pen CSS Color-Contrast() - Target Contrast Ratio Demo [forked] by Daniel Yuschick.
Results of dynamic target contrasts

When a target contrast is defined, color-contrast() will return the first value from the color list that meets the target. However, when no value in the color list meets the target contrast, it’s where the magic happens.

h1 {
  color: color-contrast(#000 vs #111, #222 to AA);
}

Looking at the base color of black and the color list of two dark shades of grey, there’s no value that would meet the AA (4.5) target contrast. So, what happens?

If the color list does not contain a value that meets the target contrast, CSS will fill in the blanks with one that does — either black or white.Low contrast results with and without a target contrast defined.

This is where color-contrast() could really empower design systems to enforce a specific level of accessibility.

Let’s break this down.

.dark-mode {
  --bg: #000;
  --color-list: #111, #222;
}

.dark-mode {
  background: var(--bg);
  color: color-contrast(var(--bg) vs var(--color-list));

  &.with-target {
    color: color-contrast(var(--bg) vs var(--color-list) to AA);
  }
}

The magic here happens when the two color declarations are compared.

The base .dark-mode class does not use a target contrast. This results in the color being defined as #222, the highest contrasting value from the color list relative to its base color of black. Needless to say, the contrast ratio of 1.35 may be the highest, but it’s far from accessible.

Compare this to when the .dark-mode and .with-target classes are combined, and a target contrast is specified. Despite using the same base color and color list, the result is much different. When no value in the color list meets the AA (4.5) target contrast, the function selects a value that does. In this case, white.

This is where the potential of color-contrast() is the brightest.

In the context of design systems, this would allow a system to enforce a level of color accessibility with very granular control. That level could also be a :root-scoped custom property allowing the target contrast to be dynamic yet global. There’s a real feeling of control on the product side, but that comes at a cost during the implementation.

There’s a logical disconnect between the code and the result. The code doesn’t communicate that the color white will be the result. And, of course, that control on the product side translates to uncertainty with the implementation. If a person is using a design system and passes specific colors into their theme, why are black and white being used instead?

The first concern could be remedied by understanding the color-contrast() feature more deeply, and the second could be alleviated by clear, communicative documentation. However, in both cases, this shifts the burden of expectation onto the implementation side, which is not ideal.

In some cases, the explicit control will justify the costs. However, there are other drawbacks to color-contrast() that will need to be considered in all cases.

ll That Glitters Is Gold

There are inevitable drawbacks to consider, as with any experimental or new feature, and color-contrast() is no different.

Color And Visual Contrasts Are Different Things

When using color-contrast() to determine text color based on its background, the function is comparing exactly that — the colors. What color-contrast() does not take into consideration are other styles that may affect visual contrast, such as font size, weight, and opacity.

This means it’s possible to have a color pairing that technically meets a specific contrast threshold but still results in an inaccessible text because its size is too small, weight is too light, or its opacity is too transparent.

To learn more about accessible typography, I highly recommend Carie Fisher’s talk, “Accessible Typography Essentials.”

Custom Properties And Fallbacks

Since CSS custom properties support fallback values for when the property is not defined, it seemed like a good approach to use color-contrast() as a progressive enhancement.

--article-color: color-contrast(#000 vs #333, #FFF);
color: var(--article-color, var(--fallback-color));

If color-contrast() is not supported, the --article-color property would not be defined, and therefore the --fallback-color would be used. Unfortunately, that’s not how this works.

An interesting thing happens in unsupported browsers — the custom property would be defined with the function itself. Here’s an example of this from Chrome DevTools:

Because the --article-color property is technically defined, the fallback won’t trigger.

However, that’s not to say color-contrast() can’t be used progressively, though. It can be paired with the @supports() function, but be mindful if you decide to do so. As exciting as it may be, with such limited support and potential for syntax and/or functionality changes, it may be best to hold off on sprinkling this little gem throughout an entire codebase.

@supports (color: color-contrast(#000 vs #fff, #eee)) {
  --article-color: color-contrast(var(--article-color) vs #fff, #000);
}

The Highest Contrast Doesn’t Mean Accessible Contrast

Despite the control color-contrast() can offer with colors and themes, there are still limitations. When the function compares the base color against the list and no target contrast is specified, it will select the highest contrasting value. Just because the two colors offer the greatest contrast ratio, it doesn’t mean it’s an accessible one.

h1 {
  background: #000;
  color: color-contrast(#000 vs #111, #222);
}

In this example, the background color of black. #000 is compared against two shades of dark grey. While #222 would be selected for having the “greatest” contrast ratio, pairing it with black would be anything but great.

No Gradient Support

In hindsight, it was maybe a touch ambitious trying gradients with color-contrast(). Nevertheless, through some testing, it seems gradients are not supported. Which, once I thought about it, makes sense.

If a gradient transitioned from black to white, what would the base color be? And wouldn’t it need to be relative to the position of the content? It’s not like the function can interpret the UI. However, Michelle Barker has experimented with using CSS color-mix() and color-contrast() together to support this exact use case.

It’s not you, color-contrast(), it’s me. Well, it’s actually the gradients, but you know what I mean.

Wrapping Up

That was a lot of code and demos, so let’s take a step back and review color-contrast().

The function compares a base color against a color list, then selects the highest contrasting value. Additionally, it can compare those values against a target contrast ratio and either select the first color to meet that threshold or use a dynamic color that does. Pair this with progressive enhancement, and we’ve got a feature that can drastically improve web accessibility.

I believe there are still plenty of unexplored areas and use cases for color-contrast(), so I want to end this article with some additional thoughts and/or questions.

How do you see this feature being leveraged when working with different color modes, like light, dark, and high contrast? Could a React-based design system expose an optional targetContrast prop on its ThemeProvider in order to enforce accessibility if the theme falls short? Would there be a use case for the function to return the lowest contrasting value instead? If there were two base colors, could the function be used to find the best contrasting value between them?

What do you think?

Resources