Responsible Web Applications

The title for this page came from a slip of my tongue. I actually had wanted to say "responsive and accessible" web applications, but somehow "responsible" slipped out.

Regardless of how I came up with the term, I do consider it to be fitting because I feel that we as developers have a responsibility to ensure that our application is responsive and accessible. If we don't, who will?

It is extremely difficult and expensive to add responsiveness and accessibility after the fact. For this reason, we need to take them into account from the very beginning.

Luckily, with modern HTML and CSS, we can create responsive and accessible web apps with relative ease. In my years of doing software development, I have learned some HTML and CSS tips and tricks, and I want to present these in this post. This list is not exhaustive, but these are tried and true patterns that I frequently use in different projects.

Responsive Web Design

Do we really need responsive web design for our web application? We will only use our web application on desktop computers!

I've heard this argument many times, but in the long term, it has never turned out to be true. For this reason, I've created Joy's two laws of web development:

Joy’s First Law of Web Development

There is no such thing as a non-responsive web application

Your web application WILL be opened in a mobile phone or tablet at some time in the future and your users WILL expect it to work correctly.

Joy’s Second Law of Web Development

Any work you do now to ensure that your web application behaves responsively WILL be appreciated in the future.

When it comes to the technical implementation of responsive design, there are two main categories of components that we need to develop:

I want to cover these two aspects in the next sections

Responsive Layout Containers

We firstly need to make sure that we put a lot of thought into designing layout containers which adjust themselves based on the size of our viewport.

In the following demo, we can see how we can define a layout conceptually using different grid areas.

A UI example showing how content will be stacked vertically on smaller devices, but on larger viewports we can then use the increased horizontal space to display certain content areas next to each other.

The demo shows five content areas: Header, Sidebar, Main Content, Second Sidebar, and Footer. On a mobile device, these areas are shown vertically stacked. On larger viewports, the example layout now has three rows and three columns. The Header area spans the full width of the top of the viewport in the first row. In the middle row, the Sidebar, Main Content, and Second Sidebar areas are positioned next to each other in a three column layout, with the Main Content area expanding to take up as much space as it can. The Footer area then spans the full width of the viewport in the bottom row of the layout.

How can we implement this? To do this, I like to use the following technique.

Explicit CSS Grid Layout with Breakpoints

Using CSS Grid, I like to use the following technique to declaratively define a default CSS grid for our content. This is then the CSS which is used for smaller devices.

CSS for Mobile Devices
.layout {
	display: grid;
	grid-template-areas:
		"header"
		"sidebar"
		"main"
		"sidebar-right"
		"footer";
	grid-template-rows: auto auto 1fr auto auto;
}
.layout > header {
	grid-area: header;
}
.layout > aside:nth-of-type(1) {
	grid-area: sidebar;
}
.layout > main {
	grid-area: main;
}
.layout > aside:nth-of-type(2) {
	grid-area: sidebar-right;
}
.layout > footer {
	grid-area: footer;
}

Note that this markup explicitly references the main, header, footer, and aside semantic HTML elements in the CSS code. This was intentional here, because it forces us to then use the semantic HTML elements and add important landmarks to our web application which improves its accessibility. With the CSS child combinator operator (>) we ensure that this element targeted is the direct child of the parent with my layout class (which we would probably add directly to our HTML body).

CSS for tablets or larger devices

Since we have already defined the grid-areas for our HTML elements, we can now declaratively change the layout using a relatively short CSS snippet within a media query:

@media (min-width: 40rem) { /* Breakpoint Tablet portrait up */
	.layout {
		grid-template-areas:
			"header header header"
			"sidebar main sidebar-right"
			"footer footer footer";
		grid-template-rows: auto 1fr auto;
		grid-template-columns: 20% 1fr 20%;
	}
}

Note that when you are defining your breakpoints for your application, you should consider the correct way to do CSS breakpoints.

Squishy Components

After we have a responsive layout, the next step is to make sure that all of our components are "squishy". This means that when we place a component into a designated area of a layout, it should never push itself outside of its designated area. Instead it should "squish" down to fit inside of the available space.

This is especially important because our layout container assumes that all of its content is going to fit, and if this assumption is not true, this could cause the whole layout to be wider than the viewport and make it necessary for the user to scroll horizontally.

Flexbox + Flex Wrap

.container {
	display: flex;
	flex-wrap: wrap;
}

We can use the display: flex; rule to make the container a flexbox and then use the flex-wrap: wrap; rule to wrap content within the flexbox.

When there is not enough space for the items to be placed horizontally, they will begin to wrap and be shown stacked vertically instead.

An example showing how we can use flex-wrap to wrap content in a flexbox.

The demo shows a flexbox containing two blocks: a block containing a title and a description, and a block containing a price, time, and amount. On a larger devices, these block can be positioned next to each other horizontally. On smaller devices with flex-wrap activated, the second block containing the price, time and amount will wrap onto the next line and be positioned underneath the first block.

