A practical example of my web development process


This is a follow up post a previous post where I had posted about my web development process. That post was more around how I think about building things for the web.

This post will be demonstrating a practical example. I will be walking through how I approach building a list articles in a bento style layout. Here is the design we will be working with.

Step one: Initial look

Typically the design will come from a designer but in this case I also designed the component. So I will be pretending that I am working with another designer as I walk through this.

On my first look at the design, here is what I am noticing:

  • There are lots of gradients
  • There seems to be some kind of cursor that turns into Read the article, maybe this appears on hover?
  • The text isn’t accessible in some areas. Namely in the bottom two squares.
  • There is no mobile design

From these observations I already have a few questions:

  • Is there a mobile design?
  • Will the articles have hover states?
  • When should the Read the article custom cursor appear?
  • How can we make this design accessible?

Additionally I also have a question on whether or not there will be any animations. This comes from personal experience and knowing that a lot of bento layouts have some animations.

I send these question back to the designer and start a conversation. Personally I like to hop on a call in real time and work through it if possible.

After the conversation, we end up with something like this.

Revised design
Hover states
The mobile design

We have made the following changes:

  • The design is now WCAG AA accessible.
  • The gradients for the articles have been be made into the hover states.
  • Because we have hover states for the articles and extra animation, we decided to remove the cursor to not overload the user.
  • There is now a mobile design, where everything collapses into squares into one column.
  • We also will be adding a reveal animation on scroll when the bento layout is in view similar to this. The animation will be staggered so the boxes show first then the text will appear.

Step 2: Breaking the design apart

With the requirements clear, I start breaking down the design. I take about 15 minutes to quickly figure out what HTML elements to use. I’m fully aware that what I decide to do now might not make it to the final build so it’s more important for me to work quickly. To save time I only break down the most complex design but keeping in mind that it needs to cover all of the provided use cases.

So in this case I break down the basic desktop design but making sure that what I have takes the mobile design and hover state into consideration.

Here is the breakdown.

I also jot down any interesting notes or possible solutions I have in mind. I note the following:

  • A pseudo element might be needed for the gradient on hover
  • I’m not sure what the accessibility implications with having everything wrapped in an <a> tag
  • Not 100 hundred sure how I would do the animation yet, maybe css mask and transitions?
  • Should I use an img tag for the background or use the background-image property on the article

I leave these questions in the back of my head and will address them as I build.

Step 3: HTML

Now it’s finally time to start coding. I won’t be going into the details for the execution piece because I use a lot of different techniques and each one could be a blog post on it’s own. The final code will be available to view though.

Here is the HTML based off my earlier breakdown.

<section class="bento">
  <article class="bento__box bento__box_main">
    <h2 class="bento__headline">A new wave of sustainable footwear</h2>
    <p class="bento__text">Latest articles covering the trendesetters of the industry</p>
  </article>
  <article class="bento__box">
    <a>
      <h3 class="bento__headline">Carbon neutral Yeezys, yes pleezy.</h3>
      <p class="bento__text">Kanye coming back with shoes that are good for the planet. This is not his dark twisted fantasy.</p>
    </h3>
  </article>
  <article class="bento__box">
    <a>
      <h3 class="bento__headline">No studs required.</h3>
      <p class="bento__text">A revolution in sports shoes.</p>
    </a>
  </article>
  <article class="bento__box">
    <a>
      <h3 class="bento__headline">Recycled and remade.</h3>
      <p class="bento__text">Nothing but the re-newest in style and material.</p>
    </a>
  </article>
</section>

A few key things:

  • I’ve decided to use a background-image for the images instead of the <img> tag since they are purely decorative in this instance.
  • I’ve named my classes based using BEM.
    • I haven’t named the <a> tags because I’m not too sure if I need to style them. I only added class names to elements that I know I’ll style. But that doesn’t mean that I won’t go back to it later.

With that the HTML is now done.

Step 4: CSS

Now it’s time for my favourite part, CSS. But I don’t write in pure CSS, I use SCSS instead. I like to use SCSS to take advantage of the nesting features. I start by gathering all of the classes and setting them up in my SCSS file.

Following BEM, I have the classes organised by block then elements and finally modifiers at the bottom.

/* Block */
.bento {

}

/* Elements */
.bento__box {
  
}

.bento__headline {
  
}

.bento__text {
  
}

