Using CSS Gradients for Dynamic Post Thumbnails

Mar 29, 2024·5 min read
by Anthony Coffey

When I first created this site, I was creating post thumbnails in Photoshop using a PSD template that I created. This process was not only time-consuming, but I was also never fully satisfied with the results.

My gut was telling me at the time to pursue a more programmatic approach to creating the design I wanted, but I ignored it. It's been driving me crazy for weeks now, so lets take a look at how we can create the same design but without images - just code!

The Problem

My blog templates used to display post thumbnail as a background image. However, this was a problem because I had to manually create each image. When trying to create content on the fly, it's not very intuitive and can ultimately slow down my workflow.

First, let's take a look at the old code for the AllArticles component that displays a grid of post thumbnails. Please note that Tailwind CSS classes have been removed for clarity.

import React from "react";
import { getAllArticles } from "utils/getArticles";
import Link from "next/link";
import Image from "next/image";
import ArticleInfo from "./ArticleInfo";

export default async function AllArticles() {
  const articles = await getAllArticles();

  return (
    <div>
      {articles.map((article) => {
        const { metadata } = article;
        const title = String(metadata.title);

        return (
          <Link key={title} href={"/articles/" + article.slug}>
            {metadata.image && (
              <Image
                className="h-66 w-full object-cover"
                src={metadata.image}
                alt={title}
              />
            )}
            <div>
              <ArticleInfo article={article} />
              <h3>{title}</h3>
              <p>{metadata.description}</p>
            </div>
          </Link>
        );
      })}
    </div>
  );
}

The Solution

Next, I'll replace the code above with an HTML/CSS based approach that will generate background gradients for each post.

To do this, I will create a function called generateRandomGradient that will take a seed value (like the post title or slug) and generate a unique gradient based on that seed.

import colorPairs from "@/utils/colorPairs";

const generateRandomGradient = (seed: string) => {
  const hash = Array.from(seed).reduce(
    (acc, char, index) => acc + char.charCodeAt(0) * (index + 1),
    0,
  );

  const index = hash % colorPairs.length;
  const [color1, color2] = colorPairs[index];

  // Use the hash to determine the gradient type
  const isRadial = hash % 2 === 0;
  const gradientType = isRadial ? "radial-gradient" : "linear-gradient";
  const angle = hash % 360;

  // Construct the gradient string based on the type
  return isRadial
    ? `${gradientType}(circle, ${color1}, ${color2})`
    : `${gradientType}(${angle}deg, ${color1}, ${color2})`;
};

export default generateRandomGradient;

The generateRandomGradient function takes a seed value, calculates a hash based on the seed, and uses that hash to select a pair of colors from the colorPairs array. It then constructs a gradient string based on the hash value, with the option to create either a linear or radial gradient.

You may have noticed that the above code includes a dependency, the colorPairs array. It contains pairs of colors to be used in the gradients. This array is defined in a separate file and imported into the generateRandomGradient function.

const colorPairs: string[][] = [
  ["#6A82FB", "#FC5C7D"], // Indigo Blue to Red Pink
  ["#B6FBFF", "#83A4D4"], // Light Cyan to Dusty Blue
  ["#67B26F", "#4ca2cd"], // Fresh Green to Cool Blue
  ["#FF758C", "#FF7EB3"], // Pale Red to Soft Pink
  ["#22E1FF", "#1D8FE1"], // Electric Blue to Blue
  ["#B721FF", "#21D4FD"], // Vivid Purple to Bright Turquoise
  // ... add more color pairs as needed
];

export default colorPairs;

Markup

Now that we have the generateRandomGradient function in place, we can update the AllArticles component to use it to generate unique gradients for each post thumbnail.

import React from "react";
import { getAllArticles } from "utils/getArticles";
import Link from "next/link";
import ArticleInfo from "./ArticleInfo";
import generateRandomGradient from "utils/generateRandomGradient";

export default async function AllArticles() {
  const articles = await getAllArticles();

  return (
    <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 px-4 sm:px-0">
      {articles.map((article) => {
        const { metadata, slug } = article;
        const title = String(metadata.title);
        const gradientStyle = {
          backgroundImage: generateRandomGradient(slug),
          minHeight: "250px",
        };

        return (
          <Link
            key={title}
            href={"/articles/" + article.slug}
            className="flex flex-col overflow-hidden rounded-lg shadow-sm transition-shadow duration-200 hover:shadow-lg"
          >
            <div
              className="w-full flex items-center justify-center text-center p-4"
              style={gradientStyle}
            >
              <h3 className="post-title">{title}</h3>
            </div>
            <div className="p-2 m-2">
              <ArticleInfo article={article} className="-mt-2 mb-2 text-xs" />
              <p className="text-base text-gray-700">{metadata.description}</p>
            </div>
          </Link>
        );
      })}
    </div>
  );
}

Closing Thoughts

Viola! Now, I can create the look and feel I wanted for each post without needing to manually create an image. This will save me time and allow me to focus on creating content rather than designing thumbnails. Now, my next mission is to tackle Open Graph tags to ensure that when I share my posts on social media they look just as good as they do on my site!

Thanks for reading! I hope this article has been helpful in showing you how to create unique post thumbnails using CSS gradients. Happy coding!