Accessibility is a key skill for developers doing any kind of work in the stack. For front-end tasks, modern CSS provides capabilities we can leverage to make layouts more accessible to users of all abilities on any device.
This article will cover a range of topics.
- Focus visibility
- Focus and source order
- Desktop zoom and reflux
- The size of the interactive target
- Respect color and contrast Settings
- Accessible Learning Resources
What does “accessibility” mean?#
A barrier-free web site is a site created without barriers for users of different abilities to access content or perform operations. An internationally recognized standard called the Web Content Accessibility Guidelines (WCAG) provides success criteria to help guide you in creating a barrier-free experience.
Common non-barrier disorders include
- Unable to see content or distinguish interface elements due to poor color contrast
- Reduce or cancel access to non-text content, such as content within an image or chart, because no alternative text is provided
- Keyboard users get bogged down with no focus for managing interactive elements
- Headaches or worse for users with vestibular disorders due to motion and flashing/flashing animations.
- Users of assistive technologies such as screen readers are unable to operate because of the failure to adapt custom controls to the expected pattern
- The lack of semantic HTML, including title hierarchies and landmark elements, limits common assistive technology navigation methods.
The success criteria “are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital TV, and more.” We’ll look at several success criteria and how modern CSS can help provide accessibility solutions.
Focus visibility#
A common transgression I’ve done myself in the past is to remove the: Focus Overview from links, buttons, and other interactive controls. If you don’t provide an alternative: Focus style, this immediately violates the WCAG success standard 2.4.11: Focus look.
More often than not, this is removed because the native browser style is deemed unappealing or does not fit the design choices of the site. However, with modern CSS, we have a new property that can help make contours more attractive.
Using outline-offset, we can provide a positive value to position the contour away from the element. For offsets, we will use em units to position the contours relative to the element based on its font size.
Bonus: We use the Max () function to set the value of the outline width to ensure that it does not shrink below a calculated value of 1px, while allowing it to use the relative size of em as well.
Select the Demo button to display :focus outline.
“CSS with positive ‘outline-offset’ focus style”
button:focus { outline: max(1px.0.1 em) solid currentColor; outline-offset: 0.25 em; }Copy the code
.outline-offset-866 { color: purple; line-height: 1; The font – size: 1.5 rem; cursor: pointer; }. Offset :focus {outline: Max (1px, 0.1em) solid currentColor; The outline – offset: 0.25 em. }
Demo button
Alternatively, you can use a negative value to set outline-offset to insert the outline from the perimeter of the element.
“Use negative ‘outline-offset’ focus style” CSS
button:focus { outline: max(1px.0.1 em) dashed currentColor; outline-offset: -0.25 em; }Copy the code
.outline-offset-127 { background-color: purple; color: white; Padding: 0.5 em. border-radius:4px; line-height: 1; The font – size: 1.5 rem; cursor: pointer; }. Outcom-offset: focus {outline: Max (1px, 0.1em) currentColor; The outline – offset: 0.25 em. }
Demo button
There is also a new pseudo class that you might consider using in some cases. The: Focus-Visible pseudo class will display the outline (or user-defined style) only when the device/browser (user agent) determines that it needs to be visible. Typically, this means that it will appear to keyboard users during TAB key interactions, but not to mouse users.
With this update, our button style _ likely _ only appears when you click the button with the keyboard.
Negative “outline-offset “focus style CSS
button:focus { outline: none; }button:focus-visible { outline: max(1px.0.1 em) dashed currentColor; outline-offset: -0.25 em; }Copy the code
.outline-offset-837 { background-color: blue; color: white; Padding: 0.5 em. border-radius:4px; line-height: 1; The font – size: 1.5 rem; cursor: pointer; } .outline-offset-837:focus { outline: none; }. Outline-offset: focus-visible {outline: Max (1px, 0.1em)}. Outline-offset: focus-visible {outline: Max (1px, 0.1em)}. The outline – offset: 0.25 em. }
Demo button
Please note that: Focal-Visible support is still rolling out to all browsers and is particularly missing in Safari. If you want to try it out, here’s an example of using it as a progressive enhancement.
We are taking advantage of the fact that we do not understand that a focus-visible browser will discard the rule that removes the :focus contour. This means that the first rule of Button: Focus will apply to browsers that do not support: Focal-Visible, and the second two rules will apply to browsers that support: Focal-Visible. Interestingly, :focus:not(: focal-visible) gives the false impression that focal-visible is valid in Safari_ or even _Internet Explorer 11.
button:focus { outline: max(1px.0.1 em) dashed currentColor; outline-offset: -0.25 em; }button:focus:not(:focus-visible) { outline: none; }button:focus-visible { outline: max(1px.0.1 em) dashed currentColor; outline-offset: -0.25 em; }Copy the code
_ Hi, how are you? _ Sign up for my CSS workshop at Smashing Conference in July. Improve with modern CSS
Focus and source order#
Another criterion related to focus is the success criterion 2.4.3. Focus order. For both visual and non-visual users, the focus order — typically initiated by a label on the keyboard — should proceed logically. Especially for visual users, the focus order should follow the expected path, which usually means following the order of the source.
As you can see from the standard file linked earlier.
“For example, screen readers interact with programmatically determined reading orders, while keyboard users with good vision interact with visual rendering of web pages. Care should be taken to make the focus order meaningful to both groups of users, rather than appear random to them.”
Modern CSS technically provides layout properties that rearrange the visual order into something different than the source order. The effect of this is that _ if _ has elements to focus on that are rearranged into a surprising order, the success criteria for focusing on the order may not pass.
If you can, use your Tab key to browse through the demo below, noting what you expect to happen and what actually happens. When you are done, or if the label key is not currently an available input for you, check the box to show the label order.
[class*=”source-order”] { color:#222; } .source-order-36 { display: grid; Grid-template-columns: repeat (3, 1fr); grid-gap: 1rem; list-style: none; } .source-order-36 li { padding:1rem; text-align: center; } .source-order-36 li a { display: grid; place-content: center; color: inherit; } .source-order__label-36 { font-weight: bold; } .source-order__label-36, .source-order__checkbox-36 { display: inline-block; Margin – top: 0.5 rem; Margin – left: 0.5 rem; Margin – bottom: 1.5 rem; } .source-order__checkbox-36:checked ~ ol { list-style: decimal; list-style-position: inside; } .source-order-36 li:nth-child(2) { grid-column:3; grid-row: 2; } .source-order-36 li:nth-child(5) { grid-column:2; grid-row: 1; } .source-order-36 li:nth-child(7) { grid-column:1; grid-row: 1; } .source-order-36 li:nth-child(3) { grid-column:1; grid-row: 3; }
Reveal the order
- link
- link
- link
- link
- link
- link
- link
The purpose of this section is more to provide an understanding of this standard as you consider how to solve layout challenges. The upcoming CSS Grid is a native “brick and stone” solution. Unfortunately, it can have a negative impact on the intended order of focus. Similar problems arise when assigning specific grid regions that do not conform to the source order, and when using the Order attribute to customize the order of items in Flexbox.
A recent challenge I faced was when a list of navigation links was asked to be broken up into several columns. In this case, the logical order of labels should be to move down one column before moving to the next. The problem is that this is a list of items on a single topic, so semantically breaking it up into a list of columns is not ideal. For users of assistive technologies such as screen readers, splitting multiple lists also incorrectly displays the number of items on a single topic, because screen readers publish the number of items in the list.
Using the following set of CSS grid properties, I was able to come up with a solution that didn’t break the list.
“List focus order solution using CSS grid” CSS
ul { display: grid; grid-column-gap: 2rem; grid-row-gap: 1rem; /* Causes items to be ordered into columns */ grid-auto-flow: column; /* # required to prevent a column per link */ grid-template-rows: repeat(3.1fr); /* Size the columns */ grid-auto-columns: minmax(0.1fr); }Copy the code
.grid-list-focus-order-943 { list-style: none; padding:2rem; display: grid; grid-column-gap: 2rem; grid-row-gap: 1rem; grid-auto-flow: column; grid-template-rows: repeat(3, 1fr); grid-auto-columns: minmax(0, 33%); } . Grid-list-focus-order-943 a { color: blue; }
- Link 1
- Link 2
- Links to 3
- Links to 4
- Links to 5
- Links to 6
- Links to 7
- Links to eight
The caveat here is that you need to be careful how much space you need to accommodate your content. In navigation scenarios, content is often tightly controlled, so this might be a reasonable solution.
Manuel Matuzovic has a good, more thorough guide to CSS grid layout and changing source order considerations.
Desktop zoom and reflux#
You’ve tested different viewport sizes using browser development tools and real mobile devices, and you’re happy with the responsive behavior of your site. But there’s one test point you might be missing: desktop zooming.
In the previous tutorial, we started looking at the WCAG success standard 1.4.10– Refluxing.
Reflow is the term that supports desktop scaling up to 400%. At a 400% resolution of 1280px wide, viewport content is equivalent to 320 CSS pixels wide. The user’s intention with this setting is to trigger content _ backflow _ into a single column for easy reading.
As mentioned in the previous tutorial, scaling usually starts to trigger response behavior that you might set up with a media query. But there are no zoom media queries yet. Therefore, enlarging the desktop to a 400% aspect ratio can cause your content to have a _ reflow _ problem.
Some examples of what can go wrong.
- Sticky navigation that covers half or more viewports
- Assuming the longitudinal aspect ratio of the phone, the contained scroll area becomes unscrollable/cut off
- Unwanted results when using fluid typesetting techniques
- Overflow or overlapping problems cut off the content
- Margins and padding are too large for the size of the content
Without scaling media queries, it can be difficult to design scaling solutions that are independent of device size assumptions.
However, with modern CSS features such as min() and Max (), we have tools to solve some scaling situations without compromising the original intent of our assumed mobile design.
** Would you like to receive CSS tips in your inbox? ** Please join my newsletter for article updates, CSS tips, and front-end resources.
We have previously looked at using MIN to adjust a grid column containing heads in a way that applies to small, large, and enlarged viewports.
Let’s look at solving the vertical spacing problem. A common practice is for designers to create a pixel ramp for spacing, perhaps based on an 8px unit. So, you might create a spacing tool that looks like.
.margin-top-xs { margin-top: 8px; }.margin-top-sm { margin-top: 16px; }.margin-top-md { margin-top: 32px; }.margin-top-lg { margin-top: 64px; }.margin-top-xl { margin-top: 128px; }Copy the code
And so on, to fit the full range of your bevels. This is usually good within the range of standard equipment. Xl but considering 128px, zoom to 400% on the desktop and that becomes half the viewport height.
Instead, we can update the upper end of the range by adding min() and selecting the lowest calculated value of _. This means that for non-scaled viewports, 128px will be used. For 400% zoom viewports, another viewport unit value can be used.
If you’re using a desktop computer, try zooming to see what happens to the space between presentation elements.
“Solve scaling margin problem” CSS
section + section { margin-top: min(128px.15vh); }Copy the code
.zoom-margin-spacing-668 { min-height: 150px; padding:1rem; margin: 0 1rem; outline: 1px solid purple; outline-offset: -.5rem; color:#222; }. Zoom-margin-spacing -668 +. Zoom-margin-spacing -668 {margin-top: min (128px, 25vh); }
Section 1
Section 2
In the third quarter
It is also possible to use this technique if you encounter overlap between absolutely positioned elements.
For more examples of how scaling affects layouts and some modern CSS solutions, check out the recording of the CSS Cafe gathering: “Modern CSS Solutions to (previously) Complex Problems”
In the next section, we’ll see how to query for touch devices. Using this approach can be a great combination to help determine the user’s context and provide an alternative layout for things like sticky navigation or custom scroll areas.
Determine the size of the interactive target#
Our next area is to consider correctly sizing interactive targets, where the term “target” comes from success criterion 2.5.5: Target size. By that standard.
“_ The intent of this success criterion is to ensure that the target size is large enough that users can easily activate them, even if the user is accessing content on a small handheld touch screen device, has limited flexibility, or is otherwise difficult to activate small targets. _ For example, mice and similar directional devices may be difficult for these users to use, and larger goals help activate them.”
Introduced in WCAG 2.2 is the success criterion 2.5.8: pointer target spacing. The guide states that in general, interactive controls should be
- There is a minimum actual size of 44px;or
- The minimum target size allowed — including the spacing between controls and other elements — is 44px.
In the linked resources, there are some exceptions and additional examples to help clarify this requirement. Adrian Roselli also provided itA good overview and history.
In addition to this site, I maintain smolcss.dev, which explores the smallest modern CSS solutions for creating layouts and components. Below is an excerpt from one of the demos, the Smol Avatar List Component.
It uses the CSS function Max () to ensure that the default display of the avatar link column is at least 44px, regardless of what might be passed within the –avatar-size value.
.smol-avatar-list { --avatar-size: 3rem; --avatar-count: 3; display: grid; /* Default to displaying most of the avatar to enable easier access on touch devices `max` ensures the WCAG touch target size is met or exceeded */ grid-template-columns: repeat( var(--avatar-count), max(44px.calc(var(--avatar-size) / 1.15)) );}
Copy the code
The solution then allows the heads to overlap when it detects that a hovering device may also take “fine” inputs, such as a mouse or stylus. This allows animation on: Hover and: Focus — two interactions that are not available on purely touch devices — to fully display the avatar in these states and meet the target size.
This fragment shows the media queries that allow this detection.
@media (any-hover: hover) and (any-pointer: fine) { /* Allow avatars to overlap by shrinking grid cell width */}
Copy the code
Using device capability testing, we can provide an experience that allows for meeting this standard while also allowing design flexibility.
Always test the solution with real devices based on real user data.
Reduce the movement#
Some of your users may have vestibular disorders that can lead to headaches and even seizures due to movement and blinking/blinking animations.
There are three main criteria.
- Success criteria 2.3.1 Three flashes or below threshold – Web pages do not contain anything that flashes more than three times in any one second, or flashes below the threshold for normal flashes and red flashes.
- Success criteria 2.3.2: Three flashes – Web pages do not contain any content that flashes more than three times in any one second.
- Success criteria 2.3.3: Animation from Interaction – Action animations triggered by an interaction can be disabled unless the animation is critical to the function or message conveyed.
For more comprehensive information, Val Head is a leading expert who has written extensively on this standard, including this CSS-Tricks article covering the SPORts-related WCAG standard.
Modern CSS provides us with a media feature query that we can use to test the user’s OS preferences for motion. While your basic animations/transitions should meet the flash-related thresholds mentioned in the WCAG standard, if the user likes to reduce actions, you should block them entirely.
Here’s a quick rule set to deal with this problem in a comprehensive way, courtesy of Andy Bell’s Modern CSS Reset.
/* Remove all animations and transitionsfor people that prefer not to see them */@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01 ms ! important; animation-iteration-count: 1 ! important; transition-duration: 0.01 ms ! important; scroll-behavior: auto ! important; }}
Copy the code
The animation only runs once, and any transformations take place immediately. In some cases, you plan animations for this possibility to make sure they “freeze” on the ideal frame.
The demo includes a gentle pulsing circle, unless the user chooses to reduce the movement.
CSS for “Demo of `prefers-reduced-motion'”
@media (prefers-reduced-motion: reduce) { div { animation-duration: 0.01 ms ! important; animation-iteration-count: 1 ! important; }}
Copy the code
@keyframes pulse {0% {transform: scale(0.85); } 100% {transform: scale(1.25); } } .prefers-reduced-motion-13 { width: 3rem; height: 3rem; border-radius: 50%; border:3px solid purple; animation: pulse 2000ms infinite alternate easy-in-out; }
You can test the results of this media feature query in Chrome/Edge by opening development Tools, selecting the kebab menu (3 vertical dots), then “More tools” and “Render”. You can then toggle the Settings in the “Simulate CSS media feature preferences – reduce motion” section.
Respect color and contrast Settings#.
Dark mode may seem like a fad, but for some users, it’s key to making sure they can read your content. While there are currently no guidelines stating that both dark and light modes of content are required, you can start being future-friendly by considering offering both modes.
One of the requirements is that if you offer dark color modes, at least continue to pass the standard color contrast criteria provided in Success criteria 1.4.3. Contrast ratio (lowest).
The minimum contrast required is at least.
- Normal text is 4.5:1
- 3:1 for large text – defined as 18.66px and bold or larger, or 24px and larger
- 3:1 for graphical and user interface components (such as form input borders).
Just a reminder. WCAG 3 is considering a new color contrast model, but it will be a few years before it becomes standard. You can learn more about the proposed Advanced Perceptual Contrast Algorithm (APCA) in the draft WCAG 3 guidelines (these may change over time until it becomes standard).
Whether or not you provide dark and light modes, users of Windows 10 devices can choose their system Settings to enable high contrast mode.
In most cases, you should allow the user’s Settings in this mode to be applied. Occasionally, applications with high contrast Settings mean that colors that are important to understanding your interface are removed.
In Windows high contrast mode, three normally critical attributes will be removed.
box-shadow
background-color
background-image
Unless it contains oneurl()
值
There are also two key attributes whose colors will be replaced with the equivalent of the “system color”.
color
border-color
You can view a complete list of properties affected by this “force color” mode.
Sometimes these attributes can be compensated for by using transparent substitutes. For example, if you use box-shadow as a substitute for outline to match border-radius on :focus, you should still include an outline with a color value set to transparent as a complement, To retain the mandatory color mode: Focus style. This example is included in my CSS button style guide. In a similar way, if the shadow is meant to convey an important border, you can include a transparent border instead of a box-shadow.
If you use SVG ICONS, passing currentColor as a value for fill or stroke will help ensure that they respond to the mandatory color setting.
Alternatively, you can use a special media function query to assign colors from the system palette. These are all respectful of user Settings, while allowing you to ensure that your key interface elements retain color.
Here is an example of using simple CSS shapes and gradients as ICONS and making sure they retain color in forced color mode. In the formed-Colors function query, we only need to set the color property because we have already set the icon to use the currentColor value.
“Force color presentation” CSS
.icon { width: 1.5 rem; height: 1.5 rem; display: inline-block; color: blue; }.filled { background-color: currentColor; border-radius: 50%; }.gradient { background-image: repeating-linear-gradient( 45deg, currentColor, currentColor 2px.rgba(255.255.255.0) 2px.rgba(255.255.255.0) 6px ); border: 1pxsolid; }@media screen and (forced-colors: active) { .icon { // Required to enable colors forced-color-adjust: none; // User-preferred " text"color color: CanvasText; }}
Copy the code
Icon – 221 {width: 1.5 rem; Height: 1.5 rem; display: inline-block; color: blue; } .fill-221 { background-color: currentColor; border-radius: 50%; } .gradient-221 { background-image: repeating-linear-gradient( 45deg, currentColor, currentColor 2px, rgba(255, 255, 255, 0) 2px, rgba(255, 255, 0) 6px ) ; border:1px solid; } @media screen and (forced-colors: active) { .icon-221 { // Required to enable colors forced-color-adjust: none; // User-preferred “text” color color: CanvasText; }}
Using Windows High contrast mode, the possible result based on the default values is that the blue turns white and the page background turns black due to the CanvasText color keyword.
Review more information on how this pattern works with a broader example on the Microsoft Edge blog
Accessible learning resources#
While we’ve linked success criteria and other resources throughout the example, there’s still a lot to learn
Here are some additional resources to learn more.
- a11y.coffee
- solidstart.info
- An extensive guide to accessible front-end components from Smashing magazine
- Search and explore the Stark repository
I also created various resources.
- An introduction to accessibility in my Beginner WebDev series
- Practice problem solving with my A11Y failed project
- Listen to this two-part Word Wrap podcast series (which I co-host) about common accessible failure and WCAG success criteria that you may not meet
- Know how to handle stateful button comparisons and generate a barrier-free palette with my web app ButtonBuddy
- Use the A11Y-Color-Tokens pack to speed up the generation of an accessible palette
I like to talk about accessibility and try to design accessibility tutorials and point out any accessibility details. If you spot a bug or want to suggest an improvement, let me know on Twitter.