CSS-in-JS is a modern approach to styling web applications where CSS is written directly in JavaScript files. This allows CSS styles to be scoped into a component. Many developers, especially in the React community, have adopted the CSS-in-JS approach.
The most popular CSS-in-JS libraries are styled-components and Emotion. MUI, a React component library, recently released a promising, zero-runtime CSS-in-JS library called Pigment CSS. This article will explore the features and benefits of Pigment CSS, offering a comparison to styled-components and Emotion based on performance, features, developer experience, and community support.
Pigment CSS is a zero-runtime CSS-in-JS library maintained by the Material UI team and built on top of WyW-in-JS, or “Whatever-you-want-in-JS.” It extracts colocated styles to their own CSS files at build time. Pigment CSS currently supports Next.js and Vite.
The concept of zero-runtime CSS-in-JS seeks to combine the benefits of CSS-in-JS with the performance benefits of traditional CSS. Here, all styles are compiled to static CSS files at build time, eliminating runtime overhead. This improves performance, especially on initial page loads.
Pigment CSS offers the benefits of CSS-in-JS, such as locally scoped styles and themeability, while avoiding the runtime performance cost typically associated with CSS-in-JS libraries. Some of the key features include:
Styles are preprocessed during the build phase. No styles are injected and recalculated during runtime. This improves the performance of the application.
Theming is an optional feature that lets you reuse the same style values across your application. The theme objects are only used at build time and are not included in the final JavaScript bundle.
Pigment CSS seamlessly integrates with Next.js and Vite with support for more bundlers in the future. This makes it easy to add Pigment CSS to existing React applications without significant configuration.
Pigment CSS allows you to define CSS in two ways: using object-style syntax, where styles are defined using JavaScript objects, or template-style syntax, where CSS styles are written using template literals.
Pigment CSS uses zero-runtime CSS-in-JS, while styled-components uses runtime CSS-in-JS, and Emotion uses runtime CSS-in-JS with options for extracting static styles.
The three libraries can be compared as follows:
Check out our article comparing styled-components and Emotion for a deeper dive.
To use Pigment CSS, you must first configure it in your Next.js or Vite application. In this tutorial, we’ll use a Next.js app.
Pigment CSS simplifies the creation of reusable styles and components for your application by providing various APIs. You can use the css
API to create reusable styles, the styled
API to create a component by passing styles at the end, or the keyframes
API to create reusable animation keyframes. A theme object can also be used to reuse the same styling values across your application.
In your Next.js application, install Pigment CSS using the following command:
npm install @pigment-css/react npm install --save-dev @pigment-css/nextjs-plugin
This command installs the Pigment CSS library and the Next.js plugin.
Next, in your next.config.mjs
file, import the withPigment
plugin, and wrap the Next.js config as shown below:
import { withPigment } from '@pigment-css/nextjs-plugin'; const nextConfig = {}; export default withPigment(nextConfig);
In your layout.tsx
file, import the Pigment CSS stylesheet as shown:
import '@pigment-css/react/styles.css';
With that, you’re ready to make use of Pigment CSS styles.
First, we’ll use the css
API to create our styles. You can use the template
or object
syntaxes as shown below:
import {css } from "@pigment-css/react"; // template syntax const bodyBackground = css` background-color: #1D2125; color: #fff; `; // object syntax const mainClass = css({ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", });
To apply the styles to your DOM element, add the styling as a class name to your element:
<html lang="en" className={bodyBackground}> </html> <main className={mainClass}> </main>
Next, we’ll use the styled
API to create our styled components. Here, we create a styled heading and add variants based on the props:
const Heading = styled('div')({ fontSize: "2rem", color: "#9FADBC", fontWeight: "bold", margin: "1rem", variants: [ { props: { variant: 'success' }, style: { color: '#23AD79' }, }, { props: { size: 'small' }, style: { fontSize: '1.5rem' }, }, ], });
We then add the component to our DOM as shown below. One heading uses the base styles while the other heading uses the variant styles on top of the base style:
<Heading>Pigment CSS</Heading> <Heading variant="success" size="small">Test Styling</Heading>
You can also style your components based on runtime values. The isError
prop value is unknown ahead of time. It’s used to style the heading in a callback function:
const Heading = styled('div')({ fontSize: "2rem", color: ({ isError }: { isError: boolean }) => (isError ? 'red' : '#9FADBC'), fontWeight: "bold", margin: "1rem", });
The prop value is passed to your component. Here, the heading color is set based on the isError
value:
<Heading isError>Test Styling</Heading>
This is an optional feature that allows you to reuse the same styling values across your application using a theme object. You can make use of the extendTheme
utility to generate CSS variables from your theme object.
First, for type safety, let’s define our theme interface in a theme.d.ts
file as shown below:
import { ExtendTheme } from "@pigment-css/react/theme"; declare module "@pigment-css/react/theme" { interface ThemeTokens { colorScheme:{ light: { primary: string; secondary: string; background: string; text: string; error: string; }; dark: { primary: string; secondary: string; background: string; text: string; error: string; }; } } interface ThemeArgs { theme: ExtendTheme<{ colorScheme: "light" | "dark" tokens: ThemeTokens }>; } }
In the code snippet above, we changed the Pigment CSS theme module. We added our ThemeTokens
, which need to match our theme object. We then redefined the ThemeArgs
with our color scheme and tokens.
Now, we’ll set the theme module to include
in the TypeScript config file:
"include": ["theme.d.ts","next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
In our next.config
file, we added a theme option to the withPigment
function. We also used the extendTheme
utility to generate CSS variables for our theme:
import { withPigment, extendTheme } from '@pigment-css/nextjs-plugin'; const nextConfig = {}; export default withPigment(nextConfig, { theme: extendTheme({ colorScheme: { light: { primary: "#9FADBC", secondary: '#23AD79', background: '#fff', text: '#000', error: '#CC3131' }, dark: { primary: "#9FADBC", secondary: '#23AD79', background: '#1D2125', text: '#fff', error: '#CC3131' }, } }), });
We can use the generated theme variables in the css
or styled
APIs as shown. You can apply styles based on the color scheme by using the prefers-color-scheme
media query or the applyStyles
functions attached to extendTheme
:
// template syntax const bodyBackground = css` background-color: ${({ theme }) => theme.colorScheme.dark.background}; color: ${({ theme }) => theme.colorScheme.dark.text}; @media (prefers-color-scheme: light) { background-color: ${({ theme }) => theme.colorScheme.light.background}; color: ${({ theme }) => theme.colorScheme.light.text}; } `; // object syntax const bodyBackground = css(({ theme }) => ({ ...theme.applyStyles("light",{ backgroundColor: theme.colorScheme.light.background, color: theme.colorScheme.light.text, }), ...theme.applyStyles("dark",{ backgroundColor: theme.colorScheme.dark.background, color: theme.colorScheme.dark.text, }) }) )
This tutorial explored the new CSS-in-JS library by MUI, Pigment CSS, a zero-runtime library. We discussed its key features and compared it to common CSS-in-JS libraries like Emotion and styled-components. We also went through some basic Pigment CSS library usage such as using the css
and styled
APIs and theming.
Pigment CSS is still in the early release as of writing this article. The library will have more features and improvements as it grows.
Happy coding!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.