A Guide To Keyboard Accessibility: HTML And CSS (Part 1)

Keyboard accessibility is an important part of the user experience. There are multiple criteria in Web Content Accessibility Guidelines (WCAG) about this topic. Still, it’s somehow overlooked, affecting the experience of many users, mainly people with motor disabilities — any condition that limits movement or coordination.

Certain conditions like having a broken arm, the loss or damage of a limb, muscular dystrophy, arthritis, and some others can make it impossible for a person to use a mouse to navigate a site. So, making a site navigable via keyboard is a very important part of ensuring the accessibility and usability of our websites.

The importance of making a site accessible for users with motor disabilities becomes even more evident when you learn that they have access to more assistive technology options. Keyboards are not even the main focus of motor disability assistance! There are tools like switches that you use with your hand (or even with your head) to work with any device, which helps a lot for people with more severe motor disabilities. You can see how those technologies work in this demonstration made by Rob Dodson or in this video of Christopher Hills.

In this article, I’ll cover how to use HTML and CSS to create an accessible experience for keyboard users while mentioning what WCAG criteria we should keep into consideration.

HTML

One of the basics of creating a website accessible site for keyboard users is knowing what elements should be navigable via keyboard. For this, a good HTML semantic is crucial because it’ll indicate the kind of elements we want to focus on with keyboard navigation.

The Basics

When a user presses the Tab key, it’ll let them select the next focusable element in the HTML, and when they press the keys Shift + Tab, it’ll take them to the last focusable element. With that said, what elements need to be focusable? Anything that requires user interaction. Between them, you can find the elements button, a, input, summary, textarea, select, and the controls of elements audio, and video (when you add the attribute controls to them). Additionally, certain attributes can make an element keyboard navigable, such as contenteditable or tabindex. In the case of Firefox, any area with a scroll will also be keyboard focusable.

Additionally to that, you can:

  • Activate the button, select, summary, and a elements using the Enter Key. Keep in mind that except for the a element, you can activate them with the Space Key as well.
  • Use the arrow keys to navigate between different input with the type radio if they share the same name attribute.
  • Check those inputs using the Space key (keep in mind that when you navigate with the arrow keys radio inputs, it’ll be checked once the keyboard is focused, but that doesn’t happen with checkbox inputs).
  • Use the up and down keys to navigate between the different options of a select element.
  • Close the select element displayed list and multiple input popups.
  • Use the arrow keys to scroll vertically or horizontally a document.

There are probably more interactions, some of which depend on differences between operating systems and browsers, but that covers mostly what you can do with the keyboard.

Does that mean those elements are automatically keyboard-accessible by default? A good HTML structure is very helpful, and it makes content mostly accessible by default, but you still need to cover some issues.

For example, certain input types like date, datetime-local, week, time, and month have popups that can work differently between browsers. Chrome, for example, allows you to open the date picker popup by pressing the Enter or Space key in a designated button in the input. However, with Firefox, you need to press Enter (or Space) in either the day, month, or year fields to open the popup and modify each field from there.

This lack of consistency can be a bit off-putting, and maybe it’s just a matter of personal preference. Still, I feel that the Firefox experience is not very intuitive, which leads to thinking that, arguably, one of those experiences is more keyboard-accessible than the other. So if you want to create a good, accessible, and consistent keyboard experience between browsers, you’d need more than HTML for that. If you're going to try it yourself, check this compilation of input types by MDN and navigate them by yourself.

Additionally to the previous point, certain components require elements to be keyboard focusable without being natively selectable. In other cases, we need to manage keyboard focus manually, and our markup needs to help us with that. For both cases, we’ll need to use an HTML attribute that will help us with this task.

tabindex Attribute

This attribute will greatly help us bring keyboard accessibility to more complex component patterns. This attribute receives an integer, and properly using it will help us make a DOM element keyboard focusable. With tabindex, we can find three different cases:

tabindex="0"

It causes the element to be keyboard focusable. You usually don’t want to add keyboard focus to an element unless it is not interactive, but some scenarios will require it.

One of them is when you have a component with a scroll beside the body element. Otherwise, keyboard users won’t be able to see the full extent of the content. Some components that could have this trouble are scroll-based carrousels, tables, and code snippets. Just to give an example, any code snippet created with the help of prism.js has the attribute tabindex="0". Open prism.js’ site and navigate it using the Tab key. You’ll be able to focus the snippets and control the vertical and horizontal scroll using the arrow keys.

Some people who start with web accessibility think it is a good idea to add the attribute tabindex="0" to every element because they think it’ll help screen reader users navigate easily through a site. This is a terrible practice because of two reasons:

  1. Screen reader users have multiple ways to navigate a site. They can jump between headers, landmarks, or form elements, and they don’t need that extra help to create an accessible site as long as the markup is appropriate.
  2. It can make keyboard navigation difficult because a user will have to press the Tab key many times to arrive at the desired content, and for certain motor disabilities, having too many focusable elements can create a physically painful experience.

So, to summarize: it’s a useful technique for some components, but most of the time, you’ll be alright if you don’t use it, and certainly, you must not use it in every single element of your site.

Negative tabindex

Before we start this section, we need to keep in mind two concepts: a DOM element is at the same time focusable (that means, you can programmatically focus on it with JavaScript) and tabbable (that means, being able to be selected with the Tab Key).

With that in mind, here is where negative tabindex comes into play because it’ll make an element unable to be tabbed (but you can still focus on it with JavaScript). This is important for specific components because, in some cases, we’ll need to make a normally tabbable element unable to be tabbed, or we’ll need an element to be focusable but not tabbable.

One example of that is tabs. A recommended pattern for this component is ensuring that when you press the Tab key when you’re located in the active tab, it goes to the active tabpanel instead of bringing the focus to the next tab. We can achieve that by adding a negative tabindex to all non-active tabs like this:

<ul role="tablist">
  <li role="presentation">
    <button role="tab" href="#panel1" id="tab1" aria-selected="true">Tab one</button>
  </li>
  <li role="presentation">
    <button role="tab" href="#panel2" id="tab2" tabindex="-1">Tab two</button>
  </li>
  <li role="presentation">
    <button role="tab" href="#panel3" id="tab3" tabindex="-1">Tab three</button>
  </li>
  <li role="presentation">
    <button role="tab" href="#panel4" id="tab4" tabindex="-1">Tab four</button>
  </li>
</ul>

We’ll see more examples later about how a negative tabindex will help us to have more control over focus state management in different components, but keep in mind a negative tabindex will be important in those cases.

Finally, you can put any negative integer in the tabindex property, and it’ll work the same. tabindex="-1" and tabindex="-1000" will make no difference, but my mere convention is that we tend to use -1 when we use this attribute.

Positive tabindex

A positive tabindex will make the element keyboard focusable, but the order will be defined according to the used integer. That means that first, the keyboard will navigate all elements with the attribute tabindex="1", then the ones with tabindex="2", and after all elements with a positive tabindex have been navigated, it’ll take into account all interactive elements by default and those with the attribute tabindex="0". This is known as the tabindex-ordered focus navigation scope.

Now, this is a pattern that you shouldn’t use. You’ll be better if you put the required focusable elements in your site in the order you need. Otherwise, you could create a very confusing experience for keyboard users, which would make a failure of the WCAG criterion 2.4.3: Focus order.

“If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.”

Success Criterion 2.4.3: Focus order

It might be useful if you want keyboard users to focus on widgets before they reach the page content, but that’d be a bit confusing for assistive technology users (like screen readers). So again, you’d be better by creating a logical order in the DOM.

inert Attribute

I have to quickly note an incoming attribute that will help us a lot with keyboard accessibility called inert. This attribute will make the content inaccessible by assistive technologies.

Now you might be asking yourself how this can be useful because if something removes keyboard accessibility, but in some cases, that’s a good thing! One component that will benefit from it is modals. Adding this attribute to all elements in the site except this modal will make it easy to create a focus trap. So you’ll ensure the user can’t accidentally navigate to other parts of the site using the Tab key unless they close that modal. Right now, creating a keyboard trap requires quite some thinking with JavaScript (I’ll explain how in the second part of this guide). So, having a way to make it easier with this attribute will be handy.

Sounds pretty cool, right? Well, unfortunately, this attribute is not recommended to be used yet. If you check the caniuse.com entry about this attribute, you’ll notice it’s very recent; Opera doesn’t have support for it yet. The most recent implementation of it was version 105 of Firefox, and at the moment of writing this article, it’s a beta version! So, it’s still very early to do it. There is a polyfill for inert attribute, but right now, it’s a bit performance costly. So, I seriously suggest not using it now for production. But once we have adequate support for this attribute, some component patterns will be easier to create.

CSS

