Engineering Blog Article

Essential UI libraries for React

March 05, 2024
6 min read

React is absolute power tool for creating great UIs, and some of the libraries in its ecosystem make it even better.

React and React frameworks such as Next are absolute power tools for creating beautiful, interactive, and performant UIs. However, what is even more enticing is the extensive and vibrant open-source community of libraries, contributors, and maintainers, that take these to the next level. Here are some of my favorite libraries for React-based web applications. These have survived the test of time, production, and the very honest open-source community, so you can best believe they are good.

Tailwind CSS

Tailwind is probably the best thing that happened to CSS since CSS. Before I used tailwind, I had two options - use a restrictive UI framework such as Bootstrap, or write my own style sheet and pray that the class names make semantic sense while also not bloating the client-side payload. These two options also came with 2 issues. Frameworks such as Bootstrap make your website look like every other site that uses it as well, and writing your own style sheet never works well because it leads to, what I term, cascading hell. Cascading hell is when you keep repeating styles in progressively more complex class names in the hope that your style actually applies. Sometimes, you may also write competing styles because you are not aware that one of them would actually override the other.

That is where Tailwind comes in. First of all, it is not a UI framework, but rather, a CSS framework. This means that it does not dictate how your UI looks or is made, but it does dictate how your CSS is written.

Using Tailwind is not as simple as just using npm install, since it does require running an npx script to initialize tailwind.config.js/ts . Full instructions are on


Let's take an example to understand the difference. This is how a primary button looks like in Bootstrap:

<button type="button" className="btn btn-primary">Primary</button>

This is how I would write a button with similar styling using Tailwind:

<button type="button" className="p-2 rounded-md bg-blue-500 hover:bg-blue-600 text-white transition-colors duration-200 ease-in-out">Primary</button>

Tailwind gives you hundreds of utility classes that are browser-agnostic, meaning they have the required styles applied that would work on most major browser engines, such as Chromium, Firefox, and Safari. Furthermore, the Tailwind compiler only ships those classes to your client payload which are actually used/referenced in your markup. Therefore, you only use those styles and classes that are actually needed. Since the class names are shorter and in your markup, they are very easy to follow. Furthermore, there is no style duplication since you use the utility class for it, not define it explicitly in a style sheet. It is generally expected that a stylesheet generated by Tailwind's JIT (just-in-time) compiler will be more concise than one made by a developer for a reasonably complex application.

Even then, there can be some arguments against Tailwind, saying that it goes against the separation of concerns by adding styling to your markup, because in the pre-web app and pre-component era, your markup, styling, and interactivity were all separated by different HTML, CSS, and JavaScript files (even style tags and script tags within HTML are defined distinctly).

There are many more benefits of Tailwind, such as adding custom styles and utility classes in tailwind.config.js but I would recommend that you explore that on your own at


Now there are valid criticisms of Tailwind as well. Since many people misinterpret it to be a UI library rather than a CSS framework (which uses PostCSS in the background), they add styles but do not create their own UI rules. Whether you are using Bootstrap or not, and whether you are defining your own styles or not, creating your own semantic UI components, hierarchy, and so on, is important regardless. This is where Tailwind-based component libraries come in. Now, I'd like to make an important distinction here. Tailwind-based UI libraries are different from Tailwind-based component libraries. For example, DaisyUI is an example of a Tailwind UI library, since it adds an abstraction layer on top of Tailwind with its own classes, rules, etc, which gives DaisyUI components a distinct feel. While you can always add custom themes, overrides, etc in the DaisyUI config and inline class names (since class names appearing later on override the previous ones), this adds a high level of complexity. Not to mention that it is another npm dependency for your application, and therefore, one more thing to manage, configure, update, and properly integrate.

The problem with libraries such as DaisyUI is that they go against the new component-based paradigm that most of us follow in React, and many other React-adjacent or similar libraries and frameworks (such as Angular, Solid, Vue, Svelte, and so on). In this component-based paradigm, styles, component-based and local state, and markup are encapsulated into one component which can then accept props (or framework-equivalents) for customization, and data population. The issue with vanilla CSS is obvious - it takes those concerns out of the component architecture into the traditional markup and stylesheet separation architecture which is neither industry-standard for these frameworks nor is developer friendly.

about why arguments against Tailwind often miss the point and make counterintuitive assumptions and claims.

React Icons

Great iconography is essential when it comes to user experience. There are hundreds of icon libraries out there. There's Bootstrap Icons, Heroicons, Ionicons, and so many more. The best way to use any of these icon libraries in React is through React Icons, which encapsulates 30+ icon libraries in easy to use React components. There are many benefits to this. The biggest benefit, arguably, is the ability to mix and match icons from different libraries. Say you like a particular avatar icon from Ionicons, but like the arrows in Hericons more. It is very easy to do that using a single library. You can also customize them fully using class names, which are optional props in every icon component imported from React Icons (and Tailwind makes this experience even better). Finally, you never have to deal with SVG files, because some libraries do not offer first-party npm packages for React applications.

All you have to do is,

npm i react-icons

And then, use them in your components like this,

// We are importing a Radix icon here import { RxClipboard } from "react-icons/rx"; const Button = (props: { href: string; children: React.ReactNode }) => { return ( <a href={props.href} className="flex flex-row gap-1 items-center text-black" > <div>{props.children}</div> <RxClipboard className="text-blue-500" /> </a> ); }; export default Button;

React Responsive

Making things responsive is very easy when it comes to using CSS media queries, for example,

@media (min-width: 1024px) { body { width: 50vw; } }

or in Tailwind CSS,

<body className="w-screen lg:w-[50vw]">Hello World</body>

However, sometimes you need viewport dimensions in your JavaScript/TypeScript code, and make UI or otherwise changes based on that, where those CSS queries or breakpoints cannot help you (especially conditional rendering). This is where my go-to library for getting such device information comes in. As with all libraries, first you install it like so,

npm i react-responsive

Then, you can use the useMediaQuery hook like so,

const ResponsiveComponent = () => { const isTabletOrMobile = useMediaQuery({ query: "(max-width: 1024px)" }); return isTabletOrMobile ? <MobileComponent /> : <DesktopComponent />; };

This is very helpful when your components are different enough that you cannot add CSS media queries or tailwind selectors without making a responsive component needlessly complex in its props, markup, or readability. It is also helpful when state, context, or APIs used in desktop vs mobile are different, and using a hook will be helpful rather than shipping a bigger component with multiple variants and unused state or variables at once.