This content originally appeared on DEV Community and was authored by Fabian Frank Werner
The most viral card component right now is the one used by Evervault on their customers’ page. And in this beginner-friendly tutorial, we’ll fully rebuild it in CSS. Along the way you’ll learn to recreate this hover effect, reveal encrypted text, and display a mixed gradient using industry best practices in CSS, HTML, and JavaScript. Regarding CSS, you’ll learn about variables, aspect ratio, mix-blend-mode, mask-image, pointer-events, pseudo-elements, responsive variables in media queries, and the scale property; in a nutshell, everything you need to flex on your resume!
To follow along with this project, open up your editor (I’m using Cursor for this one, so come fight me if you don’t like this choice), and install the Live Preview extension, then pull up a terminal session! First things first, we want to set up and link our project files. For this, I run touch index.html index.css index.js
in my terminal, followed by code index.html
to create our three files in the current directory and then open the HTML one. Next, I close the terminal and type an exclamation mark followed by enter to make use of the emmet abbreviation for generating the HTML5 boilerplate. I’m doing this to feel like a 10x HTML developer… Now, I add two links: one to the external CSS file named index.css
for styling, and another one to the external JavaScript file named index.js
. This way, the JavaScript file will be loaded after the HTML is parsed due to the defer
attribute. This prevents errors! For example, if the script tries to access HTML elements that haven’t been loaded yet. Every once in a while, I press Command
(or Control
, if you’re parents are not rich) in combination with the S
key. For most people, this will save their file; for me, it will format it using another extension called Prettier, because I love being special…
Before we go ahead with creating our HTML skeleton, let’s take a second to figure out our live server and get it up and running! Let’s type out our beloved “Hello, World!” in an h1
element and give the “Show Preview” button in the top right corner a press. This preview window will update on every file change, which will come in handy once we get into the CSS in a bit. But for now, let’s add a div
with class track
. It gets a child div
with class wrapper
, which then itself gets a child div
with class card
. Our card will itself have different elements like an image, gradient, letters, and corners. But how do we keep getting away with consistent classes? Well, a rather famous computer science quote from Phil Karlton goes like this: “There are only two hard problems in Computer Science: cache invalidation and naming things.” And because of that, we need to make use of a methodology smarter people than I invented to come up with consistent class naming. It’s called BEM and stands for Block Element Modifier. A block encapsulates a standalone entity that is meaningful on its own. In our case, it’s the div
with the class of card
. Elements are parts of a block and have no standalone meaning. In our case, it’s the image, gradient, letters, and corners elements that we need to nest within our card div
. Therefore, we need to apply a modifier that itself is just a flag on a block or element. So, to our image, gradient, letters, and corners elements, we append the block name card
followed by two dashes. This signals belonging of other elements to our card block using modifiers. BEM, that’s it. And before you go nuts in the comments: yes, I know that the corners div is not nested within the card. And yes, I know that it still gets applied the modifier signaling belonging, but I’m just a nobody online, so what do you need to care anyway? Moving on, said corners div
gets four children, because our card is a square and, as of my recollection, this shape has four corners. Last but not least, since we defined our div
with the card--image
class, let’s give it an img
element referencing an image of our choice. You can pick whatever picture you want here. My mom recommends a funny picture of a cat, yet I’ll go with the Evervault icon in an SVG format since these are the guys I’m ripping off their creativity, so it’s only fair if they get something in return.
Now, enough yapping about Hyper Text Markup Language. Next up is my favorite hipster garbage toy programming language, JavaScript, which we’ll use for creating an interactive effect on the card
div
, which after that we will beautifully style in CSS. Let’s see if I’m smart enough or end up disappointing my parents even more than when I told them I won’t move out of their basement, because look: “I’m a programmer now!” We define a string called chars
containing all lowercase letters, uppercase letters, and digits 0-9, because this will soon be used to generate random strings. Before that, though, we define a function randomChar
that returns a random character from the chars
string. Math.random()
generates a number between 0 (inclusive) and 1 (exclusive). Exclusive as in only cool people allowed, just like my subscriber base! Then we multiply it by chars.length
which gives us a number between 0 and the length of chars
. Math.floor
rounds down to the nearest integer, giving a valid index for the string. Now we define a function randomString
that returns a string of random characters of the specified length
. Array.from(Array(length))
creates an array with length
undefined elements. .map(randomChar)
replaces each element with a random character and .join("")
combines the array of characters into a single string. Yes, this is copied from Stack Overflow. Now we select the first element in the DOM with the class card
and assign it to the variable card
. And we also select the first child element of card
with the class card--letters
and assign it to the variable letters
. Now you can finally prove that your attention span is longer than that of a goldfish by understanding this next function. handleOnMove
takes an event e
(which can be either mouse or touch). card.getBoundingClientRect()
gets the position and size of the card
element relative to the viewport. x
and y
calculate the mouse or touch position relative to the top-left corner of the card. letters.style.setProperty("--x", ...)
and setProperty("--y", ...)
set custom CSS variables -x
and -y
on the letters
element, which can be used in CSS for effects that we will touch on in a bit. letters.innerText = randomString(2000);
sets the text content of letters
to a new random string of 2000 characters every time the event fires. Last but not least, we add an event listener to the card
element that calls handleOnMove
whenever the mouse moves over the card and then add another event listener to the card
element that calls handleOnMove
with the first touch point whenever a touch movement occurs. Is this memory efficient? No. Does it look cool? Yes! So what do I care?
But to fully let the shit hit the fan now, we throw in a little cascading style sheets. So if that’s not worth a follow, well, I guess it’s not worth a follow… Beginning with the pseudo-class selector :root
that targets the highest-level element in the DOM, which is the <html>
element, we define some variables here, making them available everywhere. -background-rgb: 2 6 23;
defines a custom property for RGB color values. Notice it’s not wrapped in rgb()
, just the numbers. This allows you to reuse the numbers in different contexts. -border: 1px solid rgb(var(--border-rgb) / 20%);
uses another variable inside an rgb()
function, and / 20%
sets the alpha (opacity) to 20%. This is a newer CSS color syntax that these “actually” guys call “RGB with alpha slash notation”. -logo-size: calc(var(--card-size) * 0.3);
uses the calc()
function to compute a value based on another variable. This is powerful for responsive design and sounds cool when said out loud. For styling the body, we use background: rgb(var(--background-rgb));
that uses the custom property for the background color, height: 100vh;
makes the body as tall as the viewport, display: flex; justify-content: center;
centers its children horizontally, overflow: hidden;
prevents scrollbars if content overflows, and font-family: "Noto Sans", sans-serif;
sets the font if you didn’t already conclude Sherlock. The universal selector is not a cult but a thing in CSS that targets all elements. We use it with box-sizing: border-box;
to change the box model so that padding and border are included in the element’s width and height, which is more intuitive for layout. And we remove the default spacing by setting margin: 0; padding: 0;
. We’ve got .track
and .wrapper
for our layout classes. .track
is a flex container, vertically centering its children, that also has width: var(--card-size);
for consistent sizing. .wrapper
is a full-width container, positioned relative for absolutely positioned children. .card
is a flex container, centered both ways. aspect-ratio: 1;
ensures the card is always square so that its width equals its height. This is a newer CSS property that you can use to flex offline or on YouTube like I’m doing right now… border-radius: 2rem;
rounds the corners. overflow: hidden;
clips children that overflow the card’s bounds and cursor: pointer;
shows a hand cursor on hover. .card--image
is a flex container for the image, with a high z-index
so it appears above other elements. The direct child img
uses the variable for sizing. Now, let me cook! .card--gradient
is a full-size overlay with a radial gradient background. radial-gradient(...)
creates a circular color transition. mix-blend-mode: darken;
blends this layer with the layers below, darkening the result. This is an advanced compositing technique for cool kids. pointer-events: none;
makes this layer ignore mouse events, so you can click through it. z-index: 3;
ensures it’s layered below the image. .card--letters
is absolutely positioned to cover the card. opacity: 0;
hides it by default. transition: opacity 400ms;
animates the opacity change. mask-image: radial-gradient(...)
is another advanced property: it uses a gradient as a mask, so only part of the text is visible, creating a spotlight effect. The mask’s center is controlled by -x
and -y
, which we set in JavaScript earlier, concerning the mouse position. scale: 1.03;
slightly enlarges the letters, and on hover, opacity: 1;
fades the letters in. Did I cook? Let me know in the comments! Now, this media query applies styles for screens ≤600px wide, by reducing the card size and border radius for better fit on mobiles. Just because I can, let’s add some decorative borders… These pseudo-elements create vertical lines on the left and right of .track
. translate: 0% -50%;
vertically centers the lines and border-left
and border-right
use the custom border style. And this stuff creates horizontal lines at the top and bottom of .wrapper
. Now, .card--corners
is an overlay for decorative corners. Each .card--corner
is a vertical line, and its :after
pseudo-element is a horizontal line, together forming an “+” shape. The nth-child
selectors position each corner in a different spot.
That’s pretty much everything you need to know to flex your frontend dev skills on your resume. Congratulations! If you found this post helpful, make sure to check out my YouTube channel for the video version of this post.
This content originally appeared on DEV Community and was authored by Fabian Frank Werner