/* Modifiers */
.bento__box_main {
  
}

Mobile first

Because we have a mobile design I start mobile first. I start styling the elements using the Figma file as a guide and making sure everything matches.

Here is the HTML and SCSS once the mobile design is complete.

<section class="bento">
  <article class="bento__box bento__box_main">
    <h2 class="bento__headline">A new wave of sustainable footwear</h2>
    <p class="bento__text">Latest articles covering the trendesetters of the industry</p>
  </article>
  <article class="bento__box bento__box_one">
    <a class="bento__link">
      <h3 class="bento__headline">Carbon neutral Yeezys, yes pleezy.</h3>
      <p class="bento__text">Kanye coming back with shoes that are good for the planet. This is not his dark twisted fantasy.</p>
      </h3>
    </a>
  </article>
  <article class="bento__box bento__box_two">
    <a class="bento__link">
      <h3 class="bento__headline">No studs required.</h3>
      <p class="bento__text">A revolution in sports shoes.</p>
    </a>
  </article>
  <article class="bento__box bento__box_three">
    <a class="bento__link">
      <h3 class="bento__headline">Recycled and remade.</h3>
      <p class="bento__text">Nothing but the re-newest in style and material.</p>
    </a>
  </article>
</section>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");

.bento {
  font-family: Inter;
  display: grid;
  gap: 32px;
  padding: 32px;
}

.bento__box {
  width: 324px;
  height: 324px;
  border-radius: 1rem;
  color: #fff;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.bento__box::before {
  content: "";
  width: 100%;
  height: 100%;
  position: absolute;
  border-radius: 1rem;
  background: linear-gradient(
    180deg,
    rgba(37, 34, 34, 0.08) 28.13%,
    rgba(1, 1, 1, 0.8) 79.69%
  );
  z-index: 1;
}

.bento__link {
  z-index: 2;
}

.bento__headline {
  padding-left: 18px;
  padding-right: 18px;
  font-size: 1.5rem;
  font-weight: 700;
  margin: 0;
}

.bento__text {
  padding-left: 18px;
  padding-right: 18px;
  font-size: 1.125rem;
  font-weight: 400;
  margin-top: 8px;
}

.bento__box_main {
  background: linear-gradient(
    180deg,
    #01fc38 0%,
    rgba(26, 255, 76, 0.46) 54.17%,
    #ffe81a 100%
  );
  color: #000;
  justify-content: center;

  .bento__headline {
    color: #000;
    font-size: 2rem;
    font-weight: 700;
    line-height: 2rem; /* 100% */
  }

  .bento__text {
    color: #000;
    font-size: 1.25rem;
    font-weight: 400;
    line-height: 1.5rem; /* 120% */
    margin-top: 20px;
  }
}

.bento__box_main::before {
  background: none;
}

.bento__box_one {
  background-image: url("https://assets.codepen.io/10496725/Yeezys+on+wall.jpg");
  background-size: cover;
  background-position: bottom;
}

.bento__box_two {
  background-image: url("https://assets.codepen.io/10496725/Girl+with+white+sneakers.jpg");
}

.bento__box_three {
  background-image: url("https://assets.codepen.io/10496725/Girl+with+pink+sneakers.jpg");
}

Notice that I did have to add some extra classes to the HTML be able to style some things properly. This is what I mean when I say you never know for sure. It’s best to just start building and iterating from there.

Hover states

With the base mobile design finished, I start on the hover states. Here’s what I’ve added for that

.bento__box:hover {
  .bento__headline {
    text-decoration: underline;
  }
}

.bento__box_main:hover {
  .bento__headline {
    text-decoration: none;
  }
}

