Published on

CSS in real world - Re-create Vercel Develop. Preview. Ship. with Tailwind CSS

11 minutes read - ––– views
Authors
Table of Contents

Intro

Earlier this week I tweeted about a Tailwind Play version of Vercel "Develop. Preview. Ship" Hero Header. Here is how it looks like on Vercel Homepage:

Seeing simple yet effective CSS like this is always fascinating to me. I eventually adapted the animation for this site's Hero. Let's discuss the technical details behind this piece as we re-create it using Tailwind.

Goal

Let's aim for the closest we can get to achieve this look. I will cover:

  • Typography
  • Gradient
  • Animation

Breakdown

From the Vercel homepage, we can see that the markup looks like this:

<h1 class="hero_title__2dqLj" aria-label="Develop. Preview. Ship.">
  <span
    class="animated-gradient-text_background__104Eo animated-gradient-text_background-1__2q1-A"
    style="--content:'Develop.';--padding:0.05em;--start-color:#007CF0;--end-color:#00DFD8"
  >
    ::before
    <span
      class="animated-gradient-text_foreground__2kjjY animated-gradient-text_foreground-1__3O_nT"
      >Develop.</span
    ></span
  ><span
    class="animated-gradient-text_background__104Eo animated-gradient-text_background-2__3r8da"
    style="--content:'Preview.';--padding:0.05em;--start-color:#7928CA;--end-color:#FF0080"
  >
    ::before
    <span
      class="animated-gradient-text_foreground__2kjjY animated-gradient-text_foreground-2__BYeW7"
      >Preview.</span
    ></span
  ><span
    class="animated-gradient-text_background__104Eo animated-gradient-text_background-3__3Bvxj"
    style="--content:'Ship.';--padding:0.05em;--start-color:#FF4D4D;--end-color:#F9CB28"
  >
    ::before
    <span
      class="animated-gradient-text_foreground__2kjjY animated-gradient-text_foreground-3__3Khgf"
      >Ship.</span
    ></span
  >
</h1>

Noted that each span element contains a descendant span and a pseudo-element. While the outer span contains information about gradient colors, the ::before pseudo-element is holding the black text (background), and the inner span is holding the gradient text (foreground). The background and foreground take turns to show on the screen, consecutively between the three words.

Implementation

Typography

  • Font choice: Vercel uses Inter font. (I skip this).
  • Font weight: extra-bold (800)
  • Font size: 23vw for default (mobile) and 10rem for desktop
  • Letter spacing: 0.06rem.

The last two do not default in Tailwind. Therefore, you need to customize it. Here are two ways you can customize utility classes:

  1. Use bracket
  2. Extend configuration

Let's extend letterSpacing configuration:

tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontSize: {
        '10xl': '10rem',
      },
      letterSpacing: {
        tightest: '-.06em',
      },
    }
  }
}

And create the markup using the customization:

index.html
<h1 class="py-14 text-[23vw] sm:text-10xl leading-none select-none tracking-tightest font-extrabold text-center">
  <span class="relative block">
    <span class="px-2">
      Develop.
    </span>
  </span>
  <span class="relative block">
    <span class="px-2">
      Preview.
    </span>
  </span>
  <span class="relative block">
    <span class="px-2">
      Ship.
    </span>
  </span>
</h1>

Here is how it should look like for now.

Gradient

To achieve the gradient background effect for the foreground text, we combine the Gradient Stops and Background Clip utilities. Via inspection, we can extract the colors and add them to the config:

tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontSize: {
        '10xl': '10rem',
      },
      letterSpacing: {
        tightest: '-.06em',
      },
      gradientColorStops: {
        'gradient-1-start': '#007CF0',
        'gradient-1-end': '#00DFD8',
        'gradient-2-start': '#7928CA',
        'gradient-2-end': '#FF0080',
        'gradient-3-start': '#FF4D4D',
        'gradient-3-end': '#F9CB28',
      },
    }
  }
}

Applying back these to the markup:

index.html
<h1 class="py-14 text-[23vw] sm:text-10xl leading-none select-none tracking-tightest font-extrabold text-center">
  <span class="relative block">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-r from-gradient-1-start to-gradient-1-end"> Develop.</span>
  </span>
  <span class="relative block">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-r from-gradient-2-start to-gradient-2-end"> Preview.</span>
  </span>
  <span class="relative block">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-r from-gradient-3-start to-gradient-3-end"> Ship.</span>
  </span>
</h1>

What is going on? For the foreground, we set the background color to gradient, using utilities from- and to-. Then, we use text-transparent and bg-clip-text to clip the text & achieve the gradient text effect.

