No Time Dad

A blog about web development written by a busy dad

Responsive Email Newsletter Section

Full demo of the responsive newsletter email section can be viewed here. Try changing the screen size to see how it responds.

Intro

The funny thing about me building a newsletter section is that I actually do need one of these. Most of the time I build things for fun or because I think they’ll be an interesting challenge. This example is just the client side implementation of an email newsletter form. I’d still need to build the server side portion to save the email somewhere or send it off to one of the services that help people with newsletter related things.

I really wanted to use css grid to build this email section component. In fact, I somehow had it in my head that I had to use grid. Possibly because I have been using it a lot lately to build various other components. My idea was that the call to action text would be on the left in one column, and the input form would be on the right in a second column. And I think this would work, but I quickly realized I was doing too much work.

What I started with for small screens was a single column grid by using the css below.

.Container {
  display: grid;
  grid-template-columns: 1fr;
}

This css is fine and completely valid. It would give me the single column I was looking for. My “brilliant” epiphany was that I already had a single column because that’s how html and browsers work. Your content is in a single column on a page until you tell the browser otherwise.

The next step would’ve been to switch to grid-template-columns: 1fr 1fr; on larger screens, which would give me two columns. I could’ve skipped the redundant single column grid initialization above and instead just added grid-template-columns: 1fr 1fr; in a media query for large screens, but I eventually decided that just using flexbox and switching between a column and a row would be less work. It would also make alignment easier.

Building the component

The responsive newsletter email component is comprised of four main parts. A container that creates some space between the component and the edge of the page, an inner container for the content, a call to action text section which explains what the newsletter is, and the input form and submit button for signing up.

<div class="Container">
  <div class="Email__Container">
    <div class="Email__CTA_Text">...</div>
    <form>...</form>
  </div>
</div>

It’s very helpful to think about layouts in terms of boxes. I usually try to build boxes first, then go back and fill in the boxes with my elements. Below is how the component should look on small screens using boxes and the corresponding code.

<div class="Container">
  <div class="Email__Container">
    <div class="Email__CTA_Text">CTA Text</div>
    <form>Email Form</form>
  </div>
</div>
.Container {
  padding: 1rem;
}

.Email__Container {
  color: white;
  border-radius: 0.5rem;
  padding: 1rem;
  background-color: #4b6cb7;
}

.Email__CTA_Text {
  border: 2px solid lightcoral;
  padding: 1rem;
  margin-bottom: 1rem;
}

form {
  border: 2px solid lightgreen;
  padding: 1rem;
}

On larger screens, I wanted the call to action text and the email form both on in a row. Flexbox will take care of this for me. I’ll need to add a min-width media query to handle this change since the elements were previously in a column. The content will expand the boxes as needed.

I decided that 768px (tablet) is probably the best width at which to change the layout from a column to a row. It’s sort of an awkward size to design for, though. If I left the layout as a column, the input form is stretched too far. But 768px is almost too small for the row. I think it looks a little squished as-is on 768px, but better than super stretched.

...
@media (min-width: 768px) {
  .Email__Container {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
}

A few quirks I ran into along the way with flexbox was that the input form kept crashing into the call to action text on larger screens. I fixed that (by sheer luck) after randomly adding justify-content: space-between;. I was relieved that I didn’t have to do some strange spacing hack to resolve that issue.

I also spent a some time wrangling with the inline form and sign up button. Basically, the input form kept squishing the button. It really seemed like the input wanted to push the button out of the parent container. This issue had me stumped for a while, but after a lot of googling I found a solution to this problem by applying flex: 1; on the input element which tells it to only take up the available space.

I’ve really been trying to take accessibility more seriously lately. Contantly auditing my pages with Chrome’s lighthouse audit report that is build into their browser. It has been a little eye opening if I’m being honest. Even looking at some popular sites online I see colors being used that don’t have enough contrast, or forms elements that lack visible/hidden labels or aria attributes.

I get it, not making my site accessible is easier. And making my colors accessible will likely screw up the perfect palette I picked out. But, I just feel like the alternative is so much worse. Everyone should be able to easily browse my sites. Most of my color choices in the example were made with accessibility in mind. I still have a lot to learn, but my accessibility audit score was in the upper 90s for this element so I must be doing something right in that regard.

Final code

Another React component, but I am not using state or any other React/JavaScript internals here. The code can be translated to vanilla html without too much trouble.

import React from 'react';
import * as styles from './index.module.css';

const EmailSection = () => (
  <div className={styles.Container}>
    <div className={styles.Email__Container}>
      <div className={styles.Email__CTA_Text}>
        <div className={styles.CTA_Text__Large}>
          Join our newsletter
        </div>
        <div className={styles.CTA_Text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </div>
      </div>
      <form>
        <div className={styles.Email__Form_Group}>
          <input className={styles.Input} type="text" name="email" id="email" placeholder="Email" aria-label="Email" />
          <button type="button" className={`${styles.Button}`}>Sign up</button>
        </div>
      </form>
    </div>
  </div>
);
.Container {
  padding: 1rem;
}

.Email__Container {
  color: white;
  border-radius: 0.5rem;
  padding: 1rem;
  background-color: #4b6cb7;
}

.Email__CTA_Text {
  padding-bottom: 1rem;
}

.CTA_Text__Large {
  font-size: 32px;
  font-weight: 700;
}

.CTA_Text {
  font-size: 18px;
  opacity: 0.9;
}

.Input {
  width: 100%;
  overflow: visible;
  font-size: 20px;
  border-radius: 0.5rem;
  padding-bottom: 0.75rem;
  padding-left: 1rem;
  padding-right: 1rem;
  padding-top: 0.75rem;
  border: 2px solid lightblue;
}

.Input:focus {
  outline: none;
  box-shadow: 0 0 0 0.2rem rgb(173, 216, 230, 0.5);
}

.Button {
  background-color: rgb(45, 106, 79);
  border-color: #40916c;
  border-width: 1px;
  color: white;
  font-size: 18px;
  font-weight: 600;
  border-radius: 0.5rem;
  padding: 1rem 1.5rem;
}

.Button:hover {
  background-color: rgb(45, 106, 79, 0.8)
}

.Button:focus {
  outline: none;
  border-color: #d8f3dc;
  box-shadow: 0 0 0 0.125em #d8f3dc;
}

.Input + .Button {
  margin-top: 1rem;
}

@media (min-width: 425px) {
  .Email__Form_Group {
    display: flex;
    align-items: center;
  }

  .Input {
    flex: 1;
  }

  .Input + .Button {
    margin-top: 0;
    margin-left: 0.5rem;
  }
}

@media (min-width: 768px) {
  .Email__Container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 2.5rem;
  }
}