No Time Dad

A blog about web development written by a busy dad

Contact Form Using CSS Grid and Flexbox

A well crafted contact form is hard to beat. It can draw the user in and really encourage them to send a message. A bad contact form can deter a user from wanting to reach out. If the form is sloppy and not put together well, how can the user feel confident that their message will even reach its destination?

Admittedly, I rarely fill out contact forms. Mostly for reasons unrelated to the design of the form itself, and more for the fact that I don’t want to give out my email to yet another system. But, if I saw a contact form that was well designed and really beckoned me to fill it out, I might just be tempted to do so.

The irony of this post is that I currently don’t have a contact form on this blog, or really any information on the blog about how to get in touch me. I think my blog is still in the “shouting into the ether” stage and it may remain there for a while. Maybe one day I’ll get to the point where a reader will want to talk to me and tell me about the things I’m doing wrong in my examples. If only I could be so lucky.

The contact form I’ll be working on for this post is going to use css grid for the structure of the form itself. Specifically, it’ll have two columns and four rows. I’ll also be using flexbox to help with label alignment. Since cards are all the rage these days, we’ll contain the contact form in one too. You can view a demo of the final contact form here

Building the card

Before I start implementing the grid, I’ll create the card that’ll hold the contact form fields themselves. I don’t think there’s a right or wrong way to create a card, but I tend to like little to no box-shadow on my cards, and instead just a subtle border to give the card its shape.

.Contact__Card {
  width: 600px;
  height: 500px;
  border: 1px solid lightgray;
  border-radius: 1rem;
  padding: 2rem;
}
<div class="Contact__Card"></div>

Visualizing the layout

Before I add any input fields or buttons to any component I’m building, I always try to visualize the layout first. For me, visualizing the layout means creating colored boxes in html and css so I can see where the elements are going and how they’ll behave on different sized screens. This is especially helpful for grid layouts.

CSS grid and flexbox are often difficult for me to model in my head, and having the elements on the page that I can look at is helpful. Generally, I’ll represent the various elements of a larger component as divs with the border property applied to them. Once I feel more confident about the layuout and how it behaves, I’ll go back and replace the divs with the actual elements, input fields in this case.

So, where would I even start with this layout? The elements I eventually want to use are first name, last name, email, phone number (optional), message body, and a submit button. Since I know I want two columns, so first name and last name would be aligned next to each other, I’ll initialize a two column grid. I’d then put all of my element placeholders in the parent Contact__Card div.

<form>
  <div class="Contact__Card">
    <div class="Input__Container">First name</div>
    <div class="Input__Container">Last name</div>
    <div class="Input__Container">Email</div>
    <div class="Input__Container">Phone</div>
    <div class="Text__Container">Message</div>
    <div class="Button__Container">Submit button</div>
  </div>
</form>
.Contact__Card {
  width: 600px;
  height: 500px;
  border: 1px solid lightgray;
  border-radius: 1rem;
  padding: 2rem;

  /* Initialize grid */
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.Input__Container {
  /* Init flexbox to help with alignement */
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 1px solid red;
}

.Text__Container {
  border: 1px solid blue;
}

.Button__Container {
  border: 1px solid orange;
}
First name
Last name
Email
Phone
Message
Submit button

That is pretty close to what I’m looking for, but I mentioned at the start that this would be a two column and four row grid. The reason for having four rows is that I think textarea fields look better when they have more space. Putting it in it’s own row that spans both columns makes sense.

The fourth and final row will contain the submit button. It doesn’t have to span both columns but I like having the button on the right and I’ll need to span the width of both columns to get it all the way over there.

<form>
  <div class="Contact__Card">
    <div class="Input__Container">First name</div>
    <div class="Input__Container">Last name</div>
    <div class="Input__Container">Email</div>
    <div class="Input__Container">Phone</div>
    <div class="Text__Container">Message</div>
    <div class="Button__Container">Submit button</div>
  </div>
</form>
.Contact__Card {
  width: 600px;
  height: 500px;
  border: 1px solid lightgray;
  border-radius: 1rem;
  padding: 2rem;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;

  /* Create four rows.
   The first two and the last will fill the available space, 
   while the 3rd row (message) is set at 150px */
  grid-template-rows: 1fr 1fr 150px 1fr;
}

.Input__Container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 1px solid red;
}

.Text__Container {
  border: 1px solid blue;
  /* Span both columns */
  grid-column: span 2;
}

