loading...

Thoughts on Functional CSS

duarten profile image Duarte Nunes ・3 min read

Functional CSS - or utility-first CSS - is an approach to CSS where components are styled using general purpose CSS classes, devoid of any semantic meaning.

As someone doing front-end development for the first time, I had to become freshly acquainted with contemporary CSS frameworks. TailwindCSS was the obvious choice for me.

An example

Lets use Tailwind to style a toggle component, which you can play with here:

<span class="
  flex
  h-6 w-11
  bg-teal-500
  items-center 
  border-md 
  rounded-full 
  border-teal-600
  transition
  transition-colors
  ease-in-out
  duration-200 
  cursor-pointer">
    <span class="
      bg-gray-100
      inline-block
      rounded-full
      shadow-md 
      transform 
      translate-x-5
      transition
      ease-in-out
      duration-200
      h-5 w-5" />
</span>

Note that the w-11 class doesn't exist in Tailwind out-of-the-box (version 1.12). Extending the config is very easy:

module.exports = {
  theme: {
    extend: {
      width: {
        "11": "2.75rem"
      }
    },
  }
};

Why Tailwind

The classes we're applying are single-purpose, and thus extremely reusable. They are essentially an API to the CSS box model, and can easily be composed into other reusable, possibly application-specific, primitives.

I won't be going into details, but fortunately the documentation is exhaustive and extremely well organized.

This use of utility classes contrasts with semantic classes, which have the problem of being too component-specific. Furthermore, either the HTML will have to follow the structure of the highly-specific CSS, or the CSS will be coupled to the HTML structure.

Not being parametrizable, each utility class maps to specific values (e.g., p-1 maps to a padding of 1px). This helps enforce a consistent design. There is even natural pressure to do so, since we have to think through why the current options aren't enough when we want to add a new one (like w-11).

When reading the markup, the utility classes provide enough information to reliably render the component in our heads. This is much better than having to go back-and-forth between at least two separate files to get the same mental image. Other frameworks tend to be schismatic, whereas I feel that HTML is indissociable from CSS.

Usage in React

The only thing I miss is not having a TypeScript API to validate the classes and guide their usage. We often want to dynamically calculate classes by concatenating strings, but doing that essentially hides them from tools like PurgeCSS. If we can't purge unused classes, then our bundle size will be bigger than needed. A TypeScript API would afford us programmatic manipulation while generating the class names in a way tools can understand, or it could even integrate directly with those tools.

For these reasons, in my current project we've placed an object model on top of Tailwind. Here's how that toggle component looks like in React code:

export interface ToggleProps {
    id?: string
    on: boolean
    onClick: (value: boolean) => void
}

export const Toggle: FC<ToggleProps> = ({ id: idProp, onClick, on }) => {
    const colorOn = morningGlory(500)
    const id = useUniqueId("Toggle", idProp)

    return (
        <Box
            id={id}
            component="span"
            role="checkbox"
            tabIndex={0}
            checked={on}
            background={on ? colorOn : grey(300)}
            position="relative"
            display="flex"
            alignItems="center"
            height={6}
            width={11}
            borderWidth="md"
            borderColor={on ? colorOn : grey(300)}
            borderRadius="full"
            cursor="pointer"
            transition="transition transition-colors ease-in-out duration-200"
            onClick={() => onClick(on)}
        >
            <Box
                id={`${id}-button`}
                component="span"
                display="inline-block"
                height={5}
                width={5}
                borderRadius="full"
                background={grey(100)}
                boxShadow="md"
                transform={`transform ${on ? "translate-x-5" : "translate-x-0"}`}
                transition="transition ease-in-out duration-200"
            ></Box>
        </Box>
    )
}

Taking the display attribute as an example, it is defined as:

export type Display = Responsive<"hidden" | "block" | "inline" | "inline-block" | "flex">
export const resolveDisplay = ifDefined<Display>(d => resolveResponsive(d, identity))

It accepts a value for the display wrapped in a Responsive type, which is a monad mapping a property value to one of the allowed view port dimensions, and returns the resolved Tailwind classes.

Conclusion

Tailwind - and functional CSS in general - seems like the correct approach to styling, at least from someone used to low-level systems programming. I recommend reading this post by Adam Wathan, Tailwind's author, on his journey towards utility classes. Fortunately, I was able to start his destination.

Posted on Jun 16 by:

duarten profile

Duarte Nunes

@duarten

Enjoys distributed systems and sunsets. Bitten by Count Chocula.

Discussion

markdown guide
 

I really like Tailwind CSS and ends up spending time setting it up as much as possible.

Note that the Purge CSS is now built-in, starting v1.4.0. - github.com/tailwindcss/tailwindcss...