ProNextJS
    Professional Next.js Course
    Loading price
    30-Day Money-Back Guarantee
    lesson

    Finish Styling with Pigment CSS

    Jack HerringtonJack Herrington

    Here's how it looks to apply the mainClass and use the new Card in the Home component:

    export default function Home() {
      return (
        <main className={mainClass}>
          {PRODUCTS.map((product) => (
            <Card key={product.id}>
              <ProductCard product={product} />
            </Card>
          ))}
        </main>
      );
    }
    

    After saving, we'll see a two-column layout:

    two column layout

    This indicates that we don't have a reset applied. Adding the reset to our global CSS will give us a nice three-card layout:

    /* inside globals.css */
    * {
      box-sizing: border-box;
      padding: 0;
      margin: 0;
    }
    
    three column layout

    With the layout fixed, we can move on to the ProductCard component.

    Styling the Product Card

    Inside of ProductCard.tsx we'll bring in the css and styled:

    import { css, styled } from '@pigment/css';
    

    Below that, we'll establish our vertical and horizontal container query selectors:

    const VERTICAL = "@container (max-width: 450px)";
    const HORIZONTAL = "@container (min-width: 450px)";
    

    We'll create styled components for almost all of the parts of the ProductCard component. However, for the image we'll just create a class and apply it directly.

    Here's how the styled divs will look:

    const Card = styled("div")({
      containerType: "inline-size",
      width: "100%",
    });
    
    const CardContainer = styled("div")({
      display: "flex",
      width: "100%",
      flexDirection: "row",
      [VERTICAL]: {
        flexDirection: "column",
      },
    });
    
    const ImageContainer = styled("div")({
      width: "25%",
      [VERTICAL]: {
        width: "100%",
      },
    });
    
    const imageContainerImg = css({
      width: "100%",
      height: "auto",
      borderTopRightRadius: 0,
      [VERTICAL]: {
        borderTopRightRadius: "1rem",
      },
      borderTopLeftRadius: "1rem",
      borderBottomLeftRadius: 0,
      [HORIZONTAL]: {
        borderBottomLeftRadius: "1rem",
      },
    });
    
    const Title = styled("h1")({
      fontSize: "1.5rem",
      margin: "0",
      marginTop: "1rem",
    });
    
    const Price = styled("p")({
      fontSize: "1rem",
      marginBottom: "1rem",
      fontStyle: "italic",
    });
    
    const InfoContainer = styled("div")({
      width: "75%",
      paddingLeft: "1rem",
    
      borderColor: "#666",
      borderBottomWidth: 1,
      borderBottomStyle: "solid",
    
      borderRightWidth: 1,
      borderRightStyle: "solid",
    
      borderBottomRightRadius: "1rem",
      borderBottomLeftRadius: 0,
      borderTopRightRadius: 0,
    
      [VERTICAL]: {
        width: "100%",
        borderBottomLeftRadius: "1rem",
        borderLeftWidth: 1,
        borderLeftStyle: "solid",
      },
      [HORIZONTAL]: {
        borderTopRightRadius: "1rem",
        borderTopWidth: 1,
        borderTopStyle: "solid",
      },
    });
    

    Now we can apply the styled components to the ProductCard component:

    export const ProductCard = ({ product }: Props) => {
      return (
        <Card>
          <CardContainer>
            <ImageContainer>
              <Image
                src={product.image}
                alt={product.title}
                width={300}
                height={300}
                className={imageContainerImg}
              />
            </ImageContainer>
            <InfoContainer>
              <Title>{product.title}</Title>
              <Price>{product.price}</Price>
            </InfoContainer>
          </CardContainer>
        </Card>
      );
    };
    

    Notice how nicely the ProductCard component reads now that we've applied the styled components.

    Normally we would end here since the example application has been styled, but it's worth taking a look at Pigment CSS's theming capabilities.

    Adding a Theme to Pigment CSS

    Inside of next.config.mjs, we can add a theme using the extendTheme function from Pigment's NextJS plugin:

    // inside next.config.mjs
    import { withPigment, extendTheme } from "@pigment-css/nextjs-plugin";
    

    We'll update the export to specify a theme by calling the extendTheme function with an object for color schemes:

    export default withPigment(nextConfig, {
      theme: extendTheme({
        colorSchemes: {
          light: {
            colors: {
              background: "white",
              foreground: "black",
            },
          },
          dark: {
            colors: {
              background: "black",
              foreground: "white",
            },
          },
        },
      }),
    });
    

    We get to define the schema for this theme, so we can use whatever symbols we want. What's really nice is that at build time, it's going to manage that theme for us.

    Once the theme is established, we can apply it to the html element in the RootLayout component. We'll create a new htmlClass the uses the css function and destructures the theme object, applying the theme properties as appropriate:

    // inside layout.tsx
    
    const htmlClass = css(({ theme }) => ({
      backgroundColor: theme.colorSchemes.light.colors.background,
      color: theme.colorSchemes.light.colors.foreground,
      [DARK]: {
        backgroundColor: theme.colorSchemes.dark.colors.background,
        color: theme.colorSchemes.dark.colors.foreground,
      },
    }));
    

    After adding the htmlClass to the RootLayout class names, we can switch between light adn dark mode in Polypane. Changing colors in next.config.mjs will also update the theme:

    themes are working

    TypeScript Configuration

    There's one issue at this point, and that's TypeScript is showing several errors that won't pass checks for building and deploying.

    To fix this, we'll do a little TypeScript hacking by defining types for Pigment.

    Create a new file at the top level called pigment.d.ts, and add the following:

    import { ExtendTheme } from "@pigment-css/react/theme";
    
    declare module "@pigment-css/react/theme" {
      interface ThemeTokens {
        colorSchemes: {
          light: {
            colors: {
              background: string;
              foreground: string;
            };
          };
          dark: {
            colors: {
              background: string;
              foreground: string;
            };
          };
        };
      }
    
      interface ThemeArgs {
        theme: ExtendTheme<{
          colorScheme: "light" | "dark";
          tokens: ThemeTokens;
        }>;
      }
    }
    

    The extendTheme from Pigment CSS's React theme is now being extended to add types that match what we specified in next.config.mjs. Add the pigment.d.ts file to the tsconfig.json, and the TypeScript errors will be resolved:

    // inside tsconfig.json
    ...
      "include": [
        "pigment.d.ts",
        "next-env.d.ts",
        "**/*.ts",
        "**/*.tsx",
        ".next/types/**/*.ts"
      ],
    ...
    

    Similarities to Material UI

    If you've used Material UI, a lot of this is going to look very familiar to you. The way you specify classes and use style is all the same as what you get with Material UI version 5.

    The team designed it this way in order to more easily port between the Emotion system they had before and what they'll have with a subsequent versions of Material UI built on top of Pigment CSS.

    It's also cool that Pigment CSS looks to be compatible with other frameworks as well. That means you could potentially create design tokens and classes using Pigment CSS that are transportable amongst all of the different view libraries. Great stuff!

    Transcript

    All right, we got our main class. Let's go and apply that to our main. We just simply add that as a class name. Then for the card, let's just replace our div with a card. That's how the style stuff works. So really nice. So save and see how it goes. All right,

    now cool. We got a two-column layout, which is indicative of the fact that we don't have a reset going. So let's go and add our reset to our global CSS. Let's save. Now, we got that nice 3 card layout. It's a drag that we have to do these resets, right? Okay. There is 1 more variant I want to talk to you

    about and you can also use, for example, style.div if you want to do that. Hit save and that also works. Really up to you. All right, let's talk about product card. So again, we'll bring in the CSS and style, we're going to use a combination of both.

    Down below that, I will establish my vertical and horizontal container query selectors, and I'll create for almost all of these styled components, but I think it's a better choice in this case. But for the image tag, I'm going to choose to create a class for that because I'm just going to apply the class to the image. Now let's go and adjust our JSX.

    And Then we'll take those divs and we'll turn it into what I would actually consider myself to be a more literate version of this component. You've got card, card container, image. It just reads more nicely to me. Now, I normally wrap it up here, but I do want to show how theming works in this

    because it's actually quite interesting. So let's go over to our Next.js configuration, MGS, and I'm going to add a theme. To do that, I'm going to use the extend theme function. I'm going to specify that we have a theme, I'm going to pass my theme to extend theme, and then I'm going to give it some color schemes. I'm going to say that we have

    some colors, I'm going to define what they are. I actually get to define what the schema is for this theme. So I can use whatever symbols I want. What's really nice is that at build time it's going to manage that theme for us. Now what I've done here is I've established the background and foreground

    colors for light and dark modes. So the place where I would apply that is over in the layout. 1 way to do that is instead of giving CSS an object like this, you instead give it a function. That function then takes that theme and then applies those theme

    colors. So if I hit save here, I'll take a look and see how it's going. I'll switch this 1, for example, to light mode, and it looks like we're doing okay. That's great. So let's actually try this out. We'll go to next config.mjs, and then we'll change

    in light mode, the foreground color to red. Hit save, hit refresh. Now we actually see that that theme is being applied. Only 1 small issue at this point, and that's that TypeScript is freaking out. It's actually larger than a small issue because at

    this point you couldn't build, you have to actually pass the TypeScript check in order to build and deploy. So we need to get the TypeScript thing figured out. To do that, we do a little TypeScript hacking. We'll create a new file at the top level, call it pigment.d.ts. We're going to be defining types. Then what we'll do is we'll bring

    in extend theme from the pigment CSS React theme. We'll create our theme token definition. That has to match whatever we specify over here in our next JS config MJS. Then we will extend theme args. That's what actually gets sent to the theme over

    here with our color scheme. Then down in our TS config, we can include that pigment TS file, and now layout's happy. We actually get theme colors and everything's fine. So if you're a Material UI user, I got to say a lot of this is going to look very

    familiar to you. The way that you specify classes, the way that you can do style, It's all the same as what you get with material UI version 5. And there's a big reason for that, because they want you to be able to do a very easy port between the emotion system that they had before, and what they're going to have, I suppose, with a

    subsequent version of Material UI that is built on top of this Pigment CSS. I got to say, I think it's also cool that Pigment CSS looks to be compatible with other frameworks as well. That means you could potentially create design tokens and classes using Pigment CSS that are transportable

    amongst all of the different view libraries. I think it's great stuff.