Nested Flexboxes

It is also possible to nest flexboxes inside each other to create more advanced responsive behavior. This next example shows the same flex container from our last example, but highlights the inner flexbox instead of the outer flexbox.

An example highlighting the items in the inner flexbox that can also wrap.

The demo shows the same demo as the one in the previous example, but in this instance, the price, time, and amount items are highlighted because they are also contained within an inner flexbox. This adds an extra layer to the responsiveness: when the viewport gets so small that there is no longer space for these three items to be positioned next to each other horizontally, they will begin to wrap as well.

Intrinsic Grid

.container {
	display: grid;
	grid-template-columns:
		repeat(auto-fill, minmax(var(—col-width), auto));
}

We can use the CSS repeat function with grid-template-columns in order to create an intrinsic grid which will generate as many columns as fit in the given space. An example using this type of grid is available in my demo for live-coding css layout.

An example of an intrinsic grid which fits as many columns as possible within the viewport.

The demo shows a grid with 6 different content blocks that are positioned within an intrinsic CSS grid. On mobile devices, there is only enough space for a single column, so the blocks are all positioned within this column. When there is enough space for two columns, the layout will shift and the elements will be positioned within the grid in two columns and end up filling three rows. When there is enough space for three columns, the layout will shift again and the elements will be positioned within the grid in three columns and will end up filling two rows. This layout pattern will continue, and a grid will always be generated with as many columns as fit within the viewport size.

Horizontal Scrolling

.horizontal-scroll {
	overflow-x: auto;
}

When we talk about creating an application which is responsive, this doesn't mean that we need to optimize all of our layouts for small device screens. In certain contexts, we will have larger data representations (e.g. tables or code examples), which we also want to be available for smaller devices even if they are not optimized for that layout.

For this, I like to use a wrapper around tables and code examples which make them able to be scrolled horizontally when there is not enough space for them in the current layout.

An example showing how to use a container with horizontal scrolling to make larger content available on smaller viewports.

In the example, a large table with twenty columns is shown. On smaller devices, there is not enough space for the whole table to be shown. In this case, the large table is shown within a container that fits within the viewport, and the user can scroll within that container in order to view all of the contents.

Squishy Text

.squishy-text {
	word-break: break-word; /* Samsung browser */
	word-wrap: break-word; /* IE 11 */
	overflow-wrap: anywhere;
	-webkit-hyphens: auto;
	-ms-hyphens: auto;
	hyphens: auto;
}

The following example is a CSS meme now. By default, long words in an HTML document will not be hyphenated by default, so they will break out of their containing box instead of squishing to fit inside of it. This is especially important when we are dealing with a language which has a lot of long words (*cough* German *cough*).

The previous CSS snippet is one I have successfully used to make my text squishy in different contexts.

CSS is awesome
CSS is awesome

Accessible Web Design

We now come to accessibility. Here I feel that I can only scratch the surface of the different things that we should consider. I also am learning new things all the time, so I'm sure that this list is not exhaustive. But I do think it is a good starting off point.

Headings

Don’t skip heading levels.

In your HTML Document, you need to ensure that your document hierarchy is complete and doesn't skip levels. Otherwise, users who depend on assistive technologies will get confused because it will seem like content is missing.

This is a mistake that many developers make, because we pay attention to how the heading appears visually without making sure that an h2 is always directly followed by a h3.

Landmarks

We should make sure that we use elements like main and header because then we get HTML landmarks out of the box. This makes the page much easier to navigate.

Note that the main element is not well supported for IE11, so if you have to support older browsers, you should also consider using skip links.

When you are providing links for a user to navigate within your page (e.g. a navbar or a table of contents), you should wrap them in a nav.

label Element

Always add a label to let assistive technologies know which data is supposed to be entered in an input field.

<label>
	First Name
	<input type="text" value="name" placeholder="Jane" />
</label>

Here we either wrap the input field directly in the label, or we can use the for attribute and link it to a specific input field.

Here it is important to not use the placeholder attribute to label the input field. The placeholder attribute should be used to show an example of how the data we expect should appear. This is particularly important for users who have congnitive disabilities, because when the instructions in the placeholder disappear, they may not remember what they were supposed to enter into the field. Please also read this article about why placeholder are problematic.

List Elements

Using lists (an unordered list, an ordered list, or a description list) for things in a UI will also add extra context for assistive technologies. For instance, it will tell assistive technologies how many elements are contained in a list.

In practice, description lists can be difficult to style because we are not allowed to add an extra div as a wrapper around the dt and dd elements. For this reason, I've also done some experiments about how to best group information in the UI. Here there isn't a single correct solution. You will have to find out what works best for your UI.

Update: Originally, I discussed having difficulties styling description lists. This statement is no longer correct for modern browsers which implement the HTML 5.2 Recommendation because they now allow a div as a wrapper around the dt and dd elements.

