Managing User Focus with :focus-visible

This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out Accessible Forms with Pseudo Classes. In this post we are going to look at :focus-visible and how to use it in your web sites!

Focus Touchpoint

Before we move forward with :focus-visible, let’s revisit how :focus works in your CSS. Focus is the visual indicator that an element is being interacted with via keyboard, mouse, trackpad, or assistive technology. Certain elements are naturally interactive, like links, buttons, and form elements. We want to make sure that our users know where they are and the interactions they are making.

Remember don’t do this in your CSS!

:focus {
  outline: 0;
}

/*** OR ***/

:focus {
  outline: none;
}

When you remove focus, you remove it for EVERYONE! We want to make sure that we are preserving the focus.

If for any reason you do need to remove the focus, make sure there is also fallback :focus styles for your users. That fallback can match your branding colors, but make sure those colors are also accessible. If marketing, design, or branding doesn’t like the default focus ring styles, then it is time to start having conversations and collaborate with them on the best way of adding it back in.

What is focus-visible?

The pseudo class, :focus-visible, is just like our default :focus pseudo class. It gives the user an indicator that something is being focused on the page. The way you write :focus-visible is cut and dry:

:focus-visible {
  /* ... */
}

When using :focus-visible with a specific element, the syntax looks something like this:

.your-element:focus-visible {
  /*...*/
}

The great thing about using :focus-visible is you can make your element stand out, bright and bold! No need to worry about it showing if the element is clicked/tapped. If you choose not to implement the class, the default will be the user agent focus ring which to some is undesirable.

Backstory of focus-visible

Before we had the :focus-visible, the user agent styling would apply :focus to most elements on the page; buttons, links, etc. It would apply an outline or “focus ring” to the focusable element. This was deemed to be ugly, most didn’t like the default focus ring the browser provided. As a result of the focus ring being unfavorable to look at, most authors removed it… without a fallback. Remember, when you remove :focus, it decreases usability and makes the experience inaccessible for keyboard users.

In the current state of the web, the browser no longer visibly indicates focus around various elements when they have focus. The browser instead uses varying heuristics to determine when it would help the user, providing a focus ring in return. According to Khan Academy, a heuristic is, “a technique that guides an algorithm to find good choices.”

What this means is that the browser can detect whether or not the user is interacting with the experience from a keyboard, mouse, or trackpad and based on that input type, it adds or removes the focus ring. The example in this post highlights the input interaction.

In the early days of :focus-visible we were using a polyfill to handle the focus ring created by Alice Boxhall and Brian Kardell, Mozilla also came out with their own pseudo class, :moz-focusring, before the official specification. If you want to learn more about the early days of the focus-ring, check out A11y Casts with Rob Dodson.

Focus Importance

There are plenty of reasons why focus is important in your application. For one, like I stated above, we as ambassadors of the web have to make sure we are providing the best, accessible experience we can. We don’t want any of our users guessing where they are while they are navigation through the experience.

One example that always comes to mind is the Two Blind Brothers website. If you go to the website and click/tap (this works on mobile), the closed eye in the bottom left corner, you will see the eye open and a simulation begins. Both the brothers, Bradford and Bryan Manning, were diagnosed at a young age with Stargardt’s Disease. Stargardt’s disease is a form of macular degeneration of the eye. Over time both brothers will be completely blind. Visit the site and click the eye to see how they see.

If you were in their shoes and you had to navigate through a page, you would want to make sure you knew exactly where you were throughout the whole experience. A focus ring gives you that power.

Image of the home page from the Two Blind Brothers website.

Demo

The demo below shows how :focus-visible works when added to your CSS. The first part of the video shows the experience when navigating through with a mouse the second shows navigating through with just my keyboard. I recorded myself as well to show that I did switch from using my mouse, to my keyboard.

Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.
Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.

The browser is predicting what to do with the focus ring based on my input (keyboard/mouse), and then adding a focus ring to those elements. In this case, when I am navigating through this example with the keyboard, everything receives focus. When using the mouse, only the input gets focus and the buttons don’t. If you remove :focus-visible, the browser will apply the default focus ring.

The code below is applying :focus-visible to the focusable elements.

:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

If you want to specify the label or the button to receive :focus-visible just prepend the class with input or button respectively.

button:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

/*** OR ***/

input:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

Support

If the browser does not support :focus-visible you can have a fall back in place to handle the interaction. The code below is from the MDN Playground. You can use the @supports at-rule or “feature query” to check support. One thing to keep in mind, the rule should be placed at the top of the code or nested inside another group at-rule.

<button class="button with-fallback" type="button">Button with fallback</button>
<button class="button without-fallback" type="button">Button without fallback</button>
.button {
  margin: 10px;
  border: 2px solid darkgray;
  border-radius: 4px;
}

.button:focus-visible {
  /* Draw the focus when :focus-visible is supported */
  outline: 3px solid deepskyblue;
  outline-offset: 3px;
}

@supports not selector(:focus-visible) {
  .button.with-fallback:focus {
    /* Fallback for browsers without :focus-visible support */
    outline: 3px solid deepskyblue;
    outline-offset: 3px;
  }
}

Further Accessibility Concerns

Accessibility concerns to keep in mind when building out your experience:

  • Make sure the colors you choose for your focus indicator, if at all, are still accessible according to the information documented in the WCAG 2.2 Non-text Contrast (Level AA)
  • Cognitive overload can cause a user distress. Make sure to keep styles on varying interactive elements consistent

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

ChromeFirefoxIEEdgeSafari
864*No8615.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12312412315.4

Managing User Focus with :focus-visible originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Infinite-Scrolling Logos In Flat HTML And Pure CSS

When I was asked to make an auto-scrolling logo farm, I had to ask myself: “You mean, like a <marquee>?” It’s not the weirdest request, but the thought of a <marquee> conjures up the “old” web days when Geocities ruled. What was next, a repeating sparkling unicorn GIF background?

If you’re tempted to reach for the <marquee> element, don’t. MDN has a stern warning about it right at the top of the page:

Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible […] Be aware that this feature may cease to work at any time.”

That’s fine because whatever infinite scrolling feature <marquee> is offered, we can most certainly pull off in CSS. But when I researched examples to help guide me, I was surprised to find very little on it. Maybe auto-scrolling elements aren’t the rage these days. Perhaps the sheer nature of auto-scrolling behavior is enough of an accessibility red flag to scare us off.

Whatever the case, we have the tools to do this, and I wanted to share how I went about it. This is one of those things that can be done in lots of different ways, leveraging lots of different CSS features. Even though I am not going to exhaustively explore all of them, I think it’s neat to see someone else’s thought process, and that’s what you’re going to get from me in this article.

What We’re Making

But first, here's an example of the finished result:

See the Pen CSS only marquee without HTML duplication [forked] by Silvestar Bistrović.

The idea is fairly straightforward. We want some sort of container, and in it, we want a series of images that infinitely scroll without end. In other words, as the last image slides in, we want the first image in the series to directly follow it in an infinite loop.

So, here’s the plan: We’ll set up the HTML first, then pick at the container and make sure the images are correctly positioned in it before we move on to writing the CSS animation that pulls it all together.

Existing Examples

Like I mentioned, I tried searching for some ideas. While I didn’t find exactly what I was looking for, I did find a few demos that provided a spark of inspiration. What I really wanted was to use CSS only while not having to “clone” the marquee items.

Geoff Graham’s “Sliding Background Effect” is close to what I wanted. While it is dated, it did help me see how I could intentionally use overflow to allow images to “slide” out of the container and an animation that loops forever. It’s a background image, though, and relies on super-specific numeric values that make it tough to repurpose in other projects.

See the Pen Untitled [forked] by @css-tricks.

There’s another great example from Coding Journey over at CodePen:

See the Pen Marquee-like Content Scrolling [forked] by Coding Journey.

The effect is what I’m after for sure, but it uses some JavaScript, and even though it’s just a light sprinkle, I would prefer to leave JavaScript out of the mix.

Ryan Mulligan’s “CSS Marquee Logo Wall” is the closest thing. Not only is it a logo farm with individual images, but it demonstrates how CSS masking can be used to hide the images as they slide in and out of the container. I was able to integrate that same idea into my work.

See the Pen CSS Marquee Logo Wall [forked] by Ryan Mulligan.

But there’s still something else I’m after. What I would like is the smallest amount of HTML possible, namely markup that does not need to be duplicated to create the impression that there’s an unending number of images. In other words, we should be able to create an infinite-scrolling series of images where the images are the only child elements in the “marquee” container.

I did find a few more examples in other places, but these were enough to point me in the right direction. Follow along with me.

The HTML

Let's set up the HTML structure first before anything else. Again, I want this to be as “simple” as possible, meaning very few elements with the shortest family tree possible. We can get by with nothing but the “marquee” container and the logo images in it.

<figure class="marquee">
  <img class="marquee__item" src="logo-1.png" width="100" height="100" alt="Company 1">
  <img class="marquee__item" src="logo-2.png" width="100" height="100" alt="Company 2">
  <img class="marquee__item" src="logo-3.png" width="100" height="100" alt="Company 3">
</figure>

This keeps things as “flat” as possible. There shouldn’t be anything else we need in here to make things work.

Setting Up The Container

Flexbox might be the simplest approach for establishing a row of images with a gap between them. We don’t even need to tell it to flow in a row direction because that’s the default.

.marquee {
  display: flex;
}

I already know that I plan on using absolute positioning on the image elements, so it makes sense to set relative positioning on the container to, you know, contain them. And since the images are in an absolute position, they have no reserved height or width dimensions that influence the size of the container. So, we’ll have to declare an explicit block-size (the logical equivalent to height). We also need a maximum width so we have a boundary for the images to slide in and out of view, so we’ll use max-inline-size (the logical equivalent to max-width):

.marquee {
  --marquee-max-width: 90vw;

  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  position: relative;
}

Notice I’m using a couple of CSS variables in there: one that defines the marquee’s height based on the height of one of the images (--marquee-item-height) and one that defines the marquee’s maximum width (--marquee-max-width). We can give the marquee’s maximum width a value now, but we’ll need to formally register and assign a value to the image height, which we will do in a bit. I just like knowing what variables I am planning to work with as I go.

Next up, we want the images to be hidden when they are outside of the container. We’ll set the horizontal overflow accordingly:

.marquee {
  --marquee-max-width: 90vw;

  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  overflow-x: hidden;
  position: relative;
}

And I really like the way Ryan Mulligan used a CSS mask. It creates the impression that images are fading in and out of view. So, let’s add that to the mix:

.marquee {
  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  overflow-x: hidden;
  position: relative;
  mask-image: linear-gradient(
    to right,
    hsl(0 0% 0% / 0),
    hsl(0 0% 0% / 1) 20%,
    hsl(0 0% 0% / 1) 80%,
    hsl(0 0% 0% / 0)
  );
  position: relative;
}

Here’s what we have so far:

See the Pen CSS only marquee without HTML duplication, example 0 [forked] by Silvestar Bistrović.

Positioning The Marquee Items

Absolute positioning is what allows us to yank the images out of the document flow and manually position them so we can start there.

.marquee__item {
  position: absolute;
}

That makes it look like the images are completely gone. But they’re there — the images are stacked directly on top of one another.

Remember that CSS variable for our container, --marquee-item-height? Now, we can use it to match the marquee item height:

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
}

To push marquee images outside the container, we need to define a --marquee-item-offset, but that calculation is not trivial, so we will learn how to do it in the next section. We know what the animation needs to be: something that moves linearly for a certain duration after an initial delay, then goes on infinitely. Let’s plug that in with some variables as temporary placeholders.

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
  animation: go linear var(--marquee-duration) var(--marquee-delay, 0s) infinite;
}

To animate the marquee items infinitely, we have to define two CSS variables, one for the duration (--marquee-duration) and one for the delay (--marquee-delay). The duration can be any length you want, but the delay should be calculated, which is what we will figure out in the next section.

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
  animation: go linear var(--marquee-duration) var(--marquee-delay, 0s) infinite;
  transform: translateX(-50%);
}

Finally, we will translate the marquee item by -50% horizontally. This small “hack” handles situations when the image sizes are uneven.

See the Pen CSS only marquee without HTML duplication, example 2 [forked] by Silvestar Bistrović.

Animating The Images

To make the animation work, we need the following information:

  • Width of the logos,
  • Height of the logos,
  • Number of items, and
  • Duration of the animation.

Let’s use the following configurations for our set of variables:

.marquee--8 {
  --marquee-item-width: 100px;
  --marquee-item-height: 100px;
  --marquee-duration: 36s;
  --marquee-items: 8;
}

Note: I’m using the BEM modifier .marquee--8 to define the animation of the eight logos. We can define the animation keyframes now that we know the --marquee-item-width value.

@keyframes go {
  to {
    inset-inline-start: calc(var(--marquee-item-width) * -1);
  }
}

The animation moves the marquee item from right to left, allowing each one to enter into view from the right as it travels out of view over on the left edge and outside of the marquee container.

Now, we need to define the --marquee-item-offset. We want to push the marquee item all the way to the right side of the marquee container, opposite of the animation end state.

You might think the offset should be 100% + var(--marquee-item-width), but that would make the logos overlap on smaller screens. To prevent that, we need to know the minimum width of all logos combined. We do that in the following way:

calc(var(--marquee-item-width) * var(--marquee-items))

But that is not enough. If the marquee container is too big, the logos would take less than the maximum space, and the offset would be within the container, which makes the logos visible inside the marquee container. To prevent that, we will use the max() function like the following:

--marquee-item-offset: max(
  calc(var(--marquee-item-width) * var(--marquee-items)),
  calc(100% + var(--marquee-item-width))
);

The max() function checks which of the two values in its arguments is bigger, the overall width of all logos or the maximum width of the container plus the single logo width, which we defined earlier. The latter will be true on bigger screens and the former on smaller screens.

See the Pen CSS only marquee without HTML duplication, example 3 [forked] by Silvestar Bistrović.

Finally, we will define the complicated animation delay (--marquee-delay) with this formula:

--marquee-delay: calc(var(--marquee-duration) / var(--marquee-items) * (var(--marquee-items) - var(--marquee-item-index)) * -1);

The delay equals the animation duration divided by a quadratic polynomial (that’s what ChatGPT tells me, at least). The quadratic polynomial is the following part, where we multiply the number of items and number of items minus the current item index:

var(--marquee-items) * (var(--marquee-items) - var(--marquee-item-index))

Note that we are using a negative delay (* -1) to make the animation start in the “past,” so to speak. The only remaining variable to define is the --marquee-item-index (the current marquee item position):

.marquee--8 .marquee__item:nth-of-type(1) {
  --marquee-item-index: 1;
}
.marquee--8 .marquee__item:nth-of-type(2) {
  --marquee-item-index: 2;
}

/* etc. */

.marquee--8 .marquee__item:nth-of-type(8) {
  --marquee-item-index: 8;
}

Here’s that final demo once again:

See the Pen CSS only marquee without HTML duplication [forked] by Silvestar Bistrović.

Improvements

This solution could be better, especially when the logos are not equal widths. To adjust the gaps between inconsistently sized images, we could calculate the delay of the animation more precisely. That is possible because the animation is linear. I’ve tried to find a formula, but I think it needs more fine-tuning, as you can see:

See the Pen CSS only marquee without HTML duplication, example 4 [forked] by Silvestar Bistrović.

Another improvement we can get with a bit of fine-tuning is to prevent big gaps on wide screens. To do that, set the max-inline-size and declare margin-inline: auto on the .marquee container:

See the Pen CSS only marquee without HTML duplication, example 5 [forked] by Silvestar Bistrović.

Conclusion

What do you think? Is this something you can see yourself using on a project? Would you approach it differently? I am always happy when I land on something with a clean HTML structure and a pure CSS solution. You can see the final implementation on the Heyflow website.

Further Reading On SmashingMag

The Power of :has() in CSS

Hey all you wonderful developers out there! In this post we are going to explore the use of :has() in your next web project. :has() is relatively newish but has gained popularity in the front end community by delivering control over various elements in your UI. Let’s take a look at what the pseudo class is and how we can utilize it.

Syntax

The :has() CSS pseudo-class helps style an element if any of the things we’re searching for inside it are found and accounted for. It’s like saying, “If there’s something specific inside this box, then style the box this way AND only this way.”

:has(<direct-selector>) {
  /* ... */
}

“The functional :has() CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument.”

For a more robust explanation, MDN does it perfectly

The Styling Problem

In years past we had no way of styling a parent element based on a direct child of that parent with CSS or an element based on another element. In the chance we had to do that, we would need to use some JavaScript and toggle classes on/off based on the structure of the HTML. :has() solved that problem.


Let’s say that you have a heading level 1 element (h1) that is the title of a post or something of that nature on a blog list page, and then you have a heading level 2 (h2) that directly follows it. This h2 could be a sub-heading for the post. If that h2 is present, important, and directly after the h1, you might want to make that h1 stand out. Before you would have had to write a JS function.

Old School Way – JavaScript

const h1Elements = document.querySelectorAll('h1');

h1Elements.forEach((h1) => {
  const h2Sibling = h1.nextElementSibling;
  if (h2Sibling && h2Sibling.tagName.toLowerCase() === 'h2') {
    h1.classList.add('highlight-content');
  }
});

This JS function is looking for all the h1’s that have a h2 proceeding it, and applying a class of highlight-content to make the h1 stand out as an important article.

New and improved with modern day CSS coming in hot! The capabilities of what we can do in the browser have come a long way. We now can take advantage of CSS to do things that we traditionally would have to do with JavaScript, not everything, but some things.

New School Way – CSS

h1:has(+ h2) {
    color: blue;
}

Throw Some :has() On It!

Now you can use :has() to achieve the same thing that the JS function did. This CSS is checking for any h1 and using the sibling combinator checking for an h2 that immediately follows it, and adds the color of blue to the text. Below are a couple use cases of when :has() can come in handy.

:has Selector Example 1

HTML

<h1>Lorem, ipsum dolor.</h1>
	<h2>Lorem ipsum dolor sit amet.</h2>
	<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>
	
	<h1>Lorem, ipsum dolor.</h1>
	<h2>Lorem ipsum dolor sit amet.</h2>
	<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>

	<h1>This is a test</h1>
	<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>

CSS

h1:has(+ h2) {
    color: blue;
}
CSS :has selector turning h1 blue when it only has a h2 following it.

:has Selector Example 2

A lot of times we as workers on the web are manipulating or working with images. We could be using tools that Cloudinary provides to make use of various transformations on our images, but usually we want to add drop shadows, border-radii, and captions (not to be confused with alternative text in an alt attribute).


The example below is using :has() to see if a figure or image has a figcaption element and if it does, it applies some background and a border radius to make the image stand out.

HTML

<section>
	<figure>
		<img src="https://placedog.net/500/280" alt="My aunt sally's dog is a golden retreiver." />
		<figcaption>My Aunt Sally's Doggo</figcaption>
	</figure>
</section>

CSS

figure:has(figcaption) {
  background: #c3baba;
  padding: 0.6rem;
  max-width: 50%;
  border-radius: 5px;
}
Example of :has selector highlighting background an image with an caption vs one that does not.

Can I :has() that?

You can see that :has() has great support across modern browsers.

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

ChromeFirefoxIEEdgeSafari
105121No10515.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12212312215.4

:has() in the Community!

I reached out to my network on Twitter to see how my peers were using :has() in their day-to-day work and this is what they had to say about it.

“One example I have is styling a specific SVG from a 3rd party package in @saucedopen because I couldn’t style it directly.”

This is what Nick Taylor from OpenSauced had to say about using :has().
svg:has(> #Mail) {
  stroke-width: 1;
}

Lol the last time I used it I was building keyboard functionality into a tree view, so I needed to detect states and classes of sibling elements, but it wasn’t in Firefox yet so I had to find another solution. 🫠

Abbey Perini from Nexcor Food Safety Technologies, Inc.

It is great to see how community members are using modern CSS to solve real world problems, and also a shout out to Abbey using it for accessibility reasons!

Things to Keep in Mind

There are a few key points to keep in mind when using :has() Bullet points referenced from MDN.

  • The pseudo-class takes on specificity of the most specific selector in its argument
  • If the :has() pseudo-class itself is not supported in a browser, the entire selector block will fail unless :has() is in a forgiving selector list, such as in :is() and :where()
  • The :has() pseudo-class cannot be nested within another :has() 
  • Pseudo-elements are also not valid selectors within :has() and pseudo-elements are not valid anchors for :has()

Conclusion

