Introduction#
CSS is a stylesheet language that is used to style the HTML elements and make them look better, more appealing. CSS seems to be easy to configure and use. You can just write some CSS code, or might use a third-party library like Bootstrap and link up their CDN in your HTML files. But now, with the rise of React and single-page applications, things are getting more complicated. Due to the nature of bundling and optimization1, it is recommended to keep your CSS during the React compiling process.
CSS styling#
Writing your own CSS has been a good way to style your React components. It’s also a good way if you want to control and customize your design system. There are many ways to achieve this.
Use vanilla CSS#
The most simple way to style your React components is to write your CSS rules.
But instead of linking up in the <head>
tag, you import your CSS file in your
React root component. This allows the CSS to be optimized and bundled better in
production build2.
.Button { padding: 20px; }
import React, { Component } from 'react'; import './Button.css'; // Tell webpack that Button.js uses these styles const Button = () => { return <button className="Button">Click me</button>; };
Use CSS modules#
CSS modules is a way to scope your CSS to a specific component. Instead of having a massive CSS file that styles all the components, CSS modules allows you to have a CSS file for each component. Another benefit of CSS modules is that it prevents CSS class name conflicts3, even if you use the same class name in a component.
. src/ └── Button/ ├── Button.module.css └── Button.tsx
.error { color: red; }
.error { background-color: red; }
import React from 'react'; import styles from './Button.module.css'; // Import css modules stylesheet as styles import './app.css'; // Import regular stylesheet const Button = () => { return <button className={styles.error}>Click me</button>; };
The result of the above code is that the button will have a red text color from
the .error
class in Button.module.css
.
<!-- This button has red text, not the red background --> <button class="Button_error_xazx5">Error Button</button>
Use SCSS#
SCSS is a superset of CSS that allows you to write CSS with more features like mixins and variables. For example, if you want to create a gutter system for margins and paddings, you can use SCSS to loop through the values and create classes for them.
// Define the breakpoints map $breakpoints: ( "xs": 0px, "sm": 576px, "md": 768px, "lg": 992px, "xl": 1200px, "xxl": 1400px ); // Mixin to generate margin rules for each breakpoint @mixin generate-margin-rules { @each $breakpoint, $min-width in $breakpoints { // Define media query for each breakpoint @media (min-width: #{$min-width}) { @for $i from 1 through 64 { .m-#{$i}-#{$breakpoint} { margin: #{($i * 0.25)}rem; } .mt-#{$i}-#{$breakpoint} { margin-top: #{($i * 0.25)}rem; } .mr-#{$i}-#{$breakpoint} { margin-right: #{($i * 0.25)}rem; } .mb-#{$i}-#{$breakpoint} { margin-bottom: #{($i * 0.25)}rem; } .ml-#{$i}-#{$breakpoint} { margin-left: #{($i * 0.25)}rem; } .mx-#{$i}-#{$breakpoint} { margin-left: #{($i * 0.25)}rem; margin-right: #{($i * 0.25)}rem; } .my-#{$i}-#{$breakpoint} { margin-top: #{($i * 0.25)}rem; margin-bottom: #{($i * 0.25)}rem; } } } } }
To use SCSS in your React project, you need to install sass
package:
npm install sass # or yarn add sass
Then you can replace your src/App.css
with src/App.scss
and import it in
your React component:
@import 'gutter.scss'; .App { // Other style rules }
import React from 'react'; import './App.scss'; const App = () => { return <div className="App">Hello World</div>; };
React has an official guide on how to use SCSS in your React projects here.
CSS frameworks#
Major CSS frameworks provide pre-built components and styles that you can use. They provide functionalities for common used components like buttons, forms, modals, etc.
Bootstrap#
Bootstrap has been a popular CSS framework since the 2011. It provides Bootstrap components and utilities that you can use to style your applications. However, Bootstrap is not React-specific so you need to install React adapters such as react-bootstrap or reactstrap.
import React from 'react'; import { Button } from 'reactstrap'; export default (props) => { return <Button color="danger">Danger!</Button>; };
Material UI#
MUI is a popular React component library maintained by teams at Google. Unlike Bootstrap, MUI is React-specific and provides React native components that you can use in your React applications. MUI also provides providers and hooks that you can use to customize the theme and styles of your components.
import * as React from 'react'; import Card from '@mui/material/Card'; import ListItem from '@mui/material/ListItem'; export default function SpecificRefType() { const cardRef = React.useRef<HTMLDivElement>(null); const listItemRef = React.useRef<HTMLLIElement>(null); return ( <div> <Card ref={cardRef}></Card> <ListItem ref={listItemRef}></ListItem> </div> ); }
Chakra UI#
Chakra UI is another powerful React component library that provides a set of functional and accessible components that you can use in your React applications. Since Chakra UI v3, you can install individual components instead of installing the whole library like MUI or Bootstrap.
For example, to install the Button
component, you can run:
npm i @chakra-ui/react @emotion/react npx @chakra-ui/cli snippet add chakra snippet add button
import { HStack } from "@chakra-ui/react" import { Button } from "@/components/ui/button" const Demo = () => { return ( <HStack wrap="wrap" gap="6"> <Button size="xs">Button (xs)</Button> <Button size="sm">Button (sm)</Button> <Button size="md">Button (md)</Button> <Button size="lg">Button (lg)</Button> <Button size="xl">Button (xl)</Button> </HStack> ) }
CSS-in-JS#
CSS-in-JS is a technique for writing CSS inside your JavaScript/TypeScript code, instead of being defined in a separate CSS file.
Inline CSS-in-JS#
Inline CSS-in-JS is the most basic form of CSS-in-JS. You can use the style
attribute in your JSX to style your components.
import React from "react"; const styles = { color: "red", backgroundColor: "blue", }; const App = () => { return <div style={styles}>Hello World</div>; };
Styled-components#
styled-components is a popular CSS-in-JS library that allows you to write rich CSS styles with features like theming, props-based styling, responsive and more in your React components, which are not possible with inline CSS-in-JS.
const StyledButton = styled.button<{ $primary?: boolean; }>` /* Adapt the colors based on primary prop */ background: ${props => props.$primary ? "#BF4F74" : "white"}; color: ${props => props.$primary ? "white" : "#BF4F74"}; font-size: 1em; margin: 1em; padding: 0.25em 1em; border: 2px solid #BF4F74; border-radius: 3px; `; const App = () => { return ( <div> <StyledButton>Normal Button</StyledButton> <StyledButton $primary>Primary Button</StyledButton> </div> ); };
The problem with styled-components
is that it adds a lot of overhead to your
applications during runtime. Many CSS-in-JS libraries like styled-components
or emotion
requires converting the styles written in these components to plain
CSS and inserting them into the document at runtime. This can cause performance
issues in your applications.
Zero-runtime CSS-in-JS#
To solve the performance issues of traditional CSS-in-JS libraries, many new
libraries like linaria are created to
generate CSS at build time, hence there is no runtime overhead. They also solve
the problem of dynamic styles with props
by using CSS custom properties like
CSS variables.
import { styled } from "@linaria/react"; import { families, sizes } from "./fonts"; // Write your styles in `styled` tag const Title = styled.h1` font-family: ${families.serif}; `; const Container = styled.div` font-size: ${sizes.medium}px; color: ${props => props.color}; border: 1px solid red; &:hover { border-color: blue; } ${Title} { margin-bottom: 24px; } `; const App = () => ( <Container color="#333"> <Title>Hello world</Title> </Container>; );
TailwindCSS#
TailwindCSS belongs to a different category than the previous ones. First, it is a utility-first class CSS framework, which means it provides a set of utility classes that you can include in your React components. Unlike writing CSS rules in the traditional way, you can just write classes in your elements like this:
<figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800"> <img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512"> <div class="pt-6 md:p-8 text-center md:text-left space-y-4"> <blockquote> <p class="text-lg font-medium"> “Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.” </p> </blockquote> <figcaption class="font-medium"> <div class="text-sky-500 dark:text-sky-400"> Sarah Dayan </div> <div class="text-slate-700 dark:text-slate-500"> Staff Engineer, Algolia </div> </figcaption> </div> </figure>
It feels like writing inline CSS, but unlike inline CSS styles, TailwindCSS supports media queries, pseudo-classes, and more.
You can also set your arbitrary values in your Tailwind classes:
<div class="top-[117px]"> <!-- ... --> </div>
Or you can write your own CSS classes with TailwindCSS:
.select2-dropdown { @apply rounded-b-lg shadow-md; } .select2-search { @apply border border-gray-300 rounded; } .select2-results__group { @apply text-lg font-bold text-gray-900; }
The way TailwindCSS works is that it will scan your HTML and JavaScript components for classnames, generate a CSS file with all the utility classes with optimization and write a static CSS files with zero runtime.
Headless components#
Headless components are recently introduced in the React community as an opposite way to UI libraries. Instead of focusing on building a beautiful UI, headless components tend to focus the functionality and the logic of the components and let you write your own styles.
Radix UI#
Radix UI is a collection of headless UI components that allows you to install what you need, instead of installing the whole UI library like MUI and having many components that you never touch.
The way Radix UI works is that it creates a component that lives inside your
projects, usually in the components
folder or the folder of your choice.
For example, to install a dropdown component, you can run:
npm install @radix-ui/react-dropdown-menu
Then you can simply use that component in your project:
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; export default () => ( <DropdownMenu.Root> <DropdownMenu.Trigger /> <DropdownMenu.Portal> <DropdownMenu.Content> <DropdownMenu.Label /> <DropdownMenu.Item /> <DropdownMenu.Group> <DropdownMenu.Item /> </DropdownMenu.Group> <DropdownMenu.CheckboxItem> <DropdownMenu.ItemIndicator /> </DropdownMenu.CheckboxItem> <DropdownMenu.RadioGroup> <DropdownMenu.RadioItem> <DropdownMenu.ItemIndicator /> </DropdownMenu.RadioItem> </DropdownMenu.RadioGroup> <DropdownMenu.Sub> <DropdownMenu.SubTrigger /> <DropdownMenu.Portal> <DropdownMenu.SubContent /> </DropdownMenu.Portal> </DropdownMenu.Sub> <DropdownMenu.Separator /> <DropdownMenu.Arrow /> </DropdownMenu.Content> </DropdownMenu.Portal> </DropdownMenu.Root> );
Radix UI provides a set of powerful components that implements the behaviors in according components very well. They make you less worry about the actual logics and behaviors. You can just focus on the styles and the design of the components.
ShadCN#
ShadCN is another headless UI library that works very similar to Radix UI. In fact, ShadCN is built on top of Radix UI and includes TailwindCSS classes to complete the components.
As Radix UI, you can install individual components as you need:
npx shadcn@latest add accordion
Then you can use the component in your project:
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion" <Accordion type="single" collapsible> <AccordionItem value="item-1"> <AccordionTrigger>Is it accessible?</AccordionTrigger> <AccordionContent> Yes. It adheres to the WAI-ARIA design pattern. </AccordionContent> </AccordionItem> </Accordion>
ShadCN also provides much more complex UI components that utilize powerful engines of other libraries. For example, the drawer component is built on top of another package called vaul and includes the TailwindCSS classes as styles.
Headless UI#
Headless UI is another headless UI library. Unlike Radix UI and ShadCN, Headless UI is a set of completely unstyled, fully-integrated with TailwindCSS. The nice thing about Headless UI is that it focuses on the accessibility and the most commonly-used components like modals, popovers, and dropdowns. Or like many people, they use Headless UI with TailwindCSS to create their own unified design system, instead of having raw classname strings in the reusable components.
To use Headless UI, you can install the package:
npm install @headlessui/react
import { Button } from '@headlessui/react' function Example() { return ( <Button className="rounded bg-sky-600 py-2 px-4 text-sm text-white data-[hover]:bg-sky-500 data-[active]:bg-sky-700"> Save changes </Button> ) }
Conclusion#
Choosing the right CSS techniques for your site is a hard question. If you are new, it’s best to try them all to see which one fits your needs. Maybe you want to learn CSS, then writing your own CSS is a good choice. Maybe you want to have your applications out in the market as soon as possible, then using a UI library like MUI sounds better.
Personally, I prefer using TailwindCSS for styling and ShadCN components for behaviors. The only reason is that I don’t want to break down my components into smaller parts and think about naming them. I also want to look at individual components and have grasp of what they look like, instead of finding their CSS styles somewhere. But that’s just me. You can choose your own way.