CSS is an essential tool for keyboard accessibility because it allows us to create a level of customization of the experience, which is important for compliance with WCAG 2.2 criteria. Additionally, CSS has multiple selectors with different uses that will help to create a good keyboard experience, but be careful because a bad use of certain properties can be counterproductive. Let’s start diving into the use of this language to create an accessible experience for keyboard users.

Focus Indicator

When you use a mouse, you can see which element you can interact with it thanks to the cursor, and you wouldn’t remove the cursor from your user, right? That’d make them unable to know what element they want to use!

We have a similar concept for keyboard navigation, and it’s called a focus indicator, which by default is an outline that surrounds a keyboard-focusable element when it’s selected by it. Being sure all your keyboard-focusable elements have a focus indicator is essential to making a website keyboard accessible, according to WCAG criteria:

“Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.”

Success Criterion 2.4.7: Focus Visible

This style is different depending on the browser you’re using. You can see how it looks in the various browsers in those pictures by default and when you use the CSS property color-scheme set to dark just to check out how the default styles would behave in dark mode.

As you can notice, Chromium-based browsers like Chrome or Edge have a black and white outline, which works in light and dark mode. Firefox opted for a blue outline which is noticeable in both modes. And Safari (and Webkit-based browsers because right now, all iOS browsers use Webkit as their browser engine) looks almost the same as Firefox but with an even subtler outline for a dark color scheme.

WCAG Criterion 2.4.11

Now, default focus indicators are visible, but are they enough? The answer is “no”. While it can work in some cases, people with visual impairments will have problems noticing it, so WCAG created the Success Criterion 2.4.11 — Focus appearance to make an accessible focus indicator. Right now, this criterion is part of WCAG 2.2, which is a Candidate Recommendation. So it’s quite unlikely it will change before the final release, but keep in mind that it’s still subject to changes.

When the keyboard focus indicator is visible, one or both of the following is true:
  1. The focus indicator:
    • encloses the visual presentation of the user interface component, and
    • has a contrast ratio of at least 3:1 between the same pixels in the focused and unfocused states, and
    • has a contrast ratio of at least 3:1 against adjacent colors.
  2. An area of the focus indicator meets all the following:
    • is at least as large as the area of a 1 CSS pixel thick perimeter of the unfocused component, or is at least as large as a 4 CSS pixel thick line along the shortest side of the minimum bounding box of the unfocused component, and
    • has a contrast ratio of at least 3:1 between the same pixels in the focused and unfocused states, and
    • has a contrast ratio of at least 3:1 against adjacent non-focus-indicator colors, or is no thinner than 2 CSS pixels.

Where a user interface component has active sub-components, if a sub-component receives a focus indicator, these requirements may be applied to the sub-component instead.

Success Criterion 2.4.11 Focus Appearance

There is something important to consider here, and that’s the area of the focus indicator. This area needs to meet the contrast requirements of this criterion. To illustrate that, I’ll use an example Sara Soueidan made for her article “A guide to designing accessible, WCAG-compliant focus indicators.”

This example uses an outline, but remember that you can use other properties to determine the focus state, like background-color or some creative ways of using box-shadow as long as it’s compliant with the requirements. However, don’t use the property outline: none to eliminate the element’s outline.

That’s important for Windows High Contrast Mode because when it’s active, your website colors will be replaced with ones chosen by the user. So depending on properties like background-color will have no effect there. Instead, use the CSS declaration outline-color: transparent with the appropriate thickness to comply with WCAG criteria. You can see examples of how it works in my article about Windows High Contrast Mode.

The Optimal Outline Size

An easy way to create a compliant focus indicator is using this method Stephanie Eckles suggested in her talk Modern CSS Upgrades To Improve Accessibility. First, we set custom properties in the interactive elements. Remember you can add more elements to the rule depending on the complexity of your project:

/* Add more selectors inside the :is rule if needed */

:is(a, button, input, textarea, summary) {
    --outline-size: max(2px, 0.08em);
    --outline-style: solid;
    --outline-color: currentColor;
}

And then, we use those custom properties to add a global focus rule:

:is(a, button, input, textarea, summary):focus {
    outline:
      var(--outline-size)
      var(--outline-style)
      var(--outline-color);
    outline-offset: var(--outline-offset, var(--outline-size));
}

The use of 0.08em here is to give it a bigger outline size if the font is bigger, helping to scale the element’s contrasting area better with the element’s font size.

Keep in mind that even when WCAG mentions that the focusing area “is at least as large as the area of a 1 CSS pixel thick perimeter of the unfocused component”, it also mentions that it needs to have “a contrast ratio of at least 3:1 against adjacent non-focus-indicator colors, or is no thinner than 2 CSS pixels.” So, a minimum thickness of 2px is necessary to comply with WCAG.

Remember that you might need a thicker line if you use a negative outline-offset because it’ll reduce the perimeter of the outline. Also, using a dashed or dotted outline will reduce the focused area roughly by half, so you’ll need a thicker line to compensate for it.

The outline’s ideal area is related to the perimeter of the element. Sara Soueidan once again did a great job explaining how this formula works in her article about focus indicators. So check it out if you want to understand better the maths behind this matter and how to apply them.

CSS Focus-related Selectors

With CSS, you normally use the pseudo-class :focus to give style to an element when it’s being focused by a keyboard, and it does its job well. But modern CSS has given us two new pseudo-classes, one that helps us with a certain use case and the other that solves an issue that happens when we use the focus pseudo-class. Those pseudo-classes are :focus-within and :focus-visible. Let’s dive into what they do and how they can help us with keyboard accessibility:

:focus-within

This pseudo-class will add a style whenever the element is being focused or any of the element’s children is also being focused. Let’s make a quick example to show how it looks:

<form>
  <label for="name">
    Name: 
    <input id="name" type="text">
  </label>
  <label for="email">
    Email:
    <input for="email" type="email">
  </label>
  <button>Submit</button>
</form>

Very quick tangent note: Consider not using label to wrap the input element. While it works in all browsers, it doesn’t work well with Dragon speech recognition software because it won’t be recognized appropriately.

form {
  display: grid;
  gap: 1em;
}

label {
  display: grid;
  gap: 1em;
  padding: 1em;
}

label:focus-within {
  background-color: rebeccapurple;
  color: white
}

This pseudo-class is interesting to enrich the styles of certain components, as previously shown, but in others, it helps a lot to make content accessible for keyboard users. For example, I created a card for my article about media queries hover, pointer, any-hover, and any-pointer. This card shows the content when the user puts the cursor on it, but it also shows the content when you focus the button inside of it using the :focus-within pseudo-class by using the same rules that are triggered on hover. You can check out the code in the mentioned article as well as in this CodePen:

If you use keyboard navigation, you’ll notice the order is pretty straightforward. It reads from left to right and from top to bottom, and the navigation will be the same. Now let’s use grid properties to make some changes:

ul li:where(:nth-child(1), :nth-child(5), :nth-child(7), :nth-child(9)) {
  grid-row: span 2;
  grid-column: span 2
}

ul li:where(:nth-child(1), :nth-child(5)) {
  order: 2;
}

ul li:where(:nth-child(7), :nth-child(8)) {
  order: -1;
}

ul li:nth-child(4) {
  grid-row: 3;
  grid-column: 2 / span 2;
}

ul li:nth-child(3) {
  grid-row: 5 / span 3;
  grid-column: 3;
}

Now it looks completely disarrayed. Sure, the layout looks funny, but when you start navigating it with the Tab key, it’ll have a very random order. There is some degree of predictability now because I used numbers as the button’s label, but what happens if they have different content? It’d be impossible to predict which would be the next button to be focused on with a keyboard.

This is the kind of scenario that needs to be avoided. It doesn’t mean you can’t explicitly order an element within a grid or use the order property. That means you need to be careful with managing your layouts and be sure the visual and DOM order matches as much as possible.

By the way, if you want to try it by yourself, you can see the demo of this code here and experience this chaotic keyboard navigation by yourself:

Now let’s start styling this component! By default, this element uses this triangle to indicate if the details element is opened or closed. We can remove that by adding this rule to the summary element.

summary {
  list-style: none;
}

But we’ll still need a visual indicator to show if it’s opened or closed. My solution is to add a second element as a child of summary. The important part is that this element will have the attribute aria-hidden="true":

<summary>
  <p>
    How much does shipping cost?
  </p>
  <span aria-hidden="true"></span>
</summary>

The reason why I hid this span element is that we’ll be modifying its content with CSS modifying the pseudo-element ::before, and the content we add will be read by a screen reader unless, of course, we hide it from them.

With that said, we can change it because the browser manages the open state of the details element by adding the attribute open to the container. So we can add and change the content using those CSS rules:

summary span[aria-hidden="true"]::before {
  content: "+";
}

