Design Systems Hot Takes

Antidatalossconfigurationism

9 min read

I’m in the middle of deep thought about what a text component looks like. There’s the easy stuff like having the ability to choose the appropriate semantic element. There’s the less obvious stuff, like adding a flag for screenreader only content. But then there’s something that we tend to overlook, how to manage overflow content.

Data loss

It’s not uncommon for someone to choose the solution of clipping the content, maybe in the form of an ellipsis. We even have more options coming about when those ellipsis appear. The problem with approaches that include overflow: hidden; is that the content is no longer clearly accessible. This goes against a core principle of mine: if you place it on the page, that means it is important. There should be no condition that obstructs that element in any way. Otherwise that can introduce data loss. Adding ellipsis is not an inclusive solution to large blocks of text. Instead, we’ll need to ensure the text remains readable while balancing the expectations of the layout.

Properties

There’s several CSS properties that are directly related to the way text flows on a webpage. We’ll go over each of the properties below but first a few definitions:

white-space

This property has been around for a while, but recently it has been altered to host separate properties. Overall, this property and its related new properties are meant to handle how whitespace characters effect the surrounding text.

First, the white-space-collapse property has the following possible values:

Remember that white-space-collapse is only one part of the white-space property. The other part is text-wrap-mode which only has two values, wrap and nowrap. As you might expect, you’ll primarly want to have text-wrap-mode: wrap and this is the default.

When considering the keyword options provided by the original white-space property, it’s not immediately clear how they align to these values:

text-wrap

To make things more confusing, text-wrap is also a shorthand. We already saw one of the properties text-wrap-mode which determines if the text is allowed to wrap or not. The newer part to this is text-wrap-style which has added many of the fancy algorithmic ways that text can wrap based on the number of characters. Here are the values for this property:

Harry Roberts posted some benchmarks on using the newer text-wrap values. The setup was a page with 10,000 <p/> elements which, by default, have orphans. Here’s what he found:

He said that impact on balance was almost zero, while pretty is more noticeable. The reality is that you most likely won’t have 10,000 paragraphs on your page.

The properties so far are only configuring how the whitespace characters behave in a block of text. None of these properties will affect the strings of characters that typically behave as words. The following properties are meant to target when these strings are meant to break and avoid poor layout artifacts.

overflow-wrap

The overflow-wrap property helps tell the browser when it should insert line breaks in otherwise unbreakable strings of text to prevent overflowing. This is an alias of the word-wrap property. Originally, this only allowed two values normal the default which keeps strings in tact and break-word which will break words sometimes. When a word is meant to break depends on the width of the container and where soft breaks can exist. This is usually very helpful for places where a long URL is written that needs to be broken to maintain a reasonable text layout.

The newer value option is anywhere which is typically less desirable as soft wrap characters are also used to determine how big the line box should be. This will allow the box to be much smaller instead of trying to fill the box as is normally expected.

word-break

Continuing with the theme of confusingly similar CSS properties, word-break is a property that determines what happens when a string would overflow outside of its container. To make things worse, one of the values here is an override to overflow-wrap. We’ll start with that one:

A question you might have, what’s the difference between all of these properties? They all look like they handle the same sort of consideration for breaking strings of text. The CSSWG has an excellent breakdown of the differences:

  • The line-break property allows choosing various levels of “strictness” for line breaking restrictions.
  • The word-break property controls what types of letters are glommed together to form unbreakable “words”, causing CJK characters to behave like non-CJK text or vice versa.
  • The hyphens property controls whether automatic hyphenation is allowed to break words in scripts that hyphenate.
  • The overflow-wrap property allows the user-agent to take a break anywhere in otherwise-unbreakable strings that would otherwise overflow.

On top of the other properties that we didn’t cover, there’s a lot more detail within that page about how line breaks are expected to work. I’ve made a little playground to see how the properties would affect text samples combined from MDN examples:

Recommendations

Based on all of this, here’s how I’d try making an API for a text component when it comes to avoiding data loss. Here’s my considerations:

Here’s what the CSS might look like:

.text {
    overflow: visible !important;
    overflow-wrap: break-word;

    &:where(h1, h2, h3, h4, h5, h6, dt, legend, label, button, th) {
        text-wrap: balance;
    }

    &:where(p, li, figcaption, dd, summary, caption) {
        text-wrap: pretty;
    }

    &:where(textarea, [contenteditable]) {
        text-wrap: stable;
    }

    &:where([data-wrap="true"]):where(pre, kbd) {
        text-wrap: pre-wrap;
    }

    &:where([data-wrap="false"]) {
        text-wrap: nowrap;
    }

    &:where([data-break="true"]) {
        word-break: break-all;
    }

    &:where([data-break="false"]) {
        word-break: keep-all;
    }
}

I’m using the keyword value for text-wrap over the shorthand because it’s easier to update this value instead of unsetting it to reapply in some cases. This allows us to leverage the default of text-wrap-mode: wrap for most elements.

The elements I’ve chosen to receive text-wrap-style are based on their expected content. Elements that expect shorter amounts of content receive balance while elements that expect longer amounts use pretty. Note that these are targeting elements that would normally have content as their direct children. While you could put pretty on sectioning content for it to cascade, it’s more performant to be more specific.

If you were paying attention, you’ll notice that there’s a lot of property values that we went over which are missing from the API. The reason is that they’re probably not going to be useful in day-to-day web development. This isn’t meant to be a CSS reset, this is an opinionated setup for what I believe would be a text component that allows for some customizability while keeping the options limited.

This includes my own rule-breaking for use of !important in this case to call out that hiding overflow on text is a bad practice. Makes me want to set z-index: auto !important on all my elements too.

Font size dimensions