Learn Computing from the Experts | The Rheinwerk Computing Blog

CSS in JavaScript Using Emotion

Written by Rheinwerk Computing | May 16, 2024 1:00:00 PM

Emotion is a library for CSS-in-JS, an approach where you make style specifications in JavaScript code.

 

There are quite a few libraries you can draw on. Probably the best known in this area are Styled Components and Emotion. Styled Components specializes in React, while Emotion takes a framework-independent approach. In the next sections, we'll introduce Emotion as a possible solution because it provides the same syntax as Styled Components, but more features, and you can use the library very well with React.

 

You can use Emotion either with the css prop or with the styled approach. The css prop allows you to write the styles in a prop and apply them directly in the element. This variant is similar to direct styling via the style prop. The styled approach uses a kind of higher-order component. This exploits a rarely used part of the ECMAScript standard by using the tag function of the template strings, a function executed to process the template string.

 

By default, the JavaScript environment provides a standard tag function that is used to perform variable substitution. Emotion, or which follows the same principles as the idea generator styled components, takes this idea further and allows you to create components with local style specifications via template strings. In addition to just styling components, the Styled Components library offers numerous other useful features, some of which we’ll take a closer look at ahead and integrate into an example application.

 

Installing Emotion

Emotion is an open-source library developed under the MIT license on GitHub. Depending on which variant of Emotion you want to use, you need to install different packages. For use in the css prop, all you need is the @emotion/react package, which you can install using npm install @emotion/react. If you want to use the styled approach, you have to install the @emotion/styled package in addition to that; that is, you need to run the npm install @emotion/react @emotion/styled command. Both packages come with their own type definitions, so you don't need to install additional packages to use them with TypeScript.

 

Using the “css” Prop

The css prop of Emotion digs deep into React's build process and makes the Babel plugin, which is responsible for translation, use the jsx function instead of the React.createElement function. To use this variant of Emotion with Create React App and TypeScript, you need to adjust the TypeScript configuration and add the "jsxImportSource":"@emotion/react" entry in the compilerOptions of the tsconfig.json file. Then you still need to add a comment at the beginning of the file so that Babel processes it correctly. Below shows an example of using the css prop of Emotion:

 

/** @jsxRuntime classic */

/** @jsx jsx */

import { jsx, css } from '@emotion/react';

import React from 'react';

import { Book } from './Book';

 

const headerStyle = {

   borderBottom: '3px solid black',

};

const cellStyle = {

   padding: '5px 10px',

};

 

const books: Book[] = […];

 

const BooksList: React.FC = () => {

   return (

      <table css=>

         <thead css={headerStyle}>

            <tr>

               <th>Title</th>

               <th>Author</th>

               <th>ISBN</th>

               <th>Rating</th>

            </tr>

         </thead>

         <tbody>

            {books.map((book) => {

               return (

                  <tr

                     key={book.id}

                     css={css`

                        &:nth-of-type(2n) {

                            background-color: #ddd;

                        }

                     `}

                  >

                     <td css={cellStyle}>{book.title}</td>

                     <td css={cellStyle}>{book.author}</td>

                     <td css={cellStyle}>{book.isbn}</td>

                     <td css={cellStyle}>

                        {book.rating && <span>{'*'.repeat(book.rating)}</span>}

                     </td>

                  </tr>

               );

            })}

         </tbody>

      </table>

   );

};

 

export default BooksList;

 

The entry point to the file consists of two comments that Emotion and the build process respectively need to handle the css prop correctly. The first comment switches the runtime of the preset-react plugin to classic mode. The second comment makes sure that the css prop is processed correctly by Emotion. If your development environment has problems with the correct processing of the css prop, you can fix it with the line: /// <reference types="@emotion/react/types/css-prop" />

 

Before you get into the component, you define two style objects. The headerStyle object is responsible for the appearance of the table's header row, while cellStyle is responsible for the appearance of the table's individual cells.

 

