10. Defensive

Defensive CSS is a set of techniques that keep the design from breaking when you don't have control over the content. That is, when the content is generated by the user, or by a CMS such as WordPress, whose length we don't know.

The most common problems appear when the content is:

  • ... longer than expected and the height is fixed. It will overflow below.
  • ... short and doesn't fill all the space. It will leave a blank space.
  • ... several lines long but the design only accounts for 1. It will overflow to one side or overlap with the content of the next line.
  • ... an image whose size we don't know, because it's the user who provides it. It will be distorted if its aspect ratio doesn't match the design (the space is square but the image is rectangular).
  • ... in several languages, with words longer or shorter than expected. There will be overflow on some lines that have containers with a fixed width.

Let's avoid all of the problems above with a practical example where we'll fix a presentation card with an image, a full name (one line), a description and some buttons with variable text.

Resize to see the finished design we want to achieve.

We'll start with the following HTML, using the BEM nomenclature in the form of nested blocks or a component:

<article class="speaker">
    <img aria-hidden="true" class="image speaker__avatar" alt="Avatar" src="">
    <h2 class="speaker__fullname">Christina Zamora Mccormick</h2>
    <p class="speaker__bio">Egestas dui, id ornare arcu odio ut sem nulla pharetra! Sit amet cursus sit amet, dictum sit amet justo donec enim diam, vulputate ut pharetra sit amet, aliquam id diam!</p>
    <nav class="nav">
        <ul class="nav__list speaker__nav">
            <li class="nav__item">
                <a href="#" class="button">Website</a>
            </li>
            <li class="nav__item">
                <a aria-label="Twitter" href="#" class="button">🐦</a>
            </li>
            <li class="nav__item">
                <a aria-label="Mastodon" href="#" class="button">🐘</a>
            </li>
        </ul>
    </nav>
</article>

We'll begin from the point where we've already defined its Grid on both small-screen and large-screen devices.

Techniques

1. Avoid image distortion

When the user uploads an image, we don't know its size. If the proportions don't match the container, the image will inevitably be distorted.

In the example the image is rectangular.

Cat

But it has to fill a square space, which we'll round off later.

It gets squashed on the sides.

To avoid this, whenever you design an adaptable component you should apply the following styles to all images (globally if possible):

img {
  width: 100%;
  object-fit: cover;
  object-position: center;
}
  • width: 100% so the image fills the entire width of the container.
  • object-fit: cover so the image fits the container without deforming, cropping the content that sticks out.
  • object-position: center so the image is centered in the container.

2. Break long words

When we work with texts in several languages, some words may be longer than expected. For example, in English the word antidisestablishmentarianism has 28 characters and it exists. If the design doesn't account for such long words, the text will overflow its container.

We'll apply the overflow-wrap and min-width properties whenever we expect a similar situation.

.speaker__fullname {
  overflow-wrap: break-word;
  min-width: 0;
}

3. Prevent a line from breaking

For design reasons, at certain times a text won't be able to take up more than one line. For example, in the full name on the card. If the name is very long, it will break into two lines and the design will look ugly and broken.

Reduce the width to see the effect.

An elegant solution is to hide all the text that overflows and add an ellipsis at the end. To do this we'll use the white-space, overflow and text-overflow properties.

.speaker__fullname {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
  • white-space: nowrap prevents the text from breaking into several lines.
  • overflow: hidden hides the text that overflows.
  • text-overflow: ellipsis adds an ellipsis at the end of the text.

4. Include safety spacing

To keep the content from running right up to the component's edges we can include some safety spacing. That is, an inner margin that separates the content from the border. Otherwise it won't look good.

When we work with Flex or Grid, always include gap so the text doesn't end up flush with other elements. And if necessary, add a padding to give the content more breathing room from the component's border.

.speaker {
  gap: 1rem
  padding: 1rem;
}

5. Minimum width of an element when the content is shorter

Our buttons barely have any content and look very small, reducing the tappable area as well as ruining the design.

We can solve it with a minimum width.

.button {
  min-width: 4rem;
}

Additionally, it will make the button more visible, improving accessibility.

6. Set the maximum width of a component

When a component has been designed with a specific width in mind but you need it to be adaptable, we have a problem. If the container is wider than expected, we'll end up with a disproportionate design.

We'll apply a maximum width. This way, it won't be able to grow more than expected but it can shrink.

.speaker {
  max-width: 40rem;
}

7. Flexible wrapping against the lack of space in Flex

When you use Flex, the available space may not be enough and the elements overflow. To avoid this, use the flex-wrap property with the value wrap.

.elemento {
    display: flex;
    flex-wrap: wrap;
}

Below, flex-wrap: wrap has NOT been applied and the buttons overflow when the width is reduced.

Now flex-wrap: wrap HAS been applied and the buttons jump to another line.

8. Disable accidental hover on touch devices

A common problem on touch devices is that when the user scrolls and places their finger over an element designed with a hover, it gets activated accidentally. This can be annoying for the user since the hover stays active until they tap somewhere else on the screen.

We can avoid this behavior with the following rule:

@media (hover: hover) {
    .button:hover {
        background-color: red;
    }
}

It will only apply the hover (in the example, a red background) when the device has a pointer such as a mouse or a trackpad, never on a touch device.

With Sass we can create a mixin to avoid repeating the rule and simplify its use:

/*
 * Create a hover that prevents the hover from appearing when you scroll in mobile
 *
 * Examples: @include hover()
 */
@mixin hover()
  @media (hover: hover)
    &:hover
      @content

.button
  @include hover()
    background-color: red

Instead of &:hover we can use @include hover().

Try scrolling in the following demo. On a touch device the black background color isn't activated, but if you hover the mouse cursor over it, it will happen.

Below, the strategy has been applied to the buttons.

Final notes

We've learned to create a defensive design with the most common solutions against dynamic content. The card we've created is full of small details and tweaks that make the design more reliable. As you gain experience, you'll incorporate each strategy naturally into the designs you make. Always assume the worst, from little to a lot of text. Just as you prepare your designs for different screen sizes, prepare your designs for different content sizes.

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Desafíos de programación atemporales y multiparadigmáticos

Desafíos de programación atemporales y multiparadigmáticos

Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.

Buy the book

Will you buy me a coffee?

This is how I keep writing without ads or paywalls.

Comments

There are no comments yet.