Harnessing the power of CSS, including advanced features like the :has() pseudo-class, empowers us to craft exceptional web experiences. CSS’s strengths lie in its cascade and specificity…the best part, allowing us to leverage its full potential. By embracing the capabilities of CSS, we can drive web design and development forward, unlocking new possibilities and creating groundbreaking user interfaces.

Links:


The Power of :has() in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Accessible Forms with Pseudo Classes

Hey all you wonderful developers out there! In this post, I am going to take you through creating a simple contact form using semantic HTML and an awesome CSS pseudo class known as :focus-within. The :focus-within class allows for great control over focus and letting your user know this is exactly where they are in the experience. Before we jump in, let’s get to the core of what web accessibility is.


Form Accessibility?


You have most likely heard the term “accessibility” everywhere or the numeronym, a11y. What does it mean? That is a great question with so many answers. When we look at the physical world, accessibility means things like having sharps containers in your bathrooms at your business, making sure there are ramps for wheel assisted people, and having peripherals like large print keyboards on hand for anyone that needs it.

The gamut of accessibility doesn’t stop there, we have digital accessibility that we need to be cognizant of as well, not just for external users, but internal colleagues as well. Color contrast is a low hanging fruit that we should be able to nip in the bud. At our workplaces, making sure that if any employee needs assistive tech like a screen reader, we have that installed and available. There are a lot of things that need to be kept into consideration. This article will focus on web accessibility by keeping the WCAG (web content accessibility guidelines) in mind.

MDN (Mozilla Developer Network)

The :focus-within CSS pseudo-class matches an element if the element or any of its descendants are focused. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by :focus. (This includes descendants in shadow trees.)

This pseudo class is really great when you want to emphasize that the user is in fact interacting with the element. You can change the background color of the whole form, for example. Or, if focus is moved into an input, you can make the label bold and larger of an input element when focus is moved into that input. What is happening below in the code snippets and examples is what is making the form accessible. :focus-within is just one way we can use CSS to our advantage.

How To Focus


Focus, in regards to accessibility and the web experience, is the visual indicator that something is being interacted with on the page, in the UI, or within a component. CSS can tell when an interactive element is focused.

“The :focus CSS pseudo-class represents an element (such as a form input) that has received focus. It is generally triggered when the user clicks or taps on an element or selects it with the keyboard’s Tab key.”

MDN (Mozilla Developer Network)

Always make sure that the focus indicator or the ring around focusable elements maintains the proper color contrast through the experience.

Focus is written like this and can be styled to match your branding if you choose to style it.

:focus {
  * / INSERT STYLES HERE /*
}

Whatever you do, never set your outline to 0 or none. Doing so will remove a visible focus indicator for everyone across the whole experience. If you need to remove focus, you can, but make sure to add that back in later. When you remove focus from your CSS or set the outline to 0 or none, it removes the focus ring for all your users. This is seen a lot when using a CSS reset. A CSS reset will reset the styles to a blank canvas. This way you are in charge of the empty canvas to style as you wish. If you wish to use a CSS reset, check out Josh Comeau’s reset.

*DO NOT DO what is below!

:focus {
  outline: 0;
}

:focus {
  outline: none;
}


Look Within!


One of the coolest ways to style focus using CSS is what this article is all about. If you haven’t checked out the :focus-within pseudo class, definitely give that a look! There are a lot of hidden gems when it comes to using semantic markup and CSS, and this is one of them. A lot of things that are overlooked are accessible by default, for instance, semantic markup is by default accessible and should be used over div’s at all times.

<header>
  <h1>Semantic Markup</h1>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<section><!-- Code goes here --></section>

<section><!-- Code goes here --></section>

<aside><!-- Code goes here --></aside>

<footer><!-- Code goes here --></footer>

The header, nav, main, section, aside, and footer are all semantic elements. The h1 and ul are also semantic and accessible.

Unless there is a custom component that needs to be created, then a div is fine to use, paired with ARIA (Accessible Rich Internet Applications). We can do a deep dive into ARIA in a later post. For now let’s focus…see what I did there…on this CSS pseudo class.

The :focus-within pseudo class allows you to select an element when any descendent element it contains has focus.


:focus-within in Action!

HTML

<form>
  <div>
    <label for="firstName">First Name</label><input id="firstName" type="text">
  </div>
  <div>
    <label for="lastName">Last Name</label><input id="lastName" type="text">
  </div>
  <div>
    <label for="phone">Phone Number</label><input id="phone" type="text">
  </div>
  <div>
    <label for="message">Message</label><textarea id="message"></textarea>
  </div>
</form>

CSS

form:focus-within {
  background: #ff7300;
  color: black;
  padding: 10px;
}

The example code above will add a background color of orange, add some padding, and change the color of the labels to black.

The final product looks something like below. Of course the possibilities are endless to change up the styling, but this should get you on a good track to make the web more accessible for everyone!

First example of focus-within css class highlighting the form background and changing the label text color.

Another use case for using :focus-within would be turning the labels bold, a different color, or enlarging them for users with low vision. The example code for that would look something like below.

HTML

<form>
  <h1>:focus-within part 2!</h1>
  <label for="firstName">First Name: <input name="firstName" type="text" /></label>
  <label for="lastName">Last Name: <input name="lastName" type="text" /></label>
  <label for="phone">Phone number: <input type="tel" id="phone" /></label>
  <label for="message">Message: <textarea name="message" id="message"/></textarea></label>
</form>

CSS

label {
  display: block;
  margin-right: 10px;
  padding-bottom: 15px;
}

label:focus-within {
  font-weight: bold;
  color: red;
  font-size: 1.6em;
}
Showing how to bold, change color and font size of labels in a form using :focus-within.

:focus-within also has great browser support across the board according to Can I use.

Focus within css pseudo class browser support according to the can i use website.

Conclusion

Creating amazing, accessible user experience should always be a top priority when shipping software, not just externally but internally as well. We as developers, all the way up to senior leadership need to be cognizant of the challenges others face and how we can be ambassadors for the web platform to make it a better place.

Using technology like semantic markup and CSS to create inclusive spaces is a crucial part in making the web a better place, let’s continue moving forward and changing lives.

Check out another great resource here on CSS-Tricks on using :focus-within.


Accessible Forms with Pseudo Classes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Chris’ Corner: More Like Celebrating Style Skills

It’s January and we’re seeing a little round of CSS wishlists make the rounds.

  • Tyler Sticka is mostly hoping for better support on stuff that already is starting to cook, like View Transitions, anchor positioning, balanced text, and scroll-driven animations. But he’s got his own new wishes and has done the leg-work to properly propose them, like more useful vertical alignment (without extra work).
  • Christopher Kirk-Nielsen also has some wishes for better support for thing that ought to be doing that naturally, like Style Queries, but also has some really interesting ideas of his own like using transform to force an element of an unknown size into a specific size, which he also wrote up properly.
  • Manuel Matuzović has more love for Style (and State) Queries (agreed there is some real potential here to reduce CSS size and complexity) and @scope. On the doesn’t-exist-yet side, a vote for mixins to which I’ll echo: Yes, please!
  • Elly Loel weighted in last year using the same distinctions: stuff that is already cooking (e.g. Custom Media) and stuff that doesn’t exist yet (e.g. detecting flex-wrap). Good news for Elly, some stuff from the list got done, like subgrid.
  • Nathan Knowler joins the choir with strong votes for Style Queries, Scroll-Driven Animations, and @scope, among others.
  • Sarah Gebauer has a great one on her wishlist about borders and controlling the offset. Hey, SVG can do it and it’s super cool. Plus a shout out to a personal favorite: more useful attributes and the attr() function.

Phew! That’s a lot of people taking the time to be pumped about CSS and make clear what they want. The way CSS is going lately I would not be surprised to see massive progress again in 2024.


Stephanie Eckles points out 12 one-liners in CSS that are all tremendously useful and powerful.

One liners.

Stuff like p { text-wrap: pretty; } that just makes web type all the nicer. Five years ago our gapping mouths would have been on the floor if they knew what was coming.


Brecht De Ruyte took two brand new CSS and HTML things and smashed them together:

  • The new <selectmenu> element, which is exactly like <select> except fully CSS styleable.
  • The Anchor Position API

It’s a cool interaction that is supposed to end up like this:

But this was actually published mid-last-year, and the demo is already broken even. It only ever worked in Chrome Canary with the Experimental Web Platform Features flag turned on, so that just goes to show you how fast this experimental stuff moves.

It’s not <selectmenu> any more, it’s <selectlist>, but even after forking the demo and trying to fix it, I couldn’t get it. I suspect it’s changes to the Anchor Positioning API that I’m not up to snuff on. I do suspect this interaction is possible today, but you’d either be using cutting edge APIs that may or may not be polyfillable, and/or recreating a ton of interaction and accessibility stuff that you’d get for free with Brecht’s implementation.


Ben Frain has a very clever idea for drawing angled lines in CSS (like a line chart). Which, weirdly, isn’t a particularly easy thing to do in CSS. You can make lines various ways and rotate them and stuff, but it’s harder to be like: draw a line from here to here. SVG is typically the path for that.

In Ben’s technique, you take advantage of the polygon() function and clip-path. You “draw” points in the clip path at certain coordinates, then go back over the exact same coordinates with a 1px difference, leaving that little gap where a new color can “shine through”.

Like:

clip-path: polygon(
  0% 60%,
  20% 90%,
  40% 43.33%,
  60% 61.67%,
  80% 23.33%,
  100% 18.33%,
  100% calc(18.33% - 1px),
  80% calc(23.33% - 1px),
  60% calc(61.67% - 1px),
  40% calc(43.33% - 1px),
  20% calc(90% - 1px),
  0% calc(60% - 1px)
);
Demo

Robin Rendle once made a bunch of different chart types in CSS only, but I don’t think I’ve seen a CSS “sparkline” like this until now.

A Simple Guide To Retrieval Augmented Generation Language Models

Suppose you ask some AI-based chat app a reasonably simple, straightforward question. Let’s say that app is ChatGPT, and the question you ask is right in its wheelhouse, like, “What is Langchain?” That’s really a softball question, isn’t it? ChatGPT is powered by the same sort of underlying technology, so it ought to ace this answer.

So, you type and eagerly watch the app spit out conversational strings of characters in real-time. But the answer is less than satisfying.

In fact, ask ChatGPT — or any other app powered by language models — any question about anything recent, and you’re bound to get some sort of response along the lines of, “As of my last knowledge update…” It’s like ChatGPT fell asleep Rumplestiltskin-style back in January 2022 and still hasn’t woken up. You know how people say, “You’d have to be living under a rock not to know that”? Well, ChatGPT took up residence beneath a giant chunk of granite two years ago.

While many language models are trained on massive datasets, data is still data, and data becomes stale. You might think of it like Googling “CSS animation,” and the top result is a Smashing Magazine article from 2011. It might still be relevant, but it also might not. The only difference is that we can skim right past those instances in search results while ChatGPT gives us some meandering, unconfident answers we’re stuck with.

There’s also the fact that language models are only as “smart” as the data used to train them. There are many techniques to improve language model’s performance, but what if language models could access real-world facts and data outside their training sets without extensive retraining? In other words, what if we could supplement the model’s existing training with accurate, timely data?

This is exactly what Retrieval Augmented Generation (RAG) does, and the concept is straightforward: let language models fetch relevant knowledge. This could include recent news, research, new statistics, or any new data, really. With RAG, a large language model (LLM) is able to retrieve “fresh” information for more high-quality responses and fewer hallucinations.

But what exactly does RAG make available, and where does it fit in a language chain? We’re going to learn about that and more in this article.

Understanding Semantic Search

Unlike keyword search, which relies on exact word-for-word matching, semantic search interprets a query’s “true meaning” and intent — it goes beyond merely matching keywords to produce more results that bear a relationship to the original query.

For example, a semantic search querying “best budget laptops” would understand that the user is looking for “affordable” laptops without querying for that exact term. The search recognizes the contextual relationships between words.

This works because of text embeddings or mathematical representations of meaning that capture nuances. It’s an interesting process of feeding a query through an embedded model that, in turn, converts the query into a set of numeric vectors that can be used for matching and making associations.

The vectors represent meanings, and there are benefits that come with it, allowing semantic search to perform a number of useful functions, like scrubbing irrelevant words from a query, indexing information for efficiency, and ranking results based on a variety of factors such as relevance.

Special databases optimized for speed and scale are a strict necessity when working with language models because you could be searching through billions of documents. With a semantic search implementation that includes test embedding, storing and querying high-dimensional embedding data is much more efficient, producing quick and efficient evaluations on queries against document vectors across large datasets.

That’s the context we need to start discussing and digging into RAG.

Retrieval Augmented Generation

Retrieval Augmented Generation (RAG) is based on research produced by the Meta team to advance the natural language processing capabilities of large language models. Meta’s research proposed combining retriever and generator components to make language models more intelligent and accurate for generating text in a human voice and tone, which is also commonly referred to as natural language processing (NLP).

At its core, RAG seamlessly integrates retrieval-based models that fetch external information and generative model skills in producing natural language. RAG models outperform standard language models on knowledge-intensive tasks like answering questions by augmenting them with retrieved information; this also enables more well-informed responses.

You may notice in the figure above that there are two core RAG components: a retriever and a generator. Let’s zoom in and look at how each one contributes to a RAG architecture.

Retriever

We already covered it briefly, but a retriever module is responsible for finding the most relevant information from a dataset in response to queries and makes that possible with the vectors produced by text embedding. In short, it receives the query and retrieves what it evaluates to be the most accurate information based on a store of semantic search vectors.

Retrievers are models in and of themselves. But unlike language models, retrievers are not in the business of “training” or machine learning. They are more of an enhancement or an add-on that provides additional context for understanding and features for fetching that information efficiently.

That means there are available options out there for different retrievers. You may not be surprised that OpenAI offers one, given their ubiquity. There’s another one provided by Cohere as well as a slew of smaller options you can find in the Hugging Face community.

Generator

After the retriever finds relevant information, it needs to be passed back to the application and displayed to the user. Or what’s needed is a generator capable of converting the retrieved data into human-readable content.

What’s happening behind the scenes is the generator accepts the embeddings it receives from the retriever, mashes them together with the original query, and passes through the trained language model for an NLP pass on the way to becoming generated text.

The entire tail end of that process involving the language model and NLP is a process in its own right and is something I have explained in greater detail in another Smashing Magazine article if you are curious about what happens between the generator and final text output.

RAG Full View

Pulling everything together, a complete RAG flow goes like this:

  1. A query is made.
  2. The query is passed to the RAG model.
  3. The RAG model encodes the query into text embeddings that are compared to a dataset of information.
  4. The RAG’s retriever decides the most relevant information with its semantic search abilities and converts it into vector embeddings.
  5. The RAG’s retriever sends the parsed embeddings to the generator.
  6. The generator accepts the embeddings and combines them with the original query.
  7. The generator passes its work off to the language model to produce natural-sounding content presented to the user.

LLM Hallucinations And Knowledge Limitations

We opened this article up by describing “hallucinations” in LLMs’ incorrect responses or something along the lines of “I don’t know, but here’s what I do know.” The LLM will “make stuff up” because it simply doesn’t have updated information to respond with.

Let’s revisit the first query we used to kick off this article — “What is LangChain?” — and compare responses from the Vicuna and GPT-4 language models:

Here’s the transcription for the second query using OpenAI’s GPT-4 for posterity:

“LangChain is a blockchain-based decentralized translation platform. It’s designed to connect translators and those seeking translation services directly without the need for intermediaries. This system uses artificial intelligence and blockchain technologies to provide high-quality, efficient translation services. LangChain also has a token-based economy, where users can earn and spend tokens within the LangChain ecosystem.”

Both Vicuna and GPT-4 refer to LangChain as a blockchain platform. Blockchain is a technology that stores data in a decentralized manner using chained blocks, so the models’ responses sound plausible given the “chain” in the name. However, LangChain is not actually a blockchain-based technology.

This is a prime example demonstrating how LLMs will fabricate responses that may seem believable at first glance but are incorrect. LLMs are designed to predict the next “plausible” tokens in a sequence, whether those are words, subwords, or characters. They don’t inherently understand the full meaning of the text. Even the most advanced models struggle to avoid made-up responses, especially for niche topics they lack knowledge about.

Let’s look at another example by querying: “What is the most preferred framework used by developers for building applications leveraging large language models?”

While Vicuna offers a couple of reasonable starting points for answering the question, the frameworks it refers to have limitations for efficiency and scalability in production-level applications that use LLMs. That could quite possibly send a developer down a bad path. And as bad as that is, look at the GPT-4 response that changes topics completely by focusing on LLVM, which has nothing to do with LLMs.

What if we refine the question, but this time querying different language models? This time, we’re asking: “What is the go-to framework developed for developers to seamlessly integrate large language models into their applications, focusing on ease of use and enhanced capabilities?”

Honestly, I was expecting the responses to refer to some current framework, like LangChain. However, the GPT-4 Turbo model suggests the “Hugging Face” transformer library, which I believe is a great place to experiment with AI development but is not a framework. If anything, it’s a place where you could conceivably find tiny frameworks to play with.

Meanwhile, the GPT-3.5 Turbo model produces a much more confusing response, talking about OpenAI Codex as a framework, then as a language model. Which one is it?

We could continue producing examples of LLM hallucinations and inaccurate responses and have fun with the results all day. We could also spend a lot of time identifying and diagnosing what causes hallucinations. But we’re here to talk about RAG and how to use it to prevent hallucinations from happening in the first place. The Master of Code Global blog has an excellent primer on the causes and types of LLM hallucinations with lots of useful context if you are interested in diving deeper into the diagnoses.

Integrating RAG With Language Models

OK, so we know that LLMs sometimes “hallucinate” answers. We know that hallucinations are often the result of outdated information. We also know that there is this thing called Retrieval Augmented Generation that supplements LLMs with updated information.

But how do we connect RAG and LLMs together?

Now that you have a good understanding of RAG and its benefits, we can dive into how to implement it yourself. This section will provide hands-on examples to show you how to code RAG systems and feed new data into your LLM.

But before jumping right into the code, you’ll need to get a few key things set up:

  • Hugging Face
    We’ll use this library in two ways. First, to choose an embedding model from the model hub that we can use to encode our texts, and second, to get an access token so we can download the Llama-2 model. Sign up for a free Hugging Face in preparation for the work we’ll cover in this article.
  • Llama-2
    Meta’s powerful LLM will be our generator model. Request access via Meta’s website so we can integrate Llama-2 into our RAG implementation.
  • LlamaIndex
    We’ll use this framework to load our data and feed it into Llama-2.
  • Chroma
    We’ll use this embedding database for fast vector similarity search and retrieval. This is actually where we can store our index.

With the key tools in place, we can walk through examples for each phase: ingesting data, encoding text, indexing vectors, and so on.

Install The Libraries

We need to install the RAG libraries we identified, which we can do by running the following commands in a new project folder:

# Install essential libraries for our project
!pip install llama-index transformers accelerate bitsandbytes --quiet
!pip install chromadb sentence-transformers pydantic==1.10.11 --quiet

Next, we need to import specific modules from those libraries. There are quite a few that we want, like ChromaVectorStore and HuggingFaceEmbedding for vector indexing and embeddings capabilities, storageContext and chromadb to provide database and storage functionalities, and even more for computations, displaying outputs, loading language models, and so on. This can go in a file named app.py at the root level of your project.

## app.py

## Import necessary libraries
from llama_index import VectorStoreIndex, download_loader, ServiceContext
from llama_index.vector_stores import ChromaVectorStore
from llama_index.storage.storage_context import StorageContext
from llama_index.embeddings import HuggingFaceEmbedding
from llama_index.response.notebook_utils import display_response
import torch
from transformers import BitsAndBytesConfig
from llama_index.prompts import PromptTemplate
from llama_index.llms import HuggingFaceLLM
from IPython.display import Markdown, display
import chromadb
from pathlib import Path
import logging
import sys

Provide Additional Context To The Model

The data we will leverage for our language model is a research paper titled “Enhancing LLM Intelligence with ARM-RAG: Auxiliary Rationale Memory for Retrieval Augmented Generation” (PDF) that covers an advanced retrieval augmentation generation approach to improve problem-solving performance.

We will use the download_loader() module we imported earlier from llama_index to download the PDF file:

PDFReader = download_loader("PDFReader")
loader = PDFReader()
documents = loader.load_data(file=Path('/content/ARM-RAG.pdf'))

Even though this demonstration uses a PDF file as a data source for the model, that is just one way to supply the model with data. For example, there is Arxiv Papers Loader as well as other loaders available in the LlamaIndex Hub. But for this tutorial, we’ll stick with loading from a PDF. That said, I encourage you to try other ingestion methods for practice!

Now, we need to download Llama-2, our open-source text generation model from Meta. If you haven’t already, please set up an account with Meta and have your access token available with read permissions, as this will allow us to download Llama-2 from Hugging Face.

# huggingface api token for downloading llama2
hf_token = "YOUR Access Token"

To fit Llama-2 into constrained memory, like in Google Colab, we’ll configure 4-bit quantization to load the model at a lower precision.

quantization_config = BitsAndBytesConfig(
  load_in_4bit=True,
  bnb_4bit_compute_dtype=torch.float16,
  bnb_4bit_quant_type="nf4",
  bnb_4bit_use_double_quant=True,
)

Google Colab is where I typically do most of my language model experiments. We’re shrinking the language model down with that last snippet so it’s not too large for Colab to support.

Next, we need to initialize HuggingFaceLLM to set up our model for generating text on the fly:

llm = HuggingFaceLLM(
    model_name="meta-llama/Llama-2-7b-chat-hf", 
    tokenizer_name="meta-llama/Llama-2-7b-chat-hf",
    query_wrapper_prompt=PromptTemplate("<s> [INST] {query_str} [/INST] "),
    context_window=3900,
    model_kwargs={"token": hf_token, "quantization_config": quantization_config},
    tokenizer_kwargs={"token": hf_token},
    device_map="auto",
)

Note that what’s happening here is that we’ve defined our model (Llama-2), but a specific subset of it is designed for a chat UI (Llama-2-Chat). Even though this is what we are using for this article, you can also pick from other text generation models in the Hugging Face hub, such as Neural-chat from Intel.

Compare Outputs

OK, so we have the model loaded and configured. Let’s try it out by asking it questions that it could reasonably answer using the content we provided via the ARM-RAG PDF file to see how the model responds without any context. The following code is for displaying the text output without the PDF providing data to the model for comparison sake:

from IPython.display import HTML, display

# Assuming resp contains the response
resp = llm.complete("What is ARM-RAG?")

# Using HTML with inline CSS for styling (gray color, smaller font size)
html_text = f'<p style="color: #1f77b4; font-size: 14px;"><b>{resp}</b></p>'

Let’s see the response:

display(HTML(html_text))

ARM-RAG (ARM Register-Allocating Generator) is a register allocation algorithm used in compiler design. nobody knows who invented it, but it was first described in a 1995 paper by Mark H. T. Kim and Andrew Y. Chen. The ARM-RAG algorithm is a register-allocating generator, meaning it is a heuristic algorithm that takes a program as input and generates a register allocation plan for it. The goal of the algorithm is to assign registers to variables in a way that minimizes the number of spills (i.e., loads or stores to/from memory) while satisfying all the memory access constraints of the program. The ARM-RAG algorithm works by first partitioning the program into a set of basic blocks, and then iteratively allocating registers to variables within each basic block. The algorithm uses a cost function to evaluate the quality of each allocation, and it selects the allocation with the lowest cost. The cost function takes into account factors such as the distance between the variable and the current register, the distance between the variable and the next allocation, and the number of spills that would be required to allocate the variable. ARM-RAG

Obviously, without any context from the PDF file, the model generates a hallucinated response that describes some unrelated algorithm. To fix this, we need to provide relevant passages from the paper to ground Llama-2’s responses. We’ll encode the document into embeddings and index them for retrieval; then, when we query, we can feed LLama-2 relevant retrieved passages to steer it toward accurate, on-topic responses based on the contents of the PDF file.

First, we need to create a client to interact with our ChromaDB database and a new collection that will hold our vector index.

# create client and a new collection
chroma_client = chromadb.EphemeralClient()
chroma_collection = chroma_client.create_collection("firstcollection")

Then we need to set up the HuggingFaceEmbedding class with the specified model name for embedding the text into vectors:

# Load the embedding model
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-base-en-v1.5")

This initializes HuggingFaceEmbedding, passing the name of the pre-trained model we want to use, BAAI/bge-base-en-v1.5. There are other options, of course.

Now, we can set up the vector store and use it to index the embedded document vectors:

# set up ChromaVectorStore and load in data
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
index = VectorStoreIndex.from_documents(
  documents, storage_context=storage_context, service_context=service_context
)

This creates a ChromaVectorStore connected to our collection, defines the storage and service contexts, and generates a VectorStoreIndex from the loaded documents using the embedding model. The index is what allows us to quickly find relevant passages for a given query to augment the quality of the model’s response.

We should also establish a way for the model to summarize the data rather than spitting everything out at once. A SummaryIndex offers efficient summarization and retrieval of information:

summary_index = SummaryIndex.from_documents(documents, service_context=service_context)

Earlier, the model hallucinated when we queried it without the added context from the PDF file. Now, let’s ask the same question, this time querying our indexed data:

#Define your query
query="what is ARM-RAG?"

from llama_index.embeddings.base import similarity
query_engine =index.as_query_engine(response_mode="compact")
response = query_engine.query(query)
from IPython.display import HTML, display

# Using HTML with inline CSS for styling (blue color)
html_text = f'<p style="color: #1f77b4; font-size: 14px;"><b>{response}</b></p>'
display(HTML(html_text))

Here’s the output:

Final Response: Based on the context information provided, ARM-RAG is a system that utilizes Neural Information Retrieval to archive reasoning chains derived from solving grade-school math problems. It is an Auxiliary Rationale Memory for Retrieval Augmented Generation, which aims to enhance the problem-solving capabilities of Large Language Models (LLMs). The system surpasses the performance of a baseline system that relies solely on LLMs, demonstrating the potential of ARM-RAG to improve problem-solving capabilities.

Correct! This response is way better than the one we saw earlier — no hallucinations here.

Since we’re using the chat subset of the Llama-2 model, we could have a back-and-forth conversation with the model about the content of the PDF file with follow-up questions. That’s because the indexed data supports NLP.

chat_engine = index.as_chat_engine(chat_mode="condense_question", verbose=True)
response = chat_engine.chat("give me real world examples of apps/system i can build leveraging ARM-RAG?")
print(response)

This is the resulting output:

Querying with: What are some real-world examples of apps or systems that can be built leveraging the ARM-RAG framework, which was discussed in our previous conversation?
Based on the context information provided, the ARM-RAG framework can be applied to various real-world examples, including but not limited to:

1. Education: ARM-RAG can be used to develop educational apps that can help students learn and understand complex concepts by generating explanations and examples that can aid in their understanding.

2. Tutoring: ARM-RAG can be applied to tutoring systems that can provide personalized explanations and examples to students, helping them grasp difficult concepts more quickly and effectively.

3. Customer Service: ARM-RAG can be utilized in chatbots or virtual assistants to provide customers with detailed explanations and examples of products or services, enabling them to make informed decisions.

4. Research: ARM-RAG can be used in research environments to generate explanations and examples of complex scientific concepts, enabling researchers to communicate their findings more effectively to a broader audience.

5. Content Creation: ARM-RAG can be applied to content creation systems that can generate explanations and examples of complex topics, such as news articles, blog posts, or social media content, making them more engaging and easier

Try asking more questions! Now that the model has additional context to augment its existing dataset, we can have a more productive — and natural — interaction.

Additional RAG Tooling Options

The whole point of this article is to explain the concept of RAG and demonstrate how it can be used to enhance a language model with accurate and updated data.

Chroma and LlamaIndex were the main components of the demonstrated RAG approach, but there are other tools for integrating RAG with language models. I’ve prepared a table that outlines some popular options you might consider trying with your own experiments and projects.

RAG Type of System Capabilities Integrations Documentation / Repo
Weaviate Vector Database Vector & Generative search LlamaIndex, LangChain, Hugging Face, Cohere, OpenAI, etc.
Pinecone Vector Database Vector search, NER-Powered search, Long-term memory OpenAI, LangChain, Cohere, Databricks
txtai Embeddings Database Semantic graph & search, Conversational search Hugging face models
Qdrant Vector Database Similarity image search, Semantic search, Recommendations LangChain, LlamaIndex, DocArray, Haystack, txtai, FiftyOne, Cohere, Jina Embeddings, OpenAI
Haystack Framework QA, Table QA, Document search, Evaluation Elasticsearch, Pinecone, Qdrant, Weaviate, vLLM, Cohere
Ragchain Framework Reranking, OCR loaders Hugging Face, OpenAI, Chroma, Pinecone
metal Vector Database Clustering, Semantic search, QA LangChain, LlamaIndex
Conclusion

In this article, we examined examples of language models producing “hallucinated” responses to queries as well as possible causes of those hallucinations. At the end of the day, a language model’s responses are only as good as the data it provided, and as we’ve seen, even the most widely used models consist of outdated information. And rather than admit defeat, the language model spits out confident guesses that could be misconstrued as accurate information.

Retrieval Augmented Generation is one possible cure for hallucinations.

By embedding text vectors pulled from additional sources of data, a language model’s existing dataset is augmented with not only new information but the ability to query it more effectively with a semantic search that helps the model more broadly interpret the meaning of a query.

We did this by registering a PDF file with the model that contains content the model could use when it receives a query on a particular subject, in this case, “Enhancing LLM Intelligence with ARM-RAG: Auxiliary Rationale Memory for Retrieval Augmented Generation.”

This, of course, was a rather simple and contrived example. I wanted to focus on the concept of RAG more than its capabilities and stuck with a single source of new context around a single, specific subject so that we could easily compare the model’s responses before and after implementing RAG.

That said, there are some good next steps you could take to level up your understanding:

  • Consider using high-quality data and embedding models for better RAG performance.
  • Evaluate the model you use by checking Vectara’s hallucination leaderboard and consider using their model instead. The quality of the model is essential, and referencing the leaderboard can help you avoid models known to hallucinate more often than others.
  • Try refining your retriever and generator to improve results.

My previous articles on LLM concepts and summarizing chat conversations are also available to help provide even more context about the components we worked with in this article and how they are used to produce high-quality responses.

References

The View Transitions API And Delightful UI Animations (Part 2)

Last time we met, I introduced you to the View Transitions API. We started with a simple default crossfade transition and applied it to different use cases involving elements on a page transitioning between two states. One of those examples took the basic idea of adding products to a shopping cart on an e-commerce site and creating a visual transition that indicates an item added to the cart.

The View Transitions API is still considered an experimental feature that’s currently supported only in Chrome at the time I’m writing this, but I’m providing that demo below as well as a video if your browser is unable to support the API.

Those diagrams illustrate (1) the origin page, (2) the destination page, (3) the type of transition, and (4) the transition elements. The following is a closer look at the transition elements, i.e., the elements that receive the transition and are tracked by the API.

So, what we’re working with are two transition elements: a header and a card component. We will configure those together one at a time.

Header Transition Elements

The default crossfade transition between the pages has already been set, so let’s start by registering the header as a transition element by assigning it a view-transition-name. First, let’s take a peek at the HTML:

<div class="header__wrapper">
  <!-- Link back arrow -->
  <a class="header__link header__link--dynamic" href="/">
    <svg ...><!-- ... --></svg>
  </a>
  <!-- Page title -->
  <h1 class="header__title">
    <a href="/" class="header__link-logo">
      <span class="header__logo--deco">Vinyl</span>Emporium </a>
  </h1>
  <!-- ... -->
</div>

When the user navigates between the homepage and an item details page, the arrow in the header appears and disappears — depending on which direction we’re moving — while the title moves slightly to the right. We can use display: none to handle the visibility.

/* Hide back arrow on the homepage */
.home .header__link--dynamic {
    display: none;
}

We’re actually registering two transition elements within the header: the arrow (.header__link--dynamic) and the title (.header__title). We use the view-transition-name property on both of them to define the names we want to call those elements in the transition:

@supports (view-transition-name: none) {
  .header__link--dynamic {
    view-transition-name: header-link;
  }
  .header__title {
    view-transition-name: header-title;
  }
}

Note how we’re wrapping all of this in a CSS @supports query so it is scoped to browsers that actually support the View Transitions API. So far, so good!

To do that, let’s start by defining our transition elements and assign transition names to the elements we’re transitioning between the product image (.product__image--deco) and the product disc behind the image (.product__media::before).

@supports (view-transition-name: none) {
  .product__image--deco {
    view-transition-name: product-lp;
  }
 .product__media::before {
    view-transition-name: flap;
  }
  ::view-transition-group(product-lp) {
    animation-duration: 0.25s;
    animation-timing-function: ease-in;
  }
  ::view-transition-old(product-lp),
  ::view-transition-new(product-lp) {
    /* Removed the crossfade animation */
    mix-blend-mode: normal;
    animation: none;
  }
}

Notice how we had to remove the crossfade animation from the product image’s old (::view-transition-old(product-lp)) and new (::view-transition-new(product-lp)) states. So, for now, at least, the album disc changes instantly the moment it’s positioned back behind the album image.

But doing this messed up the transition between our global header navigation and product details pages. Navigating from the item details page back to the homepage results in the album disc remaining visible until the view transition finishes rather than running when we need it to.

Let’s configure the router to match that structure. Each route gets a loader function to handle page data.

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Category, { loader as categoryLoader } from "./pages/Category";
import Details, { loader as detailsLoader } from "./pages/Details";
import Layout from "./components/Layout";

/* Other imports */

const router = createBrowserRouter([
  {
    /* Shared layout for all routes */
    element: <Layout />,
    children: [
      {
        /* Homepage is going to load a default (first) category */
        path: "/",
        element: <Category />,
        loader: categoryLoader,
      },
      {
      /* Other categories */
        path: "/:category",
        element: <Category />,
        loader: categoryLoader,
      },
      {
        /* Item details page */
        path: "/:category/product/:slug",
        element: <Details />,
        loader: detailsLoader,
      },
    ],
  },
]);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

With this, we have established the routing structure for the app:

  • Homepage (/);
  • Category page (/:category);
  • Product details page (/:category/product/:slug).

And depending on which route we are on, the app renders a Layout component. That’s all we need as far as setting up the routes that we’ll use to transition between views. Now, we can start working on our first transition: between two category pages.

Transition Between Category Pages

We’ll start by implementing the transition between category pages. The transition performs a crossfade animation between views. The only part of the UI that does not participate in the transition is the bottom border of the category filter menu, which provides a visual indication for the active category filter and moves between the formerly active category filter and the currently active category filter that we will eventually register as a transition element.

Since we’re using react-router, we get its web-based routing solution, react-router-dom, baked right in, giving us access to the DOM bindings — or router components we need to keep the UI in sync with the current route as well as a component for navigational links. That’s also where we gain access to the View Transitions API implementation.

Specifically, we will use the component for navigation links (Link) with the unstable_viewTransition prop that tells the react-router to run the View Transitions API when switching page contents.

import { Link, useLocation } from "react-router-dom";
/* Other imports */

const NavLink = ({ slug, title, id }) => {
  const { pathname } = useLocation();
  /* Check if the current nav link is active */
  const isMatch = slug === "/" ? pathname === "/" : pathname.includes(slug);
  return (
    <li key={id}>
      <Link
        className={isMatch ? "nav__link nav__link--current" : "nav__link"}
        to={slug}
        unstable_viewTransition
      >
        {title}
      </Link>
    </li>
  );
};

const Nav = () => {
  return 
    <nav className={"nav"}>
      <ul className="nav__list">
        {categories.items.map((item) => (
          <NavLink {...item} />
        ))}
      </ul>
    </nav>
  );
};

