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.
Discussion (1)
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...