Text Spacing / Support for user-defined text metrics

URL: /
Criterion: 1.4.12 Text Spacing (AA)

Text size and spacing issues are the second most common failure. And it probably affects a wider range of users, both those considered disabled and those that aren’t (including yours truly), than other failures.

The first thing to keep in mind is that the base size of the text on a web page should generally be assumed to be user-defined. All browser include some kind of setting for the default base text size. For instance, in Brave (Chrome-based):

This setting is usually 16px out of the box. Some people have it set as high as 32px or more, and this depends on only on their ability, but also on the screen type, distance from the screen (e.g., a large home theater screen placed at several meters away can take up a smaller portion of our field of view than a smaller 27" LED at arm’s length), screen resolution, etc.

Applications must never interfere with the user’s ability to adjust text-related parameters.

Also, the smallest text size used anywhere should never be below 87.5% of the base size (14px on unadulterated desktop browsers, but this is just a reference value). In addition to text size, it should be assumed that users are able to adjust such text-related parameters as letter spacing and leading (line spacing) without affecting the layout.

When expressing text size, we ideally want to use all relative units everywhere, meaning em and rem units, and percentages. And this is usually a lot easier said than done. When text sizes are converted to relative units, it automatically results in layout breakage in any place where layout is specified in non-text-relative units like px.

Here are a few examples of what happens when the adherence to text-relative units is partial:

From LinkedIn:

From Google fonts:

A proper fix for this issue requires a thorough and systematic transition to fully text-relative styling with no exception. This means:

  • All dimensions, not limited to text size, is expressed in em or rem (and vh, vw for layout related dimensions).
  • Media queries are expressed relative to the content and not the screen.
  • Pixels are permitted only to express very small dimensions related to non-text-relative elements (mainly borders)
  • There is no “default base text size” specified by the application author anywhere
  • The lower limit of the font size must be capped at 0.875rem (e.g., by making sure, or by using something like max(0.875rem, $desiredEmSize)).

Once this is done, developers that touch the UI must be briefed and trained to use this approach.

This task must also be done before tackling any layout-related fixes as switching to text-relative units will usually require the layout to be redone.

I understand that some of you reading this might feel quite skeptical about it, but it is what it is. Please give it some thought, and play with your browser settings. As there is no way around this, I intend to take this task on as the very first one following the assessment of the home page.

1 Like

Today I will start working on the text size fixes.

This is going to be a mini project that will be executed in several stages.

Stage 1

  • Allow the Indico UI to respond to changes in browser’s default font size
  • Match the existing layout as closely as possible at the default text setting (16px), let it break at other settings

Stage 2

  • Make the layout fully support text sizes other than the default one without breaking

Stage 3

  • Make the layout support changes in line spacing, letter spacing, and paragraph spacing according to user overrides (using Text Spacing Editor extension)

Risks

The main risk for this fix is that it takes a long time to fully implement. For instance, my expectation is that there will be new code written while this is ongoing, and the new code may rely on the existing behavior/style.

The second risk is that, depending on the approach taken, it may requires a rather radical departure from the current way of addressing dimensions in Indico’s stylesheets, which is primarily pixel-based.

This is the first time I’m making a transition in a code base of this size and vintage, so I feel the need to spend some time figuring out ways to mitigate these risks or at least minimize their impact.

Currently, one idea that I have is to use some kind of SCSS function that would allow me to identify new code. Something along the lines of:

.foo {
  height: px(28); // instead of 28px
}

The px() would output just 28px as usual. When I’m close to finalizing the fixes on my end, I could simply remove the px() function and the compiler would tell me what lines I need to address. This reduces the first risk.

Mitigating the second risk requires practice, a deliberate effort from the other engineers in adopting the new mindset. I will try to find material that explains this topic a bit more in-depth, as well as exercises. Ultimately it boils down to whether engineers involved with the Indico UI understand the importance and impact of switching to the text-relative units.

em vs rem

As we all know (hopefully), the em units are contextual, while rem are global. The rem is, in that sense, closer to pixel-based mental model.

Both units will generally achieve the objectives in all stages. Therefore, a natural inclination would be to prefer the method that is closer to the current mental model.

I will highlight some of the implications of using one versus the other, and I would like to solicit feedback regarding your preference.

With rem, it’s a matter of translating your existing pixel dimensions into rem values. This can be done by reusing the px() function mentioned before, and changing its definition to output rem based on the assumption that 1rem = 14px (current default).

With em, the benefit is that it’s contextual. This means that if all dimensions of an element are expressed using em, we only need to change the font-size property to scale the element and any of its children. When executed correctly, this reduces the overall amount of code to create different variants based on size. On the other hand, it requires the developers to completely switch to text-relative way of thinking, and that requires practice. The icon is no longer “24px wide” but “1.5x the text size”.

// Rem-base button

.icon {
  width: 1.5rem;
  height: 1.5rem;
}

.button {
  padding: 0.5rem 1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: 0.2rem;
  font-size: 1rem;
}

.button-l {
  padding: 0.75rem 1.5rem; // all dimensions manually scaled by 1.5x according to font size change
  gap: 0.75rem;
  border-radius: 0.3rem;
  font-size: 1.5rem;
}

.button-l .icon {
  width: 2.25rem;
  height: 2.25rem;
}

// Em-based alternative

.icon {
  width: 1.5em;
  height: 1.5em;
}

.button {
  padding: 0.5em 1em;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  border-radius: 0.2em;
  font-size: inherit;
}

.button-l {
  font-size: 150%; // This may also come from the context and the button may not even need a .button-l class.
}

The options here are that we either go with the approach of using rem in order to mitigate the second risk, or we take the second risk in order to make the code easier to maintain in the long run.

One of the things to keep im mind are the custom (per-event) stylesheets that may exist, especially in larger installations. How to handle those without breaking too much should considered as well.

1 Like

Thanks for pointing that out. What would you suggest as a solution for those?

I think the risk is somewhat limited as long as there are no huge structural changes in conferences, e.g. on how the event logo is included. I believe the logo stuff mentioned above was about the main Indico logo.

1 Like

At @ThiefMaster’s suggestion, we now have a second option for handling the font size fixes, a postcss-pxtorem plugin. What it does is it replaces all occurrences of pixels with rem, in order to avoid intrusive changes like physically replacing occurrences of px with a facade.

It is also capable of processing all CSS including CSS coming from 3rd party libraries, but I have not yet tested the impact of doing that. The impact of not doing that is, as you can imagine, a bit unsavory. It can also replace media queries, which is probably not as useful in the long run, but a desirable stop-gap measure.

Since using this plugin skips Stage 1, it will require a bit more work. Also, right now, I’m not quite sure of the long-term implications of using this method. I therefore intend to spend a couple of days next week take a closer look at how the app reacts to this plugin before committing to it.

1 Like

While exploring the postcss-pxtorem plugin, I discovered that exclusion doesn’t work as expected. It does not process code in node_modules, which creates layout issues due to a mix of rem and px values. I’ve looked at the source code and also looked at a few alternatives. Basically, all plugins that do this kind of processing are based on the same source code so we practically have no alternatives, and the sources are not very high quality. The issue itself is probably unrelated to how the plugin is coded. I will mess with this a bit more to see if I can get it to work.

As an additional note, addressing 3rd party code is part of the plan regardless of whether this plugin works or not. Ultimately, the most efficient way to take care of them is to simply make them part of the Indico core source. This means that the code becomes Indico team’s responsibility. I don’t think we need to worry about synchronizing upstream changes (that much) as this only pertains to CSS. I would also recommend setting a long-term goal of removing libraries such as Semantic UI because they don’t make good design choices from the standpoint of accessibility. It is probably better to just fork and make it our own.

We will not vendor in libraries and/or their CSS, this is not scalable/maintainable…

While having your own UX framework is great - if it’s well done - this is not something one can simple do without some dedicated resources… So I don’t see that happen.

The way I see it is that the current setup is also not scalable and maintainable given the issues that we are running into. The idea that blackboxing UI code scales rests on the assumption that the library actually achieves all the stated goals, including accessibility, right?

Sure, it’s not ideal. But your suggestion would mean manually going through - possibly large - CSS files of our dependencies, extracting them, making sure they stay in sync with the version (which would also be incompatible with non-exact version specifiers in package.json since you never know when some CSS changes). And that’d be necessary during every update.

Making things worse, some packages may use their own build tools (not just sass/scss, but less or whatever else), so this would also make the build pipeline more complex.

So I think it’s probably worth focusing on issues that can be fixed more easily on the Indico side. We have to be pragmatic here. There’s enough that should be fixable on our side, without introducing huge maintenance burdens.

PS: I think at some point the idea was to initially focus on publicly-visible parts of Indico - and in those parts SUI etc. are used much less since the contents there are much more static!

I have successfully integrated the postcss-pxtorem plugin. The confusion about whether it works has been due to the plugin’s use of a property whitelist. It doesn’t convert all px values to rem by default, but only a small subset of properties related to fonts. Disabling the whitelist yields the results we wanted.

At this stage, it’s difficult to say what kind of long-term impact this plugin will have. A few risks to note:

  • The plugin is essentially an abandonware. It hasn’t been maintained for 2 years, there are 12 unaddressed PRs and no responses in the issue trackers from the author.
  • The code quality is just ok. I don’t see any big issues with it, but it could’ve been better.
  • The choices made by the author suggests relatively poor understanding of the why this kind of conversion is done to begin with (e.g., limiting to a small subset of properties is guaranteed to result in broken layout at some point), so we should expect that there are edge cases that this plugin doesn’t cover.

Although the plugin is abandonware, it’s not inconceivable that the Indico team can take ownership over the code as there’s not a lot of it. It does, however, require some understanding of the PostCSS API, and in case of a breaking release of the preprocessor, the plugin will need to be updated. Personally, I would count this as low-to-medium risk.