That is literally all we need to register and run the default crossfading view transition! That’s again because react-router-dom is giving us access to the View Transitions API and does the heavy lifting to abstract the process of setting transitions on elements and views.

Creating The Transition Elements

We only have one UI element that gets its own transition and a name for it, and that’s the visual indicator for the actively selected product category filter in the app’s navigation. While the app transitions between category views, it runs another transition on the active indicator that moves its position from the origin category to the destination category.

I know that I had earlier described that visual indicator as a bottom border, but we’re actually going to establish it as a standard HTML horizontal rule (<hr>) element and conditionally render it depending on the current route. So, basically, the <hr> element is fully removed from the DOM when a view transition is triggered, and we re-render it in the DOM under whatever NavLink component represents the current route.

We want this transition only to run if the navigation is visible, so we’ll use the react-intersection-observer helper to check if the element is visible and, if it is, assign it a viewTransitionName in an inline style.

import { useInView } from "react-intersection-observer";
/* Other imports */

const NavLink = ({ slug, title, id }) => {
  const { pathname } = useLocation();
  const isMatch = slug === "/" ? pathname === "/" : pathname.includes(slug);
  return (
    <li key={id}>
      <Link
        ref={ref}
        className={isMatch ? "nav__link nav__link--current" : "nav__link"}
        to={slug}
        unstable_viewTransition
      >
        {title}
      </Link>
      {isMatch && (
        <hr
          style={{
            viewTransitionName: inView ? "marker" : "",
          }}
          className="nav__marker"
        />
      )}
    </li>
  );
};

First, let’s take a look at our Card component used in the category views. Once again, react-router-dom makes our job relatively easy, thanks to the unstable_useViewTransitionState hook. The hook accepts a URL string and returns true if there is an active page transition to the target URL, as well as if the transition is using the View Transitions API.

That’s how we’ll make sure that our active image remains a transition element when navigating between a category view and a product view.

import { Link, unstable_useViewTransitionState } from "react-router-dom";
/* Other imports */

const Card = ({ author, category, slug, id, title }) => {
  /* We'll use the same URL value for the Link and the hook */
  const url = /${category}/product/${slug};

  /* Check if the transition is running for the item details pageURL */
  const isTransitioning = unstable_useViewTransitionState(url);

  return (
    <li className="card">
      <Link unstable_viewTransition to={url} className="card__link">
        <figure className="card__figure">
          <img
            className="card__image"
            style=}}
              /* Apply the viewTransitionName if the card has been clicked on */
              viewTransitionName: isTransitioning ? "item-image" : "",
            }}
            src={/assets/$&#123;category&#125;/${id}-min.jpg}
            alt=""
          />
         {/* ... */}
        </figure>
        <div className="card__deco" />
      </Link>
    </li>
  );
};

export default Card;

We know which image in the product view is the transition element, so we can apply the viewTransitionName directly to it rather than having to guess:

import {
  Link,
  useLoaderData,
  unstable_useViewTransitionState,
} from "react-router-dom";
/* Other imports */

const Details = () => {
  const data = useLoaderData();
  const { id, category, title, author } = data;
  return (
    <>
      <section className="item">
        {/* ... */}
        <article className="item__layout">
          <div>
              <img
                style={{viewTransitionName: "item-image"}}
                className="item__image"
                src={/assets/${category}/${id}-min.jpg}
                alt=""
              />
          </div>
          {/* ... */}
        </article>
      </section>
    </>
  );
};

export default Details;

We’re on a good track but have two issues that we need to tackle before moving on to the final transitions.

One is that the Card component’s image (.card__image) contains some CSS that applies a fixed one-to-one aspect ratio and centering for maintaining consistent dimensions no matter what image file is used. Once the user clicks on the Card — the .card-image in a category view — it becomes an .item-image in the product view and should transition into its original state, devoid of those extra styles.


/* Card component image */
.card__image {
  object-fit: cover;
  object-position: 50% 50%;
  aspect-ratio: 1;
  /* ... */
}

/* Product view image */
.item__image {
 /* No aspect-ratio applied */
 /* ... */
}

Jake has recommended using React’s flushSync function to make this work. The function forces synchronous and immediate DOM updates inside a given callback. It’s meant to be used sparingly, but it’s okay to use it for running the View Transition API as the target component re-renders.

// Assigns view-transition-name to the image before transition runs
const [isImageTransition, setIsImageTransition] = React.useState(false);

// Applies fixed-positioning and full-width image styles as transition runs
const [isFullImage, setIsFullImage] = React.useState(false);

/* ... */

// State update function, which triggers the DOM update we want to animate
const toggleImageState = () => setIsFullImage((state) => !state);

// Click handler function - toggles both states.
const handleZoom = async () => {
  // Run API only if available.
  if (document.startViewTransition) {
    // Set image as a transition element.
    setIsImageTransition(true);
    const transition = document.startViewTransition(() => {
      // Apply DOM updates and force immediate re-render while.
      // View Transitions API is running.
      flushSync(toggleImageState);
    });
    await transition.finished;
    // Cleanup
    setIsImageTransition(false);
  } else {
    // Fallback 
    toggleImageState();
  }
};

/* ... */

With this in place, all we really have to do now is toggle class names and view transition names depending on the state we defined in the previous code.

