Lightweight CSS: Tips to Stay Classless

  1. Why Classless?
  2. Type Selectors
  3. Language
  4. Applying Exceptions
  5. First Letter/First Line
  6. Combining Classless and Classes
  7. Summary

Using classes to style elements in bulk saves time; however, inserting this additional layer of abstraction can often generate problems.

Why Classless?

The bigger the application, the harder it gets to keep track of classes. Consider the following HTML page:

<!doctype html>

<html lang="en">
  <head>
    <style>
      body {
        background-color: black;
        text-align: center;
      }

      header {
        width: 80vw;
        margin-left: 10vw;
        margin-right: 10vw;
      }

      .title-link {
        font-size: 150%;
      }

      .list {
        display: flex;
        flex-direction: row;
        justify-content: space-evenly;
        list-style-type: none;
        padding: 0;
      }

      .list-item {
        width: 33.3%;
      }

      .align-left {
        text-align: left;
      }

      .align-right {
        text-align: right;
      }

      .link {
        display: inline-block;
        width: 100%;
        color: orange;
      }
    </style>
  </head>

  <body>
    <header>
      <h1>
        <a class="link title-link" href="/homepage">Home</a>
      </h1>

      <nav>
        <ul class="list">
          <li class="list-item">
            <a class="link align-left" href="/blog">Blog</a>
          </li>

          <li class="list-item">
            <a class="link" href="/contact">Contact</a>
          </li>

          <li class="list-item">
            <a class="link align-right" href="/resume">Resume</a>
          </li>
        </ul>
      </nav>
    </header>
  </body>
</html>

Six different classes were used to style eight elements in one page. In addition to the visual clutter, relying exclusively on classes means there is no immediate way to identify the type of element with which they're paired.

Omitting classes, when possible, can be a simple solution to this problem:

<!doctype html>

<html lang="en">
  <head>
    <style>
      body {
        background-color: black;
        text-align: center;
      }

      header {
        width: 80vw;
        margin-left: 10vw;
        margin-right: 10vw;
      }

      header h1 {
        font-size: 150%;
      }

      header ul {
        display: flex;
        flex-direction: row;
        justify-content: space-evenly;
        list-style-type: none;
        padding: 0;
      }

      header li {
        width: 33.3%;
      }

      header li:first-child {
        text-align: left;
      }

      header li:last-child {
        text-align: right;
      }

      header a {
        display: inline-block;
        width: 100%;
        color: orange;
      }
    </style>
  </head>

  <body>
    <header>
      <h1>
        <a href="/homepage">Home</a>
      </h1>

      <nav>
        <ul>
          <li>
            <a href="/blog">Blog</a>
          </li>

          <li>
            <a href="/contact">Contact</a>
          </li>

          <li>
            <a href="/resume">Resume</a>
          </li>
        </ul>
      </nav>
    </header>
  </body>
</html>

Luckily, CSS comes with many tools to style elements without over relying on classes.

Type Selectors

Type-based selectors are ideal to style two or more element of the same type.

Selector Definition
p:first-of-type Selects all <p> elements that are the first <p> element of their parent.
li:last-of-type Selects all <li> elements that are the last <li> element of their parent.
li:nth-of-type(n) Selects all <li> elements that are the nth <li> element of their parent.
li:nth-last-of-type(n) Selects all <li> elements that are the nth <li> element of their parent, counting from the last <li> element.
h3:only-of-type Selects all <h3> elements that are the only child of its type, of their parent.

There are five type selectors, but :first-of-type, :last-of-type and :nth-last-of-type(n) are, more or less, short variants of :nth-of-type(n) Clearly, all five are powerful selectors in the right context, but :nth-of-type(n) and :only-of-type are probably the most useful.

The reach of type selectors grows even further in combined configurations. For example, to reach the third list item in a webpage with two navigation bars:

nav:nth-of-type(2) > li:nth-of-type(3) {
  color: blue;
}

Language

:lang(language) is useful when a website supports multiple languages. Instead of creating a class for every language, cluttering style sheets in the process, :lang(language) targets elements based on their lang attribute

<!doctype html>

<html lang="en">
  <head>
    <style>
      body {
        background-color: black;
      }

      p:lang(eng) {
        color: red;
      }

      p:lang(ita) {
        color: blue;
      }

      p:lang(ger) {
        color: yellow;
      }
    </style>
  </head>

  <body>
    <main>
      <p lang="en">This is a paragraph.</p>

      <p lang="ita">Questo รจ un paragrafo.</p>

      <p lang="ger">Das ist ein Absatz.</p>
    </main>
  </body>
</html>

Applying Exceptions

Accepting an entire CSS selector as an argument probably makes :not(selector) the most useful on this list.

Instead of creating a separate class to apply a distinct style to a particular element, :not(p) targets every element that is not <p>, which creates powerful permutations when combined with either classes, or a classless approach.

:not(selector) is at its full potential when it receives a long selector as an argument:

<!doctype html>

<html lang="en">
  <head>
    <style>
      body {
        background-color: black;
      }

      nav li:not(li:last-of-type) > a {
        color: orange;
      }

      nav li:last-of-type > a {
        color: white;
      }
    </style>
  </head>

  <body>
    <header id="top"></header>

    <main>
      <nav>
        <ul>
          <li><a href="/home">Home</a></li>

          <li><a href="/portfolio">Portfolio</a></li>

          <li><a href="/about">About</a></li>
        </ul>
      </nav>

      <nav>
        <ul>
          <li><a href="#top">Top</a></li>

          <li><a href="#bottom">bottom</a></li>
        </ul>
      </nav>
    </main>

    <footer id="bottom"></footer>
  </body>
</html>

First Letter/First Line

Using :first-line and :first-letter is a classless, simple, and quick approach to style the first line and letter in a paragraph. Both supersede any style applied to their parent <p> element:

p:first-letter {
  font-size: 130%;
}

p:first-line {
  font-size: 120%;
}

p {
  font-size: 110%;
}

Combining Classless and Classes

JavaScript frameworks like NextJS expect developers to rely on classes to style components, and even though it's possible to use the global.css file to impose universal styles, in this context, utilizing classes is the more sensible approach.

That said, it can still be argued CSS is at its full potential when classes are combined with a classless approach.

JSX:

import styles from "../styles/Example.module.css";

export default function Example() {
  return (
    <main className={styles.mainContainer}>
      <p>This is the first paragraph.</p>

      <p>This is the second paragraph.</p>
    </main>
  );
}

CSS:

.mainContainer > p:first-child {
  font-size: 140%;
}

.mainContainer > p:last-child {
  font-size: 120%;
}

Summary

Every project has one or more suitable approaches. The objective behind this post is to encourage developers to consider a classless approach if it suits a project they're working on; otherwise, if it works, it is good enough.