How to build UI elements using CSS pseudo elements
Tags: css, react, design, tutorial
Published: 2021-12-29 14:25:47

## Introduction
These days there are a lot of designs that intuitively display information. Instead of plain old one-to-one mapping of fields in a database, we're moving towards a more human-friendly and easy-to-understand UI element. For example, icons, loaders, badges, and progress indicators.

Being front-end developers, it's our responsibility to bring such UI to life using code(or magic 🪄).

An example of such a UI element is a simple status indicator that shows how many steps have been completed in a multi-step process. Because of its visual nature, it conveys this information in an instant look.

![Image of multi-step status indicator UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96cm644mjgzcepuawaa1.png)

The problem arises when we use a bunch of `<div>`s and `<span>`s to build such UI. It gets complicated, unreadable, and hard to maintain very quickly.
In this article, we will see how we can build such UI using CSS pseudo-elements and minimising the need for `<div>`s (or `<span>`s).

## Tools Used

I'm using [React](https://reactjs.org/) for making the UI element dynamic so that we can easily change the status of a step from _pending_ to _complete_.
Also using the [emotion](https://emotion.sh/) library for writing css styles with JavaScript because it's efficient and fun! We can achieve the same result using CSS (SCSS, SASS).

Here is the [CodeSandbox](https://codesandbox.io/s/ui-elements-using-css-pseudo-elements-7ygv1?file=/src/Circle.js) link to the final output. Let's get started.

## Building the UI

We will build this UI component in a few steps. That way, it is easier to follow and recall a step later. So without further ado, let's go!

### First Step

```jsx
import styled from "@emotion/styled";
import checkmarkImage from "path-to-file/file-name.svg";

const Circle = styled.div`
/* We're using CSS variables here. */
  --primaryColor: #00ccb0;
  --secondaryColor: #e1e1e1;
  --scale: 2;
  --size: calc(16px * var(--scale));

  border-radius: 50%;
  position: relative;
  width: var(--size);
  height: var(--size);
  box-sizing: border-box;
  background-color: ${(props) =>
    props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
  margin-right: var(--size);
`;

export default Circle;
```

- First, we import `styled` from the `emotion` library and an image that we will use in a moment.
- Then, we create a styled component named `Circle` and add a few CSS rules that make it a nice circle.

Let's decode this cryptic looking line:
```jsx
 background-color: ${(props) =>
    props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
```
Here we are using [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) syntax to dynamically assign the value of `background-color` based on the `active` prop which will be passed by the parent component.

At this point, if we wrap a couple of this components in a box, we will have a few nice circles:

```jsx
(...)
   <Circle active={true} />
   <Circle active={false} />
   <Circle active={false} />
(...)
```

![Few nice circles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pzc7iju3xgfbp90jbva.png)

### Connect the Dots :)

Let's go ahead and connect the dots(pun intended) by creating the link between these circles.

We use the `::after` pseudo-element for this as shown below:

```jsx
const Circle = styled.div`
  --primaryColor: #00ccb0;
  --secondaryColor: #e1e1e1;
  --scale: 2;
  --size: calc(16px * var(--scale));
  
  --linkWidth: calc(10px * var(--scale));
  --linkHeight: calc(2px * var(--scale));

  border-radius: 50%;
  position: relative;
  width: var(--size);
  height: var(--size);
  box-sizing: border-box;
  background-color: ${(props) =>
    props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
  margin-right: var(--size);

  /* Make a pill shaped element that will act as link between two circles. */
  &::after {
    content: "";
    width: var(--linkWidth);
    height: var(--linkHeight);
    border-radius: 100px;

    position: absolute;
    left: calc(var(--size) + ((var(--size) - var(--linkWidth)) / 2));
    top: calc((var(--size) - var(--linkHeight)) / 2);
    background-color: ${(props) =>
      props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
  }
`;
```

Let's understand the code:
- First, make a rectangle with rounded borders to give it a pill-like shape using `width`, `height`, and `border-radius` properties.

- Then, align it centrally relative to the circle using `top` and `left` properties.

> Note: We use `calc` function to figure out values for `top` and `left` properties based on the dimension of the `Circle` and `Link` so that changing scale won't affect the alignment.

With that change in place our UI look as follow:
![circles with link between them](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z64h8u1zstd8leohn2u8.png)

### Remove Extras

Nice job! But, there is also a line at the end of the last circle that we don't need. So, let's remove it real quick with the following change:

```jsx
const Circle = styled.div`
  --primaryColor: #00ccb0;
  --secondaryColor: #e1e1e1;
  --scale: 2;
  --size: calc(16px * var(--scale));

  --linkWidth: calc(10px * var(--scale));
  --linkHeight: calc(2px * var(--scale));

  border-radius: 50%;
  position: relative;
  width: var(--size);
  height: var(--size);
  box-sizing: border-box;
  background-color: ${(props) =>
    props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
  margin-right: var(--size);

  /* Make a pill shaped element that will act as link between two circles. */
  &::after {
    content: "";
    position: absolute;
    width: var(--linkWidth);
    height: var(--linkHeight);
    left: calc(var(--size) + ((var(--size) - var(--linkWidth)) / 2));
    top: calc((var(--size) - var(--linkHeight)) / 2);
    background-color: ${(props) =>
      props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
    border-radius: 100px;
  }

  /* We don't want to show the link after the last element. */

  &:last-child {
    &::after {
      display: none;
    }
  }
`;
```
Now, that looks better:
![circles without extra line](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vp8ipflp4qeo85el2yd4.png)
 
### Final Step

The last missing piece in this UI is the checkmark icon which renders when the step is active.
We use `::before` pseudo-element to create it as shown below:

```jsx
const Circle = styled.div`
  --primaryColor: #00ccb0;
  --secondaryColor: #e1e1e1;
  --scale: 2;
  --size: calc(16px * var(--scale));

  --linkWidth: calc(10px * var(--scale));
  --linkHeight: calc(2px * var(--scale));

  --checkmarkWidth: calc(9px * var(--scale));
  --checkmarkHeight: calc(7px * var(--scale));

  border-radius: 50%;
  position: relative;
  width: var(--size);
  height: var(--size);
  box-sizing: border-box;
  background-color: ${(props) =>
    props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
  margin-right: var(--size);

  /* Center svg (checkmark in this case). */
  &::before {
    content: "";
    display: ${(props) => (props.active ? "block" : "none")};
    position: absolute;
    top: calc((var(--size) - var(--checkmarkHeight)) / 2);
    left: calc((var(--size) - var(--checkmarkWidth)) / 2);
    width: var(--checkmarkWidth);
    height: var(--checkmarkHeight);
    background-image: url(${checkmarkImage});
  }

  /* Make a pill shaped element that will act as link between two circles. */
  &::after {
    content: "";
    position: absolute;
    width: var(--linkWidth);
    height: var(--linkHeight);
    left: calc(var(--size) + ((var(--size) - var(--linkWidth)) / 2));
    top: calc((var(--size) - var(--linkHeight)) / 2);
    background-color: ${(props) =>
      props.active ? "var(--primaryColor)" : "var(--secondaryColor)"};
    border-radius: 100px;
  }

  /* We don't want to show the link after the last element. */

  &:last-child {
    &::after {
      display: none;
    }
  }
`;
```
Voila! Nice and clean:

![Final UI element](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdwgl0wodl6tr9paf2k9.png)

## Conclusion
We can build many UI elements using this approach. And,
that way, we eliminate the need for extra HTML elements such as `<div>`.

I hope you find this article interesting and had fun reading it because I for sure had fun writing it. 
If you find it helpful, please give it a like and share it with someone who might benefit from it. 

My name is Ashutosh, and apart from working as a Full-stack engineer, I love to share my learnings with the community.
You can connect with me on [LinkedIn](https://www.linkedin.com/in/iamashutoshbhardwaj/) or follow me on [Twitter](https://twitter.com/BhardwajAshu96).

If you prefer video format please do check out my YouTube video:

{% youtube Kz8dc4UPmZY %}