details[open] summary span[aria-hidden="true"]::before {
  content: "-";
}

Now, you can add the styling you need to adapt it (remember to use adequate focus states!). You can check this demo I made to see how it works. Test it with a keyboard, and you’ll notice you can interact with it without a problem.

But there can be multiple skip links in a site that will lead you to various parts of the site, as Smashing Magazine does. When you use the Tab Key to navigate this website, you’ll notice there are three skip links, all of them taking you to important points of the page:

They’re usually located on the site’s header, but it’s not always the case. You can add them where needed, as Manuel Matuzović shows in this tweet. He added an inline skip link to a project because the interactive map has a lot of keyboard-focusable elements.

Working on a feature that allows users to skip areas with many tab stops (inline skip link). 🔥

Video alt: A page with a bunch of links followed by an embedded map. Pressing the Tab key reveals a link that, when activated, moves focus to the next tabbable element after the map. pic.twitter.com/utSPgzs2Kh

— Manuel Matuzović (@mmatuzo) April 6, 2022

Now, as the usefulness of skip links is clear, let’s create one. It’s very simple; we just need to create an a element that takes you to the desired element:

<header>
  <a class="skip-link" href="#main-content">Go to main content</a>
</header>
<main id="main-content"></main>

Next, we need to hide visually the a element. What I do there is use the transform CSS property to remove it from the visual range:

.skip-link {
    display: block;
    transform: translate(-9999px);
}

Then, we move it to the needed position when the element is being focused:

.skip-link:focus {
  transform: translate(0)
}

And that’s it! Creating a skip link is easy and offers a lot of help for keyboard accessibility.

Tooltips

Those little text bubbles that show extra information to an element can be done with pure CSS as well, but a little disclaimer here: it is suggested that you can close a tooltip by pressing the Escape key, which it’s only possible with JavaScript. I’ll explain how to add this feature in the second part of this article, but everything else can be done in a very simple way using HTML and CSS only.

A common problem with tooltips is that a keyboard user cannot see them, so we need to ensure the component that triggers it is a keyboard-focusable element. Our best bet here is using the button element. The semantics is really simple, as Heydon Pickering shows in his book Inclusive Components.

<div class="tooltip-container">
  <button>
  </button>
  <div role="tooltip"></div>
</div>

The container with the class tooltip-container is there just to allow us to manipulate the container’s position with the attribute role="tooltip" later using CSS. Speaking of this element, you would think this role adds enough semantics to make it work, but as a matter of fact, it doesn’t, so we’ll have to rely upon a couple of aria attributes to link it to our button.

This attribute depends of what’s the intention of the tooltip. If you are planning to use it to name an element, you need to use the attribute aria-labelledby:

<div class="tooltip-container">
  <button aria-labelledby="tooltip1">
    <svg aria-hidden="true">
      <!--  SVG Content  -->
    </svg>
  </button>
        <div id="tooltip1" role="tooltip">Shopping cart</div>
</div>

However, if you want to use the tooltip to describe what an element does, you’ll need to link it using the attribute aria-describedby:

<div class="tooltip-container">
  <button aria-label="Shopping cart" aria-describedby="tooltip2">
    <svg aria-hidden="true">
      <!--  SVG Content  -->
    </svg>
  </button>
  <div id="tooltip2" role="tooltip">Check, modify and finish your purchase</div>
</div>

Be careful with this approach; use it only to give auxiliary descriptions, not to give information that is absolutely necessary to understand what this element does. That’s because when a screen reader user generates a list of the form elements (including buttons) in the site, the description won’t be displayed unless the user is focusing on the element, as Adrian Roselli shows in his test on aria-description attribute.

Now, it’s time to talk about what concerns us in this article — keyboard accessibility! For this, we need to hide the tooltip and show it until the user is either focusing the pointer on the element or when it’s being focused with a keyboard. For this, we’ll use the :hover and :focus pseudo-classes in tandem with the adjacent sibling combinator.

Additionally, it’s important you can see the tooltip when you hover over it to comply with WCAG Criterion 1.4.13: Content on Hover or Focus. With those considerations in mind, this is how our code should look:

[role="tooltip"] {
  position: absolute;
  bottom: 0;
  left: 50%;
  display: none;
  transform: translate(-50%, 100%);
} button:hover + [role="tooltip"], button:focus + [role="tooltip"], [role="tooltip"]:hover { display: block; }

And this is how you create a keyboard-accessible tooltip using HTML and CSS. You can check how both examples of tooltip behave in this demo. Remember, this is not fully ready for production. You need JavaScript to close the tooltip when you press the Esc key. We’ll cover that later in the next part of this article, so keep it in mind.

See the Pen Tooltip demo - CSS only [forked] by Cristian Diaz.

As Heydon mentions in his book, tooltips have a problem when you use them for devices that don’t have a pointer, like cellphones or tablets, then a different approach for them is required. You can use CSS for that using the media queries hover and pointer, as I explain in my article.

Wrapping Up

Keyboard accessibility is an essential part of accessibility. I hope this article has helped you understand how vital HTML and CSS are to make keyboard navigation a good and accessible user experience. That’s not the end of keyboard accessibility, though! I’ll be covering how we can use JavaScript to manipulate keyboard navigation and how we can use it in more complex component patterns.

Building an Interactive Sparkline Graph with D3

D3 is a great JavaScript library for building data visualizations using SVG elements. Today we’re going to walk through how to use it to build a simple line graph with an interactive element, taking inspiration from the NPM website.

What we’re building

Visit any individual package page on the NPM website and you’ll notice a small line graph on the right, showing the total weekly downloads trend over a period, with the total figure to the left.

Screenshot of the D3 NPM page with sparkline graph shown on the right

This type of chart is known as a sparkline. If you hover over the graph on the NPM site, you can scrub through the trend and see the weekly downloads figure for the preceding week, marked by a vertical line and circle. We’re going to build something similar, with a few of our own adaptations. If you’d rather jump straight into the code yourself, you can find the complete demo here.

It’ll come in handy if you have some familiarity with SVG elements. SVGs use their own internal co-ordinate system. For a primer I recommend this timeless article by Sara Soueidan on SVG Coordinate Systems and Transformations.

Data

The first thing we’ll need is some data to work with. I’ve created an API endpoint we can use to fetch some data here. We could use the Fetch API to retrieve it, but instead we’re going to use D3, which conveniently parses the data for us.

HTML

Now let’s add some HTML. We should make sure that our markup makes sense without JS in the first instance. (Let’s assume that our weekly downloads total is a known value, perhaps coming from a CMS.)

We’re adding some data attributes that we’ll need to reference in our JS.

<div class="chart-wrapper" data-wrapper>
	<div>
		<h3 data-heading>Weekly downloads</h3>
		<p data-total>800</p>
	</div>
	<figure data-chart></figure>
</div>

If we choose to, we could append a static image to the figure element, to display while our data is loading.

CSS

We’re going to use D3 to draw an SVG chart, so we’ll include some base CSS styles to set a maximum width on the SVG and center the component within the viewport:

* {
	box-sizing: border-box;
}

body {
	min-height: 100vh;
	display: grid;
	place-items: center;
}

figure {
	margin: 0;
}

svg {
	width: 100%;
	height: auto;
}

.chart-wrapper {
	max-width: 600px;
}

D3

To use the D3 library we’ll first need to add it to our project. If you’re using a bundler you can install the NPM package and import it as follows:

import * as d3 from 'd3'

Otherwise, you can download it direct from the D3 website. D3 is quite a large library, and we’re only going to use parts of it for our line graph. Later on we’ll look at how to reduce the size of our bundle by only importing the modules we need.

Now we can fetch the data using D3’s json method:

d3.json('https://api.npoint.io/6142010a473d754de4e6')
	.then(data => {
		console.log(data)
	})
	.catch(error => console.log(error))

We should see the data array logged to the console in our developer tools.

Preparing the data

First let’s create a function for drawing our chart, which we’ll call once we’ve successfully fetched the data:

const draw = (data) => {
	console.log(data)
}

d3.json('https://api.npoint.io/6142010a473d754de4e6')
	.then(data => {
		draw(sortedData)
	})
	.catch(error => console.log(error))

We should still see our data logged to the console. But before we can draw our chart, we’ll need to sort the data array by date. Currently our data array looks something like this, with the dates as strings:

[
	{ 
		date: "2021-12-23T04:32:20Z",
		downloads: 445
	},
	{ 
		date: "2021-07-20T13:41:01Z",
		downloads: 210
	}
	// etc.
]

We’ll need to convert the date strings into JavaScript date objects. Let’s write a function that first of all converts the string to a date object, then sorts the values by date in ascending order, using D3’s ascending method:

