No Time Dad

A blog about web development written by a busy dad

Responsive Blog Starter Template with Tailwindcss Part 2

In this second part of the responsive blog starter template with Tailwindcss series I’m going to turn my attention to the large screen view. You can view part 1 of the series here. The first part covers the small screen view, as well as some thoughts on preparing to design a large layout.

If I’m being honest, this is the first time I’ve written a mobile view all the way through before shifting to the large screen view. I know I gave some strong warnings about how doing small screen and large screen design at the same time can be problematic, but I almost always do it anyways.

I have to say though, I did enjoy writing the small screen version, stepping away for a bit, then writing the large screen version. The break between the two versions was a nice change of pace from cramming them both together. It gave me a chance to reflect a little bit on the design decisions I’d made, and think about how I might change things.

Note: The demos below have breakpoints applied, so they might look different than described on small screens.

Building

Luckily, most of the work has already been done in part 1. I did, however, run into an issue with the structure of the hero section, but it was easy to fix. Again, I think this is part of the benefit of mobile first design. You get most of the work done up front, then you can go back and (hopefully) only need small changes to get things looking good on larger screens.

Header section

I didn’t have enough room for the nav links to be in the same row as the blog title on small screens. That isn’t the case for large screens, though. So, I’ll switch the parent flexbox container direction from a column to a row. I’ll also use Tailwind’s justify-between which is equivalend to justify-content: space-between to push the nav links to the end of the row. I’ll also bump up the left and right padding via px-16 since I have a bit more room now.

I’ll also change the nav element’s flexbox container from a column to a row. I’ll also remove some of the extra vertical spacing and padding since the links are in a row now.

const HeaderSection = () => (
  <div className="flex flex-col p-4 md:flex-row md:items-center md:justify-between md:px-16">
    <a href="#home" className="text-2xl font-bold no-underline text-gray-800 hover:text-gray-600">Cycle Blog</a>
    <nav className="flex flex-col pt-4 space-y-2 md:flex-row md:space-y-0 md:space-x-3 md:pt-0">
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#home">Home</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#about">About</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#contact">Contact</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#projects">Projects</a>
    </nav>
  </div>
);
Hero section

There are probably more than a few changes I could make to the hero section for larger screens. I decided it’d be nice to initialize a two column grid and display the text in one column and the image in the other. This ended up looking good all the way up through large screen sizes too.

I did run into an issue right away, though. I had three divs inside of the two column grid, which made the layout a little strange looking. To fix this, I wrapped the two divs containing the text in a single div. This meant that I now only had two divs in the two column grid, which forces two text divs into a single column.

const HeroSection = () => (
  <div className="bg-gray-100 p-4 space-y-3 md:grid md:grid-cols-2 md:gap-2 md:py-10 md:px-16">
    {/* Added a new div here to help with the grid */}
    <div className="space-y-2">
      <div className="text-4xl font-extrabold">A blog about cycling</div>
      <div className="opacity-70">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
    </div>
    <img className="max-w-full h-auto rounded-md" src={cyclist} alt="cyclist" />
  </div>
);
A blog about cycling
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
cyclist
Blog posts section

Things get more interesting in this section. I wanted this section to be a two column grid for 768px screens and a three column grid for 1024px screens. To make this happen, I initialize the grid via Tailwind’s md:grid, and add md:grid-cols-2 and lg:grid-cols-3. Adding md:gap-4 will take care of the spacing the between the cards for me, and I can remove the veritcal spacing by adding md:space-y-0.

const BlogPostsSection = () => (
  <div className="p-4 md:px-16">
    <div className="text-2xl font-bold py-4">Posts</div>
    <div className="space-y-6 md:space-y-0 md:grid md:grid-cols-2 lg:grid-cols-3 md:gap-4">
      {[...Array(10)].map((_, i) => <Post key={i} />)}
    </div>
    <div className="flex justify-between pt-6">
      <PreviousButton />
      <NextButton />
    </div>
  </div>
);
Posts
Lorem ipsum
Jan 21, 2021
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
Lorem ipsum
Jan 21, 2021
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
Lorem ipsum
Jan 21, 2021
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
Lorem ipsum
Jan 21, 2021
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
Footer section

The last section to deal with for large screens is the footer section. This section required the least amount of changes because I think it looks pretty good as it is. I did increase the padding a little bit via md:px-16 and md:py-10, but the rest of the component remains the same.

const FooterSection = () => (
  <div className="bg-gray-100 p-4 space-y-3 md:px-16 md:py-10">
    <a href="#home" className="text-4xl font-extrabold no-underline text-gray-800 hover:text-gray-600">Cycling Blog</a>
    <div className="opacity-70">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
    <div className="flex items-center space-x-2">
      <a href="#email" aria-label="email"><EmailSvg /></a>
      <a href="#chat" aria-label="chat"><ChatSvg /></a>
      <a href="#phone" aria-label="call"><PhoneSvg /></a>
    </div>
  </div>
);
Cycling Blog
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Conclusion

