No Time Dad

A blog about web development written by a busy dad

Responsive Testimonial Section with CSS Grid

If I had an app, I’d want people who are visiting the homepage for the first time to know how popular and well liked it is. The best way to do that is with a testimonal section that shares quotes from your users.

Honestly, this is just another excuse to practice more with css grid and responsive design. Repition is the key to mastery for me.

I took a look at some of the examples over on TailwindUI and found one that looked interesting (pictured below). I assume it’s responsive, but I’m not certain because I can only see the screenshot on their page.

testimonial

I’ll do my best to try and recreate a version of it, but mine won’t be nearly as good as what you’d get from the folks at TailwindUI. They’re just next level. They’re also a great place to draw inspiration from.

The screenshot above looks like a large screen view to me, and I’d have to assume that the two columns drop down to a single column on smaller screens. It also looks like they’re using flexbox in a few places, specifically towards the bottom of each panel to align the avatar icons with the text.

I think a good starting point for me will is to get the layout working on mobile first, then add the media queries to change the layout for desktop. I usually use colored borders to separate sections when I’m first working on a layout, and that’s what I’ll do here. I’ve said it a lot in the past, but visualizing the elements with colors before trying to add intricate details inside the elements is very helpful for me.

Initial layout

For this testimonial section, I won’t need to use grid on small screens. This is because I want each testimonial to stack on top of one another in a column. This will naturally happen in the browser. But, once the screen is larger I’ll initialize the grid layout for a two columns that divide the full width of the page evenly.

I’ll set the first min-width breakpoint at 768px, which is a common size for tablets. What that means is that for screen sizes 768px or larger, a two column grid will be initialized and two testimonial divs will be displayed in a row. On screen sizes less than 768px, the two testimonial will be displayed in a column.

Below is an example of the basic structure on screens less than 768px wide.

Testimonial 1
Testimonial 2

Here is the how the testimonal section should look on screens 768px or larger wide.

Testimonial 1
Testimonial 2
.Testimonial {
  /* Temporary border for visualizing layout */
  border: 1px solid lightseagreen;
  padding: 1rem;
}

@media (min-width: 768px) {
  .Testimonial__Container {
    /* Initialize the grid */
    display: grid;
    /* Two columns that take all of the available space */
    grid-template-columns: 1fr 1fr;
    width: 100%;
  }
}
<div class="Testimonial__Container">
  <div class="Testimonial">Testimonial 1</div>
  <div class="Testimonial">Testimonial 2</div>
</div>

Building the testimonial sections

Now that I have the basic layout of the section I can focus on the content. I generated a random color palette from coolors for this example. In keeping with the mobile first design mentality, I’ll be focusing on and displaying the small screen view of the testimonial section. The large screen view will contain the same contents, but displayed differently by initializing css grid.

The first thing I’ll do is change the background to a dark color and change the text color to a light color. I’ll then work from the top down to recreate the rest of the content, starting with the company logos.

Buildy
Global Co.
.Testimonial {
  /* Color palette */
  --ash-gray: hsla(97, 13%, 80%, 1);
  --dark-sea-green: hsla(133, 18%, 59%, 1);
  --hookers-green: hsla(165, 19%, 40%, 1);
  --dark-slate-gray: hsla(186, 21%, 26%, 1);
  --charcoal: hsla(201, 20%, 23%, 1);

  /* I like to be verbose, but shorthand can be used instead. */
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  padding-top: 3rem;
  padding-bottom: 3rem;
  color: var(--ash-gray);
  background-color: var(--charcoal);
}

.Testimonial__Header {
  /* Init flexbox to help align text with icon */
  display: flex;
  align-items: center;
  /* Make this section less dramatic than the testimonial text itself */
  opacity: 0.4;
}

.Header__Icon {
  width: 3rem;
  height: 3rem;
}

.Header__Company_Text {
  font-size: 26px;
  font-weight: 700;
  padding-left: 0.5rem;
}

@media (min-width: 768px) {
  .Testimonial__Container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    width: 100%;
  }
}
<div class="Testimonial__Container">
  <div class="Testimonial">
    <div class="Testimonial__Header">
      <svg class="Header__Icon">...</svg>
      <div class="Header__Company_Text">Buildy</div>
    </div>
  </div>
  <div class="Testimonial">...</div>
</div>

Next up is the testimonial text. This is a fun one because there is an image behind the text. It also looks like the testimonial text section has max-height and height properties set to ensure consistent spacing for the avatar icon section beneath it. If it didn’t have these properties set then the section beneath it would be arbitrarily larger or smaller and the two sections in a row would not line up.

As mentioned above, the TailwindUI example has a quotation icon behind the text. I tried a few different variations of css to implemenet this, but I just couldn’t get it to work. I ended up deciding that it was more work than I wanted right now. The closest I came to getting it to look right was to use a ::before pseudo-element with a content svg, but I had a tough time with the alignment. This was all pretty frustrating for me, so I plan to spend some more time on this specific part of the element later. But for now, I’m moving on.

Buildy
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Global Co.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
<div class="Testimonial__Container">
  <div class="Testimonial">
    <div class="Testimonial__Header">...</div>
    <div class="Testimonial__Content">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </div>
  </div>
  <div class="Testimonial">...</div>
</div>
...
.Testimonial__Content {
  height: 150px;
  max-height: 150px;
}