.bento__box_one:hover.bento__box_one::before {
  background: linear-gradient(180deg, #0085ff 0%, rgba(1, 9, 17, 0.25) 100%);
}

.bento__box_two:hover.bento__box_two::before {
  background: linear-gradient(
    180deg,
    rgba(6, 75, 21, 0) 0%,
    rgba(15, 156, 46, 0.38) 0.01%,
    rgba(0, 0, 0, 0.6) 100%
  );
}

.bento__box_three:hover.bento__box_three::before {
  background: linear-gradient(
    180deg,
    rgba(217, 217, 217, 0) 0%,
    rgba(206, 36, 36, 0.4) 0.01%,
    rgba(0, 0, 0, 0.6) 100%
  );
}

Making it responsive

Te hover states are done and now I need to make it responsive, meaning time to work on the desktop design. Here are the styles I added for the responsive code.

@media (min-width: 800px) {
  .bento {
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 1fr);
    grid-template-areas:
      "main main one"
      "two three one";
  }

  .bento__box {
    width: 100%;
    background-size: cover;
    background-position: center center;
    min-height: 420px;
  }

  .bento__headline {
    padding-left: 2rem;
    font-size: 2.25rem;
  }

  .bento__text {
    padding-left: 2rem;
    margin-top: 1rem;
    margin-bottom: 4rem;
    font-size: 1.125rem;
  }

  .bento__box_main {
    width: 100%;
    grid-area: main;

    .bento__headline {
      padding-left: 2rem;
      font-size: 4rem;
      line-height: 1; /* 100% */
      max-width: 45.625rem;
    }

    .bento__text {
      max-width: 31.25rem;
      padding-left: 2rem;
      font-size: 2.25rem;
      line-height: 1.3;
      margin-bottom: 0;
      margin-top: 1rem;
    }
  }

  .bento__box_one {
    justify-content: flex-start;
    grid-area: one;
    height: 100%;
    background-image: url("https://assets.codepen.io/10496725/Yeezys+on+wall.jpg");
    background-size: cover;
    .bento__headline {
      margin-top: 32px;
    }
  }

  .bento__box_one:before {
    background: linear-gradient(
      180deg,
      rgba(1, 1, 1, 0.8) 28.65%,
      rgba(37, 34, 34, 0.08) 100%
    );
  }

  .bento__box_two {
    grid-area: two;
  }

  .bento__box_three {
    grid-area: three;
  }
}

Here is what it looks like at this point

See the Pen Untitled by Paul Truong (@Paul-Truong-the-scripter) on CodePen.

This wraps up our CSS. And there’s one more step to go. I need to add the reveal animation.

Step 5: Javascript

By this point I have a nicely built component, it’s responsive and looks good but we can make it better. Now it’s time to add the reveal animation, which needs JavaScript. But before that I have a little confession.

I’ve never actually coded this kind of reveal animation before writing this blog post.

So I need to closely look at the example. I don’t want to just start copy and pasting things from the example for a few reasons:

  • The example is just a guide and isn’t exactly what I want.
  • The demo is a few years old and might not be doing things in the best way
  • I want to actually understand what I am building. Web development is a craft and web developers should be able to look at a problem, come up with a solution and understand what they are doing to solve that problem.

Looking at the example. My first thought is that maybe overflow:hidden or a CSS mask is required because we are hiding elements before sliding them into view. I have used overflow:hidden for something similar so I try that first, if it doesn’t work then I’ll look at another solution.

This means we probably need to wrap the revealing elements in a parent element in order to make use of overflow:hidden.

This is a little complicated so instead of trying to get it working for everything I focus on just getting it to work for the main box. The HTML for the bento boxes are all similar so if I can get it working for one then I should be able to get it working for the rest.

It took me a few tries but I was able to get something going. It required me to change the HTML.

Here is the old HTML from before:

<article class="bento__box bento__box_main">
    <h2 class="bento__headline">A new wave of sustainable footwear</h2>
    <p class="bento__text">Latest articles covering the trendesetters of the industry</p>
  </article>

And here is the new HTML.

<article class="bento-box-wrapper bento-box-wrapper_main">
    <div class="bento__box bento__box_main bento__box_reveal">
      <h2 class="bento__headline">
        <div class="reveal-wrapper">A new wave of sustainable footwear</div>
      </h2>
      <p class="bento__text">
        <span class="reveal-wrapper">Latest articles covering the trendesetters of the industry</span>
      </p>
    </div>
  </article>

I turned the <article> element into a wrapper and moved the bento box styles into a <div>. I did this so I could make use of overflow:hidden to mask our element as it is revealed. I also add wrappers to the text inside.

.bento-box-wrapper {
  border-radius: 1rem;
  overflow: hidden;
}

.bento__headline {
/**
Other styles
**/
  overflow: hidden;
}

.bento__text {
/**
Other styles
**/
  overflow: hidden;
}

From here I figured I could use keyframe animations with transform: translateY to move our elements into view.

I created separate classes to handle our animations, its important not to add too much functionality to one class. This also helps us out later as well, which you’ll see.

