Modular Frontend Development

30. August, 2024 6 min read Teach

Using shadcn/ui

Building UIs in React often means handling a lot of complexity, especially when scaling a project or maintaining consistent, reusable components across different application parts.

Enter shadcn/ui, a headless UI component library built on top of Tailwind CSS that focuses on enabling flexible, modular frontend development. With shadcn/ui, you can keep your components decoupled from specific styles while maintaining the flexibility to customise the design as needed.

In this article, we’ll dive into why modular UI development is crucial for building scalable frontend systems and how shadcn/ui helps you achieve this. We’ll explore practical examples to illustrate how to create modular, maintainable UI components for your applications.

Why Modular Frontend Development?

In any sizable application, a modular approach to UI is essential. Components should be reusable, isolated, and composable to allow the system to scale efficiently without introducing unnecessary complexity. In React, this means breaking your interface into smaller, single-purpose components that can be reused throughout the application. The key benefits of a modular approach are clear:

  • Reusability: Create components that work across different parts of your app.
  • Maintainability: Components are more accessible to debug, maintain, and extend when built in isolation.
  • Consistency: A well-defined set of components promotes consistency in the UI, reducing the risk of visual drift.
  • Separation of Concerns: Decoupling logic from presentation lets you iterate faster on either aspect without breaking things elsewhere.

What shadcn/ui Brings to the Table

The shadcn/ui library offers a headless approach to building UI components. This approach provides components’ underlying functionality and behaviour (like modals, dropdowns, and buttons) without enforcing specific styling. You can add whatever styles you want using Tailwind CSS or another CSS framework, giving you complete control over the look and feel.

In contrast to more opinionated libraries, shadcn/ui doesn’t lock you into predefined themes. It provides raw, accessible components that you can easily extend and style. This makes it perfect for teams looking to build a design system or a component library that matches their specific needs while ensuring a consistent UX across the board.

Getting Started with shadcn/ui

To start building modular components with shadcn/ui, run

mkdir shadcn-project
cd shadcn-project
npx shadcn@latest init

Follow the prompts to configure your project. This will get you started with a basic Next.js project where you can start importing the shadcn/ui components. Let’s start by building a modular button component. Run the following command in your project:

npx shadcn-ui add button

Than add a button.tsx to your project:

import { Button } from 'shadcn-ui';

export default function MyButton() {
  return (
    <Button className="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded">
      Click Me
    </Button>
  );
}

With shadcn/ui, you can add your own Tailwind classes or extend the base component’s functionality without overriding the core behaviour. Giving you the flexibility to style buttons according to your design system while keeping the structure consistent.

Let’s customise our component further, shadcn/ui components are easy to customise. Let’s say you need to add a loading state to the button:

import { Button } from 'shadcn-ui';
import { useState } from 'react';

export default function LoadingButton() {
  const [loading, setLoading] = useState(false);

  return (
    <Button
      className="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded"
      onClick={() => setLoading(!loading)}>
      {loading ? 'Loading...' : 'Submit'}
    </Button>
  );
}

With this, you’ve created a reusable button component that can be customised for various parts of your application—whether it’s changing colors, adding icons, or handling states like loading.

Let’s create a Modal Component

Another common pattern in frontend development is the modal component. Here’s how you might implement one with shadcn/ui.

import { useState } from 'react';
import { Modal } from 'shadcn-ui';

export default function MyModal() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button onClick={() => setIsOpen(true)}>Open Modal</Button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <div className="p-6 bg-white rounded-lg shadow">
          <h2 className="text-lg font-bold">Modal Title</h2>
          <p>Modal content goes here.</p>
          <Button onClick={() => setIsOpen(false)}>Close</Button>
        </div>
      </Modal>
    </div>
  );
}

Rhn npx shadcn-ui add modal to add the modal dependency. In this example, we use shadcn/ui’s Modal component, but the modal’s content is fully customisable with Tailwind. You control the structure and design, while shadcn/ui handles the accessibility and logic for showing and hiding the modal.

Why You Should Care About Headless UI

As a frontend developer, one of the main challenges is maintaining flexibility while ensuring consistency across your app. shadcn/ui hits the sweet spot by offering headless components that you can plug into any design system. Here’s why it matters:

  • Control Over Styling: Libraries like Material UI or Bootstrap often impose their design language, making it difficult to align with a custom design system. With shadcn/ui, you have complete control over the styles since the components come unstyled.
  • Accessible by Default: shadcn/ui components come with accessibility baked in. Whether it’s focus management or keyboard navigation, you can rely on solid core behaviour without reinventing the wheel.
  • Composability: The headless nature of shadcn/ui components makes them highly composable. You can wrap components, extend them, or combine them with other UI logic to create custom solutions without being constrained by the library.
  • Tailwind Integration: If you already use Tailwind CSS, shadcn/ui feels natural. It doesn’t come with the opinionated styles of many other libraries, so you can use Tailwind to style the components precisely the way you want.

Building a Design System with shadcn/ui

Let’s say your project has outgrown ad-hoc components, and you’re moving towards a design system. shadcn/ui fits this use case perfectly. You can start by building a library of base components (buttons, inputs, modals, etc.) that are unstyled or lightly styled with Tailwind classes. These can then be reused and customised across your app.

Here’s a quick example of how you could set up a button as part of a design system:

// components/ui/Button.tsx
import { Button } from 'shadcn-ui';

const PrimaryButton = props => (
  <Button
    className="bg-primary-500 text-white hover:bg-primary-600"
    {...props}
  />
);

const SecondaryButton = props => (
  <Button className="bg-gray-500 text-white hover:bg-gray-600" {...props} />
);

export { PrimaryButton, SecondaryButton };

With this structure, your design system grows organically. You can add more variations or mix logic and styles as needed while keeping everything modular and easy to maintain.

Summary

The shadcn/ui library is a powerful tool for front-end developers who want to build modular, scalable, and customisable UI components. Decoupling logic from styles and offering headless components allows you to create a design system that fits your exact needs without being locked into specific design patterns or visual styles.

For frontend developers who value flexibility, reusability, and maintainability, shadcn/ui is the perfect choice for building a modular frontend architecture. Whether you’re creating simple components or a comprehensive design system, shadcn/ui empowers you to do so efficiently and with complete control over the final product.

‘Till next time!