In the BooksList component itself, you can see different variants of using the css prop:

  • Inline object: In the table element, you define the css prop and set the styling directly inline. On the one hand, this has the advantage that you can see at a glance which styles are defined for an element. If you use this approach for multiple elements and have multiple style specifications per element, the source code of your component quickly becomes confusing. You should therefore only use this variant sparingly.
  • External object: For the table header and the individual cells, you swap out the styles to separate objects so that the style definitions do not appear directly in the component. Using this variant, you achieve a tidier source code in the component and can reuse the individual objects in several places, as you can see with the td Also, you can swap out these style objects to a separate file if needed.
  • CSS template string: In the third variant, you define a template string with the styles and provide it with the css function of Emotion. With this approach, you can use pseudoselectors such as nth-of-type or hover, for example. The same approach applies to the template string as to an object: you can define it either inline as in the example or outside the component. Emotion offers a second variant—the styled approach—to style your components. 

The Styled Approach of Emotion

The styled approach, also referred to as Emotion styled components, is a styling variant in which you use template strings to produce new components. Emotion is not the first library to implement this approach. The solution was originally derived from libraries like Styled Components and Glamorous.

 

The advantage of styled components over the css prop is that you don't need to make any adjustments to your TypeScript configuration and your component, especially when using TypeScript. To be able to use styled components, you just need to install the @emotion/styled package.

 

There are several approaches to organizing styled components. These range from placing the components directly in the file where they’re needed to having a separate file per Styled Component. The first approach has the disadvantage that styled components are relatively tightly coupled to the component in which they are used. The second approach results in a very large number of files that may make your application very confusing. A good middle ground is to group styled components thematically into files.

 

For the sample application, you create a new file named BooksList.styles.ts. The styled components of Emotion provide tagging capabilities for all HTML elements, which you can use to create corresponding elements with style definitions for your application. You can directly convert the static styles of the table for the table element, the table header, and the cells. The syntax is similar to the CSS template strings you learned about in the context of the css prop. This listing shows the implementation of the components as styled components:

 

import styled from '@emotion/styled';

 

export const Table = styled.table`

   border-collapse: collapse;

`;

 

export const THead = styled.thead`

   border-bottom: 3px solid black;

`;

 

export const TD = styled.td`

   padding: 5px 10px;

`;

 

To create the styled components, you can use the styled.table, styled.thead, and styled.td tag functions. You pass the CSS information to them in the template string.

 

Because styled components are JavaScript strings and not regular CSS, you have no direct support in your development environment. However, a plugin is available for both WebStorm and Visual Studio that gives you syntax highlighting and autocompletion for styled components.

 

The result of the tag function is a full-fledged React component that you can use within your application:

 

import React from 'react';

import { Book } from './Book';

import { Table, TD, THead } from './BooksList.styles';

 

const books: Book[] = […];

const BooksList: React.FC = () => {

   return (

       <Table>

           <THead>

               <tr>

                   <th>Title</th>

                   <th>Author</th>

                   <th>ISBN</th>

                   <th>Rating</th>

               </tr>

         </THead>

         <tbody>

            {books.map((book) => {

               return (

                  <tr key={book.id}>

                     <TD>{book.title}</TD>

                     <TD>{book.author}</TD>

                     <TD>{book.isbn}</TD>

                     <TD>{book.rating && ↩

                        <span>{'*'.repeat(book.rating)}</span>}</TD>

                  </tr>

               );

            })}

         </tbody>

      </Table>

   );

};

 

export default BooksList;

 

As you can see, you can choose the names of your styled components in such a way that, except for the initial uppercase letter, you won't notice any difference from the React elements you've been using so far. However, the styled components of Emotion can do much more for you and your application.

 

Pseudoselectors in Styled Components

Up to this point, you’ve used pseudoselectors to ensure that the individual rows of the table were colored differently. You can also achieve this with styled components by using such selectors in the template string. The following listing contains the implementation of the TR component that takes care of the coloring of the table rows:

 

