Skip to content
All Taste components

Blog

Blog — three framed cards with authors

Three recent articles as framed cards with a cover, topic tag, excerpt and an author row with date.

3 framed cards · topic tag · author + date footer · hover lift

type Post = { title: string; excerpt: string; tag: string; href: string; cover: string; author: string; date: string; dateTime: string };

const posts: Post[] = [
  { title: "How to write payment terms clients actually read", excerpt: "Five lines that cut late payments without sounding strict, with examples you can copy.", tag: "Guides",
    href: "/blog/payment-terms", cover: "/blog/payment-terms.jpg", author: "Ingrid Solberg", date: "Oct 8, 2026", dateTime: "2026-10-08" },
  { title: "Late fees: when to charge them and how much", excerpt: "What’s normal, what’s legal and how to bring it up with a client you want to keep.", tag: "Getting paid",
    href: "/blog/late-fees", cover: "/blog/late-fees.jpg", author: "Samir Haddad", date: "Sep 29, 2026", dateTime: "2026-09-29" },
  { title: "What’s new: recurring invoices for retainers", excerpt: "Bill the same client every month without lifting a finger, with prorating built in.", tag: "Product",
    href: "/blog/recurring-invoices", cover: "/blog/recurring.jpg", author: "Felix Navarro", date: "Sep 17, 2026", dateTime: "2026-09-17" },
];

const initials = (name: string) => name.split(" ").map((w) => w[0]).join("").slice(0, 2);

export function BlogCardsWithAuthors() {
  return (
    <section aria-labelledby="blog-cards-title" className="bg-surface px-5 py-16 font-sans sm:px-10 sm:py-20">
      <div className="mx-auto max-w-6xl">
        <div className="flex flex-wrap items-end justify-between gap-4">
          <div className="max-w-xl">
            <h2 id="blog-cards-title" className="text-3xl font-semibold tracking-tight text-fg sm:text-4xl">From the blog</h2>
            <p className="mt-4 text-base text-fg-muted sm:text-lg">Practical advice on getting paid, from people who send a lot of invoices.</p>
          </div>
          <a href="/blog" className="inline-flex h-9 items-center rounded border border-border-strong bg-bg px-3.5 text-sm font-medium text-fg transition hover:bg-surface">View all posts</a>
        </div>

        <div className="mt-10 grid gap-4 md:grid-cols-3">
          {posts.map((p) => (
            <a key={p.href} href={p.href} className="group grid grid-rows-[auto_1fr_auto] overflow-hidden rounded-xl border border-border bg-bg transition hover:border-border-strong hover:shadow-xl">
              <img src={p.cover} alt="" className="aspect-video w-full bg-primary-soft object-cover" />
              <div className="grid content-start gap-2.5 p-[22px]">
                <span className="w-fit rounded-full bg-surface-2 px-2 py-0.5 text-xs font-medium text-fg-muted">{p.tag}</span>
                <h3 className="text-[17px] font-semibold leading-snug tracking-tight text-fg group-hover:underline group-hover:underline-offset-4">{p.title}</h3>
                <p className="text-sm leading-relaxed text-fg-muted">{p.excerpt}</p>
              </div>
              <div className="flex items-center gap-2.5 border-t border-border px-[22px] py-3.5 text-[13px]">
                <span className="grid size-7 place-items-center rounded-full bg-primary-soft text-[11px] font-medium text-primary-soft-fg" aria-hidden>{initials(p.author)}</span>
                <span className="font-medium text-fg">{p.author}</span>
                <time dateTime={p.dateTime} className="ml-auto whitespace-nowrap text-fg-subtle">{p.date}</time>
              </div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.