Tip: Besides from- and to-, you can also use via- to add a middle color in your design.

We are making good progress! Now the text should have vivid gradient colors. Here is how it should look like.

Animation

This is the most exciting part. It should be clear now how the animations are designed. You can see the timeline for animation using Firefox inspector tools. Open the inspector window and check the Animation tab. Take the first span as an example. Here we can see background and foreground animations:

Background

Fig. 1.1 The foreground animation

Background

Fig. 1.2 The foreground animation

The figures show that the two animations overlapping each other (see the charts). Before the foreground appears, the background smoothly fades in. This keeps running infinitely, with an 8-sec interval.

::before pseudo element

Let's first style the pseudo-element:

index.html
<h1 class="py-14 text-[23vw] text-center sm:text-10xl leading-none select-none tracking-tightest font-extrabold">
  <span data-content="Develop." class="relative block before:content-[attr(data-content)] before:w-full before:z-0 before:block before:absolute before:top-0 before:bottom-0 before:left-0 before:text-center before:text-black">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-br from-gradient-1-start to-gradient-1-end"> Develop.</span>
  </span>
  <span data-content="Preview." class="relative block before:content-[attr(data-content)] before:w-full before:z-0 before:block before:absolute before:top-0 before:bottom-0 before:left-0 before:text-center before:text-black">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-br from-gradient-2-start to-gradient-2-end"> Preview.</span>
  </span>
  <span data-content="Ship." class="relative block before:content-[attr(data-content)] before:w-full before:z-0 before:block before:absolute before:top-0 before:bottom-0 before:left-0 before:text-center before:text-black">
    <span class="px-2 text-transparent bg-clip-text bg-gradient-to-br from-gradient-3-start to-gradient-3-end"> Ship.</span>
  </span>
</h1>
  • We want the pseudo-element to be absolute positioned within the outer span.
  • Use data content and before:content-[attr(data-content)] to fill content inside pseudo-elements.
  • You should see the display is all black now, as the pseudo-element is overlaying the descendantspan. This is expected.

Animations

Animation utilities in Tailwind CSS are enabled by default. To write custom animation, go to tailwind.config.js. We need to extend keyframes & animation. The value are extracted from Vercel:

tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontSize: {
        '10xl': '10rem',
      },
      letterSpacing: {
        tightest: '-.06em',
      },
      gradientColorStops: {
        'gradient-1-start': '#007CF0',
        'gradient-1-end': '#00DFD8',
        'gradient-2-start': '#7928CA',
        'gradient-2-end': '#FF0080',
        'gradient-3-start': '#FF4D4D',
        'gradient-3-end': '#F9CB28',
      },
      keyframes: {
        'gradient-foreground-1': {
          'from, 16.667%, to': {
            opacity: 1,
          },
          '33.333%, 83.333%': {
            opacity: 0,
          },
        },
        'gradient-background-1': {
          'from, 16.667%, to': {
            opacity: 0,
          },
          '25%, 91.667%': {
            opacity: 1,
          },
        },
        'gradient-foreground-2': {
          'from, to': {
            opacity: 0,
          },
          '33.333%, 50%': {
            opacity: 1,
          },
          '16.667%, 66.667%': {
            opacity: 0,
          },
        },
        'gradient-background-2': {
          'from, to': {
            opacity: 1,
          },
          '33.333%, 50%': {
            opacity: 0,
          },
          '25%, 58.333%': {
            opacity: 1,
          },
        },
        'gradient-foreground-3': {
          'from, 50%, to': {
            opacity: 0,
          },
          '66.667%, 83.333%': {
            opacity: 1,
          },
        },
        'gradient-background-3': {
          'from, 58.333%, 91.667%, to': {
            opacity: 1,
          },
          '66.667%, 83.333%': {
            opacity: 0,
          },
        },
      },
      animation: {
        'gradient-background-1': 'gradient-background-1 8s infinite',
        'gradient-foreground-1': 'gradient-foreground-1 8s infinite',
        'gradient-background-2': 'gradient-background-2 8s infinite',
        'gradient-foreground-2': 'gradient-foreground-2 8s infinite',
        'gradient-background-3': 'gradient-background-3 8s infinite',
        'gradient-foreground-3': 'gradient-foreground-3 8s infinite',
      }
    }
  }
}

It's time to apply the class back to our markup. Here is how it should look like at the end. Spoiler alert: The classnames are long 😆

Conclusion

You can see the source code of this site Hero to see how I implemented it. Hope you enjoyed the read.