Journal
Web animation tips
Warning: this entry is a work-in-progress and incomplete. That said, it's still a useful reference to me which is why I've published it. I’ll flesh it out soon!
There are lots of different strands of web development. You try your best to be good at all of them, but there’s only so much time in the day! Animation is an area where I know a little but would love to know more, and from a practical perspective I’d certainly benefit from having some road-ready solutions to common challenges. As ever I want to favour web standards over libraries where possible, and take an approach that’s lean, accessible, progressively-enhanced and performance-optimised.
Here’s my attempt to break down web animation into bite-sized chunks for ocassional users like myself.
Defining animation
Animation lets us make something visually move between different states over a given period of time.
Benefits of animation
Animation is a good way of providing visual feedback, teaching users how to use a part of the interface, or adding life to a website and making it feel more “real”.
Simple animation with transition
properties
CSS transition
is great for simple animations triggered by an event.
We start by defining two different states for an element—for example opacity:1
and opacity:0
—and then transition
between those states.
The first state would be in the element’s starting styles (either defined explicitly or existing implicitly based on property defaults) and the other in either its :hover
or :focus
styles or in a class applied by JavaScript following an event.
Without the transition
the state change would still happen but would be instantaneous.
You’re not limited to only one property being animated and might, for example, transition between different opacity
and transform
states simultaneously.
Here’s an example “rise on hover” effect, adapted from Stephanie Eckles’s Smol CSS.
<div class="u-animate u-animate--rise">
<span>rise</span>
</div>
.u-animate > * {
--transition-property: transform;
--transition-duration: 180ms;
transition: var(--transition-property) var(--transition-duration) ease-in-out;
}
.u-animate--rise:hover > * {
transform: translateY(-25%);
}
Note that:
- using custom properties makes it really easy to transition a different property than
transform
without writing repetitious CSS. - we have a parent and child (
<div>
and<span>
respectively in this example) allowing us to avoid the accidental flicker which can occur when the mouse is close to an animatable element’s border by having the child be the effect which animates when the trigger (the parent) is hovered.
Complex animations with animation
properties
If an element needs to animate automatically (perhaps on page load or when added to the DOM), or is more complex than a simple A to B state change, then a CSS animation
may be more appropriate than transition
. Using this approach, animations can:
- run automatically (you don’t need an event to trigger a state change)
- go from an initial state through multiple intermediate steps to a final state rather than just from state A to state B
- run forwards, in reverse, or alternate directions
- loop infinitely
The required approach is:
- use
@keyframes
to define a reusable “template” set of animation states (or frames); then - apply
animation
properties to an element we want to animate, including one or more@keyframes
to be used.
Here’s how you do it:
@keyframes flash {
0% { opacity: 0; }
20% { opacity: 1; }
80% { opacity: 0; }
100% { opacity: 1; }
}
.animate-me {
animation: flash 5s infinite;
}
Note that you can also opt to include just one state in your @keyframes
rule, usually the initial state (written as either from
or 0%
) or final state (written as either to
or 100%
). You’d tend to do that for a two-state animation where the other “state” is in the element’s default styles, and you’d either be starting from the default styles (if your single @keyframes
state is to
) or finishing on them (if your single @keyframes
state is from
).
Should I use transition
or animation
?
As far as I can tell there’s no major performance benefit of one over the other, so that’s not an issue.
When the animation will be triggered by pseudo-class-based events like :hover
or :focus
and is simple i.e. based on just two states, transition
feels like the right choice.
Beyond that, the choice gets a bit less binary and seems to come down to developer preference. But here are a couple of notes that might help in making a decision.
For elements that need to “animate in” on page load such as an alert, or when newly added to the DOM such as items in a to-do list, an animation
with keyframes
feels the better choice. This is because transition
requires the presence of two CSS rules, leading to dedicated JavaScript to grab the element and apply a class, whereas animation
requires only one and can move between initial and final states automatically including inserting a delay before starting.
For animations that involve many frames; control over the number of iterations; or looping… use @keyframes
and animation
.
For utility classes and classes that get added by JS to existing, visible elements following an event, either approach could be used. Arguably transition
is the slightly simpler and more elegant CSS to write if it covers your needs. Then again, you might want to reuse the animations applied by those classes for both existing, visible elements and new, animated-in elements, in which case you might feel that instead using @keyframes
and animation
covers more situations.
Performance
A smooth animation should run at 60fps (frames per second). Animations that are too computationally expensive result in frames being dropped, i.e. a reduced fps rate, making the animation appear janky.
Cheap and slick properties
The CSS properties transform
and opacity
are very cheap to animate. Also, browsers often optimise these types of animation using hardware acceleration. To hint to the browser that it should optimise an animation property (and to ensure it is handled by the GPU rather than passed from CPU to GPU causing a noticeable glitch) we should use the CSS will-change
property.
.my-element {
will-change: transform;
}
Expensive properties
CSS properties which affect layout such as height
are very expensive to animate. Animating height causes a chain reaction where sibling elements have to move too. Use transform
over layout-affecting properties such as width
or left
if you can.
Some other CSS properties are less expensive but still not ideal, for example background-color
. It doesn't affect layout but requires a repaint per frame.
Test your animations on a popular low-end device.
Timing functions
- linear goes at the same rate from start to finish. It’s not like most motion in the real world.
- ease-out starts fast then gets really slow. Good for things that come in from off-screen, like a modal dialogue.
- ease-in starts slow then gets really fast. Good for moving somethng off-screen.
- ease-in-out is the combination of the previous two. It‘s symmetrical, having an equal amount of acceleration and deceleration. Good for things that happen in a loop such as element fading in and out.
- ease is the default value and features a brief ramp-up, then a lot of deceleration. It’s a good option for most general case motion that doesn’t enter or exit the viewport.
Practical examples
You can find lots of animation inspiration in libraries such as animate.css (and be sure to check animate.css on github where you can search their source for specific @keyframe
animation styles).
But here are a few specific examples of animations I or teams I’ve worked on have had to implement.
Skip to content
The anchor’s State A sees its position fixed
—i.e. positioned relative to the viewport—but then moved out of sight above it via transform: translateY(-10em)
. However its :focus
styles define a State B where the intial translate
has been undone so that the link is visible (transform: translateY(0em)
). If we transition
the transform
property then we can animate the change of state over a chosen duration, and with our preferred timing function for the acceleration curve.
HTML:
<div class="u-visually-hidden-until-focused">
<a
href="#skip-link-target"
class="u-visually-hidden-until-focused__item"
>Skip to main content</a>
</div>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/">News</a></li>
<li><a href="/">About</a></li>
<!-- …lots more nav links… -->
<li><a href="/">Contact</a></li>
</ul>
</nav>
<main id="skip-link-target">
<h1>This is the Main content</h1>
<p>Lorem ipsum <a href="/news/">dolor sit amet</a> consectetur adipisicing elit.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
</main>
CSS:
.u-visually-hidden-until-focused {
left: -100vw;
position: absolute;
&__item {
position: fixed;
top: 0;
left: 0;
transform: translateY(-10em);
transition: transform 0.2s ease-in-out;
&:focus {
transform: translateY(0em);
}
}
}
To see this in action, visit my pen Hiding: visually hidden until focused and press the tab key.
Animating in an existing element
For this requirement we want an element to animate from invisible to visible on page load. I can imagine doing this with an image or an alert, for example. This is pretty straightforward with CSS only using @keyframes
, opacity
and animation
.
Check out my fade in and out on page load with CSS codepen.
Animating in a newly added element
Stephanie Eckles shared a great CSS-only solution for animating in a newly added element which handily includes a Codepen demo. She mentions “CSS-only” because it’s common for developers to achieve the fancy animation via transition
but that means needing to “make a fake event” via a JavaScript setTimeout()
so that you can transition from the newly-added, invisible and class-free element state to adding a CSS class (perhaps called show
) that contains the opacity:1
, fancy transforms and a transition
. However Stephanie’s alternative approach combines i) hiding the element in its default styles; with ii) an automatically-running animation
that includes the necessary delay and also finishes in the keyframe’s single 100%
state… to get the same effect minus the JavaScript.
Avoiding reliance on JS and finding a solution lower down the stack is always good.
HTML:
<button>Add List Item</button>
<ul>
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum facilis perspiciatis dignissimos, et dolores pariatur.</li>
</ul>
CSS:
li {
animation: show 600ms 100ms cubic-bezier(0.38, 0.97, 0.56, 0.76) forwards;
// Prestate
opacity: 0;
// remove transform for just a fade-in
transform: rotateX(-90deg);
transform-origin: top center;
}
@keyframes show {
100% {
opacity: 1;
transform: none;
}
}
Jhey Tompkins shared another CSS-only technique for adding elements to the DOM with snazzy entrance animations. He also uses just a single @keyframes
state but in his case the from
state which he uses to set the element’s initial opacity:0
, then in his animation he uses an animation-fill-mode
of both
(rather than forwards
as Stephanie used).
I can’t profess to fully understand both
however if you change Jhey’s example to use forwards
instead, then the element being animated in will temporarily appear before the animation starts (which ain’t good) rather than being initially invisible. Changing it to backwards
gets us back on track, so I guess the necessary value relates to whether you’re going for from
/0%
or to
/100%
… and both
just covers you for both cases. I’d probably try to use the appropriate one rather than both
just in case there’s a performance implication.
Animated disclosure
Here’s an interesting conundrum.
For disclosure (i.e. collapse and expand) widgets, I tend to either use the native HTML <details>
element if possible or else a simple, accessible DIY disclosure in which activating a trigger
toggles a nearby content element’s visibility. In both cases, there’s no animation; the change from hidden to revealed and back again is immediate.
To my mind it’s generally preferable to keep it simple and avoid animating a disclosure widget. For a start, it’s tricky! The <details>
element can’t be (easily) animated. And if using a DIY widget it’ll likely involve animating one of the expensive properties. Animating height
or max-height
is also gnarly when working with variable (auto) length content and often requires developers to go beyond CSS and reach for JavaScript to calculate computed element heights. Lastly, forgetting the technical challenges, there’s often no real need to animate disclosure; it might only hinder rather than help the user experience.
But let’s just say you have to do it, perhaps because the design spec requires it (like in BBC Sounds’ expanding and collapsing tracklists when viewed on narrow screens).
Options:
- Animate the
<details>
element. This is a nice, standards-oriented approach. But it might only be viable for when you don’t need to mess with<details>
appearance too much. We’d struggle to apply very custom styles, or to handle a “show the first few list items but not all” requirement like in the BBC Sounds example; - Animate CSS Grid. This is a nice idea but for now the animation only works in Firefox*. It’d be great to just consider it a progressive enhancement so it just depends on whether the animation is deemed core to the experience;
- Animate from a max-height of 0 to “something sufficient” (my pen is inspired by Scott O’Hara’s disclosure example). This is workable but not ideal; you kinda need to set a max-height sweetspot otherwise your animation will be delayed and too long. You could of course add some JavaScript to get the exact necessary height then set it. BBC use
max-height
for their tracklist animation and those tracklists likely vary in length so I expect they use some JavaScript for height calculation.
* Update 20/2/23: the “animate CSS Grid” option now has wide browser support and is probably my preferred approach. I made a codepen that demonstrates a disclosure widget with animation of grid-template-rows
.
Ringing bell icon
To be written.
Pulsing “radar” effect
To be written.
Accessibility
Accessibility and animation can co-exist, as Cassie Evans explains in her CSS-Tricks article Empathetic Animation. We should consider which parts of our website are suited to animation (for example perhaps not on serious, time-sensitive tasks) and we can also respect reduced motion preferences at a global level or in a more finer-grained way per component.
Notes
transition-delay
can be useful for avoiding common annoyances, such as when a dropdown menu that appears on hover disappears when you try to move the cursor to it.
References
- Inspiration: the animate.css library
- animate.css on github (good for searching for keyframe CSS)
- CSS transitions and transforms on Thoughtbot
- CSS Transitions by Josh Comeau
- Keyframe Animations by Josh Comeau
- Transition vs animation on CSS Animation
- Keyframe animation syntax on CSS-Tricks
- CSS animation for beginners on Thoughtbot
- Using CSS Transions on auto dimensions on CSS-Tricks
- Jhey Tompkins’s Image fade with interest codepen
Limbo
Enjoyed this funny and touching film about a Syrian asylum seeker searching for happiness on a remote Scottish island.
I’ve started reading Station Eleven, by Emily St. John Mandel.
GOV.UK introduce an experimental block link component
Here’s an interesting development in the block link saga: GOV.UK have introduced one (named .chevron-card
) on their Homepage, citing how it’ll improve accessibility by increasing mobile touch targets. It’s not yet been added to their Design System while they’re monitoring it to see if it is successful. They’ve chosen the approach which starts with a standard, single, non-wrapping anchor then “stretches” it across the whole card via some pseudo elements and absolute positioning magic. I’m slightly surprised at this choice because it breaks the user’s ability to select text within the link. Really interested to see how it pans out!
Lovely write-up, & great rationale re. larger mobile tap targets! I’ve wrestled with “block links” & found that each approach has issues so it’s v. interesting that you chose the route that impacts text selection. Is that the lesser of the evils? Keen to hear how it pans out!
— Laurence Hughes (@fuzzylogicx) December 13, 2021
Front-end architecture for a new website (in 2021)
Just taking a moment for some musings on which way the front-end wind is blowing (from my perspective at least) and how that might practically impact my approach on the next small-ish website that I code.
I might lean into HTTP2
Breaking CSS into small modules then concatenating everything into a single file has traditionally been one of the key reasons for using Sass, but in the HTTP2 era where multiple requests are less of a performance issue it might be acceptable to simply include a number of modular CSS files in the <head>
, as follows:
<link href="/css/base.css" rel="stylesheet">
<link href="/css/component_1.css" rel="stylesheet">
<link href="/css/component_2.css" rel="stylesheet">
<link href="/css/component_3.css" rel="stylesheet">
The same goes for browser-native JavaScript modules.
This isn’t something I’ve tried yet and it’d feel like a pretty radical departure from the conventions of recent years… but it‘s an option!
I’ll combine ES modules and classes
It’s great that JavaScript modules are natively supported in modern browsers. They allow me to remove build tools, work with web standards, and they perform well. They can also serve as a mustard cut that allows me to use other syntax and features such as async/await
, arrow functions, template literals, the spread operator etc with confidence and without transpilation or polyfilling.
In the <head>
:
<script type="module" src="/js/main.js"></script>
In main.js
import { Modal } from '/components/modal.js';
const Modal = new Modal();
modal.init();
In modal.js
export class Modal {
init() {
// modal functionality here
}
}
I’ll create Web Components
I’ve done a lot of preparatory reading and learning about web components in the last year. I’ll admit that I’ve found the concepts (including Shadow DOM) occasionally tough to wrap my head around, and I’ve also found it confusing that everyone seems to implement web components in different ways. However Dave Rupert’s HTML with Superpowers presentation really helped make things click.
I’m now keen to create my own custom elements for javascript-enhanced UI elements; to give LitElement a spin; to progressively enhance a Light DOM baseline into Shadow DOM fanciness; and to check out how well the lifecycle callbacks perform.
I’ll go deeper with custom properties
I’ve been using custom properties for a few years now, but at first it was just as a native replacement for Sass variables, which isn’t really exploiting their full potential. However at work we’ve recently been using them as the special sauce powering component variations (--gap
, --mode
etc).
In our server-rendered components we’ve been using inline style
attributes to apply variations via those properties, and this brings the advantage of no longer needing to create a CSS class per variation (e.g. one CSS class for each padding variation based on a spacing scale), which in turn keeps code and specificity simpler. However as I start using web components, custom properties will prove really handy here too. Not only can they be updated by JavaScript, but furthermore they provide a bridge between your global CSS and your web component because they can “pierce the Shadow Boundary”, make styling Shadow DOM HTML in custom elements easier.
I’ll use BEM, but loosely
Naming and structuring CSS can be hard, and is a topic which really divides opinion. Historically I liked to keep it simple using the cascade, element and contextual selectors, plus a handful of custom classes. I avoided “object-oriented” CSS methodologies because I found them verbose and, if I’m honest, slightly “anti-CSS”. However it’s fair to say that in larger applications and on projects with many developers, this approach lacked a degree of structure, modularisation and predictability, so I gravitated toward BEM.
BEM’s approach is a pretty sensible one and, compared to the likes of SUIT, provides flexibility and good documentation. And while I’ve been keeping a watchful eye on new methodologies like CUBE CSS and can see that they’re choc-full of ideas, my feeling is that BEM remains the more robust choice.
It’s also important to me that BEM has the concept of a mix because this allows you to place multiple block classes on the same element so as to (for example) apply an abstract layout in combination with a more implementation-specific component class.
<div class="l-stack c-news-feed">
Where I’ll happily deviate from BEM is to favour use of certain ARIA attributes as selectors (for example [aria-current=page]
or [aria-expanded=true]
because this enforces good accessibility practice and helps create equivalence between the visual and non-visual experience. I’m also happy to use the universal selector (*
) which is great for owl selectors and I’m fine with adjacent sibling (and related) selectors.
Essentially I’m glad of the structure and maintainability that BEM provides but I don’t want a straitjacket that stops me from using my brain and applying CSS properly.
Enhance! by Jeremy Keith—An Event Apart video (on Vimeo)
A classic talk by Jeremy Keith on progressive enhancement and the nature of the web and its technologies.
Learn Responsive Design (on web.dev)
Jeremy Keith’s new course for Google’s web.dev learning platform is fantastic and covers a variety of aspects of responsive design including layout (macro and micro), images, icons and typography.
Life lessons from Larry David and Adam Buxton
Recently, during a period when pandemic woes were taking their toll and likely making me a little cranky at home, I found some funny and helpful words of wisdom on Adam Buxton’s podcast and when re-watching Ricky Gervais’s interview with Larry David.
On sustaining a happy marriage, Buxton said:
Every now and then you think “they’re not doing what I want them to do and I think it’s important that they do do this thing that I want them to do! (But) you have to think really hard: is this thing actually important? Is it worth getting bent out of shape about?
It’s good advice. He mentioned that he’s slowly getting better at that, and I’m trying my best to, too.
Meanwhile, on the reasons why he got into acting, Larry David said:
it gets you out of the house!
So simple but true—it’s good to get out of the house! We’re now emerging from a period of sustained isolation and I should probably avoid being completely remote (and going stir crazy) and get back into the office occasionally.
Resources for learning front-end web development
A designer colleague recently asked me what course or resources I would recommend for learning front-end web development. She mentioned React at the beginning but I suggested that it’d be better to start by learning HTML, CSS, and JavaScript. As for React: it’s a subset or offshoot of JavaScript so it makes sense to understand vanilla JS first.
For future reference, here are my tips.
Everything in one place
Google’s web.dev training resource have also been adding some excellent guides, such as:
Another great one-stop shop is MDN Web Docs. Not only is MDN an amazing general quick reference for all HTML elements, CSS properties, JavaScript APIs etc but for more immersive learning there are also MDN’s guides.
Pay attention to HTML
One general piece of advice is that when people look at lists of courses (whether or not they are new to web development) they ensure to learn HTML. People tend to underestimate how complicated, fast-moving and important HTML is.
Also, everything else – accessibility, CSS, JavaScript, performance, resilience – requires a foundation of good HTML. Think HTML first!
Learning CSS, specifically
CSS is as much about concepts and features – e.g. the cascade and specificity, layout, responsive design, typography, custom properties – as it is about syntax. In fact probably more so.
Most tutorials will focus on the concepts but not necessarily so much on practicalities like writing-style or file organisation.
Google’s Learn CSS course should be pretty good for the modern concepts.
Google also have Learn Responsive Design.
If you’re coming from a kinda non-CSS-oriented perspective, Josh W Comeau’s CSS for JavaScript Developers (paid course) could be worth a look.
If you prefer videos, you could check out Steve Griffith’s video series Learning CSS. Steve’s videos are comprehensive and well-paced. It contains a whole range of topics (over 100!), starting from the basics like CSS Box Model.
In terms of HTML and CSS writing style (BEM etc) and file organisation (ITCSS etc), here’s a (version of a) “style guide” that my team came up with for one of our documentation websites. I think it’s pretty good!
CSS and HTML Style Guide (to do: add link here)
For more on ITCSS and Harry Roberts’s thoughts on CSS best practices, see:
- Manage large projects with ITCSS
- Harry’s Skillshare course on ITCSS
- Harry’s CSS Guidelines rulebook
- Harry’s Discovr demo project
Learning JavaScript
I recommended choosing a course or courses from CSS-Tricks’ post Beginner JavaScript notes, especially as it includes Wes Bos’s Beginner JavaScript Notes + Reference.
If you like learning by video, check out Steve Griffith’s JavaScript playlist.
Once you start using JS in anger, I definitely recommend bookmarking Chris Ferdinandi’s Methods and APIs reference guide.
If you’re then looking for a lightweight library for applying sprinkles of JavaScript, you could try Stimulus.
Learning Responsive Design
I recommend Jeremy Keith’s Learn Responsive Design course on web.dev.
Lists of courses
You might choose a course or courses from CSS-Tricks’ post Where do you learn HTML and CSS in 2020?
Recommended books
- Resilient Web Design by Jeremy Keith. A fantastic wide-screen perspective on what we’re doing, who we’re doing it for, and how to go about it. Read online or listen as an audiobook.
- Inclusive Components by Heydon Pickering. A unique, accessible approach to building interactive components, from someone who’s done this for BBC, Bulb, Spotify.
- Every Layout by Heydon Pickering & Andy Bell. Introducing layout primitives, for handling responsive design in Design Systems at scale (plus so many insights about the front-end)
- Atomic Design by Brad Frost. A classic primer on Design Systems and component-composition oriented thinking.
- Practical SVG by Chris Coyier. Learn why and how to use SVG to make websites more aesthetically sharp, performant, accessible and flexible.
- Web Typography by Richard Rutter. Elevate the web by applying the principles of typography via modern web typography techniques.
Collected web accessibility guidelines, tips and tests
At work, I’m sometimes asked accessibility questions or to provide guidelines. I’m with Anna Cook in considering myself an accessibility advocate rather than an expert however I have picked up lots of tips and knowledge over many years of developing websites. So I thought it’d be useful to gather some general web accessibility tips and tests in one place as a useful reference.
Caveats and notes:
- this is a living document which I’ll expand over time;
- I’m standing on the shoulders of real experts and I list my references at the foot of the article; and
- if I’ve got anything wrong, please let me know!
Table of contents
- If you only had 5 minutes
- Content structure
- Semantic HTML and ARIA
- Favour native over custom components except where they have known issues
- Make custom components convey state accessibly
- Forms
- Links and buttons
- Ensure keyboard support
- Content resizing
- Better link text
- Supporting high contrast mode
- Skip links
- Navigation and menus
- Modal dialogues
If you only had 5 minutes
If someone had a web page and only had 5 minutes to find and tackle the lowest hanging fruit accessibility-wise, I’d probably echo Jeremy Keith’s advice to ensure that the page covers the following:
- uses heading elements sensibly
- uses landmarks (representing roles like
banner
,navigation
,main
,contentinfo
) - marks up forms sensibly (for example using labels and appropriate buttons)
- provides images with decent text alternatives
(Note: headings and landmarks are used by screen reader users to get a feel for the page then jump to areas of interest.)
Spending just 5 minutes would be bad, of course, and you shouldn’t do that. The point is that if pushed, the above give good bang-for-your-buck.
Content structure
The page’s content should be well structured as this makes it easier to understand for all, especially people with reading and cognitive disabilities.
It should consist of short sections of content preceded by clear headings. Use the appropriate heading level for the place in the page. Don’t use an inappropriate heading level to achieve a given appearance such as a smaller size. Instead use the appropriate heading element then use CSS to achieve your desired style.
It should employ lists where appropriate. It should place the most important content at the beginning of the page or section to give it prominence.
Check your page for any long passages of text with no structure. Ensure that sufficient prominence is given to the most important information and calls to action.
Semantic HTML and ARIA
While there are generic HTML elements like div
and span
, there are many more HTML elements that perform a specific role and convey that role to browers and other technologies. Choosing and using semantic HTML elements appropriately is a very good practice.
Also, using semantic HTML elements is preferable to bolting on semantics via attributes since the semantics are conveyed natively avoiding redundancy and duplication. As Bruce Lawson says, “Built-in beats bolt-on, bigly”.
Apply ARIA carefully. No ARIA is better than bad ARIA.
Landmarks
Create a small number of landmarks using the appropriate HTML elements.
For some landmark-generating elements it’s appropriate to bolster them with a label or accessible name. For example with nav
and aside
, i) there’s a decent chance there might be multiple on the page; and ii) each instance creates a landmark even when it’s nested within a deeper HTML element. So it’s helpful to distinguish each different landmark of the same type by using sensible accessible names otherwise you’d get multiple navigation menus all represented by the same “navigation” in the Landmarks menu. In the case of the section
element it needs an acessible name in order for it to act as a region
landmark. For all of these you can use aria-labelledby
set to the id
of an inner heading, or use aria-label
.
Note that when using multiple <header>
(or footer
) elements on a page, where one and one only is a direct child of body
while the others are used within article
or similar elements, there’s perhaps less need to add custom accessible names. That’s because only a direct child of body
will be treated as a landmark and the others won’t, therefore they won’t be butting against each other in a screen reader’s Landmarks menu and need distinguished.
Correct use of aria-label and aria-labelledby
Use the aria-label
or aria-labelledby
attributes (only when necessary) on interactive elements – buttons, links, form controls – and on landmark regions. Don’t use them on <div>
s, <span>
s, or other elements representing static/noninteractive text-level semantics, such as <p>
, <strong>
, <em>
, and so forth, unless those elements’ roles have been overridden with roles that expect accessible names.
Favour native over custom components except where they have known issues
Native components require very little work, are familiar to users, and are generally accessible by default. Custom components can be built to appear and behave as designers want, but require much more effort to build and are challenging to make accessible.
There are exceptions. Since the native options are flawed across browsers, accessibility experts recommend using custom solutions for:
- form error field messages
- focus indicator styles
Make custom components convey state accessibly
Now that you’re building a custom component you don’t get accessibility out of the box. Whether it’s a Like button or a disclosure widget, you can’t rely on a visual change alone to convey a UI change to all users. You’ll need to use the right element (note – it often starts with a button
) and then use ARIA to convey states such as pressed or expanded to screen reader users.
Forms
Because in the industry form fields are often handled with JavaScript and not submitted, people sometimes question whether form fields should live inside a form (<form>
). My answer is yes, and here’s why.
Using the form element improves usability and accessibility
Using a <form>
provides additional semantics allowing additional accessibility. It helps assistive devices like screen readers better understand the content of the page and gives the person using them more meaningful information.
By putting form fields inside a form we also ensure we match user expectations. We support the functionality (such as the different ways of submitting a form) that users expect when presented with form fields.
If you’re thinking “but what about form fields that don’t look like form fields?” then you’ve entered the problem territory of “deceptive user interfaces” – the situation where perceived affordances don’t match actual functionality, which causes confusion for some people. This is to be avoided. We shouldn’t use form fields (nor a <form>
) when they are not appropriate. A checkbox, radio button, or select menu is meant to gather information. So if your goal is instead to let the user manipulate the current view, use a button
rather than checkboxes or radio buttons.
References:
- Why use a form element when submitting fields with JavaScript
- Lea Verou and Leonie Watson’s discussion regarding Toggles
- My conversation about forms with accessibility expert Adrian Roselli
Using the form element simplifies your JavaScript for event handling
Using the form
element can also make it easier for you to meet user expectations in your JS-powered experience. This is because it gives you a single element (form
) and event combination that allows listening to multiple interactions. With a form element you can add a listener for the submit()
event. This event fires automatically in response to the various ways users expect to submit a form, including pressing enter inside a field.
Anchors and buttons
To let the user navigate to a page or page section, or download a file, use an anchor element.
To let the user trigger an action such as copying to clipboard, launching a modal or submitting a form, use a button element.
Anchors should include an href
attribute otherwise the browser will treat it like a non-interactive element. This means the link will not be included in the expected focus order and will not present a pointer to mouse users like it should. These days there is no remaining use case for an anchor without an href
. We no longer need named anchors to create link-target locations within the page because we can use the id
attribute (on any element) for that. And if you want an interactive element that does not link somewhere, you should use button
.
Do not remove the focus outline from links and buttons in CSS, unless it’s to provide a better version.
Ensure you always give links and buttons an accessible name, even when they use icons rather than text. This might be through visually hidden text or perhaps using an ARIA-related attribute.
Ensure keyboard support
Web pages need to support those who navigate the page by keyboard.
Use the tab key to navigate your page and ensure that you can reach all actionable controls such as links, buttons and form controls. Press the enter key or space bar to activate each control.
If during your test any actionable control is skipped, receives focus in an illogical order, or you cannot see where the focus is at any time, then keyboard support is not properly implemented.
Content resizing
Try zooming your page up to 400%. In Chrome, Zoom is available from the kebab menu at the top-right, or by holding down command with plus or minus.
Content must resize and be available and legible. Everything should reflow.
Relative font settings and responsive design techniques are helpful in effectively handling this requirement.
Relatedly, setting font-sizes in px
should be avoided because although a user can override the “fixed-ness” with zoom, it breaks the user’s ability to choose a larger or smaller default font size (which users often prefer over having to zoom every single page).
Better link text
Blind and visually impaired users use a screen reader to browse web pages, and screen readers provide user-friendly access to all the links on the page via a Links menu. When links are encountered in that context, link text like “Click here” and “Read more” are useless.
Check your web page to ensure that links clearly describe the content they link to when read out of context.
Better link text also improves the flow and clarity of your content and so improves the experience for everyone.
Supporting high contrast mode
Some people find it easier to read content when it’s in a particular colour against a specific background colour. Operating systems provide options to allow users to configure this to their preference. Websites must support support the user’s ability to apply this.
On a Windows computer go to Settings > Ease of access and turn on High contrast mode. On macOS go to System preferences > Accessibility settings > Display and select “Invert colours”.
Having changed the contrast, check that your web page’s content is fully visible and understandable, that images are still visible and that buttons are still discernible.
Skip links
Websites should provide a “Skip to content” link because this provides an important accessibility aid to keyboard users and those who use specialised input devices. For these users, having to step through (typically via the tab key) all of the navigation links on every page would be tiring and frustrating. Providing a skip link allows them to bypass the navigation and skip to the page’s main content.
To test that a website contains a skip link, visit a page then press the tab key and the skip link should appear. Then activate it using the enter key and check that focus moves to the main content area. Press tab again to ensure that focus moves to the first actionable element in the main content.
Navigation and menus
When developing a collapsible menu, place your menu <button>
within your <nav>
element and hide the inner list rather than hiding the <nav>
element itself. That way, we are not obscuring from Assistive Technologies the fact that a navigation still exists. ATs can still access the nav via landmark navigation. This is important because landmark discovery is one of the fundamental ways AT users scan, determine and navigate a site’s structure.
Modal dialogues
You probably don’t want to set the modal’s heading as an <h1>
. It likely displays content that exists on the page (which already has an <h1>
) at a lower level of the document hierarchy.
References
- Using HTML landmark roles to improve accessibility MDN article. And Adrian R’s suggestions for additions
- Navigation (landmark) role, on MDN
- Tetralogical’s Quick Accessibility Tests YouTube playlist
- Basic accessibility mistakes I often see in audits by Chris Ferdinandi
- Sara Soueidan’s video tutorial Practical tips for building more accessible front-ends
- Adrian Roselli’s Responsive type and zoom
- Heydon Pickering’s tweet about buttons in navs and Scott O’Hara’s follow up article Landmark Discoverability
- Tetralogical’s Foundations: native versus custom components
- Ben Myers on where to use aria labelling attributes