Decoupling logic and styling with Headless UI components
As a React.js developer, the first thing I think about when starting a project is the components. I’ve often found myself struggling with components from design systems like Material UI and similar libraries. While these libraries can significantly accelerate development, it’s important to consider that at a certain point in the project, you might encounter situations where you need to create workarounds on top of workarounds just to make things work, particularly in terms of styling.
When choosing a UI component library, I always aim to find the best of both worlds:
That's why I’ve been on the lookout for a UI library that meets these criteria, and I'm happy to say that I’ve found it: Headless UI.
Why Headless UI Components?
In this article, I’ll focus on what I’ve learned while building general components and how I transitioned from using UI libraries like Material UI and React Bootstrap to using headless components.
When building my design system, I needed to address several key requirements:
In my earlier projects, I started with React Bootstrap, using it for more complex components like sliders, multi-level dropdown lists, and accordions. Later, I moved to Material UI for another project. However, as the project scaled and required more components with different styles, I found myself struggling with MUI. That’s when I decided to build my components from scratch.
After researching the best and most flexible approach to building general components that could be easily customized, I discovered the concept of Headless UI.
Diving Deeper: What is "Headless" UI?
Headless UI libraries provide the underlying logic, state, and processing for UI elements without enforcing any specific markup, styles, or themes. This approach offers developers the freedom to design and style components based on their unique project needs. It’s aimed at those who seek custom designs, providing only the essential HTML structure and JavaScript functionality. Developers can then customize the appearance and behavior by adding their own CSS styles and JavaScript enhancements.
领英推荐
The primary goal of headless UI is to offer a flexible and customizable solution that caters to diverse project needs and design requirements.
Popular headless UI libraries:
Pros and Cons of Headless UI Libraries
Pros:
Cons:
Example:
npm install @headlessui/react
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import {
ArchiveBoxXMarkIcon,
ChevronDownIcon,
PencilIcon,
Square2StackIcon,
TrashIcon,
} from '@heroicons/react/16/solid'
export default function Example() {
return (
<div className="fixed top-24 w-52 text-right">
<Menu __demoMode>
<MenuButton className="inline-flex items-center gap-2 rounded-md bg-gray-800 py-1.5 px-3 text-sm/6 font-semibold text-white shadow-inner shadow-white/10 focus:outline-none data-[hover]:bg-gray-700 data-[open]:bg-gray-700 data-[focus]:outline-1 data-[focus]:outline-white">
Options
<ChevronDownIcon className="size-4 fill-white/60" />
</MenuButton>
<MenuItems
transition
anchor="bottom end"
className="w-52 origin-top-right rounded-xl border border-white/5 bg-white/5 p-1 text-sm/6 text-white transition duration-100 ease-out [--anchor-gap:var(--spacing-1)] focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0"
>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
<PencilIcon className="size-4 fill-white/30" />
Edit
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-[focus]:inline">?E</kbd>
</button>
</MenuItem>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
<Square2StackIcon className="size-4 fill-white/30" />
Duplicate
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-[focus]:inline">?D</kbd>
</button>
</MenuItem>
<div className="my-1 h-px bg-white/5" />
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
<ArchiveBoxXMarkIcon className="size-4 fill-white/30" />
Archive
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-[focus]:inline">?A</kbd>
</button>
</MenuItem>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
<TrashIcon className="size-4 fill-white/30" />
Delete
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-[focus]:inline">?D</kbd>
</button>
</MenuItem>
</MenuItems>
</Menu>
</div>
)
}