Styling with Material UI's Pigment CSS
Now we'll look at using Pigment CSS, a build-time CSS solution that works seamlessly with React Server Components.
Comparing Pigment with Material UI
Material UI is a popular component framework for Next.js applications that use the Pages Router. However, it doesn't work natively with React Server Components. This is because Material UI is based on Emotion, which uses context for theming. Since React Server Components can't handle context, Material UI components need to be client components to manage their own styling. This is a fundamental issue that can't be fixed with a spot fix, so the team behind Material UI created Pigment CSS.
Installation
As usual, the installation instructions for Pigment CSS can be found in the official documentation, but we'll work through them here as well.
First, we need to install the @pigment-css/react
library:
pnpm add @pigment-css/react
Then we'll update the next.config.mjs
file to import the Pigment CSS plugin. The nextConfig
object is currently empty, but we'll wrap it in the withPigment
function to set up Pigment CSS which will be the default export:
// inside next.config.mjs
import { withPigment, extendTheme } from "@pigment-css/nextjs-plugin";
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withPigment(nextConfig);
Implementing Dark and Light Mode
In layout.tsx
, import the base styles from Pigment as well as its css
function:
// inside layout.tsx
import { css } from "@pigment-css/react";
import "@pigment-css/react/styles.css";
Styles can be defined by using either Pigment's object syntax or the backtick syntax
Here's how the html
class would be defined using both methods:
const DARK = "@media (prefers-color-scheme: dark)";
// Using object syntax
const htmlClass = css(({ theme }) => ({
backgroundColor: "white",
color: "black",
[DARK]: {
backgroundColor: "black",
color: "white",
},
}));
Then the htmlClass
can be added to the html
element in the RootLayout
component:
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.className} ${htmlClass}`}>
<body>{children}</body>
</html>
);
}
Saving our work, Polypane should show the dark background is working as expected.
Styling the Main Content
Over in the page.tsx
file, we'll bring in the styled
and css
functions from Pigment::
// inside page.tsx
import { styled, css } from "@pigment-css/react";
This time we'll use the backtick syntax to specify CSS for the mainClass
:
const mainClass = css`
margin: 0 auto;
max-width: 960px;
font-family: sans-serif;
display: flex;
flex-wrap: wrap;
`;
It's generally a good idea to stick with one syntax for your entire application, but Pigment CSS allows you to choose the one that works best for you. There's no runtime performance issue here since all the styles are generated at build time.
Next, we'll create a new Card
component that will be a div
element that has CSS applied to it with the styled
function:
const MOBILE = "@media screen and (max-width: 768px)";
const Card = styled("div")`
padding: 0.2rem;
width: 33%;
max-width: 33%;
${() => MOBILE} {
width: 100%;
max-width: 100%;
}
`;
We'll stop here so you can update the existing Home
component to use apply the mainClass
styles and use the new Card
component. In the next video, we'll review how it's done.