import React from "react";
import { flushSync } from "react-dom";

/* Other imports */

const Details = () => {
  /* React state, click handlers, util functions... */

  return (
    <>
      <section className="item">
        {/* ... */}
        <article className="item__layout">
          <div>
            <button onClick={handleZoom} className="item__toggle">
              <img
                style={{
                  viewTransitionName:
                    isTransitioning || isImageTransition ? "item-image" : "",
                }}
                className={
                  isFullImage
                    ? "item__image item__image--active"
                    : "item__image"
                }
                src={/assets/${category}/${id}-min.jpg}
                alt=""
              />
            </button>
          </div>
          {/* ... */}
        </article>
      </section>
      <aside
        className={
          isFullImage ? "item__overlay item__overlay--active" : "item__overlay"
        }
      />
    </>
  );
};

We are applying viewTransitionName directly on the image’s style attribute. We could have used boolean variables to toggle a CSS class and set a view-transition-name in CSS instead. The only reason I went with inline styles is to show both approaches in these examples. You can use whichever approach fits your project!

Let’s round this out by refining styles for the overlay that sits behind the image when it is expanded:

.item__overlay--active {
  z-index: 2;
  display: block;
  background: rgba(0, 0, 0, 0.5);
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}

.item__image--active {
  cursor: zoom-out;
  position: absolute;
  z-index: 9;
  top: 50%;
  left: 50%;
  transform: translate3d(-50%, -50%, 0);
  max-width: calc(100vw - 4rem);
  max-height: calc(100vh - 4rem);
}

Demo

The following demonstrates only the code that is directly relevant to the View Transitions API so that it is easier to inspect and use. If you want access to the full code, feel free to get it in this GitHub repo.

Conclusion

We did a lot of work with the View Transitions API in the second half of this brief two-part article series. Together, we implemented full-view transitions in two different contexts, one in a more traditional multi-page application (i.e., website) and another in a single-page application using React.

We started with transitions in a MPA because the process requires fewer dependencies than working with a framework in a SPA. We were able to set the default crossfade transition between two pages — a category page and a product page — and, in the process, we learned how to set view transition names on elements after the transition runs to prevent naming conflicts.

From there, we applied the same concept in a SPA, that is, an application that contains one page but many views. We took a React app for a “Museum of Digital Wonders” and applied transitions between full views, such as navigating between a category view and a product view. We got to see how react-router — and, by extension, react-router-dom — is used to define transitions bound to specific routes. We used it not only to set a crossfade transition between category views and between category and product views but also to set a view transition name on UI elements that also transition in the process.

The View Transitions API is powerful, and I hope you see that after reading this series and following along with the examples we covered together. What used to take a hefty amount of JavaScript is now a somewhat trivial task, and the result is a smoother user experience that irons out the process of moving from one page or view to another.

That said, the View Transitions API’s power and simplicity need the same level of care and consideration for accessibility as any other transition or animation on the web. That includes things like being mindful of user motion preferences and resisting the temptation to put transitions on everything. There’s a fine balance that comes with making accessible interfaces, and motion is certainly included.

References

The View Transitions API And Delightful UI Animations (Part 1)

Animations are an essential part of a website. They can draw attention, guide users on their journey, provide satisfying and meaningful feedback to interaction, add character and flair to make the website stand out, and so much more!

On top of that, CSS has provided us with transitions and keyframe-based animations since at least 2009. Not only that, the Web Animations API and JavaScript-based animation libraries, such as the popular GSAP, are widely used for building very complex and elaborate animations.

With all these avenues for making things move on the web, you might wonder where the View Transitions API fits in in all this. Consider the following example of a simple task list with three columns.

We’re merely crossfading between the two screen states, and that includes all elements within it (i.e., other images, cards, grid, and so on). The API is unaware that the image that is being moved from the container (old state) to the overlay (new state) is the same element.

We need to instruct the browser to pay special attention to the image element when switching between states. That way, we can create a special transition animation that is applied only to that element. The CSS view-transition-name property applies the name of the view transition we want to apply to the transitioning elements and instructs the browser to keep track of the transitioning element’s size and position while applying the transition.

We get to name the transition anything we want. Let’s go with active-image, which is going to be declared on a .gallery__image--active class that is a modifier of the class applied to images (.gallery-image) when the transition is in an active state:

.gallery__image--active {
  view-transition-name: active-image;
}

Note that view-transition-name has to be a unique identifier and applied to only a single rendered element during the animation. This is why we are applying the property to the active image element (.gallery__image--active). We can remove the class when the image overlay is closed, return the image to its original position, and be ready to apply the view transition to another image without worrying whether the view transition has already been applied to another element on the page.

So, we have an active class, .gallery__image--active, for images that receive the view transition. We need a method for applying that class to an image when the user clicks on that respective image. We can also wait for the animation to finish by storing the transition in a variable and calling await on the finished attribute to toggle off the class and clean up our work.

// Start the transition and save its instance in a variable
const transition = document.startViewTransition(() =&gtl /* ... */);

// Wait for the transition to finish.
await transition.finished;

/* Cleanup after transition has completed */

Let’s apply this to our example:

function toggleImageView(index) {
  const image = document.getElementById(js-gallery-image-${index});

  // Apply a CSS class that contains the view-transition-name before the animation starts.
  image.classList.add("gallery__image--active");

  const imageParentElement = image.parentElement;

  if (!document.startViewTransition) {
    // Fallback if View Transitions API is not supported.
    moveImageToModal(image);
  } else {
    // Start transition with the View Transitions API.
    document.startViewTransition(() => moveImageToModal(image));
  }

  // This click handler function is now async.
  overlayWrapper.onclick = async function () {
    // Fallback if View Transitions API is not supported.
    if (!document.startViewTransition) {
      moveImageToGrid(imageParentElement);
      return;
    }

    // Start transition with the View Transitions API.
    const transition = document.startViewTransition(() => moveImageToGrid(imageParentElement));

    // Wait for the animation to complete.
    await transition.finished;

    // Remove the class that contains the page-transition-tag after the animation ends.
    image.classList.remove("gallery__image--active");
  };
}

Alternatively, we could have used JavaScript to toggle the CSS view-transition-name property on the element in the inline HMTL. However, I would recommend keeping everything in CSS as you might want to use media queries and feature queries to create fallbacks and manage it all in one place.

// Applies view-transition-name to the image
image.style.viewTransitionName = "active-image";

// Removes view-transition-name from the image
image.style.viewTransitionName = "none";

And that’s pretty much it! Let’s take a look at our example (in Chrome) with the transition element applied.

Customizing Animation Duration And Easing In CSS

What we just looked at is what I would call the default experience for the View Transitions API. We can do so much more than a transition that crossfades between two states. Specifically, just as you might expect from something that resembles a CSS animation, we can configure a view transition’s duration and timing function.

In fact, the View Transitions API makes use of CSS animation properties, and we can use them to fully customize the transition’s behavior. The difference is what we declare them on. Remember, a view transition is not part of the DOM, so what is available for us to select in CSS if it isn’t there?

When we run the startViewTransition function, the API pauses rendering, captures the new state of the page, and constructs a pseudo-element tree:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

Each one is helpful for customizing different parts of the transition:

  • ::view-transition: This is the root element, which you can consider the transition’s body element. The difference is that this pseudo-element is contained in an overlay that sits on top of everything else on the top.
    • ::view-transition-group: This mirrors the size and position between the old and new states.
      • ::view-transition-image-pair: This is the only child of ::view-transition-group, providing a container that isolates the blending work between the snapshots of the old and new transition states, which are direct children.
        • ::view-transition-old(...): A snapshot of the “old” transition state.
        • ::view-transition-new(...): A live representation of the new transition state.

Yes, there are quite a few moving parts! But the purpose of it is to give us tons of flexibility as far as selecting specific pieces of the transition.

So, remember when we applied view-transition-name: active-image to the .gallery__image--active class? Behind the scenes, the following pseudo-element tree is generated, and we can use the pseudo-elements to target either the active-image transition element or other elements on the page with the root value.

::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(active-image)
   └─ ::view-transition-image-pair(active-image)
      ├─ ::view-transition-old(active-image)
      └─ ::view-transition-new(active-image)

In our example, we want to modify both the cross-fade (root) and transition element (active-image ) animations. We can use the universal selector (*) with the pseudo-element to change animation properties for all available transition elements and target pseudo-elements for specific animations using the page-transition-tag value.

/* Apply these styles only if API is supported */
@supports (view-transition-name: none) {
  /* Cross-fade animation */
  ::view-transition-image-pair(root) {
    animation-duration: 400ms;
    animation-timing-function: ease-in-out;
  }

  /* Image size and position animation */
  ::view-transition-group(active-image) {
    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  }
}

Accessible Animations

Of course, any time we talk about movement on the web, we also ought to be mindful of users with motion sensitivities and ensure that we account for an experience that reduces motion.

That’s what the CSS prefers-reduced-motion query is designed for! With it, we can sniff out users who have enabled accessibility settings at the OS level that reduce motion and then reduce motion on our end of the work. The following example is a heavy-handed solution that nukes all animation in those instances, but it’s worth calling out that reduced motion does not always mean no motion. So, while this code will work, it may not be the best choice for your project, and your mileage may vary.

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Final Demo

Here is the completed demo with fallbacks and prefers-reduced-motion snippet implemented. Feel free to play around with easings and timings and further customize the animations.

This is a perfect example of how the View Transitions API tracks an element’s position and dimensions during animation and transitions between the old and new snapshots right out of the box!

See the Pen Add to cart animation v2 - completed [forked] by Adrian Bece.

Conclusion

It amazes me every time how the View Transitions API turns expensive-looking animations into somewhat trivial tasks with only a few lines of code. When done correctly, animations can breathe life into any project and offer a more delightful and memorable user experience.

That all being said, we still need to be careful how we use and implement animations. For starters, we’re still talking about a feature that is supported only in Chrome at the time of this writing. But with Safari’s positive stance on it and an open ticket to implement it in Firefox, there’s plenty of hope that we’ll get broader support — we just don’t know when.

Also, the View Transitions API may be “easy,” but it does not save us from ourselves. Think of things like slow or repetitive animations, needlessly complex animations, serving animations to those who prefer reduced motion, among other poor practices. Adhering to animation best practices has never been more important. The goal is to ensure that we’re using view transitions in ways that add delight and are inclusive rather than slapping them everywhere for the sake of showing off.

In another article to follow this one, we’ll use View Transitions API to create full-page transitions in our single-page and multi-page applications — you know, the sort of transitions we see when navigating between two views in a native mobile app. Now, we have those readily available for the web, too!

Until then, go build something awesome… and use it to experiment with the View Transitions API!

References

Building Components For Consumption, Not Complexity (Part 1)

Design systems are on the tip of every designer’s tongue, but the narrative in the industry mainly focuses on why you need a design system and its importance rather than the reality of endless maintenance and internal politics. The truth is that design teams spend years creating these systems only to find out that few people adhere to the guidelines or stay within the “guardrails.”

I’ve been fortunate with Figma to host and run workshops at various conferences across Europe that center on one very specific aspect of product design: components.

I love components! They are the start, the end, the hair-tearing-out middle, the “building blocks,” and the foundation — the everything within every great product design organization.

The reason they are so important to me is because, firstly, who doesn’t like efficiency? Second, and more importantly, they are proven to increase the time-to-delivery from design to engineering, and here comes a buzzword — they offer a route to return on investment (ROI) within design teams. This is becoming increasingly important in a tough hiring market. So what’s not to love?

Note: You may be interested in watching Matt Gottschalk’s talk from Figma’s Config 2023 conference, which was dedicated to the topic of ROI within small design teams. The talk explored how small design teams can use design systems and design operations to help designers have the right environment for them to deliver better, more impactful results.

Like in most things, a solution to this is education and communication. If you aren’t comfortable with modifications on styles, you may want to set up your components in such a way as to indicate this. For example, using emojis in layers is the quickest way to say, “Hey, please don’t edit this!” or “You can edit this!”.

Or, consider shipping out components that are named for intention. Would separating components entirely (components named for intention) work better? An Input/Error, rather than a customizable Input?

When considering the emoji name approach, here’s a set that I’ve relied on in the past:

  1. Components
    • 🚨 Deprecated
    • 🟠 Not ready
  2. Component instances
    • 🔐️ Not editable
    • ✍️ Overwritten name
  3. Layers
    • ✍️ Editable
    • 🚨 Important
  4. Component properties
    • ◈ Type
    • ✍️ Edit text
    • 🔁️ Swap instance
    • 🔘 Toggle
    • ←️ Left
    • →️ Right
    • 🖱 Interaction
    • 📈 Data
    • ↔️ Size

Flexible: Responsive Design

Ahh, our old friend, responsive design (RWD)!

“The control which designers know in the print medium, and often desire in the web medium, is simply a function of the limitation of the printed page. We should embrace the fact that the web doesn’t have the same constraints and design for this flexibility. But first, we must accept the ebb and flow of things.”

— John Allsopp, “A Dao of Web Design

As it stands, there is no native solution within Figma to create fully responsive components. What I mean by “fully responsive” is that layout directions and contents change according to their breakpoint.

Here’s an example:

Note: It is technically possible to achieve this example now with auto layout wrapping and min/max widths on your elements, but this does not mean that you can build fully responsive components. Instead, you will likely end up in a magic numbers soup with a lengthy list of variables for your min and max widths!

With this limitation in mind, we may want to reconsider goals around responsive design within our component libraries. This may take the form of adapting their structure by introducing… more components! Do we want to be able to ship one component that changes from mobile all the way up to the desktop, or would it be easier to use, find, and customize separate components for each distinct breakpoint?

“Despite the fun sounding name, magic numbers are a bad thing. It is an old school term for ‘unnamed numerical constant,’ as in, just some numbers put into the code that are probably vital to things working correctly but are very difficult for anyone not intimately familiar with the code to understand what they are for. Magic numbers in CSS refer to values which ‘work’ under some circumstances but are frail and prone to break when those circumstances change.”

— Chris Coyier, “Magic Numbers in CSS

Never be hesitant to create more components if there is a likelihood that adoption will increase.

Then, what does this look like within Figma? We have a few options, but first, we need to ask ourselves a few questions:

  1. Does the team design for various screen sizes/dimensions? E.g., mobile and desktop web.
  2. Does the development team build for a specific platform/screen size(s)? E.g., an iOS team.
  3. Do you build apps aligning with native style guides? E.g., Material Design.

The answers to these questions will help us determine how we should structure our components and, more importantly, what our library structures will look like.

If the answer to questions 1. (Design for various screen sizes/dimensions?) and 2. (build for a specific platform/screen sizes?) is “No,” and to 3. (Build apps aligning with native style guides?) is “Yes,” to me, this means that we should split out components into separate component library files. We don’t want to enter into a world where an iOS component is accidentally added to a web design and pushed to production! This becomes increasingly common if we share component naming conventions across different platforms.

If the answer to question 3. (“Do we build native apps, using their design guidelines?”) is “Yes,” this definitely requires a separate component library for the platform-specific styles or components. You may want to investigate an option where you have a global set of styles and components used on every platform and then a more localized set for when designing on your native platforms.

The example below, with an example mapping of library files for an iOS design project, is inspired by my Figma community file (“Simple design system structure”), which I created to help you set up your design system more easily across different platforms.

Get “Simple design system structure” [FigJam file / Luis Ouriach, CC-BY license]

If you are designing across multiple platforms in a device-agnostic manner, you can bring components a lot closer together! If you aren’t currently working with an agnostic codebase, it might be worth checking Mitosis (“Write components once, run everywhere”).

A common challenge among development teams is using the same language; while one sub-team may be using Vue, another perhaps is using React, causing redundant work and forcing you to create shared components twice. In “Create reusable components with Mitosis and Builder.io,” Alex Merced explores in detail Mitosis, a free tool developed under the MIT license. Mitosis can compile code to standard JavaScript code in addition to frameworks and libraries such as Angular, React, and Vue, allowing you to create reusable components with more ease and speed.

Using A Variant

This may look like a variant set, with a specific property for device/size/platform added. For example,

As you can imagine, as those components increase in complexity (with different states added, for example), this can become unwieldy. Combining them into the same variant is useful for a smaller system, but across larger teams, I would recommend splitting them up.

Using Sections

You could consider grouping your components into different sections for each platform/device breakpoint. The approach would be the following:

  1. Use pages within Figma libraries to organize components.
  2. Within the pages, group each breakpoint into a section. This is titled by the breakpoint.
  3. Name the component by its semantic, discoverable name.

There is a caveat here! I’m sure you’re wondering: “But couldn’t variables handle these breakpoints, removing the need for different components?” The answer, as always, is that it’s down to your specific implementation and adoption of the system.

If your designer and developer colleagues are comfortable working within the variable workflow, you may be able to consolidate them! If not, we may be better served with many components.

Additionally, the split-component approach allows you to handle components in a structurally different manner across these different sizes — something that is not currently possible with variants.

Auto Layout

Regardless of how we organize the components, responsiveness can be pushed very far with the use of auto layout at every level of our screens. Although it can be intimidating at first, the auto layout makes components work similarly to how they would be structured in HTML and CSS, moving design and engineering teams closer together.

Let’s take a simple example: a generic input field. In your main component, you’re likely to have a text label within it with the text, e.g., “Label.” Generics are useful! It means that we can swap this content to be specific to our needs at an instance level.

Now, let’s say you insert this component into your design and swap that “Label” content for a label that reads “Email address.” This is our override; so far, so good.

However, if you then decide to change your main component structurally, you put that label at risk of losing its overrides. As an example of a structural change, your original “Placeholder” now becomes a “Label” above the input field. Instinctively, this may mean creating a new text element for the label. But! Should you try this, you are losing the mapping between your original text element and the new one.

This could potentially break your existing signed-off designs. Even though this seems like it could work — layer names are a great way to preserve overrides — they are separate elements, and Figma won’t know how to transfer that override to the new element.

At this point, introducing component properties can save us from this trouble. I’d recommend adding a text component property to all of your text layers in order to try to prevent any loss of data across the design files in which we are using the component.

As I showed before, I find adding a writing emoji (✍️) to the property name is a nice way to keep our component properties panel as scannable as possible.

Content Specificity

A decision then needs to be made about how specific the default content is within the component.

And this is where we should ask ourselves a question: do we need to change this content frequently? If the answer is yes, abstracting specific textual values from components means that they can be interpreted more widely. It’s a little bit of reverse psychology, but a text layer reading “[placeholder]” would prompt a designer to change it to their local use case.

If the answer is no, we will bake the fixed value we want into the component. Going back to our input field example, we might set the default label value to be “Email address” instead of “placeholder.” Or, we could create an entirely new email address component! (This is a call we’d need to make based on anticipated/recorded usage of the component.)

Imagery / Media

When setting up a content system within Figma, a few different questions immediately pop up:

  1. How do you use specific media for specific components?
  2. How do you fix aspect ratios for media?

Within Figma, an image is essentially a fill within a shape rather than its own content type, and this impacts how we manage that media. There are two ways to do this:

  1. Using styles.
  2. Using component sets (variants).

Before we look at styles and components, though, let’s take a look at the format that all assets within Figma could take.

Practically, I would advise setting up your media assets as their own library within Figma, potentially even setting up a few libraries if you work across various products/brands with different approaches to media.

For example, the imagery your product team uses within design files and marketing materials is likely to be very different, so we would look to set these up as different Figma libraries. A designer using those assets would toggle “on” the library they need to create an asset for a specific intention, keeping the right media in the right place.

Because this media is the same as any other style or component within Figma, we can use slash naming conventions to group types of media within the names.

Domain examples:

  • Company website,
  • Product,
  • Marketing,
  • Sub brand/s.

Media types:

  • Logo,
  • Icon,
  • Illustration,
  • Image,
  • Video.

Example names, using the format:

  • Figma.com/Logo/Figma,
  • Figma.com/Icon/Variable,
  • Figma.com/Illustration/Components,
  • Figma.com/Image/Office,
  • Designsystems.com/Logo/Stripe,
  • Designsystems.com/Icon/Hamburger,
  • Designsystems.com/Illustration/Orbs,
  • Designsystems.com/Image/Modular grid.

These are split into:

  • Library: Figma.com or Designsystems.com,
  • Media type: Illustration or Logo,
  • Media name: e.g., Component libraries, Iconography.

Although I’m using images for the examples here, it works with video assets, too! This means we can move in the direction of effectively using Figma like a mini DAM (digital asset manager) and iterate fast on designs using brand-approved media assets, rather than relying on dummy content.