export const TR = styled.tr`

   &:nth-of-type(2n) {

      background-color: #ddd;

   }

`;

 

Instead of the nth-child selector, you use the nth-of-type selector in this example. The reason is that nth-of-type produces the same result as nth-child, but the former is the better variant for server-side rendering. For this reason, you should generally use this selector. If you use nth-child, you will receive a corresponding warning in the developer tools console of your browser advising you against using this selector.

 

To activate the new component, you need to import it into the BooksList component and replace the tr element in the body of the table with the TR styled component.

 

Dynamic Styling

You can also make your styled components dependent on props. This further increases the flexibility of your components and allows you to support multiple variants. To demonstrate a styled component that can be controlled by props, you allow your users to highlight an entry in the table by clicking on a row. The BooksList component then sets the highlight prop for the respective line, and the TR component takes care of setting the correct background color.

 

import { css } from '@emotion/react';

import styled from '@emotion/styled';

 

 

type TRProps = {

   highlight: boolean;

};

 

export const TR = styled.tr`

   &:nth-of-type(2n) {

       background-color: #ddd;

   }

   ${({ highlight }: TRProps) =>

       highlight &&

       css`

           &&& {

               background-color: yellow;

           }

       `}

`;

 

To support the highlight prop, you first define a type for the component props that contains the highlight property of Boolean type. Then you can define an arrow function in the template string of the styled component. By default, this arrow function receives the props you pass to the component. Depending on whether the highlight prop is set and contains the value true, you output another template string from the @emotion/react package using the css function. The &&& selector allows you to ensure that the subsequent background-color specification is given a higher priority than the nth-oftype selector; otherwise you won’t be able to highlight the rows colored by it.

 

Based on this customization you can integrate the new prop into the BooksList component. The corresponding source code is shown in this listing.

 

import React, { useState } from 'react';

import { Book } from './Book';

import { Table, TD, THead, TR } from './BooksList.styles';

 

const books: Book[] = […];

 

const BooksList: React.FC = () => {

   const [active, setActive] = useState<number | null>(null);

 

   return (

      <Table>

         <THead>

            <tr>

               <th>Title</th>

               <th>Author</th>

               <th>ISBN</th>

               <th>Rating</th>

            </tr>

         </THead>

         <tbody>

            {books.map((book) => {

               return (

                  <TR

                     key={book.id}

                     onClick={() => setActive(book.id)}

                     highlight={active === book.id}

                     >

                        <TD>{book.title}</TD>

                        <TD>{book.author}</TD>

                        <TD>{book.isbn}</TD>

                        <TD>{book.rating && ↩

                            <span>{'?'.repeat(book.rating)}</span>}</TD>

                   </TR>

              );

            })}

         </tbody>

      </Table>

   );

};

 

export default BooksList;

 

The changes to the BooksList component are limited to the state in which you store the information about which row is highlighted, and to the TR component itself. Here you define a click handler that marks the current row as the active row and the highlight prop that contains the value true or false depending on the currently active row.

 

Other Features of Styled Components

Styled components offer a comparatively large range of functions. For example, in addition to the features already described, it’s also possible to add additional styles to existing components. For this purpose, you can pass a component to the styled function and then define the additional styles via a template string. In this listing, you can see this with the example of a table cell:

 

const BaseTd = styled.td`

   padding: 5px 10px;

`;

 

export const TD = styled(BaseTd)`

   border: 1px solid black;

`;

 

In the example, you first define a BaseTd component that you create using styled.td. This component already contains a basic style with the padding specification. Then you call the styled function with the BaseTd component, and you can define more styles. This approach becomes particularly interesting when you use it to implement style inheritance.

 

As an alternative to the approaches presented so far, you can also include pure CSS solutions in your React application, such as Tailwind.

 

Editor’s note: This post has been adapted from a section of the book React: The Comprehensive Guide by Sebastian Springer.