Inline styles no longer considered harmful

We often follow and spread universal rules about development. "Never use inline styles" is one of those. However, this particular rule stems from a time, where HTML was static and thus, inline styles would only be overwritten by adding !important to your CSS. With modern development however, HTML is no longer static but the result of dynamic frameworks (frontend and backend alike), that remove this main drawback of inline styles. This way, we can make use of benefits like colocation of structure and layout, readability and easier understanding by using this once frowned upon feature of HTML.

A couple of days ago, I overheard a conversation between two developers. The junior of the two wrote some piece of code, that I guess looked like this:

<form style="max-width: 500px;">
  <div style="display: flex; flex-direction: column;">
    <label for="contact-form__name">Name</label>
    <input type="text" id="contact-form__name" name="name">
  </div>
</form>

The mid-level developer gave this feedback: "This looks good already, however you should never use inline styles in HTML. Always move those styles into CSS-files." The junior asked: "I did not know that, thanks! Why should we do this?" "It is best practice", was the reply of the other developer. This was the end of this conversation. I started thinking about this.

Only a developer from the dark side deals in absolutes

We developers like to have rules. Especially the absolute ones: Never use eval, don't use the loose equality check == and never use inline styles. Those give us clear guidelines and make our lives easier. No discussion, no context required, just plain and simple. Best case, we even add linting rules for them, which forbid us to ever use these patterns in our code. This may become a problem, when we forget why these "rules" exist or what the reasoning behind them was. This is why today, I want to take a closer look at inline styles.

Benefits of inline styles

First I want to elaborate on the advantages we get from inline styles. We don't need to use a technology when others are strictly supperior.

Colocation

One principle of software development is colocation: Code that changes together should be as close together as possible. This makes it easier for us to see all required changes for a new feature or a new requirement. Imagine a code base where you edit a HTML file or component and are 5 or 6 levels deep in your directory structure.

In a traditional development workflow, you'd have some accompanying CSS file(s) that styles the contents of your HTML. The further the distance between those files, the more likely it is that we forget to change or remove any classes that were used in the HTML file. If the CSS-file is directly next to the HTML file, we open it up side by side and can compare easily.

- HTML
  - layouts
    - head
      - navigation
        - links
          - links.html
- CSS
  - layouts
    - head
      - navigation
        - links
          - links.css

On the other extreme, we would mirror the directory structure, only for CSS files. In this case we would have to open the same 5 or 6 folders to get to the corresponding CSS file. I'd probably forget to check that file if I just need to make small change to the HTML file.

In our example from earlier, colocation is taken to the extreme: All sturcture and styles are directly next to each other in the same file. Each element has its own styles directly attached to itself. It is absolutely impossible to leave styles for elements that are no longer in use and near impossible not to look at the styles when you only want to move around some elements. This has the additional benefit that you can take this code, move or copy it to a completely different file and it keeps working exactly the same, as there are no external dependencies.

Colocation makes it easer to change, move or delete code, because relvant things are next to each other.

Plain CSS-rules

CSS-utility libraries like Tailwind or to some extent Bootstrap also follow the colocation principle:

<div class="md:flex">
  <div class="md:flex-shrink-0">
    <img
      class="rounded-lg md:w-56"
      src="https://some.image.com"
      alt="Woman paying for a purchase"
    >
  </div>
  <div class="mt-4 md:mt-0 md:ml-6">
    <div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">
      Marketing
    </div>
    <a
      href="#"
      class="block mt-1 text-lg leading-tight font-semibold text-gray-900 hover:underline">
        Finding customers for your new business
    </a>
    <p class="mt-2 text-gray-600">
      Getting a new business off the ground is a lot of hard work.
      Here are five ideas you can use to find your first customers.
    </p>
  </div>
</div>

You just need to learn all those utility classes and can then sprinkle them in your HTML and get a completely styled application without touching a CSS file. The only problem is, that you need to learn this entirely new "language". Yes it uses the some of the same vocabulary from CSS but also adds abbreviations (like mt for margin-top) and other conventions (like the media queries md for medium or lg for large screens).

When sticking to inline styles, you make a trade-off between a shorter learning curve and fine grained control with inline styles and terser syntax, more features (hover styles or media queries are not possible with inline styles) and a unified design system with the CSS utilities.

Dynamic inline styles

The main problem with inline styles was the missing ability to overwrite the defined styles as they have a very high priority in the CSS cascade. This becomes a problem when you build reusable components with inline styles, as users of this component are not able to tweak the styles. However, this was more relevant in times, where your written HTML is static. Web frameworks kind of revert this downside. Take a look at the following React component:

import { CSSProperties } from "react";
import theme from "./theme.ts";

function LoadingButton(props: {
  onClick: () => Promise<any>;
  styles?: CSSProperties;
}) {
  /**
   * Some logic here
   */

  return (
    <button style={{ background: theme.colors.background, ...props.styles }}>
      Load
    </button>
  );
}

In this case we still used inline styles while still getting benefits from the CSS world: We can use variables from our imported theme and stick to a consistent style guide. In addition, we spread the inline styles from props, and get our extensibility of styles:

import theme from "./theme.ts";

function App() {
  return (
    <>
      <LoadingButton onClick={loadData} />
      <LoadingButton onClick={loadData} styles={{ fontSize: "2em" }} />
      <LoadingButton
        onClick={loadData}
        styles={{ background: theme.colors.danger }}
      />
    </>
  );
}

By encapuslating HTML in components or partials we also don't need to repeat the inline styles as it was the case in static HTML files. Another disadvantage resolved. The only remaining missing features of inline styles are media-queries and pseudo-selectors. So when you need those, you still might want to stick to "Never use inline styles". (Or use more modern approaches like CSS-in-JS)

Conclusion

This article was not meant to convince you to only use inline styles in your next project. It was meant to make you think about your assumptions and rules that you are adhering to. Next time you review some piece of code and find something that you don't like, stop and think about the reasons and if they apply to your situation. And most importantly: Convey this reasoning to your coworkers during reviews, so that they also learn not to follow rules blindly.