The last piece of this element is the avatar and name section. I’m going to use a simple circle for the avatar here, but an image could easily be dropped in. Flexbox is key to this section. I want to align the text with the avatar and ensure that the text is stacked.

<div class="Testimonial__Container">
  <div class="Testimonial">
    <div class="Testimonial__Header">...</div>
    <div class="Testimonial__Content">...</div>
    <div class="Testimonial__Avatar">
      <div class="Avatar__Icon"></div>
      <div class="Avatar__Text">
        Susan Wright
        <span class="Avatar__Subtext">
          CTO, Buildly
        </span>
      </div>
    </div>
  </div>
  <div class="Testimonial">...</div>
</div>
.Testimonial:nth-child(1) {
  /* Provide some visual separation */
  border-bottom: 2px solid var(--dark-slate-gray);
}

@media (min-width: 768px) {
  .Testimonial__Container {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .Testimonial:nth-child(1) {
    /* Remove bottom border */
    border-bottom: none;
    /* Add vertical border for separation */
    border-right: 2px solid var(--dark-slate-gray);
  }
}

.Testimonial__Avatar {
  display: flex;
  align-items: center;
}

.Avatar__Icon {
  border: 2px solid var(--ash-gray);
  background-color: var(--dark-slate-gray);
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 100%;
}

.Avatar__Text {
  /* Init flexbox */
  display: flex;
  /* Ensure items are stacked on top of each other */
  flex-direction: column;
  padding-left: 1rem;
}

.Avatar__Subtext {
  /* Subtle subtext */
  opacity: 0.6;
}
Buildy
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Susan WrightCTO, Buildly
Global Co.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Andy JonesCEO, Global Co.

Conclusion

Well, that turned out to be more difficult than I assumed. The main struggle was with reproducing the quotation icon. I spent more time on that than I’d like to admit and, sadly, didn’t come up with a solution. It was a good learning experience, though.

One thing I’ve learned so far in building things for this blog is that not every element is going to be perfect, or even close to an exact reproduction of the elements I pull inspiration from. The main point for me is to keep trying to get as close as possible without spending all of my time on the small parts. It’s difficult, but sometimes I have to keep reminding myself to push on when I get stuck.

Final code & demo

Below is how the testimonial section would look on small screens and large screens, respectively. The full code is beneath the demos. The svg icons are from heroicons

Buildy
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Susan WrightCTO, Buildly
Global Co.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Andy JonesCEO, Global Co.
Buildy
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Susan WrightCTO, Buildly
Global Co.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Andy JonesCEO, Global Co.
.Testimonial {
  --ash-gray: hsla(97, 13%, 80%, 1);
  --dark-sea-green: hsla(133, 18%, 59%, 1);
  --hookers-green: hsla(165, 19%, 40%, 1);
  --dark-slate-gray: hsla(186, 21%, 26%, 1);
  --charcoal: hsla(201, 20%, 23%, 1);
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  padding-top: 3rem;
  padding-bottom: 3rem;
  color: var(--ash-gray);
  background-color: var(--charcoal);
}

.Testimonial:nth-child(1) {
  border-bottom: 2px solid var(--dark-slate-gray);
}

@media (min-width: 768px) {
  .Testimonial__Container {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .Testimonial:nth-child(1) {
    border-bottom: none;
    border-right: 2px solid var(--dark-slate-gray);
  }
}

.Testimonial__Dark_Bg {
  border: none;
  color: var(--ash-gray);
  background-color: var(--charcoal);
}

.Testimonial__Header {
  display: flex;
  align-items: center;
  opacity: 0.4;
  padding-bottom: 1.5rem;
}

.Header__Icon {
  width: 3rem;
  height: 3rem;
}

.Header__Company_Text {
  font-size: 26px;
  font-weight: 700;
  padding-left: 0.5rem;
}

.Testimonial__Content {
  height: 150px;
  max-height: 150px;
}

.Testimonial__Avatar {
  display: flex;
  align-items: center;
}

.Avatar__Icon {
  border: 2px solid var(--ash-gray);
  background-color: var(--dark-slate-gray);
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 100%;
}

.Avatar__Text {
  display: flex;
  flex-direction: column;
  padding-left: 1rem;
}

.Avatar__Subtext {
  opacity: 0.6;
}
<div class="Testimonial">
  <div class="Testimonial__Header">
    <svg class="Header__Icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
    </svg>
    <div class="Header__Company_Text">Buildy</div>
  </div>
  <div class="Testimonial__Content">
    Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  </div>
  <div class="Testimonial__Avatar">
    <div class="Avatar__Icon"></div>
    <div class="Avatar__Text">
      Susan Wright
      <span class="Avatar__Subtext">
          CTO, Buildly
      </span>
    </div>
  </div>
</div>
<div class="Testimonial">
  <div class="Testimonial__Header">
    <svg class="Header__Icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
    </svg>
    <div class="Header__Company_Text">Global Co.</div>
  </div>
  <div class="Testimonial__Content">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  </div>
  <div class="Testimonial__Avatar">
    <div class="Avatar__Icon"></div>
    <div class="Avatar__Text">
      Andy Jones
      <span class="Avatar__Subtext">
          CEO, Global Co.
      </span>
    </div>
  </div>
</div>