Thoughts on TailwindUI, SCSS, UIkit, CSS-in-JS and MaterialUI

Introduction

This small review of different approaches for creating UIs was inspired by a video by Theo from T3. I highly recommend watching this video, as I'll be building on his advice to employ inline styles and create a custom component library for your application.

TailwindUI

TailwindUI offers a collection of UI components, not in the traditional sense - as software components - but as a set of HTML code snippets. To utilize a component, simply copy the snippet to your source file and modify it as required. Note that the same snippet can appear in different places in your code, but that's considered to be okay. Here's an example of a snippet for an email form field:

<div class="mb-6">
  <label
    for="email"
    class="mb-2 block text-sm font-medium leading-6 text-gray-900"
    >
    Email address
  </label
  >
  <div>
    <input
      id="email" name="email" type="email"
      class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
    />
  </div>
</div>

There are many advantages to this approach:

  • Isolation: you can tweak every component instance to suit its specific context, without the risk of breaking the code elsewhere;
  • Explicitness: it's easy to inspect the styles of an element in the code, as they are directly embedded in the JSX;
  • Naming: there is no need to devise suitable CSS class names;
  • Consistency: while copying from an HTML snippet does not enforce consistency, it tends towards it since all form fields originate from the same template.

When it comes to the drawbacks, I would include:

  • Consistency: maintaining consistency between components when locally changing inline styles requires discipline from the developer.
  • Maintenance: when you update a component's appearance throughout the application, you may need to modify inline styles in multiple locations.
  • Readability: inline styles can appear cluttered and may not convey an intended purpose to the code reader. This can make it hard to spot mistakes.
  • Naming: because names are absent from the HTML code, it's hard to find the related code when you inspect the DOM in the browser.

To mitigate the last drawback, I often add descriptive CSS class names to JSX code that uses inline styles (at least, when running in development mode). In most cases these names are not used to style the elements but they make it easier to find that piece of code.

Overall, I think the inline styling approach that is offered by TailwindUI is compelling. As we shall see later, it's the starting point for my hybrid approach.

Custom components ≠ third party components

Before we continue I would like to point out that it can be a good idea to create custom components based on TailwindUI snippets. You can do this on a case-by-case basis. It implies trading some flexibility for increased consistency and reusability.

It may seem counter-intuitive to start with TailwindUI and then go into the direction of components. Shouldn't we just use MaterialUI then? The answer is no, because there is an important difference. Custom components are already shaped to meet the needs of the application. Therefore, there will usually be no need to "break into" them. On the other hand, when using MaterialUI, it can be very challenging to get the component to do exactly what you want.

UIkit

UIkit is a Javascript framework that offers a collection of UI components. It takes an interesting approach:

  • you can create a custom style by setting SCSS variables. UIkit comes with a very large set of variables, so you can tweak almost every aspect of the style.
  • you can add both style and behaviour to your JSX elements by adding class names and JSX attributes. Instead of using attributes, you can also add behaviour by adding small snippets of Typescript.

Here is an example of creating a drop-down. Note that the button is followed by a div that has the uk-dropdown attribute. This tells UIkit that the button must be converted into a drop-down button.

<div class="uk-inline">
    <button class="uk-button uk-button-default" type="button">Hover</button>
    <div uk-dropdown>I'm a dropdown.</div>
</div>

Interestingly, there is no separate drop-down component in this code snippet. Instead, you turn the button into a drop-down by mixing in the uk-dropdown attribute. Since this is somewhat analogous to using inline styles, you could call it an "inline components" approach. It suggests a more loose concept of what a component is. Instead of an object or function, it can also be a collection of utilities that add styles and behaviours to a JSX structure.

This is a very flexible approach. For example, if you want to add an icon to the drop-down button, then instead of having to extend a DropDownButton component function you can just add the icon directly to the inlined code. In my hybrid approach, I will build on this idea.

Advantages of UIkit:

  • Consistency: different JSX elements are all styled by the same global SCSS variables;
  • Flexibility: there is no need to break into components, as behaviour is added to the JSX elements inline using CSS classes and JSX attributes.

Disadvantages are:

  • Isolation: if you change an SCSS variable, it will have a global effect;
  • Explicitness: since the SCSS variables basically replace the CSS class names, it adds another layer of indirection to the code;
  • Maintenance: the addition of behaviour based on attributes is magical. I'm expecting problems when you want to tweak the behaviour.

Initially, I was a fan of UIkit, because I was impressed by the philosophy behind it. In practice, I have found it annoying to work with the large set of SCSS variables. Still, the idea of creating components as sets of utilities has been an inspiration to me.

SCSS

Since we all know what SCSS is, let's go straight to the advantages and disadvantages. Here, I should add that much depends on how SCSS is used. Some of its features are an advantage if used in the right way, and a disadvantage if used wrongly.

  • Flexibility: since SCSS is a powerful language for styling elements, we can cover a lot of use-cases with it.
  • Flexibility: we can use CSS variables and scoping to make the effect of the SCSS code depend on the context (e.g. the current theme);
  • Readability: when we move styles to an .scss file, the remaining source code usually becomes more readable;
  • Performance: I'm not an expert on this but the consensus seems to be that using SCSS gives very good performance;
  • Maintenance: when inspecting a component in the DOM, it's usually easy to find the related style rules (though these rules may be complex, see below);

Disadvantages are:

  • Isolation: if you define SCSS rules that have a global scope then it can become very hard to understand the combined effect of different .scss files. Changing an .scss file can become very risky;
  • Maintenance: because SCSS is very flexible, it can easily lead to complex solutions that are hard to understand and maintain;
  • Explicitness: if multiple SCSS rules are responsible for styling an element, then it can be hard to figure out which rules apply;
  • Explicitness: even when you co-locate JSX and SCSS, you still need to jump between these files to understand how elements are styled.

I think SCSS offers a very powerful approach, that is also risky. For my own styling system, I would like to use the power of SCSS, but put some restrictions in place to limit the risk.

CSS-in-JS

I have limited experience with CSS-in-JS. In part, this is because I like SCSS and have not found a compelling reason to switch. One could say that - apart from the isolation aspect, which is important - SCSS and CSS-in-JS provide a similar developer experience. For developing my hybrid approach, the difference between SCSS and CSS-in-JS is not that relevant.

MaterialUI

I haven't used MaterialUI much but the consensus seems to be that it offers a pleasant and efficient developer experience, as long as you are not trying to do things that are too far outside of what MaterialUI offers. Since my priority is nice and clean code, I will usually not choose MaterialUI, because I want to avoid a situation where hacks are needed to overcome the limits of the framework.