ProNextJS
    Loading
    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