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

    Comparing Emotion with Tailwind and Pigment

    Jack HerringtonJack Herrington

    Here are the styled components for Main and Card:

    // inside page.tsx
    
    const Main = styled.main`
      margin: 0 auto;
      max-width: 960px;
      background-color: black;
      display: flex;
      flex-wrap: wrap;
    `;
    
    const Card = styled.div`
      width: 33%;
      padding: 0.2rem;
      @media screen and (max-width: 768px) {
        width: 100%;
      }
    `;
    

    With the components created, we can update the Home component to use them:

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

    When you save the file, you'll probably see an error message that says "createContext only works in Client Components":

    error message

    We get the error because our page component is not currently a Client Component. To fix this, we need to add the 'use client' directive at the top of the file:

    // inside page.tsx
    "use client";
    import styled from '@emotion/styled'
    ...
    

    Save the file again, and the styles should now be applied correctly:

    the page loads

    Adding Light/Dark Theme Styles

    Inside of globals.css we can remove the background-color: black; style from the body in our CSS reset since we don't need it anymore. We can also add CSS styles for light and dark themes:

    *,
    *::before,
    *::after {
      box-sizing: border-box;
    }
    img {
      max-width: 100%;
      display: block;
    }
    
    body {
      font-family: Arial, Helvetica, sans-serif;
      background-color: white;
      color: black;
    }
    
    @media (prefers-color-scheme: dark) {
      body {
        background-color: black;
        color: white;
      }
    }
    

    This defaults the body styles to a white background with black text. Then, it uses a media query to change the styles to a black background with white text when the user's system is set to dark mode.

    Save the file and take a look at the results. The page should now respond to your system's light/dark mode setting, automatically switching between the two themes.

    dark mode switching works

    Styling the Product Card

    To finish things off, let's style our ProductCard component.

    First, import the styled function from @emotion/styled:

    import styled from '@emotion/styled'
    

    Like before, we'll create some styled components for the Card, CardContainer, ImageContainer, InfoContainer, and the Title and Price:

    const Card = styled.div`
      container-type: inline-size;
      width: 100%;
    `;
    
    const CardContainer = styled.div`
      display: flex;
      width: 100%;
      @container (max-width: 450px) {
        flex-direction: column;
      }
    `;
    
    const ImageContainer = styled.div`
      width: 25%;
      @container (max-width: 450px) {
        width: 100%;
      }
      & img {
        width: 100%;
        height: auto;
        @container (max-width: 450px) {
          border-top-right-radius: 1rem;
          border-top-left-radius: 1rem;
        }
        @container (min-width: 450px) {
          border-top-left-radius: 1rem;
          border-bottom-left-radius: 1rem;
        }
      }
    `;
    
    const InfoContainer = styled.div`
      padding-left: 1rem;
      @container (max-width: 450px) {
        width: 100%;
        border-bottom: 1px solid #666;
        border-top: none;
        border-left: 1px solid #666;
        border-right: 1px solid #666;
        border-bottom-right-radius: 1rem;
        border-bottom-left-radius: 1rem;
      }
      @container (min-width: 450px) {
        width: 75%;
        border-bottom: 1px solid #666;
        border-top: 1px solid #666;
        border-right: 1px solid #666;
        border-top-right-radius: 1rem;
        border-bottom-right-radius: 1rem;
      }
    `;
    
    const Title = styled.h1`
      margin: 1rem 0 0 0;
      font-size: 1.5rem;
    `;
    
    const Price = styled.p`
      margin: 0 0 1rem 0;
      font-size: 1rem;
      font-style: italic;
    `;
    

    Note that the ImageContainer includes & img to style the image inside the container, since we will apply different styles based on the container query.

    Apply the components to the ProductCard JSX:

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

    Now save the file, and you should see the styles applied to the product card:

    the styled cards

    A Note on Client Components

    You might be wondering why this works even though we didn't add the 'use client' directive to the ProductCard component. The reason is that the page.tsx file is already a Client Component because of the 'use client' directive at the top. This establishes a boundary, and anything that page.tsx imports and uses inherits that directive and becomes a Client Component as well.

    So in this case, ProductCard doesn't need the 'use client' directive because it's being imported and used by a component that is already a Client Component. However, if page.jsx was a Server Component, then ProductCard would need the 'use client' directive to use Emotion's styled function.

    A Note on Using Emotion with the App Router

    I want to be honest with you- personally, I wouldn't recommend using Emotion with the App Router. I don't think it's the ideal combination.

    When you're working with the App Router, I believe it's better to use either CSS Modules or a build-time solution like StyleX or Pigment.

    Pigment, in particular, offers a similar styled-component syntax to Emotion, so if you like that style of defining your styles, I'd recommend switching to Pigment.

    The main benefits of using a build-time solution over Emotion are that you don't need to set up the Emotion registry, and the CSS will be more performant because there's no runtime CSS generation on the client.

    While it's certainly possible to use Emotion with the App Router, I think there are other styling solutions out there that are better suited to the App Router's design and goals.

    Just remember to consider the tradeoffs and explore alternative styling solutions that might be a better fit for your project.

    Transcript

    All right, so we've got our main and card. I'm going to go and replace main with our main, and our div with our card. Should work, right? Now, let's save. There it is. Create context only works in client components, and our page component is not a client

    component. So let's bring in use client hit save and Now we get our styles Now let's remove background color black don't need that. All right, that looks pretty good. But we got our to column layout Let's go and add our reset and then while we're here. Let's just do our light dark theme

    right here. We'll say that our standard body tag is a light mode, so white background, black text, and then we'll use media selector to say that when in dark mode, do a dark mode. All right, looks pretty good. Let's go and I guess this is probably on light. Yep, there we go. Auto dark, looks great. Okay, cool. Let's go and finish this off by styling our

    product card. We'll bring in style again. We'll create a bunch of style components. We'll create 1 for the card, for the card container. Apparently, Emotion is not a fan of container queries. Inside that image container, because we need to also style the image, we're going to add the at image.

    That's going to allow us to go and give a subselector for image relative to this component. Then let's go and alter our JSX to add these new style components. This version looks nice and literate. It's nice to move from the div to the style components. I do appreciate that particular styling. Let's hit save and see how it

    looks. This actually works. Why does it work? Because if we look over at our product card, we're not actually defining this as a client component. Well, what's happening is, client components actually just define a boundary. So the boundary

    at this point is the page. So that means anything that page invokes, which would be the product card, inherits that use client. It becomes a client component, so that's why you don't actually have to define use client over in the product card is simply inheriting that from the component that

    invoked it. So if the component that invoked it was a RSC, then the product card would have to have the use client directive at the top. So I'll be honest with you, if I was to do this, which I wouldn't, I would probably add use client at the top of any file

    that would use anything imported from Emotion. As I say, I personally wouldn't do this. I personally would not use Emotion with the App Writer. I think it is not a match made in heaven. I think when you use the App Writer, you want to either use CSS modules or a build time solution. Like we've already seen

    with StyleX and Pigment, and Pigment handles this style of styled specificity. So if you want to use that styled way of defining your classes, just switch over to Pigment. You don't need to use a motion, you don't need to establish that registry,

    and in all honesty the CSS is going to be much more performant because there's no runtime CSS that's going to happen on the client. I am.