.reveal {
  animation: 1.5s 1 normal cubic-bezier(0.65, 0.05, 0.36, 1) reveal;
}

.reveal-wrapper {
  transform: translateY(100%);
  animation: 1s 1 normal cubic-bezier(0.65, 0.05, 0.36, 1) 0.5s reveal
    forwards;
  display: block;
}

@keyframes reveal {
  from {
    transform: translateY(100%);
  }

  to {
    transform: translateY(0%);
  }
}

This give us this.

See the Pen Untitled by Paul Truong (@Paul-Truong-the-scripter) on CodePen.

With the basic animation in place, I refactor the code to make the other boxes do the same.

See the Pen Untitled by Paul Truong (@Paul-Truong-the-scripter) on CodePen.

Wheres the JavaScript?

There isn’t any yet because it turns out I didn’t need JavaScript to get the animation running. There is a problem though.

The animation, as it is, will run on page load. We don’t want this since it’s very likely this component will be lower down on the page. Having the animation running on page load means the user might not even see it and get the warm and fuzzy feeling we want them to feel.

What we want to do is to have the animation run when the component comes into view. And for that, we need JavaScript

Enter: Intersection observer

Whenever I see a requirement for something to trigger when it comes into view, my first thought is to use intersection observer. And because I have the animation in a separate class, I’m thinking I can use intersection observer to add the animation class when the component is into view, triggering the animation.

So let’s do it.

As a first step I need to hide the boxes by default. We need to hide the boxes before we can reveal them. I do this by creating a new class to hide everything. My plan is to use an Intersection Observer to add the .reveal class which will overwrite the styles in the reveal_hidden class and trigger the animation.

.reveal_hidden {
  transform: translateY(100%);
  .reveal-wrapper {
    transform: translateY(100%);
  }
}

I also add a big placeholder element before the bento component so we can scroll and test the Intersection Observer.

Now we can start the JavaScript. I create an Intersection Observer which will observe the bento component. The reveal class will get added to each individual bento box when the bento component is scrolled into view.

const animateReveal = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const bento = entry.target;
      const boxes = bento.querySelectorAll(".js-bento__box");

      boxes.forEach((box) => {
        box.classList.add("reveal");
      });
    }
  });
};

let options = {
  rootMargin: "0px",
  threshold: 0.8
};

let observer = new IntersectionObserver(animateReveal, options);

const target = document.querySelector(".js-bento");

observer.observe(target);

The code above was what I got to as a first pass. With a little refactoring we can code it like this.

const observer = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const bento = entry.target;
        const boxes = bento.querySelectorAll(".js-bento__box");

        boxes.forEach((box) => {
          box.classList.add("reveal");
        });
      }
    });
  },
  {
    rootMargin: "0px",
    threshold: 0.8
  }
);

const target = document.querySelector(".js-bento");
observer.observe(target);

Making it work without JS and respecting users motion settings.

The bento component is almost done. There is just one more thing to do which is to make sure this code works for all users.

The markup is already semantic and accessible but we need to make sure it works without JS and to also respect the users motion settings.

Right now it’s hidden by default, so if JS fails to load then the animation will never run, meaning our articles will never show.

I decided to fix this by using JS to add the class reveal_hidden in the first place and remove it all from the HTML. If there’s no JS then the class isn’t added.

const bento = document.querySelector(".js-bento");
const boxes = bento.querySelectorAll(".js-bento__box");

boxes.forEach((box) => {
  box.classList.add("reveal_hidden");
});

That solves our problem with no JS. Now to respect the user settings. Some people don’t like all the animations, it can make them nauseous. Because we are good web developers, we need to respect that.

Once again, because we separated the animation, we can use the prefers-reduced-motion media query to stop the animations running. Here’s what it looks like.

@media (prefers-reduced-motion) {
  .reveal_hidden {
    transform: none;
    .reveal-wrapper {
      transform: none;
    }
  }

  .reveal {
    animation: none;

    .reveal-wrapper {
      animation: none;
    }
  }
}

We wrap the animation classes in the prefers-reduced-motion media query and remove all animations if the system has that set.

That’s a wrap

We’re done. At this point I will go through a polish up the code and make sure its in a good state. Once it’s polished up and I’m happy, I ship it. Here is what the finished product looks like. And here the Codepen with the full cleaned up code.

Need a web developer? Let’s chat