Accordions

If we need an accordion, or an element which shows/hides a content area, we can use the HTML details element.

<details>
	<summary>Toggle Button</summary>
	Content which will be expanded/collapsed when clicking the
	summary element
</details>

We can also implement this in JavaScript using a button and the aria-expanded attribute. The aria-expanded attribute adds context information to the button which will tell the screenreader if the area that the button is toggling is currently collapsed or expanded.

I have seen incorrect implementations of this where the developer thought that the attribute was intended to be added to the content block which is expanded or collapsed, but this is not the case and that implementation would be confusing to any user of an assistive technology. If we do spend the effort to set aria roles in our application (which we should), we need to test our application and make sure they are used correctly! No aria usage is better than incorrect aria usage.

I have implemented this behavior many times, and when I do, I prefer to use a custom element and activate the toggle with progressive enhancement. This means that my toggle button is hidden by default before JavaScript is activated, and the content area that is to be collapsed/expanded will only be hidden once JavaScript is activated.

The contract for the toggle-button component that I usually end up writing therefore usually looks something like this:

<button is="toggle-button" data-target="#section2"
		  aria-expanded="false" hidden>
		Toggle Section 2
</button>

aria-expanded is an attribute of the BUTTON which tells assistive technologies if the content area that is being expanded/collapsed is currently visible or not.

And my toggle-button implementation usually looks a lot like the following (I really need to standardize this and publish a custom element one of these days...):

class ToggleButton extends HTMLButtonElement {
	connectedCallback () {
		this.removeAttribute("hidden");
		if (this.getAttribute("aria-expanded") !== "true") {
			this.setAttribute("aria-expanded", "false");
			this.target.classList.add("hide");
		}
		this.addEventListener("click", this.toggle.bind(this));
	}

	toggle () {
		let classList = this.target.classList;
		if (classList.contains("hide")) {
			classList.remove("hide");
			this.setAttribute("aria-expanded", "true");
		} else {
			classList.add("hide");
			this.setAttribute("aria-expanded", "false");
		}
	}

	get target () {
		return document.querySelector(this.getAttribute("data-target"));
	}
}
customElements.define("toggle-button", ToggleButton, { extends: "button" });

Here is a code demo of this component.

aria-label and aria-labelledby

Sometimes we have visual elements which add information to the UI, but are missing in the UI. For these cases, we can use an aria-label attribute with a textual description of what the visual element is showing. If there is already a UI element available, we can also use aria-labelledby and reference the HTML id of the existing element. The HTML title attribute is not well supported by assistive technologies, so it should not be used to do this.

Here it really helps to test your UI in a screenreader so that you can figure out where extra labels would be helpful 😉.

Hiding content visually (but not from screenreaders)

.visually-hidden {
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	height: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
	width: 1px;
}

Sometimes we want to hide elements from our UI. If we use a CSS rule like display: none; this not only hides the element visually, but also hides it from assistive technologies. We can use the previous CSS snippet (or one like it), to hide the content visually without removing it from the accessibility tree.

Hiding content from assistive technologies

In the same vein, sometimes we add visual elements to our UI which are not necessary for assistive technologies and could potentially cause confusion or unnecessary noise. This could be the case, for instance, when we add icons to our visual design in addition to existing textual labels. We may then want to hide these from assistive technologies using the aria-hidden attribute.

Descriptive alt text for images

When using images in a user interface, we need to set the alt attribute for the image. This alt-text should provide a description of the same information that we are trying to convey visually with the image that we have chosen. This guide provides more information about alt-texts.

Adding clear focus styles

By default, the browser will add focus styles to the UI to indicate to users which element on the screen is currently active. This is especially important for keyboard users, who need the focus styles in order to correctly navigate throughout your page.

If you find the default focus styles irritating in your design, you can customize them with the :focus CSS pseudo-selector. However, here it is very important that you still provide a focus style which has a high contrast and is easily identifiable within the UI.

Setting focus correctly

As long as you are writing only HTML and CSS, there isn't any good reason why you would want to mess with the focus within your webpage, because the semantics and structure of HTML is very well designed. However, when we begin to modify the HTML of our application with client-side JavaScript, we need to think about the focus and if we need to update it as well.

For instance, if we are adding content to our DOM with JavaScript, as I did with this “Show More” Pagination Example, we should consider whether we should move the focus to the new content after it has loaded.

In this case, we need to make absolutely sure that you test the application with a screenreader and keyboard to ensure that the focus is set correctly. There is little that can break your UI more than incorrectly setting the focus.

I hope you enjoyed this little collection of tips and tricks for creating responsive and accessible web applications. I also hope you were able to learn something that you can use in your next project. Let's make a responsible web together.

If you have suggestions for other useful tricks or tips, please open an issue on the GitHub Repo for this page and I will do my best to add it to this list.