Decoupling logic and styling with Headless UI components

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:

  • Accessibility
  • Minimal need for workarounds

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:

  • Accessibility: Components must be accessible to all users.
  • Theming: Each component should support multiple themes, such as light mode and dark mode.
  • Uniqueness: The product’s appearance should be unique, not just another Material or Bootstrap clone. Our design team plays a crucial role in defining how the product should look.
  • Browser Support: It should work seamlessly across all major browsers.
  • Functionality: The components should support unique use cases, allowing for complete control over behavior.
  • Responsiveness: They should adapt to all screen sizes and devices.
  • Maintainability: Modifying and maintaining the components should be easy and seamless.

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:

  • Radix UI
  • Headless UI
  • Ark UI
  • Chakra UI

Pros and Cons of Headless UI Libraries

Pros:

  • Full control over markup and styles
  • Supports all styling patterns (CSS, CSS-in-JS, UI libraries, etc.)
  • Smaller bundle sizes
  • Portable: Can run anywhere JavaScript runs

Cons:

  • Requires more setup
  • Does not provide markup, styles, or themes out of the box

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>
  )
}
        

要查看或添加评论,请登录

Maha Taha的更多文章

社区洞察

其他会员也浏览了