.Button__Container {
  border: 1px solid orange;
  /* Span both columns */
  grid-column: span 2;
}
First name
Last name
Email
Phone
Message
Submit button

Adding the form elements

Now that I have the grid structured how I want, I can start adding the form elements. I’ll likely have to do some small spacing changes, but css grid has done the bulk of the work for me already. I’ll use flexbox to help with some of the alignment throughout the form.

In a previous post I detailed a baseline input field, which I’ll be using for this contact form (with some small changes). Since four of the five form elements are inputs, I’ll start with one, see how it looks, and reuse the selector for the others if it looks good.

<form>
  <div class="Contact__Card">
    <div class="Input__Container">
      <label for="first-name" class="Label">First Name</label>
      <input
        name="first-name"
        class="Input"
        type="text"
        placeholder="Jessica"
        required
      />
    </div>
    <div class="Input__Container">Last name</div>
    <div class="Input__Container">Email</div>
    <div class="Input__Container">Phone</div>
    <div class="Text__Container">Message</div>
    <div class="Button__Container">Submit button</div>
  </div>
</form>
... .Input {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
  overflow: visible;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  padding-top: 0.5rem;
  border: 2px solid lightblue;
  border-radius: 0.5rem;
  width: 100%;
}

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

.Label {
  padding-bottom: 0.25rem;
  font-weight: 600;
}
Last name
Email
Phone
Message
Submit button

I think that looks good so far, so I’ll update the remaining input fields with the same Input selectors and remove the border.

Message
Submit button

But, I still need to implement the last two rows. Conveniently, I can re-use the Input selector for the textarea. For the button row, I’ll use a flexbox container to center the button and push it to the right. I’ll be using a modified version of the baseline button.

It’s worth mentioning that I’ve been explicitly declaring class names for elements like input and label on purpose. I do this out of habit because I’ve been bitten in the past by elements being styled directly and I’ve vowed not to make that mistake again. It’s personal preference, of course, but I prefer to be more explicit in my element selector naming.

Conclusion, demo, & final code

And now I have a clean and inviting version of a contact form that I can use on my blog. Sometimes css grid seems a little too magical to me, especially since I went out of my way to avoid using it for so long. Once I wrapped my head around it I think my layouts drastically improved. The same can also be said for flexbox. These turned out to be highly valuable features to learn. The value here, for me, is time saved.

Demo
Final code
<form>
  <div class="Contact__Card">
    <div class="Input__Container">
      <label for="first-name" class="Label">First Name</label>
      <input
        name="first-name"
        class="Input"
        type="text"
        placeholder="Jessica"
        required
      />
    </div>
    <div class="Input__Container">
      <label for="last-name" class="Label">Last Name</label>
      <input
        name="last-name"
        class="Input"
        type="text"
        placeholder="Smith"
        required
      />
    </div>
    <div class="Input__Container">
      <label for="email" class="Label">Email</label>
      <input
        name="email"
        class="Input"
        type="email"
        placeholder="Jessica"
        required
      />
    </div>
    <div class="Input__Container">
      <label for="phone" class="Label">Phone</label>
      <input name="phone" class="Input" type="tel" placeholder="555-555-5555" />
    </div>
    <div class="Text__Container">
      <label for="message" class="Label">Message</label>
      <textarea
        class="Input Message"
        rows="5"
        name="message"
        placeholder="Enter your message"
        required
      ></textarea>
    </div>
    <div class="Button__Container">
      <button type="submit" class="Button">Send</button>
    </div>
  </div>
</form>
/* Structural styles */
.Contact__Card {
  width: 600px;
  height: 500px;
  border: 1px solid lightgray;
  border-radius: 1rem;
  padding: 2rem;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  grid-template-rows: 1fr 1fr 150px 1fr;
}

.Input__Container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 1px solid red;
}

.Text__Container {
  grid-column: span 2;
}

.Button__Container {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

/* Element styles */
.Input {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
  overflow: visible;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  padding-top: 0.5rem;
  border: 2px solid lightblue;
  border-radius: 0.5rem;
  width: 100%;
}

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

.Message {
  resize: none;
}

.Label {
  padding-bottom: 0.25rem;
  font-weight: 600;
}

.Button {
  background-color: darkblue;
  border-color: #dbdbdb;
  border-width: 1px;
  color: white;
  font-weight: 600;
  border-radius: 0.5rem;
  padding: 1rem 1.5rem;
}

.Button:hover {
  border-color: #b5b5b5;
}

.Button:focus {
  outline: none;
  border-color: #485fc7;
  color: #363636;
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}