const sortData = (data) => {
	/* Convert to date object */
	return data.map((d) => {
		return {
			...d,
			date: new Date(d.date)
		}
	})
	/* Sort in ascending order */
	.sort((a, b) => d3.ascending(a.date, b.date))
}

We’ll pass the sorted data into our draw function:

fetch('https://api.npoint.io/897b3f7b5f6a24dcd0cf')
	.then(response => response.json())
	.then(data => {
		const sortedData = sortData(data)
		draw(sortedData)
	})
	.catch(error => console.log(error))

Drawing the chart

Now we’re ready to start creating our data visualization. Let’s first of all define the dimensions of our chart, which we’ll use to draw the SVG at the required size:

const dimensions = {
	width: 600,
	height: 200
}

In our draw function, we’re going to use D3’s select method to select the wrapper element containing our figure, heading and downloads count:

/* In `draw()` function */
const wrapper = d3.select('[data-wrapper]')

D3 selections are more powerful than using querySelector, as they allow us to bind data to DOM elements, as well as easily append elements and add or modify attributes. We can then select the figure element and append a new SVG, using our pre-defined dimensions to set the viewbox:

/* In `draw()` function */
const svg = wrapper
	/* Select the `figure` */
	.select('[data-chart]')
	/* Append the SVG */
	.append('svg')
	.attr('viewBox', `0 0 ${dimensions.width} ${dimensions.height}`)

If we inspect our page, we should now see an SVG element is present inside the figure, but it’s not yet visible as we haven’t given it any color. It might be a good idea to add an outline in our CSS, so that we can easily see that the SVG has been created!

svg {
	width: 100%;
	height: auto;
	outline: 1px solid;
}

You might notice a jump in the layout once the SVG is created. We can fix that by adding an aspect ratio to the figure element. That way it’ll be rendered at the correct height straight away (in browsers that support the aspect-ratio property).

figure {
	margin: 0;
	aspect-ratio: 6 / 2;
}

Drawing the line

So far so good, but here’s where things get a little more complex. Don‘t worry, we’ll walk through it step-by-step!

We’re going to draw the trend line on our chart by appending a path element. But before we can do that, we need to create the scales that will enable us to plot the data within the SVG co-ordinate system. (For more on this, read the tutorial Introduction to D3’s scales by Observable.)

Accessor functions

In Amelia Wattenberger’s book, Fullstack D3 and Data Vizualisation, she recommends creating accessor functions to return the x and y values for any given data point. We’re going to need to refer to those values quite a bit, so let’s do that now.

const xAccessor = (d) => d.date
const yAccessor = (d) => d.downloads

It may seem unnecessary given their simplicity, but if we ever need to make any changes (say, a dataset with a different set of keys) we’ll be grateful to have just one place to update those values!

Scales

Our chart’s x-axis will be time-based — using the date values from our data, while the y-axis will use a linear scale to plot the number of downloads. We’ll need D3’s scaleTime and scaleLinear methods respectively.

When creating our scales we need to set both the domain and the range properties. The domain contains the smallest and largest data values that need to be plotted. The range contains the dimensions onto which we’ll plot the data. D3 does the work behind the scenes to scale the domain to the range and plot the position of each data point accordingly. The concept is illustrated in this demo. Hover over the range area and you’ll see the pointer’s position scaled within the domain area.

See the Pen
D3 domain/range
by Michelle Barker (@michellebarker)
on CodePen.0

As our data is already sorted in the correct order, the domain value for the x-axis will be an array containing the date values of our first and last data items:

/* In `draw()` function */
const xDomain = [data[0].date, data[data.length - 1].date]

This is where our accessor functions come in. We could instead use the xAccessor() function to get the desired values for the x-axis:

/* In `draw()` function */
const xDomain = [xAccessor(data[0]), xAccessor(data[data.length - 1])]

However, there is a simpler way, using D3’s extent method. We pass in our data array and the accessor function, and it returns the highest and lowest values as an array. It works even if the data is unsorted.

/* In `draw()` function */
const xDomain = d3.extent(data, xAccessor)

The range is simpler still: As our line will need to go all the way across our SVG, from left to right, our range will go from 0 to the SVG viewbox width.

/* In `draw()` function */
const xDomain = d3.extent(data, xAccessor)

const xScale = d3.scaleTime()
	.domain(xDomain)
	.range([0, dimensions.width])

Our y-axis will be similar, but with a small difference: If we use only the smallest and largest values for the domain, our trend line may appear to fluctuate wildly with even a small difference in the number of downloads. For example, if the number of downloads stayed fairly steady at between 1000 and 1100 per day, our chart would nonetheless display a line that zig-zags right from the bottom to the top of the chart, because a narrow domain is mapped to a (comparatively) wide range. It would be better if we mapped our domain with the lowest value as zero (as it’s impossible to have a negative number of downloads!).

So for the y-axis we’ll set the domain in a slightly different way, using D3’s max function to return only the highest value. We’ll also use the height instead of width from our dimensions object for the range, and D3’s scaleLinear method (which creates a continuous scale) rather than scaleTime.

You might notice that we’ve flipped the range values in this case. That’s because the SVG co-ordinate system begins with 0 at the top, and higher values move an SVG element downwards. We need the low values in our domain to be displayed further down the SVG view box than high values — which in fact means mapping them to higher viewbox co-ordinates!

/* In `draw()` function */
const yDomain = [0, d3.max(data, yAccessor)]

const yScale = d3.scaleLinear()
	.domain(yDomain)
	.range([dimensions.height, 0])
Illustration showing the chart’s axis increasing from bottom to top, whereas the viewbox y co-ordinates increase from top to bottom

Line generator

Once we have our scales set up, we can use D3’s line() function to plot the path scaled to fit our SVG viewbox. We’ll create a line generator:

const lineGenerator = d3.line()
	.x((d) => xScale(xAccessor(d)))
	.y((d) => yScale(yAccessor(d)))

Then we’ll append a path element to our SVG, and use the line generator for the d attribute (the attribute that actually defines the shape of the path). We’ll use the datum() method to bind the data to the path element. (Read more about data binding in this article.)

/* In `draw()` function */
const line = svg
	/* Append `path` */
	.append('path')
	/* Bind the data */
	.datum(data)
	/* Pass the generated line to the `d` attribute */
	.attr('d', lineGenerator)
	/* Set some styles */
	.attr('stroke', 'darkviolet')
	.attr('stroke-width', 2)
	.attr('stroke-linejoin', 'round')
	.attr('fill', 'none')

We’re also setting some styles for the fill and stroke of the path. You should now see the plotted path.

Creating the filled area

Now that we have our line, our next step is to create the filled area below the path. We could try setting a fill color on our line:

/* In `draw()` function */
line.attr('fill', 'lavender')

Unfortunately that won’t produce the desired effect!

Purple line with light purple fill

Luckily, D3 has an area() function that works similarly to line(), and is designed exactly for this use case. Instead of a single y parameter, it requires two y values: y0 and y1. This is because it needs to know where to start and end the filled area. In our case, the second y value (y1) will be the height value from our dimensions object, as the area needs to be filled from the bottom of the chart.

/* In `draw()` function */
const areaGenerator = d3.area()
	.x((d) => xScale(xAccessor(d)))
	.y1((d) => yScale(yAccessor(d)))
	.y0(dimensions.height)

Just like the line before, we’ll append a path element to the SVG and pass in the area generator for the d attribute.

/* In `draw()` function */
const area = svg
	.append('path')
	.datum(data)
	.attr('d', areaGenerator)
	.attr('fill', 'lavender')

At this point our filled area is partially obscuring the stroke of the primary line (you might notice the stroke appears thinner). We can fix this by changing the order so that we draw the filled area before the line within the draw() function. (We could also fix it with z-index in our CSS, but I prefer this way as it doesn’t require any additional code!)

Curved lines

Our line currently looks quite jagged, which is not especially pleasing to the eye. D3 provides us with a number of curve functions to choose from. Let’s add a curve to our line and area generators:

/* In `draw()` function */

/* Area */
const areaGenerator = d3.area()
	.x((d) => xScale(xAccessor(d)))
	.y1((d) => yScale(yAccessor(d)))
	.y0(dimensions.height)
	.curve(d3.curveBumpX)

/* Line */
const lineGenerator = d3.line()
	.x((d) => xScale(xAccessor(d)))
	.y((d) => yScale(yAccessor(d)))
	.curve(d3.curveBumpX)

Interaction

The next step is to add an interactive marker, which will move as the user hovers over the chart. We’ll need to add a vertical line, which will move horizontally, and a circle, which will move both horizontally and vertically.

Let’s append those SVG elements. We’ll give them each an opacity of 0, and position them on the far left. We only want them to appear when the user interacts with the chart.