“A digital asset management solution is a software solution that provides a systematic approach to efficiently storing, organizing, managing, retrieving, and distributing an organization’s digital assets. DAM functionality helps many organizations create a centralized place where they can access their media assets.”

— IBM, “What is digital asset management?

Using Fill Styles

Fill styles aren’t just for color! We can use them for images, videos, and even illustrations if we want to. It’s worth bearing in mind that because of the flexible nature of fills, you may want to consider working within fixed sizes or aspect ratios to ensure cropping is kept to a minimum.

Figma’s redline “snapping” feature lets us know when the original asset’s aspect ratio is being respected as we resize. It’s a pretty cool trick!

You can get the above example from Figma’s community:

Fixed aspect ratio images with variants” [Figma file / Luis Ouriach, CC-BY license]

For this world, though, I would advise against trying to hack Figma into setting up fully responsive images. Instead, I’d recommend working with a predefined fixed set of sizes in a component set. This may sound like a limitation, but I strongly believe that the more time we spend inside Figma, the further we get from the production environment. “Can we test this in the actual product?” is a question we should be asking ourselves frequently.

Practically, this looks like creating a component set where we set the fixed sizes along one dimension and the aspect ratio along the other. Creating a matrix like this means we can use the Component Properties panel to toggle between sizes and aspect ratios, preserving the media inside the component.

This can be used in tandem with a separate set of components specifically for images. If we combine this with Figma’s “nested instances” feature within variant components, we can “surface” all the preferred images from our component set within every instance at the aspect ratios needed!

Arrangement

This is the hardest thing to predict when we think through the usability of customizable components. The simplest example here is our old enemy: the form. Instinctively, we may create a complete form in a component library and publish it to the team. This makes sense!

The issue is that when a designer working on a particular project requires a rearrangement of that structure, we are kind of in trouble.

This problem extends to almost all component groups that require manipulation. Tables, menus, lists, forms, navigation… we will hit this wall frequently. This is where I’d like to introduce the concept of fixed vs flexible content within components, which should help to address the future problems of a world where we put the DRY (don’t repeat yourself) principles at risk.

As design system maintainers, we naturally want to keep components as composable as possible. How can this one component be used in lots of different ways without requiring us to ship an infinite number of variations? This is the central theme of the DRY principle but can be challenging in design tools because of the lack of component order management within the main components.

As a result, we often end up in a world where we build, maintain, and ship endless variations of the same component in an attempt to keep up with snowflake implementations of our core component.

“‘When should we make something a component?’ is a question I’ve been fielding for years. My strong answer: right from the start. Creating things with a component-based mindset right out the gate saves countless hours — everything is a component!”

— Brad Frost, “Design system components, recipes, and snowflakes

For example, the form we spoke about before could be one for:

  • Logging in;
  • Registering;
  • Signing up to the newsletter;
  • Adding billing information.

These are all clearly forms that require different data points and functionality from an engineering perspective, but they will most likely share common design foundations, e.g., padding, margins, headings, labels, and input field designs. The question then becomes, “How can we reduce repetition whilst also encouraging combinatorial design?”

A concept that has been long-used in the developer world and loosely agreed upon in the design community is termed “component slots.” This approach allows the design system maintainers to ship component containers with agreed properties — sizing, padding, and styles — whilst allowing for a flexible arrangement of components inside it.

Taking our previous form examples, we can then abstract the content — login form, register form, newsletter form, and billing information form — and provide a much simpler shell component from the library. The designers using this shell (let’s call it a “form/wrapper”) will then build their forms locally and replace the slot component inside the shell with this new custom main component.

This is best explained visually:

Does this custom component need to live in multiple files? If yes, we move it to the next level up, either team-level libraries or global, if working on a smaller system. If not, we can comfortably keep that component local to the specific Figma file on a page (I like to call it “❖ Components”).

Important: For this premise to really work, we must employ auto layout at every level, with no exceptions!

Conclusion

That was a lot to process (over five thousand words, actually), and I think it‘s time for us to stop staring at the computer screen and take a little break before walking through the next set of principles.

Go grab a drink or take some rest, then meet me in Part 2, where you will learn even more about the adoptable, indexable, logical, and specific components.

Chris’ Corner: More Surprising Powers of CSS

Kilian Valkhof has a good one this week, You don’t need JavaScript for that, as part of the HTMLHell yearly Advent Calendar (of blog posts). He opens with the rule of least power:

Choose the least powerful language suitable for a given purpose.

That’s fun for us CSS nerds, because CSS is a pretty low language when it comes to building websites. (It doesn’t mean you should try to write an accordion component in Assembly.) It means, as he puts it:

On the web this means preferring HTML over CSS, and then CSS over JS.

If you’re at the JS level already, it means preferring JS over a JS framework or meta language. It’s not that those things aren’t valuable (Heck, one of the purposes of CodePen is making using language abstractions and library easier) it’s that, well, it’s just a better idea to go lower level. There is less to download, less to break, higher chances of the browser optimizing it, higher chances it will be accessible, higher chances it will last over time. That stuff matters.

Killian opens with a custom “toggle” component, and really, component is probably too strong a word. It’s just a styled HTML checkbox. It’s not even all that much CSS, and no JS is used at all, to make this:

While I was reading, where I thought Killian was going was using an <input type="checkbox"> actually turn on and off features on a website. That’s the kind of thing that feels like definite-JavaScript territory, and yet, because of the classic “Checkbox Hack” (e.g. using the :checked selector and selector combinators) we actually can control a lot of a website with just a checkbox.

The ability to do that, control a website with a checkbox, has increased dramatically now that we have :has() in CSS. For instance:

<body>
  ... literally anywhere deep in the bowels of the DOM ...
  <input class="feature" type="checkbox">

We don’t have to worry about where in the DOM this checkbox is anymore, we can style anything there like this now:

body:has([.feature:checked]) .literally-anything {
  /* do styles */
}

We can start the styling choices way up at the body level with :has(), looking for that checkbox (essentially a toggle), and style anything on the page on if it is checked or not. That feels extraordinarily powerful to me.


Speaking of the mental crossover between CSS and JavaScript, Yaphi Berhanu’s Write Better CSS By Borrowing Ideas From JavaScript Functions was interesting. CSS doesn’t actually have functions (but just wait for it!) but that doesn’t mean we can’t consider how our thinking about the code can relate. I liked the bit about considering too many parameters vs not enough parameters. Yaphi connects that idea too too many selectors with too many repeat declarations, and yeah I can get behind that.

But where my mind goes is taking that lesson and trying to apply it to Custom Properties. I have seen (and written!) CSS that just takes Custom Property usage just too far — to the point where the code feels harder to reason about and maintain, not easier.

Like if you were going to make a variable for an animation…

.do-animation {
  animation: 3s ease-in 1s infinite reverse both running slidein;
}

You could decide that the right way to make this controllable and re-suable is something like:

:root {
  --animation-duration: 3s;
  --animation-easing: ease-in;
  --animation-delay: 1s;
  --animation-direction: reverse;
  --animation-fill-mode: both;
  --animation-play-state: running;
  --animation-name: slidein;
}

.do-animation {
  animation: var(--animation-duration) var(--animation-easing) var(--animation-delay) var(--animation-direction) var(--animation-fill-mode) var(--animation-play-state) var(--animation-name);
}

Maybe you love that? But I think it’s already going too far. And it could easily go further, since you could have another whole set of variables that set default fallbacks, for example.

It depends on what you want to do, but if your goal is simply “reusability” then doing like…

:root {
  --brandAnimation: 3s ease-in 1s infinite reverse both running slidein;
}

And then using it when you needed it is closer to baby bear’s porridge.


Little reminder: don’t sleep on View Transitions.

We won’t know what Interop 2024 will do until January, but based on the amount of upvotes from the proposals, I think it stands a good chance.

Jeremy Keith has a well measured take in Add view transitions to your website. It’s got links to good resources and examples on using it, like Tyler’s Gaw’s great post. It ends with an update and warning about how maybe it’s not such a great idea because it may “poison the feature with legacy content”. Meaning if too many people do this, the powers that be will be hesitant to change anything, even for the better. I agree that would suck if such a choice is made, as I do think there is room for improvement (e.g. transition groups). I dunno though, I think because this stuff is literally behind a feature flag, that’s enough of an at your own risk warning. If they want to change something that breaks any code I’ve shipped, I’m cool with that. I know the stakes.

I think we’ll start seeing more interesting examples and experiences soon. And! Gotchas! Like Nic Chan’s View transitions and stacking context: Why does my CSS View Transition ignore z-index? It is confusing. It’s like the elements kinda stop being elements when that are being transitioned. They look like elements, but they are really temporary rasterized ghosts. You can still select them with special pseudo element selectors though, so you should be able to get done what you need.


There is this inherit complexity with doing the whole Dark Mode / Light Mode thing on websites. There is both a system-wide preference that you can choose to honor, and that’s a good idea. And it’s likely that a site implementing this also offers a UI toggle to set the theme. So that’s two separate bits of preference you need to deal with, and the code likely handles them separately.

Christopher Kirk-Nielsen’s A Future of Themes with CSS Container Style Queries gives us a good taste of this. Let’s jump right to code:

/* No theme has been set, or override set to light mode */
html:where(:not([data-theme])),
:root[data-theme=light] {
  --color: black;
  --background: antiquewhite;
  /* … and all your other "variables" */
}

/* Apply dark mode if user preferences call for it, and if the user hasn't selected a theme override */
@media (prefers-color-scheme: dark) {
  html:where(:not([data-theme])) {
    --color: ghostwhite;
    --background: midnightblue;
    /* … and all your other "variables" */
  }
}

/* Explicitly set the properties for the selected theme */
:root[data-theme=dark] {
  --color: ghostwhite;
  --background: midnightblue;
  /* … and all your other "variables" */
}

If you read though that I think you’ll see, you need @media to deal with system preferences, then the HTML attributes to deal with a direct-set preference, and one needs to overlap the other.

Enter Style Queries. What they really are is: “when an element has a certain custom property and value, also do this other stuff.” Christopher’s “future approach” using them is more code, but I’d agree that’s more clean and readable.

/* Optionally, we can define the theme variable */
@property --theme {
  syntax: '<custom-ident>'; /* We could list all the themes separated by a pipe character but this will do! */
  inherits: true;
  initial-value: light;
}

/* Assign the --theme property accordingly */
html:where(:not([data-theme])),
:root[data-theme=light] {
  --theme: light;
}

@media (prefers-color-scheme: dark) {
  html:where(:not([data-theme])) {
    --theme: dark;
  }
}

:root[data-theme=dark] {
  --theme: dark;
}

/* Then assign the custom properties based on the active theme */
@container style(--theme: light) {
  body {
    --color: black;
    --background: antiquewhite;
    /* … and all your other "variables" */
  }
}

@container style(--theme: dark) {
  body {
    --color: ghostwhite;
    --background: midnightblue;
    /* … and all your other "variables" */
  }
}

Check out Christopher’s article though, there is a lot more to go over.


Let’s end with a little good ol’ fashioned CSS enthusiasm.

For many, many years, creating high-quality websites largely meant that designers had to fight what felt like an uphill battle to somehow make their ideas work in browsers. At the same time, sentences like “I’m really sorry, but this solution you designed just can’t be done with CSS” […]

This has now changed to the opposite.

Want to emulate and confidently design a layout that leverages the potential of CSS Grid in any of the major design tools like Figma, Adobe XD, or Sketch? Not possible. 

Matthias Ott, The New CSS

And:

I still keep Sass around, as well as PostCSS, for things that CSS just wont ever be able to do (nor should it), but they’re fading into the background, rather than being at the forefront of my mind when writing styles. And for small, simple projects, I don’t use them at all. Just pure CSS. I haven’t found it lacking.

Jeff Sandberg, CSS is fun again

How to Change Margins in WordPress (Beginner’s Guide)

Do you want to change margins in WordPress?

Margins are one of the most important design elements that can significantly improve user experience and the aesthetic appeal of any design. They add spacing between elements so that your content is readable and everything is easily identifiable.

In this beginner’s guide, we’ll show you how to add and change margins in WordPress. We’ll show you various methods to change margins in different areas of your WordPress website.

Add or change margins in WordPress

What are Margins in WordPress and Web Design?

Margins are the space added around a web page or other elements inside a web page.

Think of a typical web page as an empty piece of paper. Margins are the white or blank space around the edges of the paper.

Margins around a page

The purpose of using margins is to ensure that elements inside a web page don’t look squished together.

Similarly, margins can be used around different elements inside the page layout.

For instance, you can change margins around images so they are not too close to the text or add a margin to leave space between your content area and the sidebar.

In this article, we’re going to cover a lot of ground. Click the links below to jump to any section you’d like.

What is the Difference Between Margin and Padding?

Margin and padding are both used to add white space in web design. However, they are used very differently.

Margins add empty space outside an element, and padding adds empty space inside it.

Margin vs padding diagram

Margins are used to add space outside an element. They allow you to ensure there is plenty of space between elements on a web page.

Examples:

1. Adding margins to increase space between an image and text in an article.

Adding margin between an image and surrounding text.

2. Adjusting margins to add space between sections, like the header and content area.

Increasing margins around container elements

Padding, however, is used to add cushion space between content and the edges of a box or element.

Examples:

1. Adjusting padding to increase cushion space in your call-to-action buttons.

Padding to increase empty space inside a call to action button

2. Increasing Padding in a Text Column

Padding inside a text column

Both padding and margins are widely used in web design.

Using empty spaces adds breathing room to any design, which makes it more user-friendly and elegant.

Why You May Need to Add or Change Margins in WordPress?

Margins are a crucial aspect of web design. They make your website look pleasant and easy to use for your users.

WordPress themes handle the design aspect of your WordPress website. Most of them already do an excellent job of setting CSS rules to ensure plenty of white space using margins across your theme’s layout.

Margins used in a typical website layout

However, occasionally, you may need to add margins to adjust things.

For instance, you may not like the margin around your navigation menus or want to add more margin around your call-to-action buttons.

Similarly, you may sometimes feel that items are too close to each other or too far apart.

In that case, you will need to change margins in WordPress by yourself.

How to Add Margins in WordPress?

There are plenty of ways to add margins in WordPress.

Depending on where you want to add margin and the options available in your WordPress theme, you’ll need to choose a method that works for you.

Let’s start with the default built-in options in WordPress itself, as they are the easiest for beginners.

Adding Margins in WordPress Using The Full Site Editor

If you use a block-based theme with full site editor support, you can use the built-in site editor to change margins anywhere on your WordPress website.

First, you need to visit the Appearance » Editor to launch the site editor.

Launch site editor

Once inside the site editor, click to choose a template from the left column or click anywhere on the preview window.

Next, click on the area or element where you want to change margins. In the right column, you will see the option to adjust margins under the Style tab.

Margins in site editor

As you adjust the margins, the editor will highlight the margin area.

You can also choose to add margins to the top, bottom, right, or left side.

Note: The margin option may not be available for all blocks in the site editor. If you cannot see the margin option for an element, try an alternative method below.

Adding Margins in The Block Editor

If you are working on a blog post or a page, you will use the block editor.

Block editor in WordPress allows you to add and change margins for various blocks.

Simply click on the block where you want to add/adjust margins. Under the block settings, switch to the Style tab and scroll down to the Dimensions or Margins option.

Adding margins in block editor

Note: The margin option may not be available for all blocks in the content editor. If you cannot see the margin option for an element, try an alternative method below.

Adding Margins in WordPress Using SeedProd

SeedProd is the best WordPress page builder plugin on the market. It allows you to create custom pages for your website easily. You can even use it to create a custom WordPress theme from scratch.

SeedProd

SeedProd’s intuitive drag-and-drop page builder allows you to adjust margins for any element inside the editor easily.

First, you need to install and activate the SeedProd plugin. For more details, see our tutorial on how to install a WordPress plugin.

Next, you need to visit the SeedProd » Landing Pages and then click on the Add New Landing Page button.

Add new landing page

After that, you will be asked to choose a template for your page.

SeedProd comes with dozens of ready-made templates that you can use as a starting point, or you can start with a blank template.

Choose template

Click to choose your template, and then provide a name for your landing page.

This will launch SeedProd’s page builder.

You’ll see a live preview of your page on the right side. And elements you can add to your page in the left column.

SeedProd page builder

You can point and click on any item on the page to edit it.

Clicking on an element will select it, and you’ll see its options in the left column. From here, switch to the Advanced tab and click the Spacing option.

Adding margins in SeedProd

You can change margins and padding for the selected element from here.

Once you finish editing your page, don’t forget to click the Save and Publish button at the top right corner.

SeedProd save and publish

After that, you can visit your website to see the changes in action.

Change Margins Using Thrive Architect

Thrive Architect is one of the best WordPress page builder tools that lets you use a drag-and-drop interface to design WordPress pages.

It comes with over 200+ templates you can use as a starter point. Plus, you can also use it to edit your WordPress posts and pages, borrowing the layout and style of your existing WordPress theme.

Thrive Architect

To install Thrive Architect, you’ll first need to log into your account on the Thrive Themes website.

From there, you need to download and install the Thrive Product Manager plugin. For more details, see our tutorial on how to install a WordPress plugin.

Download and install Thrive Product Manager

Upon activation, you need to visit the Thrive Product Manager page.

Click the ‘Log into my account’ button to connect WordPress to your Thrive Themes account.

Log into your Thrive Themes account

Once connected, you’ll see the list of available Thrive Themes products under your account.

Go ahead and click on the ‘Install Product’ checkbox under Thrive Architect, and then click on the ‘Install selected products’ button at the bottom.

Install Thrive Architect

Thrive Product Manager will now install the Thrive Architect plugin for you.

After that, you can edit or create a new WordPress post or page and click on the Edit with Thrive Architect button.

Launch Thrive Architect

Thrive Architect will ask you to choose a template if it is a new page.

You can use your theme template to create a Normal Page or a Pre-built Landing Page template.

Choose templating option

If you choose a Pre-built Landing Page option, then the plugin will show you a bunch of templates to choose from.

Simply click to select the one that resembles what you want to create.

Choosing template in Thrive Architect

Whether it is a normal page (using your theme’s styles) or a landing page, Thrive Architect’s page builder would have the same features.

You’ll see a live preview of your page with a toolbar on the right and a settings panel to the left.

Thrive Architect interface

You can point on click on an element to select it. Or click on the add [+] button in the toolbar to add a new element.

Once you click to select and edit an element, its settings will appear in the left column.

From here, click the Layout & Position tab to change the margins and padding.

Adjust margins and padding

You’ll see a visual representation of margin and padding.

Take your mouse over to any side of the margin and drag the handle to increase or decrease the margin.

drag to increase or decrease margins

You can repeat the process to change margins on any of the four sides.

Once you are done, don’t forget to click on the Save Work button and then select Save and Exit to Post Editor option.

Publish or update WordPress post or page

You can now click on the Publish or Save button to save your WordPress post or page.

Changing Margins in WordPress Using CSS Code

This method requires you to add CSS code to your WordPress theme. You’ll also need a very basic understanding of HTML and CSS.

However, this method gives you more flexibility as you can manually choose the area where you want to add or adjust the margins.

Adding and Changing Margins Using Custom CSS in WordPress Theme

WordPress allows you to save custom CSS in your WordPress theme options. However, depending on your WordPress theme, there are multiple ways to do that.

Before you add or change margins using CSS, you may need to find out which element you need to target with your CSS code.

For instance, if you want to change margins around the body of the page, then you can use the following code:

body { 
margin:50px; 
}

The easiest way to find which element to target is by using the Inspect tool in your browser.

Open your website in a new browser tab and take the mouse over to the element that you want to change margins around. After that, right and select Inspect from the browser menu.

Using inspect tool

This will split your browser screen, and you will see the HTML code and CSS behind the page.

You can move your mouse over the code, and your browser will highlight the area affected by it.

Target element

In the code, you can see the HTML element or CSS class you need to target with your custom CSS.

You can even try your margins here to preview how it will look.

Trying margins in the Inspect tool

However, these changes are not saved in your theme and will disappear when you reload or close the browser tab.

Let’s go through different ways you can save this custom CSS in WordPress.

Using Custom CSS to Change Margins in Site Editor

If you are using a block theme with full site editor support. Then, here is how you can add custom CSS to your theme.

First, go to the Appearance » Editor page to launch the site editor and then switch to the Styles panel.

Additional CSS option in site editor

At the bottom of the Styles panel, click on the Additional CSS tab.

This will bring up a text editor where you can add your custom CSS code. Your CSS code will immediately apply, and you will be able to see the changes appear on screen.

