Rethinking Text Resizing on Web
Airbnb has made significant strides in improving web accessibility for Hosts and guests who require larger text sizes.
This post takes an in-depth look at:
- The problems encountered on mobile web when relying solely on browser zoom.
- The challenges of introducing changes that would impact the workflow of all frontend engineers.
- The benefits seen since launching these accessibility improvements.
by: Steven Bassett
Improving web accessibility is a critical priority at Airbnb, and we use the Web Content Accessibility Guidelines (WCAG) to help guide our compliance efforts. One area that often leads to accessibility issues is WCAG 1.4.4 Resize Text (Level AA). This guideline, which we’ll refer to as Resize Text, is particularly beneficial for people with low vision, whether correctable or not (for example with glasses or prescription contacts). The standard specifies that web content and functionality must be maintained when text is scaled 200% (2x) of its original size. Ensuring our site meets this guideline is an important part of our ongoing work to enhance accessibility for all of our users.
In this blog post, we’ll explore our investigation into the importance of this guideline, how we analyzed our site issues, the technical benefits for using rem units, how we decided on an approach, the cross-browser support issues we encountered, and the benefits we saw in reducing the number of reported issues for Resize Text.
Meeting the Needs of Users with Vision Difficulties
“90 million Americans over 40 have vision and eye problems. That’s more than 3 in 5.”
Looking Ahead: Improving Our Vision for the Future” CDC
To illustrate, consider how the Airbnb homepage might appear to someone who has experienced a significant loss of visual acuity. As shown below, the text becomes extremely impossible to read comfortably.
Browser Zoom
To better understand the accessibility challenge, let us explore how browser zoom functionality works. You may already be familiar with this feature, using keyboard shortcuts like Command / Ctrl + or Command / Ctrl — to scale all content within a window. When you increase the zoom level beyond 100%, the viewport’s height and width proportionally decrease, while the content is blown up to fit the larger window.
As part of our accessibility testing strategy, we were using browser zoom to test the usability of our pages both on desktop and mobile sizes. Desktop testing showed that our pages did relatively well at the 200% zoom level with our responsive web approach across the site. We saw fewer issues in the overall user experience when compared to mobile web.
This works well on desktop, where we serve a smaller breakpoint (e.g., wide to compact) and the viewport is relatively spacious. However, the limitations of browser zoom become more pronounced on mobile web, where the viewport is smaller. If we were to scale the content in a mobile viewport, it would have to fit into a viewport that is half the width and half the height of the original. This can result in significant accessibility issues, as the text and UI elements become extremely difficult to read and interact with. As shown in the image on the right, the ability to view even a single listing within a screen’s worth of space is not possible without scrolling, leading to a frustrating experience.
Font Scaling
Font scaling is the term we’ll use to describe the ability to adjust text size independently of overall page zoom. Unlike browser zoom, which scales all content proportionally, Font Scaling applies only to the text elements on the page. This allows users to customize the font size to their preferred reading size without affecting much of layout or responsiveness of the rest of the content.
Font Scaling, is also the term we will use for scaling the font based on a user’s preferred size. Unlike zoom, this setting will be applied to all sites. Below is an example of how the font scaling applies to just the text on the screen, showing that the only scale of the text increases, instead of all the content.
Video Description: Airbnb text is scaled by setting the font size on arc browser, showing the scaling from 16px to 32px.
This concept of independent font scaling is similar to the Dynamic Type feature on iOS, as we discussed in our blog post “Supporting Dynamic Type at Airbnb”. Dynamic Type allows users to set a preferred system-wide text size, which then automatically adjusts the font size across all compatible apps.
Considering our existing strategies for accessibility on iOS, incorporating font scaling (vs zoom scaling) into our web accessibility approach was a natural next step to help add parity in approaches across our platforms.
Understanding px, em vs rem
Now that we understand why font scaling is so powerful for mobile web, we should focus on why we might choose one CSS length unit over another for supporting font scaling. In this blog post we are only going to focus on px, em and rem but there are other units as well. CSS length units are connected to font scaling because they determine how text and other elements are sized on a web page. Some length units are fixed, meaning they don’t change based on the user’s font size settings, while others are relative, meaning they scale proportionally with the font size.
Let’s take a deep look at 3 CSS length units and how they relate to font scaling:
- px units are the most commonly used on the web, theoretically they should represent one pixel on the screen. They are a fixed unit meaning the rendered value does not change.
- em units however are a relative unit that are based on the parent element’s font size. The name ‘em’ comes from the width of the capital letter ‘M’ in a given typeface, which was traditionally used as the reference point for font sizes. 1 em unit is equal to the height of the current font size, roughly 16px at the default value. em units scale proportionally, so they can be affected by their parent’s font sizes
- rem units, short for “root em”, are similar to em units in that they are proportional to font size, but they only use the root element (the html element) to calculate their font size. This means that rem units offer font scaling, but are not affected by their parent’s font size.
The choice between em and rem units often comes down to the level of control and predictability required for font scaling. While em units can be used, they can lead to cascading font size changes that may be difficult to manage, especially in complex layouts. In contrast, rem units provide a more consistent and predictable approach to font scaling, as they are always relative to the root element’s font size.
This is illustrated in the CodePen example, where the different font scaling behaviors of px, em, and rem units are demonstrated. In situations where font scaling is a critical requirement, such as the Airbnb example mentioned, the use of rem units can be a more reliable choice to ensure a consistent and maintainable font scaling solution.
Relative units like rem can be used anywhere a fixed unit like px can be used. However, indiscriminate use of rem units across all properties can lead to unwanted scaling behavior and increased complexity.
In the case of Airbnb, the team decided to prioritize the use of rem units specifically for font scaling, rather than scaling all elements proportionally. This targeted approach provided the key benefit of consistent text scaling, without the potential downsides of scaling every aspect of the layout.
The rationale behind this decision was twofold:
- Scaling everything using rem units would have been similar to Browser Zoom and potentially introduced unintended layout issues,
- The primary focus was on providing a mobile-friendly font scaling solution. By targeting font sizes with rem units, the team could ensure that the most important content — the text — scaled appropriately.
Enabling a Seamless Transition for Designers and Developers
Moving from pixel-based values to rem units as a company-wide change in CSS practice can be a significant challenge, especially when working across multiple teams. The time and effort required to educate designers and frontend developers on the new approach, and to have them convert their existing pixel-based values to rem units, can be a significant barrier to adoption. To address this, the Airbnb team decided to focus on automating the unit conversion process as much as possible, enabling a more seamless transition to the new rem-based system.
Reducing Friction in Design Iterations
Instead of requiring designers to have to think of new units or introduce some conversion for web only, we decided to continue to author our CSS in px units. This reduced the amount of training required for teams to start using rem units out the gate.
One area we did focus on with our design teams was starting to test their designs using font scaling by leveraging the Text Resizer — Accessibility Checker to help simulate what a design might look like at 2X the font size. This tool helped us spot problems earlier into the design process.
Addressing the Complexity of Two CSS-in-JS Systems
Airbnb is in the process of transitioning from React-with-Styles to a newer approach using Linaria. While the adoption of Linaria was progressing quickly, we recognized the need to support both styling systems for a consistent experience. Managing the conversion across these two different CSS-in-JS systems posed an additional challenge.
Linaria
By leveraging Linaria’s support for CSS custom properties, the team was able to create new typography theme values that automatically converted the existing pixel-based values to their rem equivalents. This approach allowed the team to introduce the new rem-based theme values in a centralized manner, making them available to child elements. This gave the team the ability to override the rem values on a per-page basis, providing the necessary flexibility during the transition process.
import { typography } from './site-theme';
// Loops through the CSS Vars we use for typography and converts them
// from px to rem units.
const theme: css`
${getCssVariables({ typography: replacePxWithREMs(typography) })}
// Changes from:
// - body-font-size: 16px;
// To
// - body-font-size: 1rem;
`;
// Use the class name generated from linaria to override the theme
// variables for the children of this component.
const RemThemeLocalProvider: React.FC = ({ children }) => {
const cx = useCx();
return <div className={linariaClassNames.theme)}>{children}</div>;
};ty
Although this approach helped us convert most of the font scaling properties, there were many places in our code that we used pxbased values outside the theme. Linaria’s support for post-CSS plugins made solving these areas relatively easy. We leveraged postcss-pxtorem to help target those values more easily. We started by using an allow list, so that we could carefully apply this change to a smaller set of early adopting pages.
It was important that we provided an escape hatch when there was some reason for front-end engineers needing to use px units. Luckily we were able to provide this by using a different casing for the px value like shown below.
/* `px` is converted to `rem` */
.convert {
font-size: 16px; /* converted to 1rem */
}
/* `Px` or `PX` is ignored by `postcss-pxtorem`
but still accepted by browsers */
.ignore {
font-size: 200Px;
font-size: clamp(16Px, 2rem, 32Px);
}
React with Styles
A good amount of our frontend code still uses react-with-styles, so we had to find another way to support these cases with an easy conversion. Through this we created a simple Higher-Order component that made the conversion pretty straightforward. First we created a wrapper for the withStyles function like below, and gave the ability to avoid conversion as well.
export const withRemStyles = (
styleFn?: Nullable<(theme: Theme) => Styles>,
options?: WithStylesOptions & { disableConvertToRemUnits?: boolean },
) => {
const disableConvertToRemUnits = getDisableConvertToRemUnits(options);
// If conversion is disabled, just return the original withStyles function
if (disableConvertToRemUnits) {
return _withStyles(styleFn, options);
}
// Otherwise, wrap the original style function with a new function
// that converts px to rem
return _withStyles((theme: Theme) => {
if (styleFn) {
const styles = styleFn(theme);
const remStyles = convertToRem(styles);
return remStyles;
}
return {};
}, options);
};
Then the convertToRem will look through the keys and values and map a converted value for any of the font sizing attributes. This allowed us to automate the conversion process in a more straightforward way.
Improvements for Testing Components
With these two challenges out of the way, we can start testing our components to verify if there are any major issues we might need to resolve before rolling out. In our component documentation and tooling, we built an internal plugin to allow for easier testing by setting the font-size on the html element directly to test with font scaling.
Screenshot testing has helped our teams catch visual regressions. Adding support to allow for setting additional screenshots at different root font sizes has helped our product teams review what the component looks like at different font scales. To do this, we allow for adding additional font sizes to be set when capturing the screenshots so you don’t have to create new component variations just for font scaling.
Font Scaling on Mobile Safari
Supporting font scaling for Mobile Safari was more difficult. Unlike other browsers, there is not a font size preference available in Mobile Safari. However, they have released support for their own font: -apple-system-body but there are some important considerations.
Since macOS High Sierra (10.13), desktop Safari also supports the font preference, but there is not an easy “font size” configuration available in MacOS. Because there can be unexpected behavior on desktop Safari, so we used a @supports statement to prevent this. The code below will only target Mobile Safari.
// Apple's Dynamic Type requires this font family to be used
// Only target iOS/iPadOS
@supports (font: -apple-system-body) and (-webkit-touch-callout: default) {
:root {
font: -apple-system-body;
}
}
Another consideration is that the “100%” default font size selected does not equal the standard font size of 16px, but rather 17px. This is a very subtle difference, but it is critical for the design quality bar we aim to achieve at Airbnb. So to resolve this issue, we ended up using an inline head script to normalize the value, by placing it early into the page execution we avoided seeing a change in font size.
(() => {
// don't do anything if the browser doesn't match the supports statement
if (!CSS.supports('(font: -apple-system-body) and (-webkit-touch-callout: default)')) return;
// Must create an element since the root element styles are not yet parsed.
const div = document.createElement('div');
div.setAttribute('style', 'font: -apple-system-body');
// Body is not available yet so this has to be added to the root element
documentElement.appendChild(div);
const style = getComputedStyle(div);
if (style.fontSize === '17px') {
documentElement.style.setProperty('font-size', '16px');
}
documentElement.removeChild(div);
})();
Then when the page loads we use a resize observer to detect if the value changes again to unset or set the font-size property on the html element. This helps us still support scalable fonts, but not have a significant impact on the default font size (100%).
Impact
Supporting scalable fonts is an investment that should make a dramatic difference for our Hosts and guests with low vision and anyone who benefits from larger font sizes and control over their browsing experience. Below are two examples of the home page showing how the default font size (16px) appears to someone who has blurry vision and what it looks like by doubling the font size (32px). The second image is far more legible and usable.
Choosing font scaling as the product accessibility strategy brought about a range of significant benefits that notably enhanced our platform’s overall user experience. Making that change using automation to convert to rem units made this transition easier. When looking at our overall issues count after these changes were site wide, more than 80% of our existing Resize Text issues were resolved. Moreover, we are seeing fewer new issues since then.
To conclude, our journey to enhance Resize Text on the web has been filled with valuable, practical lessons. From how we strategically apply rem units, to the role of tooling and automation, each lesson has been a vital step forward in elevating our user experience on Airbnb. We hope that by sharing our journey, we can help others navigate this transition more seamlessly. Our work is ongoing, and we are committed to continuously advancing Airbnb’s accessibility. If you’re passionate about such challenges, we invite you to explore career opportunities at Airbnb.
Thanks to:
- Alan Pinto Souza, Dennis Wilkins, Jimmy Guo, and Andrew Scheuermann for advice and technical review.
- Sterling DeMille, Riley Glusker and Ryan Booth for being early product partners.
- Jordanna Kwok, Sarah Alley and JN Vollmer for supporting the approach.
- Veronica Reyes and Jamie Cristal for providing design support.
All product names, logos, and brands are property of their respective owners. All company, product and service names used in this website are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.