/* In `draw()` function */
const markerLine = svg
	.append('line')
	.attr('x1', 0)
	.attr('x2', 0)
	.attr('y1', 0)
	.attr('y2', dimensions.height)
	.attr('stroke-width', 3)
	.attr('stroke', 'darkviolet')
	.attr('opacity', 0)
	
const markerDot = svg
	.append('circle')
	.attr('cx', 0)
	.attr('cy', 0)
	.attr('r', 5)
	.attr('fill', 'darkviolet')
	.attr('opacity', 0)

Now let’s use D3’s on method to move our markers when the user hovers. We can use the pointer method which, unlike clientX/clientY, will return the SVG co-ordinates of the pointer’s position (when the event target is an SVG), rather than the viewport co-ordinates. We can update the position of the markers with those co-ordinates, and switch the opacity to 1.

/* In `draw()` function */
svg.on('mousemove', (e) => {
	const pointerCoords = d3.pointer(e)
	const [posX, posY] = pointerCoords
	
	markerLine
		.attr('x1', posX)
		.attr('x2', posX)
		.attr('opacity', 1)
	
	markerDot
		.attr('cx', posX)
		.attr('cy', posY)
		.attr('opacity', 1)
})

Now we should see the line and circle moving with our cursor when we hover on the chart. But something’s clearly wrong: The circle is positioned wherever our cursor happens to be positioned, whereas it should follow the path of the trend line. What we need to do is get the x and y position of the closest data point as the user hovers, and use that to position the marker. That way we also avoid the marker being positioned in between dates on the x-axis.

Bisecting

To get the nearest value to the user’s cursor position, we can use D3’s bisector method, which finds the position of a given value in an array.

First we need to find the corresponding value for the position of the cursor. Remember the scales we created earlier? We used these to map the position of the data values within the SVG viewbox. But we can also invert them to find the data values from the position. Using the invert method, we can find the date from the pointer position:

/* In `draw()` function */
svg.on('mousemove', (e) => {
	const pointerCoords = d3.pointer(e)
	const [posX, posY] = pointerCoords
	
	/* Find date from position */
	const date = xScale.invert(posX)
})

Now that we know the exact date at any point when we’re hovering, we can use a bisector to find the nearest data point. Let’s define our custom bisector above:

/* In `draw()` function */
const bisect = d3.bisector(xAccessor)

Remember, this is equivalent to:

const bisect = d3.bisector(d => d.date)

We can use our bisector to find the closest index to the left or right of our position in the data array, or it can return whichever is closest. Let’s go for that third option.

/* In `draw()` function */
const bisect = d3.bisector(xAccessor)