I think this is a really nice template for a blog site. I’d read this blog. The design is clean and simple, but also responsive. It can work great as-is, and would be easy to build on.

For me, the whole point of building these templates is to reinforce my skills. But it also gives me something I can actually use later.

Demo and final code

The full demo can be viewed here.

// package.json
...
"react": "^17.0.2",
"tailwindcss": "^2.1.2"
...
import React from 'react';
import cyclist from './cyclist.jpg'

const Index = () => (
  <>
    <HeaderSection />
    <HeroSection />
    <BlogPostsSection />
    <FooterSection />
  </>
);

const BlogStarterTemplate = () => (
  <div className="flex flex-col p-4 md:flex-row md:items-center md:justify-between md:px-16">
    <a href="#home" className="text-2xl font-bold no-underline text-gray-800 hover:text-gray-600">Cycle Blog</a>
    <nav className="flex flex-col pt-4 space-y-2 md:flex-row md:space-y-0 md:space-x-3 md:pt-0">
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#home">Home</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#about">About</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#contact">Contact</a>
      <a className="no-underline text-gray-800 hover:text-gray-600 font-semibold" href="#projects">Projects</a>
    </nav>
  </div>
);

const HeroSection = () => (
  <div className="bg-gray-100 p-4 space-y-3 md:grid md:grid-cols-2 md:gap-2 md:py-10 md:px-16">
    {/* Added a new div here to help with the grid */}
    <div className="space-y-2">
      <div className="text-4xl font-extrabold">A blog about cycling</div>
      <div className="opacity-70">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
    </div>
    <img className="max-w-full h-auto rounded-md" src={cyclist} alt="cyclist" />
  </div>
);

const BlogPostsSection = () => (
  <div className="p-4 md:px-16">
    <div className="text-2xl font-bold py-4">Posts</div>
    <div className="space-y-6 md:space-y-0 md:grid md:grid-cols-2 lg:grid-cols-3 md:gap-4">
      {[...Array(10)].map((_, i) => <Post key={i} />)}
    </div>
    <div className="flex justify-between pt-6">
      <PreviousButton />
      <NextButton />
    </div>
  </div>
);

const Post = () => (
  <div className="shadow-md rounded-br-md rounded-bl-md hover:shadow-lg">
    <a href="#blog-post" className="no-underline">
      <div className="max-w-full h-36 bg-gray-200 rounded-tr-md rounded-tl-md"></div>
    </a>
    <div className="p-2 space-y-1">
      <a href="#blog-post" className="text-xl font-bold no-underline text-gray-800 hover:text-gray-600">Lorem ipsum</a>
      <div className="opacity-70">Jan 21, 2021</div>
      <div className="opacity-85">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...</div>
    </div>
  </div>
);

const NextButton = () => (
  <button type="button" className="flex-shrink-0 border border-gray-500 text-base font-semibold py-2 px-4 rounded-lg shadow-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-200">
    <div className="flex items-center">
      More
      <ArrowSmRightSvg />
    </div>
  </button>
);

const PreviousButton = () => (
  <button type="button" className="flex-shrink-0 border border-gray-500 text-base font-semibold py-2 px-4 rounded-lg shadow-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-200">
    <div className="flex items-center">
      <ArrowSmLeftSvg />
      Back
    </div>
  </button>
);

const FooterSection = () => (
  <div className="bg-gray-100 p-4 space-y-3 md:px-16 md:py-10">
    <a href="#home" className="text-4xl font-extrabold no-underline text-gray-800 hover:text-gray-600">Cycling Blog</a>
    <div className="opacity-70">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
    <div className="flex items-center space-x-2">
      <a href="#email" aria-label="email"><EmailSvg /></a>
      <a href="#chat" aria-label="chat"><ChatSvg /></a>
      <a href="#phone" aria-label="call"><PhoneSvg /></a>
    </div>
  </div>
);

const EmailSvg = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-600 hover:text-gray-800" viewBox="0 0 20 20" fill="currentColor">
  <path fillRule="evenodd" d="M14.243 5.757a6 6 0 10-.986 9.284 1 1 0 111.087 1.678A8 8 0 1118 10a3 3 0 01-4.8 2.401A4 4 0 1114 10a1 1 0 102 0c0-1.537-.586-3.07-1.757-4.243zM12 10a2 2 0 10-4 0 2 2 0 004 0z" clipRule="evenodd" />
</svg>

const ChatSvg = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-600 hover:text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z" />
</svg>

const PhoneSvg = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-600 hover:text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>

const ArrowSmRightSvg = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 ml-2" viewBox="0 0 20 20" fill="currentColor">
  <path fillRule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>

const ArrowSmLeftSvg = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
  <path fillRule="evenodd" d="M9.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L7.414 9H15a1 1 0 110 2H7.414l2.293 2.293a1 1 0 010 1.414z" clipRule="evenodd" />
</svg>

export default BlogStarterTemplate;