CSS margin preview

Once you are satisfied with the changes, don’t forget to click on the Save button to store your changes.

Adding Margins with CSS in Theme Customizer

If you are using a classic theme (without site editor support), then you can save your Custom CSS in the theme customizer.

Go to the Appearance » Customize page to launch the theme customizer.

Launch WordPress theme customizer

The customizer will show different options depending on your WordPress theme.

You need to click on the Additional CSS tab to expand it.

Additional CSS in Theme Customizer

The tab will slide to show you a simple box where you can add your custom CSS.

As soon as you add a valid CSS rule, you will be able to see it applied on your website’s live preview pane.

Adding custom CSS in theme customizer

Once you are satisfied with the changes, click on the Publish button to store your changes.

Change Margins with Custom CSS Code Using WPCode

The easiest way to add Custom CSS code in WordPress is by using the WPCode plugin.

It is the best WordPress code snippets plugin that allows you to add any CSS/HTML/PHP/JavaScript code to your WordPress website without breaking it.

WPCode - Best WordPress Code Snippets Plugin

The advantage of using WPCode is that you won’t lose your CSS changes when switching your WordPress theme.

Note: There is also a free version of WPCode that you can use.

The first thing you need to do is install and activate the WPCode plugin. For more details, see our tutorial on how to install a WordPress plugin.

Upon activation, go to the Code Snippets » + Add New page.

Take the mouse over to the ‘Add Your Custom Code (New Snippet)’ option in the code snippets library, and click the ‘Use snippet’ button.

Use snippet

Next, at the top of the page, add a title for your custom CSS snippet. This can be anything that helps you identify the code.

After that, write down or paste your custom CSS into the ‘Code Preview’ box and set the ‘Code Type’ by choosing the ‘CSS Snippet’ option from the dropdown menu.

Adding custom CSS in WPCode

For instance, if you want to add or change the margins around the entire web page body, then you can use the following CSS code:

body { 
margin:50px;
}

Next, scroll down to the ‘Insertion’ section and select the ‘Auto-Insert’ method to execute the code across your entire WordPress site.

If you only want to execute the code on certain pages or posts, you can choose the ‘Shortcode’ method.

Choose an insertion method

Now, you need to go back to the top of the page and toggle the switch to ‘Active’.

Finally, click on the ‘Save Snippet’ button to store your changes.

Save and activate CSS

You can now visit your website to see your custom CSS in action.

We hope this article helped you learn how to add or change margins in WordPress. You may also want to see our complete WordPress theme development cheat sheet or take a look at our guide on customizing WordPress themes.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post How to Change Margins in WordPress (Beginner’s Guide) first appeared on WPBeginner.

CSS Responsive Multi-Line Ribbon Shapes (Part 2)

In my previous article, we tackled ribbons in CSS. The idea was to create a classic ribbon pattern using a single element and values that allow it to adapt to however much content it contains. We established a shape with repeating CSS gradients and tailor-cut the ribbon’s ends with clip-path() to complete the pattern, then used it and wound up with two ribbon variations: one that stacks vertically with straight strands of ribbons and another that tweaks the shape by introducing pseudo-elements.

If you are wondering why I am using 80%, then there is no particular logic to my approach. It’s because I found that covering more space with the color and leaving less space between lines produces a better result for my eye. I could have assigned variables to control the space without touching the core code, but there’s already more than enough complexity going on. So, that’s the reasoning behind the hard-coded value.

Styling The First Ribbon

We’ll start with the red ribbon from the demo. This is what we’re attempting to create:

It may look complex, but we will break it down into a combination of basic shapes.

Stacking Gradients

Let’s start with the gradient configuration, and below is the result we are aiming for. I am adding a bit of transparency to better see both gradients.