svg.on('mousemove', (e) => {
	const pointerCoords = d3.pointer(e)
	const [posX, posY] = pointerCoords
	
	/* Find date from position */
	const date = xScale.invert(posX)
	
	/* Find the closest data point */
	const index = bisect.center(data, date)
	const d = data[index]
}

If we console log d at this point we should see the corresponding data object.

To get the marker position, all that remains is to use our scale functions once again, mapping the data value to the SVG co-ordinates. We can then update our marker positions with those values:

/* In the `mousemove` callback */
const x = xScale(xAccessor(d))
const y = yScale(yAccessor(d))

markerLine
	.attr('x1', x)
	.attr('x2', x)
	.attr('opacity', 1)

markerDot
	.attr('cx', x)
	.attr('cy', y)
	.attr('opacity', 1)

(Read more on D3 bisectors here.)

Updating the text

We also want to update the text showing the date range and the number of weekly downloads as the pointer moves. In our data we only have the current date, so at the top of the file let’s write a function that will find the date one week previously, and format the output. We’ll use D3’s timeFormat method for the formatting.

To find the date one week previously, we can use D3’s timeDay helper. This returns a date a given number of days before or after the specified date:

const formatDate = d3.timeFormat('%Y-%m-%d')

const getText = (data, d) => {
	/* Current date */
	const to = xAccessor(d)
	
	/* Date one week previously */
	const from = d3.timeDay.offset(to, -7)
	
	return `${formatDate(from)} to ${formatDate(to)}`
}

Then we’ll call this function to update the text on mouse move:

/* In the `mousemove` callback */
d3.select('[data-heading]').text(getText(data, d))

Updating the total downloads text is a simple one-liner: We select the element, and update the inner text with the corresponding value using our accessor function:

d3.select('[data-total]').text(yAccessor(d))

Resetting

Finally, when the user’s pointer leaves the chart area we should hide the marker and set the text to display the last known value. We’ll add a mouseleave callback:

/* In `draw()` function */
svg.on('mouseleave', () => {
	const lastDatum = data[data.length - 1]
	
	/* Hide the markers */
	markerLine.attr('opacity', 0)
	markerDot.attr('opacity', 0)
	
	/* Reset the text to show latest value */
	d3.select('[data-heading]').text('Weekly downloads')
	d3.select('[data-total]').text(yAccessor(lastDatum))
})

Prevent the marker being clipped

If you hover on one of the highest peaks in the line graph, you might notice that the circular marker is being clipped at the top. That’s because we’ve mapped the domain to the full height of our SVG. At the highest point, the center of the circle will be positioned at a y co-ordinate of 0. To fix that, we can add a margin to the top of our chart equivalent to the radius of the marker. Let’s modify our dimensions object:

const dimensions = {
	width: 600,
	height: 200,
	marginTop: 8
}

Then, in our yScale function, we’ll use a the marginTop value for our range instead of 0:

const yScale = d3.scaleLinear()
	.domain(yDomain)
	.range([dimensions.height, dimensions.marginTop]

Now our marker should no longer be clipped.

Color

Now that we have all the functionality in place, let’s turn our attention to customising our chart a little more. I’ve added some styles in the demo to replicate the layout of the NPM chart (although feel free to adapt the layout as you wish!). We’re going to add some bespoke color scheme options, which can be toggled with radio buttons. First we’ll add the radio buttons in our HTML:

<ul class="controls-list">
	<li>
		<input type="radio" name="color scheme" value="purple" id="c-purple">
		<label for="c-purple">Purple</label>
	</li>
	<li>
		<input type="radio" name="color scheme" value="red" id="c-red">
		<label for="c-red">Red</label>
	</li>
	<li>
		<input type="radio" name="color scheme" value="blue" id="c-blue">
		<label for="c-blue">Blue</label>
	</li>
</ul>

We’re going to use CSS custom properties to easily switch between color schemes. First we’ll define some initial colors in our CSS, using custom properties for the fill and stroke colors of our chart, and for the heading color (the “Weekly downloads” title):

:root {
	--textHeadingColor: rgb(117, 117, 117);
	--fill: hsl(258.1, 100%, 92%);
	--stroke: hsl(258.1, 100%, 66.9%);
}

Now, where we’re using named colors in our JS, we’ll swap these out for custom properties. For the marker line and circle, we can additionally include a default value. In some of our color schemes we might want to give these a different color. But if the --marker custom property isn’t defined it’ll fall back to the stroke color.

const area = svg
	.append('path')
	.datum(data)
	/* ...other attributes */
	.attr('fill', 'var(--fill)')
		
const line = svg
	.append('path')
	.datum(data)
	/* ...other attributes */
	.attr('stroke', 'var(--stroke)')
	
const markerLine = svg
	.append('line')
	/* ...other attributes */
	.attr('stroke', 'var(--marker, var(--stroke))')
	
const markerDot = svg
	.append('circle')
	/* ...other attributes */
	.attr('fill', 'var(--marker, var(--stroke))')

Now we’ll add a function to toggle the colors when the user clicks a radio button by appending a class to the body. We could do this with regular JS, but as we’re learning D3 let’s do it the D3 way!

First we’ll select our radio buttons using D3’s selectAll method:

const inputs = d3.selectAll('input[type="radio"]')

When the user selects an option, we’ll first want to remove any color scheme classes that are already appended, so let’s create an array of color classes to check for. selectAll returns a D3 selection object rather than the actual DOM nodes. But we can use nodes() in D3 to select the elements, then map over them to return the input values (which will be the classes to append):

const colors = inputs.nodes().map((input) => {
	return input.value
})

Now we can add an event listener to our input wrapper, using D3’s on() method (using select to select the element). This will remove any pre-existing color scheme classes, and append the class related to the selected input:

d3.select('.controls-list')
	.on('click', (e) => {
		const { value, checked } = e.target
		
		if (!value || !checked) return
	
		document.body.classList.remove(...colors)
		document.body.classList.add(value)
	})

All that remains is to add some CSS for the red and blue color schemes (purple will be the default):

.red {
	--stroke: hsl(338 100% 50%);
	--fill: hsl(338 100% 83%);
	--marker: hsl(277 100% 50%);
	--textHeadingColor: hsl(277 5% 9%);
	
	background-color: hsl(338 100% 93%);
	color: hsl(277 5% 9%);
}

.blue {
	--stroke: hsl(173 82% 46%);
	--fill: hsl(173 82% 56% / 0.2);
	--marker: hsl(183 100% 99%);
	--textHeadingColor: var(--stroke);
	
	background-color: hsl(211 16% 12%);
	color: white;
	color-scheme: dark;
}

As a nice little extra touch, we can use the new CSS accent-color property to ensure that our radio buttons adopt the stroke color from the color scheme in supporting browsers too:

.controls-list {
	accent-color: var(--stroke);
}

As our blue color scheme has a dark background we can use colour-scheme: dark to give the checkboxes a matching dark background.

Performance

I mentioned earlier that the D3 library is quite extensive, and we’re only using parts of it. To keep our bundle size as small as possible, we can elect to only import the modules we need. We can modify the import statements at the top of our file, for example:

import { line, area, curveBumpX } from 'd3-shape'
import { select, selectAll } from 'd3-selection'
import { timeFormat } from 'd3-time-format'
import { extent } from 'd3-array'

The we just need to modify any d3 references in our code:

/* Previously: */
const xDomain = d3.extent(data, xAccessor)

/* Modified: */
const xDomain = extent(data, xAccessor)

See the Pen
D3 sparkline chart
by Michelle Barker (@michellebarker)
on CodePen.0

Resources

The post Building an Interactive Sparkline Graph with D3 appeared first on Codrops.

Collective #659






Practical Accessibility

Sara Soueidan is launching a web accessibility course for web designers and developers. Subscribe and be among the first to know when it’s out.

Check it out



















Madosel

A family of responsive front-end frameworks that make it easy to design responsive websites.

Check it out







The post Collective #659 appeared first on Codrops.

Rescheduling SmashingConf SF And Looking Out For Each Other

Rescheduling SmashingConf SF And Looking Out For Each Other

Rescheduling SmashingConf SF And Looking Out For Each Other

Rachel Andrew

We have all been looking forward to our SmashingConf in San Francisco, and are so sad to have to announce that the conference is being postponed until 10th–11th November 2020. If you have tickets for the event, you should already have received an email. If not, then please see the detailed information on this page, get in touch with us, and we will help you out. The team is on standby to try and help you as quickly as possible.

As you can imagine, it was a very tough decision for our team to make. We have been working hard to plan this event and were looking very much forward to it for the last few months, however, we believe it is the right decision for the conference and for everyone we hoped to meet there.

We are all faced with a difficult few weeks ahead, but we still need to do our jobs, keep learning, and also stay connected with our friends. We hope that even though we can’t all meet in person, we can help a little bit as we all get through this together.

Topple the Cat holding a guitar, excited for SmashingConf taking place in San Francisco this year
Topple the Cat agrees that this is the best way forward for the safety and health of you, our dearest Smashing Family! (Read our full statement here.)

Stay Connected

We know that many of you are working from home for the first time to help prevent the spread of COVID-19, and the events and meetups that we all love to attend are being cancelled. We’re working on some ideas for the coming weeks that can be attended virtually — with no need for awkward elbow bumps and footshakes. Our online communities are going to become even more important than usual.

The entire Smashing team are remote, and the conferences are one place that many of us get to meet and spend time in person, so we know how hard it can be to lose those opportunities. The community have been sharing some great resources online, however, for those of you who are new to working from home, we went ahead and asked Twitter for tips and got some great replies — as well as links to resources.

Some of you might be having to manage a remote team for the first time, with little time to prepare. In “You’ve Found Yourself Leading A Remote Design Team,” Mark Boulton shares some tips from his own experience having led remote teams for many years. On Twitter, Linda Eliasen wrote a thread with all her tips on leading a team remotely. Also, Holloway have released a section from their upcoming guide which covers morale, mental health, and burnout in remote teams.

Laurie wrote some tips on “Remote Work For Teams” while Zsolt exlained how we can switch from in person to remote UX Research in the time of coronavirus.

For people new to remote work, there are some useful tips in “The Leapers Little Guide To… Working Well From Home Under Self-Quarantine For Coronavirus,” and also in Carie Fisher’s post, “Top 10 Pro-Tips For Working Remotely.” Benedikt Lehnert wrote a great guide with lots of practical suggestions, “I’m Working Remotely, Now What?!Vice.com have some information which they claim will help us not to feel like a “lonely garbage slug”. (Wait, what is a garbage slug?)

As someone who usually has a smarter shirt to hand to throw on over my gym clothes when I need to do a video call, I liked this tip:

Keep Learning

We have a whole back catalog of video from previous conferences, which you can enjoy from the comfort of your sofa. The talks from last year alone should keep you occupied for a while.

SmashingConf NYC 2019

SmashingConf NYC 2019Watch videos featuring Dan Mall, Brad Frost and Ian Frost, Marcy Sutton, Denys Mishunov, Trine Falbe, Maggie Wachs, Wes Bos, dina Amin, Harry Roberts, Sara Soueidan, Remy Sharp, Scott Jehl, and Miriam Suzanne.

SmashingConf Freiburg 2019

SmashingConf Freiburg 2019Watch talks from Guillaume Kurkdjian, Joe Leech, Heather Burns, Uri Shaked and Benjamin Gruenbaum, Anna Migas, Val Head, Rémi Parmentier, Sara Soueidan, Robyn Larsen, Benjamin Hersh, and Philip Walton.

SmashingConf Toronto 2019

Smashing Toronto 2019Listen to talks from Brad Frost, Sarah Drasner, Phil Hawksworth, Jenny Shen, Kristina Podnar, Steven Hoober, Phil Nash, Dan Rose, Diana Mounter, Scott Jehl, and Chris Gannon.

SmashingConf SF 2019

SmashingConf SF 2019Watch and learn from Jen Simmons, Jason Pamental, Jeremy Wagner, Katie Sylor-Miller, Miriam Suzanne, Chris Coyier, Darin Senneff, Anna Migas, Sara Soueidan, and Brad Frost.

Last But Not Least, Smashing Podcast

If you haven’t found our Smashing Podcast yet, we’re already up to episode 11. Check out the episodes and subscribe here. Of course, we will also be bringing you an article every day here on Smashing Magazine, and perhaps if you find yourself with some extra time, you might like to join our authors and write for us.

Keep In Touch

We are obviously dealing with a fast changing situation, and can only take each day or week as it comes. Take every chance to keep in touch with your friends and peers via phone, text and video chat. Check in on people who might be having a hard time. Your outgoing extrovert friend may be the person who finds the isolation of the next few weeks the hardest. Look after yourselves, each other, and please wash your hands!

Smashing Editorial (il)

Breakout Buttons

Andy covers a technique where a semantic <button> is used within a card component, but really, the whole card is clickable. The trick is to put a pseudo-element that goes beyond the button, covering the entire card. The tradeoff is that the pseudo-element sits on top of the text, so text selection is hampered a bit. I believe this is better than making the whole dang area a <button> because that would sacrifice semantics and likely cause extreme weirdness for assistive technology.

See the Pen
Semantic, progressively enhanced “break-out” button
by Andy Bell (@andybelldesign)
on CodePen.

You could do the same thing if your situation requires an <a> link instead of a <button>, but if that's the case, you actually can wrap the whole area in the link without much grief then wrap the part that appears to be a button in a span or something to make it look like a button.

This reminds me of the nested link problem: a large linked block that contains other different linked areas in it. Definitely can't nest anchor links. Sara Soueidan had the best answer where the "covering" link is placed within the card and absolutely positioned to cover the area while other links inside could be be layered on top with z-index.

I've moved her solution to a Pen for reference:

See the Pen
Nested Links Solution
by Chris Coyier (@chriscoyier)
on CodePen.

The post Breakout Buttons appeared first on CSS-Tricks.

A11Y with Lindsey

Lindsey Kopacz has a wonderful blog about accessibility. I've seen a number of her articles making the rounds lately and I was like, dang I better make sure I'm subscribed. For example:

Regarding that last one, I remember learning from Sara Soueidan that a good tip for this to position them over the new custom checkboxes and hide them via opacity instead of hiding the native checkboxes by clipping them away. That covers the scenario of people exploring a touch screen for native interactive elements.

The post A11Y with Lindsey appeared first on CSS-Tricks.

SVG Filter Effects: Moving Forward

Over the course of six weeks, we published six articles that covered a variety of SVG Filter effects and the filter primitives used to create them:

  1. SVG Filters 101— in which we covered the basics for getting started with SVG filters.
  2. In the second article, Outline Text with <feMorphology>, we learned about feMorphology and how it can be used to shrink and expand content. We saw how we can use it co create text outlines and paint-like image effects.
  3. The third article, Poster Image Effect with <feComponentTransfer> was our first step into the world of feComponentTransfer. We learned how we can use it to create poster image effects.
  4. In the fourth article, Duotone Images with <feComponentTransfer> we dug further into feComponentTransfer. We learned how to use it to recreate Photoshop’s duotone effects by creating gradient maps and applying them to images.
  5. In the fifth article, Conforming Text to Surface Texture with <feDisplacementMap> we recreated another Photoshop-grade effect and learned how to conform text to surface texture. We saw how the steps to recreate Photoshop effects are very similar in SVG.
  6. And in the sixth article, Creating Texture with <feTurbulence> we learned how to generate our own textures using SVG’s feTurbulence primitive. We learned how to use it to distort HTML and SVG content, as well as simulate natural texture by combining it with SVG’s lighting effects. All pretty powerful stuff.

Even though we covered a lot of areas, I can confidently say that we barely scratched the surface of what’s possible with SVG Filters. In this short follow-up article, I want to share some of my favorite resources to learn more about SVG Filters.

Moving Forward: SVG Filter Learning Resources and Experiments

  • To get an even broader idea of the incredible possibilities that SVG Filters bring to the Web, I highly recommend checking out Lucas Bebber’s Codepen profile. Lucas is the guy who created the famous Gooey Effect using SVG Filters. His experiments include even more impressive effects that are guaranteed to inspire you and get you excited about the possibilities that SVG brings to the Web.

    See the Pen Gooey Menu by Lucas Bebber (@lbebber) on CodePen.light



    See the Pen CSS Text filling with water by Lucas Bebber (@lbebber) on CodePen.light

  • Michael Mullany was my go-to resource for learning when I took my first dive into the world of SVG filters. I learned a lot from his writing, his contributions to the Web Platform Docs’ SVG Filters entries, and his Codepen experiments which are literally an SVG Filters gold mine! Check out this Stranger Things logo recreated entirely with SVG Filters:

    See the Pen Stranger Things Logo in SVG (Filters) by Michael Mullany (@mullany) on CodePen.light

  • David Dailey has a fantastic introduction to SVG Filters in which he shows a wide range of possible effects that he created with feTurbulence, including but not limited to heavy cloud and bokeh-like effects.

    Screen Shot 2019-01-21 at 15.00.18

    The results of adjusting the results of feTurbulence with other filter primitives. Adjusting and saturating colors with feColorMatrix, as well as sharpening with feConvolveMatrix are examples of things you can do with generated noise.

  • Dirk Weber also created some of the best SVG Filter effects that I personally learned a lot from. He shared his experiments in an article on Smashing Magazine more than three years ago! He shares a variety of text effects created using SVG filters. You’ll find examples of grunge texture, protruding and 3D text, water splash effects (uses feTurbulence), and many more. Dirk uses two filter primitives in his experiments that we didn’t touch on in this series:

    • feTile which is a utility primitive which fills a target rectangle with a repeated, tiled pattern of an input image. Yoksel also has a great example created using a series of operations including feTile that is worth exploring.
    • and feConvolveMatrix which is one of the more complex and also more powerful primitives. It applies a matrix convolution filter effect. A convolution combines pixels in the input image with neighboring pixels to produce a resulting image. A wide variety of imaging operations can be achieved through convolutions, including blurring, edge detection, sharpening, embossing, and beveling. I haven’t personally experimented with this primitive just yet, but I know other people who have.
    Group
    Some of the text effects that Dirk Weber covers in his article o n Smashing Magazine.
  • Yoksel’s SVG experiments on Codepen are also a great resource to learn from. She even created this fantastic visual SVG Filters tool which you can use to create effects and copy-paste the generated code to use in your own projects.

    Screen Shot 2019-01-21 at 15.17.07
    The visual SVG Filters editor by Yoksel.
  • And last but not least, you can find all there is to know about elements, properties and attributes of SVG Filters in The SVG Filters Specification.

Why SVG Filters?

SVG is currently a lot more powerful than CSS when it comes to creating graphical effects on the web. And there are several reasons why creating visual effects on the web is better than importing them as images from graphics editors like Photoshop and Illustrator:

  • In the days and age of the responsive web, we are no longer dealing with one image. For every image we use on the web, we should be providing responsive versions of that image optimized for different user contexts and performance. This means that if you create an image and then decide to change something about the effect in it, you’re going to have to change the effect in multiple images, which will easily turn into a maintenance nightmare. Creating effects in the browser, on the other hand, means they are resolution-independent and easier to edit.
  • The ability to apply filter effects on the web helps to maintain the semantic structure of the document, instead of resorting to images which—aside from generally being a fixed resolution—tend to obscure the original semantics of the elements they replace. This is especially true for effects applied to text. When effects are applied to real text on the web, that text is going to be searchable, selectable and accessible.
  • Effects created on the web are easier to edit, change, and update without having to jump between the graphics editor and the code editor or browser.
  • And last but not least, effects created on the web can be animated and interacted with. This is one of their most important points of strength.

Final Words

Thank you for joining me on this SVG Filters journey over the last few weeks. I hope this series has inspired you to start experimenting with SVG Filters and using them more when and where appropriate. There are many effects that you can create that would definitely be filed as experimental. But there are also many practical applications for filters in the wild as well. My hope is that this series has given you a glimpse of what’s possible and that it encourages you to unleash your imagination and to start creating your own practical use cases.

Finally, I hope you enjoyed this series and found it useful. Thank you for reading. =)

SVG Filter Effects: Moving Forward was written by Sara Soueidan and published on Codrops.

SVG Filter Effects: Duotone Images with <feComponentTransfer>

SVGFilterEffects_feComponent2_featured

In the previous article in this series I introduced you to the <feComponentTransfer>, and we used it to limit the number of colors in an image to create a poster effect. In this article, we will take a look at how it can be used to create a Photoshop-like duotone effect. We’ll also learn how to use it to control the intensity and contrast of an image’s colors.

A Quick Recap

To quickly recap, the feComponentTransfer primitive allows you to modify each of the R, G, B and A components present in a pixel. In other words, feComponentTransfer allows the independent manipulation of each color channel, as well as the alpha channel, in the input element.

The RGBA components are modified by running different kinds of functions on these components. To do that, each component has its own element. These component elements are nested within feComponentTransfer. The RGBA component elements are: feFuncR, feFuncG, feFuncB, and feFuncA.

The type attribute is used on a component element to define the type of function you want to use to modify this component. There are currently five available function types: identity, table, discrete, linear, and gamma. These function types are used to modify the RGBA components (the colors and alpha channel) of a source graphic. We mentioned that you can modify one or more component at a time and that you can modify channels independently, applying a different function to each component element.

<feComponentTransfer>
    <!-- The RED component -->
    <feFuncR type="identity | table | discrete | linear | gamma"></feFuncR>

    <!-- The GREEN component -->
    <feFuncG type="identity | table | discrete | linear | gamma"></feFuncG>

    <!-- The BLUE component -->
    <feFuncB type="identity | table | discrete | linear | gamma"></feFuncB>

    <!-- The ALPHA component -->
    <feFuncA type="identity | table | discrete | linear | gamma"></feFuncA>
</feComponentTransfer>">

In the previous article, we demystified the discrete function and saw how it can be used to posterize images. In this article, we will start by using the table function to create a duotone effect similar to what you can create in Photoshop.

Creating a Duotone Effect in Photoshop

I’m not a designer, and I don’t know my way around graphics editors like Photoshop. When I wanted to create a duotone effect in SVG, I looked for the way to create this effect in graphics editor first to see if I can replicate it using the filter operations available in SVG. As it turns out, the steps to create duotone images in SVG turned out to be same as those used in Photoshop.

The following video is a sped-up version of this tutorial I found on YouTube.

In the video, the designer creates the duotone effect following these steps:

  1. Desaturate the image, making it grayscale.
  2. Map the grayscale range into a new range that, instead of having black and white on either end, it has two different colors that you want to use in the duotone effect. In other words, you will need to create and use a gradient map that the grayscale is mapped to.

Let’s see how these steps can be replicated in SVG.

Creating a Duotone Effect in SVG

To recreate this effect in SVG, we will need to desaturate the image first. This is possible using the <feColorMatrix> filter primitive.

Then, we need to be able to create and provide a gradient map for the browser to map the new grayscale image to.

Converting an image to grayscale using feColorMatrix

Using feColorMatrix you can provide a color matrix that specifies the amount of red, green, and blue in your image. By providing equal amounts of these three components, we are creating a matrix that converts our image into a grayscale version of itself:

<svg viewBox="0 0 266 400">
    <filter id="duotone">

        <feColorMatrix type="matrix" values=".33 .33 .33 0 0
                .33 .33 .33 0 0
                .33 .33 .33 0 0
                 0   0   0  1 0">
        </feColorMatrix>

        <!-- ... -->
    </filter>
    <image xlink:href="..." width="100%" x="0" y="0" height="100%" 
        filter="url(#duotone)"></image>
</svg>

In the following image, the image on the right is the result of applying the above filter to the image on the left:

Screen Shot 2019-01-12 at 18.50.21
The result (on the right) of converting the image on the left to grayscale using the feColorMatrix filter operation.

You can learn all about feColorMatrix and how to use it in this article by Una Kravets.

Now that our image is essentially made of a gray gradient, we need to create a duotone gradient map to map the gray gradient to.

Creating a Gradient Map using the table component transfer function

In SVG, to create the gradient map, we can use feComponentTransfer primitive with the type table.

In the previous article, we saw how you can map the colors in an image to a list of colors that you provide in the tableValues attribute using the discrete function. The browser used our list of tableValues to generate ranges that are then used to map the colors to the values we provided.

When using the table function, we will also provide color values in the tableValues attribute. Once again, the browser will use the values we provide to map the colors in the image to them. How the browser will map the colors, though, is different. Instead of mapping color ranges to discrete color values, it will create a color range from the values we provide and then map the input range to this new range.

Suppose we want to use the following two colors for our duotone effect:

Screen Shot 2019-01-12 at 19.05.26

These two colors will be used to create a gradient map:

Screen Shot 2019-01-12 at 19.11.46

..that we are going to map our grayscale map to.

IMG_4149

In order to use these colors in feComponentTransfer, we need to get the values of the R, G, and B channels of each color. Since tableValues are provided in fractions, we’ll need to convert the RGB values to fractions. Color values are usually in the range [0, 255]. To convert these values to fractions, we need to divide them by 255.

For example, the pink color has the following RGB values:

R: 254
G: 32
B: 141

Converted to fractions, these values are now equal to:

R: 254/255 = .996078431
G: 32/255  = .125490196
B: 141/255 = .552941176

Similarly, the yellow color values resolve to:

R: .984313725
G: .941176471
B: .478431373

Now that we have our color values handy, it’s time to create our gradient map. We mentioned earlier that when we provide values to tableValues with the table function in use, the browser will use the tableValues to create a range. So we start by providing the RGB values of the two colors as values for the RGB component elements:

<feComponentTransfer color-interpolation-filters="sRGB">
    <feFuncR type="table" tableValues=".996078431  .984313725"></feFuncR>
    <feFuncG type="table" tableValues=".125490196  .941176471"></feFuncG>
    <feFuncB type="table" tableValues=".552941176  .478431373"></feFuncB>
</feComponentTransfer>

Screen Shot 2019-01-12 at 19.26.35

We saw in the previous article that when using the discrete function, the browser creates n ranges for n values in tableValues. When we use the table function, the browser creates n-1 ranges for n values; and since we provided two tableValues for each component, this means that we will get one range ([pink, yellow]) for each.

Now feComponentTransfer will do its thing: The browser will go over each and every pixel in the source image. For each pixel, it will get the value of the Red, Green, and Blue components. Since our image is grayscale, the R/G/B values will be in the range [0, 1] = [black, white] (0 being fully black, 1 being fully white, and shades of gray in between). Then, the value of each component will be mapped to the new range we provided in tableValues. So:

  • The red component value will be mapped to the range [.996078431, .984313725]
  • The blue component value will be mapped to the range [.125490196, .941176471]
  • The green component value will be mapped to the range [.552941176, .478431373]

So by the time the browser goes over all the pixels in the image, you will have replaced all RGB values in the grayscale gradient with the RGB values of the duotone gradient map. As a result, the image becomes duotone.

Screen Shot 2019-01-12 at 19.40.07
The result (on the right) of mapping the grayscale image (left) to our gradient map.

Our full code now looks like this:

<svg viewBox="0 0 266 400">
    <filter id="duotone">
        <!-- Grab the SourceGraphic (implicit) and convert it to grayscale -->
        <feColorMatrix type="matrix" values=".33 .33 .33 0 0
              .33 .33 .33 0 0
              .33 .33 .33 0 0
              0 0 0 1 0">
        </feColorMatrix>

        <!-- Map the grayscale result to the gradient map provided in tableValues -->
        <feComponentTransfer color-interpolation-filters="sRGB">
            <feFuncR type="table" tableValues=".996078431  .984313725"></feFuncR>
            <feFuncG type="table" tableValues=".125490196  .941176471"></feFuncG>
            <feFuncB type="table" tableValues=".552941176  .478431373"></feFuncB>
        </feComponentTransfer>
    </filter>

    <image xlink:href=".." width="100%" x="0" y="0" height="100%" 
           filter="url(#duotone)"></image>
</svg>

And you can play with the live demo here:

See the Pen Duotone Image effect by Sara Soueidan (@SaraSoueidan) on CodePen.light

You can take this further and instead of providing only two color values for the gradient map you can provide three color values in tableValues, creating a gradient map that has three colors instead of two.

Controling color contrast and intensity with the gamma transfer function

Using the gamma component transfer function we are able to perform gamma correction on our source graphic. Gamma correction is the function of controlling an image’s luminance levels.

The gamma function has three attributes that allow you to control the gamma correction function that will be used to control the luminance: amplitude, exponent and offset. Combined, they make up the following transfer function:

C' = amplitude * pow(C, exponent) + offset

gamma can be used to control the overall contrast in an image. Increasing the exponent makes the darker areas darker while increasing the amplitude makes the lighter areas shine more. And this, in turn, increases the overall contrast of the image. The offset is used to increase the intensity of each component, and also affects the overall image: both highlights and dark areas.

Tweaking the contrast and dark and light areas of an image can sometimes be useful if you’re not getting the amount of “shine” that you’d like to see in an image.

For example, if I apply the duotone filter from the previous section to the following image, the result is not as “lively” as I’d want it to be:

Screen Shot 2019-01-14 at 18.50.04

The duotone image on the right looks a little pale and the colors slightly washed out. I want to add some contrast to it to make it look more lively. By increasing the amplitude and the exponent a little bit:

<feComponentTransfer color-interpolation-filters="sRGB">
      <feFuncR type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncR>
      <feFuncG type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncG>
      <feFuncB type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncB>
</feComponentTransfer>

I am able to make the light areas shine more and the darker areas look more intense:

Screen Shot 2019-01-14 at 18.56.11

Of course, this is just an example. You may prefer the paler version of the image, especially that it might look better with text on top. I think gamma correction is most useful in controlling the contrast of black-and-white images. If I apply the same gamma correction operation to the grayscale version of the image, I get a more favorable version:

Screen Shot 2019-01-14 at 19.08.34

Of course, you may want to do the opposite: instead of increasing contrast, you may want to lighten the dark areas a bit, in which case you’d decrease the amplitude and/or exponent values instead of increasing them. The default value for both the amplitude and exponent is 1. The default offset value is 0.

Play with the gamma function values in the following live demo to get a better feel of how it affects the brightness and contrast of an image:

See the Pen Duotone Image effect with Contrast Tweak by Sara Soueidan (@SaraSoueidan) on CodePen.light

The SVG Gradient Map Tool

Yoksel has been playing with SVG Filters for a while and has recently created a fantastic visual tool that allows you to upload an image and apply different duotone and even tritone effects, and that generates the SVG filter code for you ready to copy-paste anywhere you need it. It is a great tool to play with to learn more about feComponentTransfer.

Screen Shot 2019-01-14 at 19.13.12
The SVG Gradient Map Filter tool by Yoksel.

The tool even allows you to tweak the grayscale effect created using feColorMatrix. In our code, we used equal amounts of the R, G, and B channels to get a grayscale effect. This is one way to make an image grayscale. But there are other ways, too. For example, you could create a grayscale effect per channel, which would result in a different grayscale result for each:

b7w

I recommend playing with the tool a little bit and checking how your choice of effect changes the underlying code, as this is one of the best ways to learn more about SVG filters.

Final Words

The feComponentTransfer primitive gives us a lot of control over the color and alpha components of images and enables us to create Photoshop-grade effects in the comfort of our code editors.

In the next article in this series, we will look at a couple more primitives that allow us to replicate yet another Photoshop effect using almost exactly the same steps as those you’d take in Photoshop, showing once again how SVG can bring the power of graphic editors into the Web platform. We’ll learn how to blend text with both the color and the texture of a background image, to create some eye-catching results. Stay tuned.

SVG Filter Effects: Duotone Images with <feComponentTransfer> was written by Sara Soueidan and published on Codrops.