h1 {
  --c: #d81a14;

  padding-inline: .8lh;
  background:
    /* Gradient 1 */
    linear-gradient(var(--c) 80%, #0000 0) 
      0 .1lh / 100% 1lh,
    /* Gradient 2 */
    linear-gradient(90deg, color-mix(in srgb, var(--c), #000 35%) 1.6lh, #0000 0) 
      -.8lh 50% / 100% calc(100% - .3lh) repeat-x;
}

We already know all about the first gradient because we set it up in the last section. The second gradient, however, is placed behind the first one to simulate the folded part. It uses the same color variable as the first gradient, but it’s blended with black (#000) in the color-mix() function to darken it a smidge and create depth in the folds.

The thing with the second gradient is that we do not want it to reach the top and bottom of the element, which is why its height is equal to calc(100% - .3lh).

Note the use of padding in the inline direction, which is required to avoid text running into the ribbon’s folds.

Masking The Folded Parts

Now, it’s time to introduce a CSS mask. If you look closely at the design of the ribbon, you will notice that we are cutting triangular shapes from the sides.

We have applied a triangular shape on the left and right sides of the ribbon. Unlike the backgrounds, they repeat every two lines, giving us the complex repetition we want.

Imagine for a moment that those parts are transparent.

That will give us the final shape! We can do it with masks, but this time, let’s try using conic-gradient(), which is nice because it allows us to create triangular shapes. And since there’s one shape on each side, we’ll use two conical gradients — one for the left and one for the right — and repeat them in the vertical direction.


mask:
  conic-gradient(from 225deg at .9lh, #0000 25%, #000 0) 
    0 1lh / 50% 2lh repeat-y,
  conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0) 
    100% 0 / 50% 2lh repeat-y;

Each gradient covers half the width (50%) and takes up two lines of text (2lh). Also, note the 1lh offset of the first gradient, which is what allows us to alternate between the two as the ribbon adapts in size. It’s pretty much a zig-zag pattern and, guess what, I have an article that covers how to create zig-zag shapes with CSS masks. I highly recommend reading that for more context and practice applying masks with conical gradients.

Masking The Ribbon’s Ends

We are almost done! All we are missing are the ribbon’s cut edges. This is what we have so far:

We can fill that in by adding a third gradient to the mask:

mask:
  /* New gradient */
  linear-gradient(45deg, #000 50%, #0000 0) 100% .1lh / .8lh .8lh no-repeat,

  conic-gradient(from 225deg at .9lh, #0000 25%, #000 0) 
   0 1lh / 50% 2lh repeat-y,
  conic-gradient(from 45deg  at calc(100% - .9lh), #0000 25%, #000 0) 
   100% 0 / 50% 2lh repeat-y;

That linear gradient will give us the missing part at the top, but we still need to do the same at the bottom, and here, it’s a bit tricky because, unlike the top part, the bottom is not static. The cutout can be either on the left or the right based on the number of lines of text we’re working with:

We will fill in those missing parts with two more gradients. Below is a demo where I use different colors for the newly added gradients to see exactly what’s happening. Use the resize handle to see how the ribbon adjusts when the number of lines changes.

Styling The Second Ribbon

The second ribbon from the demo — the green one — is a variation of the first ribbon.

I am going a little bit faster this time around. We’re working with many of the same ideas and concepts, but you will see how relatively easy it is to create variations with this approach.

The first thing to do is to add some space on the top and bottom for the cutout part. I’m applying a transparent border for this. The thickness needs to be equal to half the height of one line (.5lh).

h1 {
  --c: #d81a14;

  border-block: .5lh solid #0000;
  padding-inline: 1lh;
  background: linear-gradient(var(--c) 80%, #0000 0) 0 .1lh / 100% 1lh padding-box;
}

Note how the background gradient is set to cover only the padding area using padding-box.

Now, unlike the first ribbon, we are going to add two more gradients for the vertical pieces that create the folded darker areas.

h1 {
  --c: #d81a14;

  border-block: .5lh solid #0000;
  padding-inline: 1lh;
  background:
    /* Gradient 1 */
    linear-gradient(var(--c) 80%, #0000 0) 0 .1lh / 100% 1lh padding-box,
    /* Gradient 2 */
    linear-gradient(#0000 50%, color-mix(in srgb, var(--c), #000 35%) 0) 
     0 0 / .8lh 2lh repeat-y border-box,
    /* Gradient 3 */
    linear-gradient(color-mix(in srgb, var(--c), #000 35%) 50%, #0000 0) 
     100% 0 / .8lh 2lh repeat-y border-box;
}

Notice how the last two gradients are set to cover the entire area with a border-box. The height of each gradient needs to equal two lines of text (2lh), while the width should be consistent with the height of each horizontal gradient. With this, we establish the folded parts of the ribbon and also prepare the code for creating the triangular cuts at the start and end of the ribbon.

Here is an interactive demo where you can resize the container to see how the gradient responds to the number of lines of text.

Applying only the conic gradients will also hide the cutout part, so I have to introduce a third gradient to make sure they remain visible:

mask:
  /* New Gradient */
  linear-gradient(#000 1lh, #0000 0) 0 -.5lh,
  /* Left Side */
  conic-gradient(from 225deg at .9lh, #0000 25%, #000 0) 
   0 1lh / 51% 2lh repeat-y padding-box,
  /* Right Side */
  conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0) 
   100% 0 / 51% 2lh repeat-y padding-box;

And the final touch is to use clip-path for the cutouts at the ends of the ribbon.

Notice how the clip-path is cutting two triangular portions from the bottom to make sure the cutout is always visible whether we have an odd or even number of lines.

This is how the final code looks when we put everything together:

h1 {
  --c: #d81a14;

  padding-inline: 1lh;
  border-block: .5lh solid #0000;
  background: 
    linear-gradient(var(--c) 80%, #0000 0)
      0 .1lh / 100% 1lh padding-box,
    linear-gradient(#0000 50%, color-mix(in srgb,var(--c), #000 35%) 0)
      0 0 / .8lh 2lh repeat-y border-box,
    linear-gradient(color-mix(in srgb, var(--c), #000 35%) 50%, #0000 0)
      100% 0 / .8lh 2lh repeat-y border-box;
  mask:
    linear-gradient(#000 1lh, #0000 0) 0 -.5lh,
    conic-gradient(from 225deg at .9lh,#0000 25%,#000 0)
     0 1lh/51% 2lh repeat-y padding-box,
    conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0)
     100% 0 / 51% 2lh repeat-y padding-box;
  clip-path: polygon(0 0, calc(100% - .8lh) 0,
    calc(100% - .4lh) .3lh,
    100% 0, 100% 100%,
    calc(100% - .4lh) calc(100% - .3lh),
    calc(100% - .8lh) 100%, .8lh 100%, .4lh calc(100% - .3lh), 0 100%);
}

I challenged you to find a way to reverse the direction of the first ribbon by adjusting the gradient values. Try to do the same thing here!

It may sound difficult. If you need a lifeline, you can get the code from my online collection, but it’s the perfect exercise to understand what we are doing. Explaining things is good, but nothing beats practicing.

The Final Demo

Here is the demo once again to see how everything comes together.

See the Pen Responsive multi-line ribbon shapes by Temani Afif.

Wrapping Up

There we go, two more ribbons that build off of the ones we created together in the first article of this brief two-part series. If there’s only one thing you take away from these articles, I hope it’s that modern CSS provides us with powerful tools that offer different, more robust approaches to things we used to do a long time ago. Ribbons are an excellent example of a long-living design pattern that’s been around long enough to demonstrate how creating them has evolved over time as new CSS features are released.

I can tell you that the two ribbons we created in this article are perhaps the most difficult shapes in my collection of ribbon shapes. But if you can wrap your head around the use of gradients — not only for backgrounds but masks and clipping paths as well — you’ll find that you can create every other ribbon in the collection without looking at my code. It’s getting over that initial hurdle that makes this sort of thing challenging.

You now have the tools to make your own ribbon patterns, too, so why not give it a try? If you do, please share them in the comments so I can see your work!

Further Reading On SmashingMag

Addressing Accessibility Concerns With Using Fluid Type

You may already be familiar with the CSS clamp() function. You may even be using it to fluidly scale a font size based on the browser viewport. Adrian Bece demonstrated the concept in another Smashing Magazine article just last year. It’s a clever CSS “trick” that has been floating around for a while.

But if you’ve used the clamp()-based fluid type technique yourself, then you may have also run into articles that offer a warning about it. For example, Adrian mentions this in his article:

“It’s important to reiterate that using rem values doesn’t automagically make fluid typography accessible for all users; it only allows the font sizes to respond to user font preferences. Using the CSS clamp function in combination with the viewport units to achieve fluid sizing introduces another set of drawbacks that we need to consider.”

Here’s Una Kravets with a few words about it on web.dev:

“Limiting how large text can get with max() or clamp() can cause a WCAG failure under 1.4.4 Resize text (AA), because a user may be unable to scale the text to 200% of its original size. Be certain to test the results with zoom.”

Trys Mudford also has something to say about it in the Utopia blog:

Adrian Roselli quite rightly warns that clamp can have a knock-on effect on the maximum font-size when the user explicitly sets a browser text zoom preference. As with any feature affecting typography, ensure you test thoroughly before using it in production.”

Mudford cites Adrian Roselli, who appears to be the core source of the other warnings:

“When you use vw units or limit how large text can get with clamp(), there is a chance a user may be unable to scale the text to 200% of its original size. If that happens, it is WCAG failure under 1.4.4 Resize text (AA) so be certain to test the results with zoom.”

So, what’s going on here? And how can we address any accessibility issues so we can keep fluidly scaling our text? That is exactly what I want to discuss in this article. Together, we will review what the WCAG guidelines say to understand the issue, then explore how we might be able to use clamp() in a way that adheres to WCAG Success Criterion (SC) 1.4.4.

WCAG Success Criterion 1.4.4

Let’s first review what WCAG Success Criterion 1.4.4 says about resizing text:

“Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.”

Normally, if we’re setting CSS font-size to a non-fluid value, e.g., font-size: 2rem, we never have to worry about resizing behavior. All modern browsers can zoom up to 500% without additional assistive technology.

So, what’s the deal with sizing text with viewport units like this:

h1 {
  font-size: 5vw;
}

Here’s a simple example demonstrating the problem. I suggest viewing it in either Chrome or Firefox because zooming in Safari can behave differently.

The text only scales up to 55px, or 1.67 times its original size, even though we zoomed the entire page to five times its original size. And because WCAG SC 1.4.4 requires that text can scale to at least two times its original size, this simple example would fail an accessibility audit, at least in most browsers at certain viewport widths.

Surely this can’t be a problem for all clamped font sizes with vw units, right? What about one that only increases from 16px to 18px:

h1 {
  font-size: clamp(16px, 15.33px + 0.208vw, 18px);
}

The vw part of that inner calc() function (clamp() supports calc() without explicitly declaring it) is so small that it couldn’t possibly cause the same accessibility failure, right?

Sure enough, even though it doesn’t get to quite 500% of its original size when the page is zoomed to 500%, the size of the text certainly passes the 200% zoom specified in WCAG SC 1.4.4.

So, clamped viewport-based font sizes fail WCAG SC 1.4.4 in some cases but not in others. The only advice I’ve seen for determining which situations pass or fail is to check each of them manually, as Adrian Roselli originally suggested. But that’s time-consuming and imprecise because the functions don’t scale intuitively.

There must be some relationship between our inputs — i.e., the minimum font size, maximum font size, minimum breakpoint, and maximum breakpoint — that can help us determine when they pose accessibility issues.

Thinking Mathematically

If we think about this problem mathematically, we really want to ensure that z₅(v) ≥ 2z₁(v). Let’s break that down.

z₁(v) and z₅(v) are functions that take the viewport width, v, as their input and return a font size at a 100% zoom level and a 500% zoom level, respectively. In other words, what we want to know is at what range of viewport widths will z₅(v) be less than 2×z₁(v), which represents the minimum size outlined in WCAG SC 1.4.4?

Using the first clamp() example we looked at that failed WCAG SC 1.4.4, we know that the z₁ function is the clamp() expression:

z₁(v) = clamp(16, 5.33 + 0.0333v, 48)

Notice: The vw units are divided by 100 to translate from CSS where 100vw equals the viewport width in pixels.

As for the z₅ function, it’s tempting to think that z₅ = 5z₁. But remember what we learned from that first demo: viewport-based units don’t scale up with the browser’s zoom level. This means z₅ is more correctly expressed like this:

z₅(v) = clamp(16*5, 5.33*5 + 0.0333v, 48*5)

Notice: This scales everything up by 5 (or 500%), except for v. This simulates how the browser scales the page when zooming.

Let’s represent the clamp() function mathematically. We can convert it to a piecewise function, meaning z₁(v) and z₅(v) would ultimately look like the following figure:

We can graph these functions to help visualize the problem. Here’s the base function, z₁(v), with the viewport width, v, on the x-axis:

This looks about right. The font size stays at 16px until the viewport is 320px wide, and it increases linearly from there before it hits 48px at a viewport width of 1280px. So far, so good.

Here’s a more interesting graph comparing 2z₁(v) and z₅(v):

Can you spot the accessibility failure on this graph? When z₅(v) (in green) is less than 2z₁(v) (in teal), the viewport-based font size fails WCAG SC 1.4.4.

Let’s zoom into the bottom-left region for a closer look:

This figure indicates that failure occurs when the browser width is approximately between 1050px and 2100px. You can verify this by opening the original demo again and zooming into it at different viewport widths. When the viewport is less than 1050px or greater than 2100px, the text should scale up to at least two times its original size at a 500% zoom. But when it’s in between 1050px and 2100px, it doesn’t.

Hint: We have to manually measure the text — e.g., take a screenshot — because browsers don’t show zoomed values in DevTools.

General Solutions

For simplicity’s sake, we’ve only focused on one clamp() expression so far. Can we generalize these findings somehow to ensure any clamped expression passes WCAG SC 1.4.4?

Let’s take a closer look at what’s happening in the failure above. Notice that the problem is caused because 2z₁(v) — the SC 1.4.4 requirement — reaches its peak before z₅(v) starts increasing.

When would that be the case? Everything in 2z₁(v) is scaled by 200%, including the slope of the line (v). The function reaches its peak value at the same viewport width where z₁(v) reaches its peak value (the maximum 1280px breakpoint). That peak value is two times the maximum font size we want which, in this case, is 2*48, or 96px.

However, the slope of z₅(v) is the same as z₁(v). In other words, the function doesn’t start increasing from its lowest clamped point — five times the minimum font size we want — until the viewport width is five times the minimum breakpoint. In this case, that is 5*320, or 1600px.

Thinking about this generally, we can say that if 2z₁(v) peaks before z₅(v) starts increasing, or if the maximum breakpoint is less than five times the minimum breakpoint, then the peak value of 2z₁(v) must be less than or equal to the peak value of z₅(v), or two times the maximum value that is less than or equal to five times the minimum value.

Or simpler still: The maximum value must be less than or equal to 2.5 times the minimum value.

What about when the maximum breakpoint is more than five times the minimum breakpoint? Let’s see what our graph looks like when we change the maximum breakpoint from 1280px to 1664px and the maximum font size to 40px:

Technically, we could get away with a slightly higher maximum font size. To figure out just how much higher, we’d have to solve for z₅(v) ≥ 2z₁(v) at the point when 2z₁(v) reaches its peak, which is when v equals the maximum breakpoint. (Hat tip to my brother, Zach Barvian, whose excellent math skills helped me with this.)

To save you the math, you can play around with this calculator to see which combinations pass WCAG SC 1.4.4.

Conclusion

Summing up what we’ve covered:

  • If the maximum font size is less than or equal to 2.5 times the minimum font size, then the text will always pass WCAG SC 1.4.4, at least on all modern browsers.
  • If the maximum breakpoint is greater than five times the minimum breakpoint, it is possible to get away with a slightly higher maximum font size. That said, the increase is negligible, and that is a large breakpoint range to use in practice.

Importantly, that first rule is true for non-fluid responsive type as well. If you open this pen, for example, notice that it uses regular media queries to increase the h1 element’s size from an initial value of 1rem to 3rem (which violates our first rule), with an in-between stop for 2rem.

If you zoom in at 500% with a browser width of approximately 1000px, you will see that the text doesn’t reach 200% of its initial size. This makes sense because if you were to describe 2z₁(v) and z₅(v) mathematically, they would be even simpler piecewise functions with the same maximum and minimum limitations. This guideline would hold for any function describing a font size with a known minimum and maximum.

In the future, of course, we may get more tools from browsers to address these issues and accommodate even larger maximum font sizes. In the meantime, though, I hope you find this article helpful when building responsive frontends.

What I Wish I Knew About Working In Development Right Out Of School

My journey in front-end web development started after university. I had no idea what I was going into, but it looked easy enough to get my feet wet at first glance. I dug around Google and read up on tons of blog posts and articles about a career in front-end. I did bootcamps and acquired a fancy laptop. I thought I was good to go and had all I needed.

Then reality started to kick in. It started when I realized how vast of a landscape Front-End Land is. There are countless frameworks, techniques, standards, workflows, and tools — enough to fill a virtual Amazon-sized warehouse. Where does someone so new to the industry even start? My previous research did nothing to prepare me for what I was walking into.

Fast-forward one year, and I feel like I’m beginning to find my footing. By no means do I consider myself a seasoned veteran at the moment, but I have enough road behind me to reflect back on what I’ve learned and what I wish I knew about the realities of working in front-end development when starting out. This article is about that.

The Web Is Big Enough For Specializations

At some point in my journey, I enrolled myself in a number of online courses and bootcamps to help me catch up on everything from data analytics to cybersecurity to software engineering at the same time. These were things I kept seeing pop up in articles. I was so confused; I believed all of these disciplines were interchangeable and part of the same skill set.

But that is just what they are: disciplines.

What I’ve come to realize is that being an “expert” in everything is a lost cause in the ever-growing World Wide Web.

Sure, it’s possible to be generally familiar with a wide spectrum of web-related skills, but it’s hard for me to see how to develop “deep” learning of everything. There will be weak spots in anyone’s skillset.

It would take a lifetime masterclass to get everything down-pat. Thank goodness there are ways to specialize in specific areas of the web, whether it is accessibility, performance, standards, typography, animations, interaction design, or many others that could fill the rest of this article. It’s OK to be one developer with a small cocktail of niche specialties. We need to depend on each other as much as any Node package in a project relies on a number of dependencies.

Burnout And Imposter Syndrome Are Real

My initial plan for starting my career was to master as many skills as possible and start making a living within six months. I figured if I could have a wide set of strong skills, then maybe I could lean on one of them to earn money and continue developing the rest of my skills on my way to becoming a full-stack developer.

I got it wrong. It turned out that I was chasing my tail in circles, trying to be everything to everyone. Just as I’d get an “a-ha!” moment learning one thing, I’d see some other new framework, CSS feature, performance strategy, design system, and so on in my X/Twitter feed that was calling my attention. I never really did get a feeling of accomplishment; it was more a fear of missing out and that I was an imposter disguised as a front-ender.

I continued burning the candle at both ends to absorb everything in my path, thinking I might reach some point at which I could call myself a full-stack developer and earn the right to slow down and coast with my vast array of skills. But I kept struggling to keep up and instead earned many sleepless nights cramming in as much information as I could.

Burnout is something I don’t wish on anyone. I was tired and mentally stressed. I could have done better. I engaged in every Twitter space or virtual event I could to learn a new trick and land a steady job. Imagine that, with my busy schedule, I still pause it to listen to hours of online events. I had an undying thirst for knowledge but needed to channel it in the right direction.

We Need Each Other

I had spent so much time and effort consuming information with the intensity of a firehose running at full blast that I completely overlooked what I now know is an essential asset in this industry: a network of colleagues.

I was on my own. Sure, I was sort of engaging with others by reading their tutorials, watching their video series, reading their social posts, and whatnot. But I didn’t really know anyone personally. I became familiar with all the big names you probably know as well, but it’s not like I worked or even interacted with anyone directly.

What I know now is that I needed personal advice every bit as much as more technical information. It often takes the help of someone else to learn how to ride a bike, so why wouldn’t it be the same for writing code?

Having a mentor or two would have helped me maintain balance throughout my technical bike ride, and now I wish I had sought someone out much earlier.

I should have asked for help when I needed it rather than stubbornly pushing forward on my own. I was feeding my burnout more than I was making positive progress.

Start With The Basics, Then Scale Up

My candid advice from my experience is to start learning front-end fundamentals. HTML and CSS are unlikely to go away. I mean, everything parses in HTML at the end of the day, right? And CSS is used on 97% of all websites.

The truth is that HTML and CSS are big buckets, even if they are usually discounted as “basic” or “easy” compared to traditional programming languages. Writing them well matters for everything. Sure, go ahead and jump straight to JavaScript, and it’s possible to cobble together a modern web app with an architecture of modular components. You’ll still need to know how your work renders and ensure it’s accessible, semantic, performant, cross-browser-supported, and responsive. You may pick those skills up along the way, but why not learn them up-front when they are essential to a good user experience?

So, before you click on yet another link extolling the virtues of another flavor of JavaScript framework, my advice is to start with the essentials:

  • What is a “semantic” HTML element?
  • What is the CSS Box Model, and why does it matter?
  • How does the CSS Cascade influence the way we write styles?
  • How does a screenreader announce elements on a page?
  • What is the difference between inline and block elements?
  • Why do we have logical properties in CSS when we already have physical ones?
  • What does it mean to create a stacking context or remove an element from the document flow?
  • How do certain elements look in one browser versus another?

The list could go on and on. I bet many of you know the answers. I wonder, though, how many you could explain effectively to someone beginning a front-end career. And, remember, things change. New standards are shipped, new tricks are discovered, and certain trends will fade as quickly as they came. While staying up-to-date with front-end development on a macro level is helpful, I’ve learned to integrate specific new technologies and strategies into my work only when I have a use case for them and concentrate more on my own learning journey — establish a solid foundation with the essentials, then progress to real-life projects.

Progress is a process. May as well start with evergreen information and add complexity to your knowledge when you need it instead of drinking from the firehose at all times.

There’s A Time And Place For Everything

I’ll share a personal story. I spent over a month enrolled in a course on React. I even had to apply for it first, so it was something I had to be accepted into — and I was! I was super excited.

I struggled in the class, of course. And, yes, I dropped out of the program after the first month.

I don’t believe struggling with the course or dropping out of it is any indication of my abilities. I believe it has a lot more to do with timing. The honest truth is that I thought learning React before the fundamentals of front-end development was the right thing to do. React seemed to be the number one thing that everyone was blogging about and what every employer was looking for in a new hire. The React course I was accepted into was my ticket to a successful and fulfilling career!

My motive was right, but I was not ready for it. I should have stuck with the basics and scaled up when I was good and ready to move forward. Instead of building up, I took a huge shortcut and wound up paying for it in the end, both in time and money.

That said, there’s probably no harm in dipping your toes in the water even as you learn the basics. There are plenty of events, hackathons, and coding challenges that offer safe places to connect and collaborate with others. Engaging in some of these activities early on may be a great learning opportunity to see how your knowledge supports or extends someone else’s skills. It can help you see where you fit in and what considerations go into real-life projects that require other people.

There was a time and place for me to learn React. The problem is I jumped the gun and channeled my learning energy in the wrong direction.

If I Had To Do It All Over Again…

This is the money question, right? Everyone wants to know exactly where to start, which classes to take, what articles to read, who to follow on socials, where to find jobs, and so on. The problem with highly specific advice like this is that it’s highly personalized as well. In other words, what has worked for me may not exactly be the right recipe for you.

It’s not the most satisfying answer, but the path you take really does depend on what you want to do and where you want to wind up. Aside from gaining a solid grasp on the basics, I wouldn’t say your next step is jumping into React when your passion is web typography. Both are skill sets that can be used together but are separate areas of concern that have different learning paths.

So, what would I do differently if I had the chance to do this all over again?

For starters, I wouldn’t skip over the fundamentals like I did. I would probably find opportunities to enhance my skills in those areas, like taking the FreeCodeCamp’s responsive web design course or practice recreating designs from the Figma community in CodePen to practice thinking strategically about structuring my code. Then, I might move on to the JavaScript Algorithms and Data Structures course to level up basic JavaScript skills.

The one thing I know I would do right away, though, is to find a mentor whom I can turn to when I start feeling as though I’m struggling and falling off track.

Or maybe I should have started by learning how to learn in the first place. Figuring out what kind of learner I am and familiarizing myself with learning strategies that help me manage my time and energy would have gone a long way.

Oh, The Places You’ll Go!

Front-end development is full of opinions. The best way to navigate this world is by mastering the basics. I shared my journey, mistakes, and ways of doing things differently if I were to start over. Rather than prescribing you a specific way of going about things or giving you an endless farm of links to all of the available front-end learning resources, I’ll share a few that I personally found helpful.

In the end, I’ve found that I care a lot about contributing to open-source projects, participating in hackathons, having a learning plan, and interacting with mentors who help me along the way, so those are the buckets I’m organizing things into.

Open Source Programs

Hackathons

Developer Roadmaps

Mentorship

Whatever your niche is, wherever your learning takes you, just make sure it’s yours. What works for one person may not be the right path for you, so spend time exploring the space and picking out what excites you most. The web is big, and there is a place for everyone to shine, especially you.

Gatsby Headaches: Working With Media (Part 1)

Working with media files in Gatsby might not be as straightforward as expected. I remember starting my first Gatsby project. After consulting Gatsby’s documentation, I discovered I needed to use the gatsby-source-filesystem plugin to make queries for local files. Easy enough!

That’s where things started getting complicated. Need to use images? Check the docs and install one — or more! — of the many, many plugins available for handling images. How about working with SVG files? There is another plugin for that. Video files? You get the idea.

It’s all great until any of those plugins or packages become outdated and go unmaintained. That’s where the headaches start.

If you are unfamiliar with Gatsby, it’s a React-based static site generator that uses GraphQL to pull structured data from various sources and uses webpack to bundle a project so it can then be deployed and served as static files. It’s essentially a static site generator with reactivity that can pull data from a vast array of sources.

Like many static site frameworks in the Jamstack, Gatsby has traditionally enjoyed a great reputation as a performant framework, although it has taken a hit in recent years. Based on what I’ve seen, however, it’s not so much that the framework is fast or slow but how the framework is configured to handle many of the sorts of things that impact performance, including media files.

So, let’s solve the headaches you might encounter when working with media files in a Gatsby project. This article is the first of a brief two-part series where we will look specifically at the media you are most likely to use: images, video, and audio. After that, the second part of this series will get into different types of files, including Markdown, PDFs, and even 3D models.

Solving Image Headaches In Gatsby

I think that the process of optimizing images can fall into four different buckets:

  1. Optimize image files.
    Minimizing an image’s file size without losing quality directly leads to shorter fetching times. This can be done manually or during a build process. It’s also possible to use a service, like Cloudinary, to handle the work on demand.
  2. Prioritize images that are part of the First Contentful Paint (FCP).
    FCP is a metric that measures the time between the point when a page starts loading to when the first bytes of content are rendered. The idea is that fetching assets that are part of that initial render earlier results in faster loading rather than waiting for other assets lower on the chain.
  3. Lazy loading other images.
    We can prevent the rest of the images from render-blocking other assets using the loading="lazy" attribute on images.
  4. Load the right image file for the right context.
    With responsive images, we can serve one version of an image file at one screen size and serve another image at a different screen size with the srcset and sizes attributes or with the <picture> element.

These are great principles for any website, not only those built with Gatsby. But how we build them into a Gatsby-powered site can be confusing, which is why I’m writing this article and perhaps why you’re reading it.

Lazy Loading Images In Gatsby

We can apply an image to a React component in a Gatsby site like this:

import * as React from "react";

import forest from "./assets/images/forest.jpg";

const ImageHTML = () => {
  return <img src={ forest } alt="Forest trail" />;
};

It’s important to import the image as a JavaScript module. This lets webpack know to bundle the image and generate a path to its location in the public folder.

This works fine, but when are we ever working with only one image? What if we want to make an image gallery that contains 100 images? If we try to load that many <img> tags at once, they will certainly slow things down and could affect the FCP. That’s where the third principle that uses the loading="lazy" attribute can come into play.

import * as React from "react";

import forest from "./assets/images/forest.jpg";

const LazyImageHTML = () => {
  return <img src={ forest } loading="lazy" alt="Forest trail" />;
};

We can do the opposite with loading="eager". It instructs the browser to load the image as soon as possible, regardless of whether it is onscreen or not.

import * as React from "react";

import forest from "./assets/images/forest.jpg";

const EagerImageHTML = () => {
  return <img src={ forest } loading="eager" alt="Forest trail" />;
};

Implementing Responsive Images In Gatsby

This is a basic example of the HTML for responsive images:

<img
  srcset="./assets/images/forest-400.jpg 400w, ./assets/images/forest-800.jpg 800w"
  sizes="(max-width: 500px) 400px, 800px"
  alt="Forest trail"
/>

In Gatsby, we must import the images first and pass them to the srcset attribute as template literals so webpack can bundle them:

import * as React from "react";

import forest800 from "./assets/images/forest-800.jpg";

import forest400 from "./assets/images/forest-400.jpg";

const ResponsiveImageHTML = () => {
  return (
    <img
      srcSet={`

        ${ forest400 } 400w,

        ${ forest800 } 800w

      `}
      sizes="(max-width: 500px) 400px, 800px"
      alt="Forest trail"
    />
  );
};

That should take care of any responsive image headaches in the future.

Loading Background Images In Gatsby

What about pulling in the URL for an image file to use on the CSS background-url property? That looks something like this:

import * as React from "react";

import "./style.css";

const ImageBackground = () => {
  return <div className="banner"></div>;
};
/* style.css */

.banner {
      aspect-ratio: 16/9;
      background-size: cover;

    background-image: url("./assets/images/forest-800.jpg");

  /* etc. */
}

This is straightforward, but there is still room for optimization! For example, we can do the CSS version of responsive images, which loads the version we want at specific breakpoints.

/* style.css */

@media (max-width: 500px) {
  .banner {
    background-image: url("./assets/images/forest-400.jpg");
  }
}

Using The gatsby-source-filesystem Plugin

Before going any further, I think it is worth installing the gatsby-source-filesystem plugin. It’s an essential part of any Gatsby project because it allows us to query data from various directories in the local filesystem, making it simpler to fetch assets, like a folder of optimized images.

npm i gatsby-source-filesystem

We can add it to our gatsby-config.js file and specify the directory from which we will query our media assets:

// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,

      options: {
        name: `assets`,

        path: `${ __dirname }/src/assets`,
      },
    },
  ],
};

Remember to restart your development server to see changes from the gatsby-config.js file.

Now that we have gatsby-source-filesystem installed, we can continue solving a few other image-related headaches. For example, the next plugin we look at is capable of simplifying the cures we used for lazy loading and responsive images.

Using The gatsby-plugin-image Plugin

The gatsby-plugin-image plugin (not to be confused with the outdated gatsby-image plugin) uses techniques that automatically handle various aspects of image optimization, such as lazy loading, responsive sizing, and even generating optimized image formats for modern browsers.

Once installed, we can replace standard <img> tags with either the <GatsbyImage> or <StaticImage> components, depending on the use case. These components take advantage of the plugin’s features and use the <picture> HTML element to ensure the most appropriate image is served to each user based on their device and network conditions.

We can start by installing gatsby-plugin-image and the other plugins it depends on:

npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp

Let’s add them to the gatsby-config.js file:

// gatsby-config.js

module.exports = {
plugins: [

// other plugins
`gatsby-plugin-image`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`],

};

This provides us with some features we will put to use a bit later.

Using The StaticImage Component

The StaticImage component serves images that don’t require dynamic sourcing or complex transformations. It’s particularly useful for scenarios where you have a fixed image source that doesn’t change based on user interactions or content updates, like logos, icons, or other static images that remain consistent.

The main attributes we will take into consideration are:

  • src: This attribute is required and should be set to the path of the image you want to display.
  • alt: Provides alternative text for the image.
  • placeholder: This attribute can be set to either blurred or dominantColor to define the type of placeholder to display while the image is loading.
  • layout: This defines how the image should be displayed. It can be set to fixed for, as you might imagine, images with a fixed size, fullWidth for images that span the entire container, and constrained for images scaled down to fit their container.
  • loading: This determines when the image should start loading while also supporting the eager and lazy options.

Using StaticImage is similar to using a regular HTML <img> tag. However, StaticImage requires passing the string directly to the src attribute so it can be bundled by webpack.

import * as React from "react";

import { StaticImage } from "gatsby-plugin-image";

const ImageStaticGatsby = () => {
  return (
    <StaticImage
      src="./assets/images/forest.jpg"
      placeholder="blurred"
      layout="constrained"
      alt="Forest trail"
      loading="lazy"
    />
  );
  };

The StaticImage component is great, but you have to take its constraints into account:

  • No Dynamically Loading URLs
    One of the most significant limitations is that the StaticImage component doesn’t support dynamically loading images based on URLs fetched from data sources or APIs.
  • Compile-Time Image Handling
    The StaticImage component’s image handling occurs at compile time. This means that the images you specify are processed and optimized when the Gatsby site is built. Consequently, if you have images that need to change frequently based on user interactions or updates, the static nature of this component might not fit your needs.
  • Limited Transformation Options
    Unlike the more versatile GatsbyImage component, the StaticImage component provides fewer transformation options, e.g., there is no way to apply complex transformations like cropping, resizing, or adjusting image quality directly within the component. You may want to consider alternative solutions if you require advanced transformations.

Using The GatsbyImage Component

The GatsbyImage component is a more versatile solution that addresses the limitations of the StaticImage component. It’s particularly useful for scenarios involving dynamic image loading, complex transformations, and advanced customization.

Some ideal use cases where GatsbyImage is particularly useful include:

  • Dynamic Image Loading
    If you need to load images dynamically based on data from APIs, content management systems, or other sources, the GatsbyImage component is the go-to choice. It can fetch images and optimize their loading behavior.
  • Complex transformations
    The GatsbyImage component is well-suited for advanced transformations, using GraphQL queries to apply them.
  • Responsive images
    For responsive design, the GatsbyImage component excels by automatically generating multiple sizes and formats of an image, ensuring that users receive an appropriate image based on their device and network conditions.

Unlike the StaticImage component, which uses a src attribute, GatsbyImage has an image attribute that takes a gatsbyImageData object. gatsbyImageData contains the image information and can be queried from GraphQL using the following query.

query {
  file(name: { eq: "forest" }) {
    childImageSharp {
      gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
    }

    name
  }
}

If you’re following along, you can look around your Gatsby data layer at http://localhost:8000/___graphql.

From here, we can use the useStaticQuery hook and the graphql tag to fetch data from the data layer:

import * as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage, getImage } from "gatsby-plugin-image";

const ImageGatsby = () => {
  // Query data here:

  const data = useStaticQue(graphql``);

  return <div></div>;
};

Next, we can write the GraphQL query inside of the graphql tag:

import * as React from "react";

import { useStaticQuery, graphql } from "gatsby";

const ImageGatsby = () => {
  const data = useStaticQuery(graphqlquery {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    });

  return <div></div>;
};

Next, we import the GatsbyImage component from gatsby-plugin-image and assign the image’s gatsbyImageData property to the image attribute:

import * as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage } from "gatsby-plugin-image";

const ImageGatsby = () => {
  const data = useStaticQuery(graphqlquery {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    });

  return <GatsbyImage image={ data.file.childImageSharp.gatsbyImageData } alt={ data.file.name } />;
};

Now, we can use the getImage helper function to make the code easier to read. When given a File object, the function returns the file.childImageSharp.gatsbyImageData property, which can be passed directly to the GatsbyImage component.

import * as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage, getImage } from "gatsby-plugin-image";

const ImageGatsby = () => {
  const data = useStaticQuery(graphqlquery {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    });

  const image = getImage(data.file);

  return <GatsbyImage image={ image } alt={ data.file.name } />;
};

Using The gatsby-background-image Plugin

Another plugin we could use to take advantage of Gatsby’s image optimization capabilities is the gatsby-background-image plugin. However, I do not recommend using this plugin since it is outdated and prone to compatibility issues. Instead, Gatsby suggests using gatsby-plugin-image when working with the latest Gatsby version 3 and above.

If this compatibility doesn’t represent a significant problem for your project, you can refer to the plugin’s documentation for specific instructions and use it in place of the CSS background-url usage I described earlier.

Solving Video And Audio Headaches In Gatsby

Working with videos and audio can be a bit of a mess in Gatsby since it lacks plugins for sourcing and optimizing these types of files. In fact, Gatsby’s documentation doesn’t name or recommend any official plugins we can turn to.

That means we will have to use vanilla methods for videos and audio in Gatsby.

Using The HTML video Element

The HTML video element is capable of serving different versions of the same video using the <source> tag, much like the img element uses the srset attribute to do the same for responsive images.

That allows us to not only serve a more performant video format but also to provide a fallback video for older browsers that may not support the bleeding edge:

import * as React from "react";

import natureMP4 from "./assets/videos/nature.mp4";

import natureWEBM from "./assets/videos/nature.webm";

const VideoHTML = () => {
  return (
    <video controls>
      <source src={ natureMP4 } type="video/mp4" />

      <source src={ natureWEBM } type="video/webm" />
    </video>
  );
};

P;

We can also apply lazy loading to videos like we do for images. While videos do not support the loading="lazy" attribute, there is a preload attribute that is similar in nature. When set to none, the attribute instructs the browser to load a video and its metadata only when the user interacts with it. In other words, it’s lazy-loaded until the user taps or clicks the video.

We can also set the attribute to metadata if we want the video’s details, such as its duration and file size, fetched right away.

<video controls preload="none">
  <source src={ natureMP4 } type="video/mp4" />

  <source src={ natureWEBM } type="video/webm" />
</video>

Note: I personally do not recommend using the autoplay attribute since it is disruptive and disregards the preload attribute, causing the video to load right away.

And, like images, display a placeholder image for a video while it is loading with the poster attribute pointing to an image file.

<video controls preload="none" poster={ forest }>
  <source src={ natureMP4 } type="video/mp4" />

  <source src={ natureWEBM } type="video/webm" />
</video>

Using The HTML audio Element

The audio and video elements behave similarly, so adding an audio element in Gatsby looks nearly identical, aside from the element:

import * as React from "react";

import audioSampleMP3 from "./assets/audio/sample.mp3";

import audioSampleWAV from "./assets/audio/sample.wav";

const AudioHTML = () => {
  return (
    <audio controls>
      <source src={ audioSampleMP3 } type="audio/mp3" />

      <source src={ audioSampleWAV } type="audio/wav" />
    </audio>
  );
};

As you might expect, the audio element also supports the preload attribute:

<audio controls preload="none">
  <source src={ audioSampleMP3 } type="audio/mp3" />

  <source src={a udioSampleWAV } type="audio/wav" />
</audio>

This is probably as good as we can do to use videos and images in Gatsby with performance in mind, aside from saving and compressing the files as best we can before serving them.

Solving iFrame Headaches In Gatsby

Speaking of video, what about ones embedded in an <iframe> like we might do with a video from YouTube, Vimeo, or some other third party? Those can certainly lead to performance headaches, but it’s not as we have direct control over the video file and where it is served.

Not all is lost because the HTML iframe element supports lazy loading the same way that images do.

import * as React from "react";

const VideoIframe = () => {
  return (
    <iframe
      src="https://www.youtube.com/embed/jNQXAC9IVRw"
      title="Me at the Zoo"
      allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
      allowFullScreen
      loading="lazy"
    />
  );
};

Embedding a third-party video player via iframe can possibly be an easier path than using the HTML video element. iframe elements are cross-platform compatible and could reduce hosting demands if you are working with heavy video files on your own server.

That said, an iframe is essentially a sandbox serving a page from an outside source. They’re not weightless, and we have no control over the code they contain. There are also GDPR considerations when it comes to services (such as YouTube) due to cookies, data privacy, and third-party ads.

Solving SVG Headaches In Gatsby

SVGs contribute to improved page performance in several ways. Their vector nature results in a much smaller file size compared to raster images, and they can be scaled up without compromising quality. And SVGs can be compressed with GZIP, further reducing file sizes.

That said, there are several ways that we can use SVG files. Let’s tackle each one in the contact of Gatsby.

Using Inline SVG

SVGs are essentially lines of code that describe shapes and paths, making them lightweight and highly customizable. Due to their XML-based structure, SVG images can be directly embedded within the HTML <svg> tag.

import * as React from "react";



const SVGInline = () => {

  return (

    <svg viewBox="0 0 24 24" fill="#000000">

      <!-- etc. -->

    </svg>

  );

};

Just remember to change certain SVG attributes, such as xmlns:xlink or xlink:href, to JSX attribute spelling, like xmlnsXlink and xlinkHref, respectively.

Using SVG In img Elements

An SVG file can be passed into an img element's src attribute like any other image file.

import * as React from "react";

import picture from "./assets/svg/picture.svg";

const SVGinImg = () => {
  return <img src={ picture } alt="Picture" />;
};

Loading SVGs inline or as HTML images are the de facto approaches, but there are React and Gatsby plugins capable of simplifying the process, so let’s look at those next.

Inlining SVG With The react-svg Plugin

react-svg provides an efficient way to render SVG images as React components by swapping a ReactSVG component in the DOM with an inline SVG.

Once installing the plugin, import the ReactSVG component and assign the SVG file to the component’s src attribute:

import * as React from "react";

import { ReactSVG } from "react-svg";

import camera from "./assets/svg/camera.svg";

const SVGReact = () => {
  return <ReactSVG src={ camera } />;
};

Using The gatsby-plugin-react-svg Plugin

The gatsby-plugin-react-svg plugin adds svg-react-loader to your Gatsby project’s webpack configuration. The plugin adds a loader to support using SVG files as React components while bundling them as inline SVG.

Once the plugin is installed, add it to the gatsby-config.js file. From there, add a webpack rule inside the plugin configuration to only load SVG files ending with a certain filename, making it easy to split inline SVGs from other assets:

// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: "gatsby-plugin-react-svg",

      options: {
        rule: {
          include: /\.inline\.svg$/,
        },
      },
    },
  ],
};

Now we can import SVG files like any other React component:

import * as React from "react";

import Book from "./assets/svg/book.inline.svg";

const GatsbyPluginReactSVG = () => {
  return <Book />;
};

And just like that, we can use SVGs in our Gatsby pages in several different ways!

Conclusion

Even though I personally love Gatsby, working with media files has given me more than a few headaches.

As a final tip, when needing common features such as images or querying from your local filesystem, go ahead and install the necessary plugins. But when you need a minor feature, try doing it yourself with the methods that are already available to you!

If you have experienced different headaches when working with media in Gatsby or have circumvented them with different approaches than what I’ve covered, please share them! This is a big space, and it’s always helpful to see how others approach similar challenges.

Again, this article is the first of a brief two-part series on curing headaches when working with media files in a Gatsby project. The following article will be about avoiding headaches when working with different media files, including Markdown, PDFs, and 3D models.

Further Reading

Make WordPress Sites Load Faster Than Ever With New Hummingbird Critical CSS

With Hummingbird’s much anticipated Critical CSS feature, you can expect faster-loading pages and better performing WordPress sites. Here’s why render-blocking resources are now a thing of the past…

Hummingbird Optimization - Before and After Results
Ace Google’s PageSpeed performance scores with Hummingbird’s Critical CSS feature.

If you care about page loading speed (and you should if you want visitors to stay on your website for longer than two seconds), then it’s vitally important to understand how CSS affects site performance and how to speed up your page loading time using an optimization task known as Critical CSS.

In this article, we’ll cover the following topics:

Let’s dive in…

What is Critical CSS and How Does it Improve Performance?

When users arrive on a website, all they can see initially is the content displayed on their screen before scrolling.

This area is referred to as being “above the fold.”

Image explaining above and below the fold.
All site visitors see at first is the content above the fold.

Positive user experience can be measured by how quickly users perceive content to load on a web page. The faster a page loads (or is perceived by the user as loading quickly), the better the user experience. Conversely, the slower the page loads (or is perceived by the user to load slowly), the poorer the experience.

Since all the visitor sees when they land on a page is the content above the fold before they start scrolling down, it makes sense to make the content above the fold load as quickly as possible before loading the rest of the page.

Critical CSS (also known as Critical Path CSS or Critical CSS Rendering Path) is a technique that extracts the bare minimum CSS required to render content above-the-fold as quickly as possible to the user.

While the user viewing the above-the-fold content perceives the page to be loading quickly, the rest of the CSS can load, and user experience is not impacted.

Techniques like image lazy loading, delaying JavaScript execution, and critical CSS are all ways to optimize the sequence of steps the browser goes through to convert the HTML, CSS, and JavaScript into pixels on the screen.

This sequence is referred to as the Critical Rendering Path (CRP) and includes the Document Object Model (DOM), CSS Object Model (CSSOM), render tree, and layout.

Optimizing the critical render path improves render performance.

Advantages of Critical CSS

Critical CSS can improve site performance through:

  • Faster initial rendering
  • Improved user experience
  • Better SEO performance
  • Reduced page weight
  • Simplified maintenance
  • Progressive enhancement
  • Positive impact on Core Web Vitals (especially First Contentful Paint and Speed Index)
  • Higher PageSpeed Insights scores

Note: The content displayed above-the-fold on page-load before scrolling will differ depending on the device and screen size being used to view web pages. For this reason, there is no universally defined pixel height of what can be considered above-the-fold content.

Implementing Critical CSS

So you’ve run your site through the PageSpeed Insights tool and the report recommends eliminating render-blocking resources.

Now what? How do you actually implement the recommendations?

Well, you can try to fix things manually (tedious, time-consuming, and not recommended), use web development tools (if you have technical skills), or use a WordPress plugin like Hummingbird to automatically identify, address, and resolve any issues.

We recommend using the plugin method. It’s the quickest and smartest option to get the job done.

While Critical CSS refers mostly to above-the-fold CSS, Hummingbird can extract and inline all used CSS on the page, while delaying/removing the rest.

Hummingbird not only tackles render-blocking and unused CSS for full-page optimization, it also handles above-the-fold optimization by eliminating render-blocking resources using built-in features like Critical CSS (see below), Delay JavaScript Execution for JavaScript assets, and other areas that affect Core Web Vitals results on WordPress sites.

How To Optimize WordPress Using Hummingbird’s Critical CSS feature

Note: Critical CSS is a Pro feature, so make sure you have Hummingbird Pro installed on your site.

Let’s go through the steps on how to get the most benefit from using Hummingbird’s new critical CSS feature.

First, start by running a performance test.

Hummingbird - Start Performance Test
Start optimizing your site with Hummingbird by running a performance test.

Make sure to note the initial results so you can compare before and after results.

Hummingbird performance test results
Note down Hummingbird’s performance test results before enabling critical CSS.

Next, navigate to Hummingbird > Asset Optimization > Extra Optimization and enable Critical CSS.

Hummingbird-Asset Optimization - Extra Optimization - Critical CSS
Turn on Critical CSS in the Asset Optimization > Extra Optimization screen.
Critical CSS Options
Hummingbird gives you options to control the implementation of Critical CSS on your site.

After enabling the feature, you’ll see different options for loading Critical CSS and for handling Unused CSS.

Loading Critical CSS

This section gives you the option to select Full-Page CSS Optimization (default) or Above-the-Fold CSS Optimization.

Critical CSS
Select one of the options from the drop-down menu.

We recommend choosing the default Full-Page CSS Optimization with Load on User Interaction option selected for most sites as this will provide the best results and address both issues of eliminating render-blocking resources and reducing unused CSS audits while maintaining the integrity of all the site’s visual elements.

Full-Page CSS Optimization inlines all used CSS and delays/removes loading the rest.

Choosing the Above-the-Fold CSS Optimization method is recommended for larger sites with loads of complex CSS if the default option does not give the desirable results. This method will inline all above-the-fold CSS and load the rest asynchronously.

Handling Unused CSS

Hummingbird gives you the option to load the unused CSS On User Interaction to fix any rendering issues or Remove Unused which trims unused CSS, keeping only what’s necessary and loading it inline.

Additionally, you can toggle the feature for specific post types.

Unusued CSS Post Types
Select the post types to remove unused CSS.

While the post type toggles are available for both the Full-Page CSS Optimization and Above-the-Fold CSS Optimization methods, only the Full-Page CSS method handles unused CSS.

Critical CSS - Above The Fold Method option selected.
If Above-the-Fold CSS Optimization method is selected, the option to remove unused CSS does not display.

Both optimization methods also provide an advanced option to add custom CSS manually within the <head> section of the page(s).

Unused CSS - manual inclusions
Add critical custom CSS elements manually.

Note: If you have used the legacy CSS above the fold feature in earlier versions of Hummingbird to manually feed the critical path CSS, the existing data will be automatically migrated to the Manual Inclusions box when you upgrade the plugin to the latest version and switch to using the new feature.

After configuring your options, click Save Changes. Hummingbird will start implementing Critical CSS automatically as per your settings.

Critical CSS Optimizing
Wait a few seconds for Critical CSS to optimize your site before continuing.

After you see the completion message, visit your site and confirm that everything on the front end is displaying as it should.

Critical CSS Generated message.
Wait until you see the “Critical CSS Generated” message before refreshing the page.

Refresh the page, let the cache build up again, and then run another performance test in Hummingbird so you can compare the before and after results.

Hummingbird performance test results
Compare Hummingbird’s performance test results before and after running Critical CSS.

Regenerate Critical CSS

After applying Critical CSS on your site, a “Regenerate Critical CSS” button will display at the top of the Extra Optimization screen.

Click on this button to purge the cache, clear all local or hosted assets, and automatically regenerate all required assets for your site or homepage.

Regenerate Critical CSS
Regenerate your site’s Critical CSS at any time with a simple click.

Hummingbird’s Critical CSS is Compatible with Everything WordPress

We have tested Hummingbird’s Critical CSS feature extensively and found it to be compatible with all WordPress versions and themes, page builders, fonts, WooCommerce, Learning Management Systems (LMS), etc.

It’s important to note, however, that installing poorly-coded themes or plugins containing CSS with invalid code or invalid strings on your site could cause issues and result in a Critical CSS error message.

Critical CSS error message.
Using poorly-coded themes or plugins can lead to Critical CSS errors.

If you do experience errors using Critical CSS, try the following:

  1. Click on the “Regenerate Critical CSS”  button and see if this fixes the issue.
  2. If you get the same error again, we suggest changing the theme (use a staging site if your site is live), and run Critical CSS on the new theme. If there are no problems, then the issue is most likely the theme.
  3. If you experience issues after installing a different theme, we recommend troubleshooting your plugins.
  4. If the error still persists after trying all of the above, note the error message, disable Critical CSS temporarily on your site, and contact our support team for assistance fixing the issue.

You can rest assured, however, as Hummingbird’s Critical CSS feature has been designed with the focus to preserve your site’s visual integrity while bumping performance. The feature handles errors well and and will rarely break a site, even in case of errors.

For additional information on using the Critical CSS feature, refer to the plugin documentation.

Switch On All Of Hummingbird’s Optimization Features For Best Results

If getting maximum speed and performance out of your WordPress site(s) is critically important to you, using Hummingbird’s Critical CSS is definitely a feature you shouldn’t ignore.

Hummingbird report - passed audits.
Optimizes site performance and ace Google’s PageSpeed recommendations with Hummingbird’s Critical CSS feature.

For best performance and savings, we recommend using Critical CSS with page caching and all of the asset optimization features the plugin makes available, including CDN, and Delay JavaScript Execution.

Hummingbird - Asset Optimization
For best results, enable all of Hummingbird’s asset optimization features.

In most cases, combining all of Hummingbird’s optimization features should help your site achieve PageSpeed scores of 90+ or bring it closer to a perfect 100 if your site is already performing well.

Hummingbird-100 Score PageInsights
Use all of Hummingbird’s optimization features to get the perfect performance score!

As mentioned earlier, Critical CSS is a Hummingbird Pro feature and its available to all WPMU DEV members.

If you are currently using our free Hummingbird plugin, consider becoming a member for affordable and risk-free access to our all-in-one WordPress platform. It has everything you need to launch, run, and grow your web development business.

And if you are an Agency member, you can even white label and resell Hummingbird (plus hosting, domains, our entire suite of PRO plugins, and more) all under your own brand.

Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 2)

The last time we met, I demonstrated how newer CSS features — particularly trigonometric functions — can be leveraged to accomplish a “pop-out” hover effect. This is what we made together:

Even the rotation is possible with clip-path polygon():

We can define the shape with three parameters:

  • The number of spikes (we’ll call this N);
  • The radius of the big circle, illustrated in green (we’ll call this R);
  • The radius of the small circle illustrated in blue (this will be R - d).

For the sake of simplicity, I will define d as a percentage of RR - (R * p) — where p is a number in the range [0 1]. So, in the end, we are left with three variables, N, R, and p.

If you look closely at the shape, you can see it is a series of triangular shapes that are cut out of a large circular shape. That is exactly how we are going to tackle this challenge. We can create triangles with conic-gradient and then cut them out of the circle with the mask-composite property. Getting a circle is pretty easy using border-radius: 50%.

The number of conic gradients is equal to the number of triangles in the pattern. Each gradient can use nearly the same configuration, where the difference between them is how they are rotated. That means the gradient’s code will look something like this:

conic-gradient(from -1*angle at {position}, #000 2*angle, #0000 0);

Thankfully, the position we calculated in the last article is similar enough to the point that we can rely on it here as well:

50% + (50% * (1 - p)) * cos(360deg * i/N)
50% + (50% * (1 - p)) * sin(360deg * i/N)

Again, N is the number of triangles, and p controls the radius of the small circle. R is equal to 50%, so the position can also be expressed like this:

R + (R * (1 - p)) * cos(360deg * i/N)
R + (R * (1 - p)) * sin(360deg * i/N)

We need to resort to some geometry to determine the value of angle. I will skip the boring math for the sake of brevity, but please feel free to leave a comment if you’re interested in the formula, and I will be glad to give you more details.

angle = atan(sin(180deg/N)/(p - 1 + cos(180deg/N)))

Now, we need to loop through all of that as many times as there are triangles in the pattern. So, we will do what we did in the last article and switch from vanilla CSS to Sass so we can take advantage of Sass loops.

The following snippet selects the one element in the HTML, <img>, and loops through the conic gradients for as many triangles we set ($n: 9). The output of that loop is saved as another variable, $m, that is applied to the CSS mask.

$n: 9;  /* number of spikes */

img {
--r: 160px; /* radius */ --p: 0.25; /* percent */ --angle: atan(sin(180deg/#{$n}) / (var(--p) - 1 + cos(180deg/#{$n}))); width: calc(2 * var(--r)); aspect-ratio: 1; border-radius: 50%; $m: (); @for $i from 0 through ($n - 1) { $m: append($m, conic-gradient( from calc(90deg + 360deg * #{$i/$n} - var(--angle)) at
calc(50% + (50% * (1 - var(--p))) v cos(360deg * #{$i/$n})) calc(50% + (50% * (1 - var(--p))) * sin(360deg * #{$i/$n})), #000 calc(2*var(--angle)), #0000 0), comma ); } mask: $m; }

Here’s the result of all that work:

Next, we add the scale effect to the image’s :hover state:

img {
  --f: 1.2; /* the scale factor */
  /* etc */
}
img:hover {
  scale: var(--f);
}

To make sure both starburst shapes have identical sizes (in the non-hover and hover states), --i needs a formula based on the scale factor:

img {
  --f: 1.2; /* the scale factor */
  /* etc */
}
img:hover {
  --i: calc(var(--r) * (1 - var(--p)) * (var(--f) - 1) / var(--f));
  scale: var(--f);
}

And, now, we are finally finished.

See the Pen Fancy Pop Out hover effect! by Temani Afif.

Another Example

Let’s try another fancy effect where the avatar is hidden, and on hover, it slides from the bottom to “pop out” while, at the same time, we update the starburst shape.

See the Pen Fancy Pop Out Reveal hover effect! by Temani Afif.

Cool, right? We are still using only one <img> element in the markup, but this time, I introduced the sliding effect. This will be your homework! I will let you dissect the code to understand what I have changed.

Hint: A CSS Tip where I am using the sliding effect.

Wrapping Up

I hope you enjoy having a little extra practice on the techniques we used in the previous article to create this “pop-out” hover effect. If it feels like I went a little faster this time around, it’s because I did. Rather than spending time explaining the same concepts and techniques, I was more concerned with demonstrating them in a slightly different context. So, we learned a few new ideas for working with gradients in CSS masks and background images!

In spite of the complexity of everything we covered, there is nothing that requires you to understand everything at once or even right away. Take the time to go through this and the previous article step-by-step until you grasp the parts that are toughest for you to grok. In all honesty, you will probably never find yourself in a situation where you need to use all these tricks together. This was a pretty niche exercise. But it provides us with an excuse to individually inspect the techniques that can help you solve some complex problems in CSS without resorting to scripting or extra HTML.

As for the math and the formulas, you don’t need to accurately understand them. The goal is to demonstrate that we can be as accurate as we want when it comes to calculating values and still develop something that is incredibly maintainable with only a few variables. Without trigonometric functions and calc() in CSS, we would be obliged to manually set all of the values once we need to update something, which would be incredibly tedious.

I’ll close this little series with a last demo. Enjoy!

See the Pen Pop out hover effect featuring Kevin and Alvaro by Temani Afif.