# Blog Card

> Article card with a gradient cover, category chip, author and read time, using a stretched link with hover lift and arrow nudge.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/blog-card
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/blog-card.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/blog-card-demo.tsx"
import { BlogCard } from "@/components/blog-card"

export default function BlogCardDemo() {
  return (
    <div className="flex flex-wrap items-stretch justify-center gap-6">
      <BlogCard
        category="Design"
        title="Designing interfaces that feel inevitable"
        excerpt="Great products rarely announce their design. We look at the quiet decisions in spacing, motion and copy that make an interface disappear."
        author="Mira Okafor"
        readTime="6 min read"
      />
      <BlogCard
        category="Engineering"
        title="Streaming UI with React Server Components"
        excerpt="A practical walkthrough of suspense boundaries, partial rendering and how to keep your layout stable while data arrives."
        author="Jun Takeda"
        readTime="9 min read"
        coverClassName="from-emerald-400 via-teal-500 to-sky-500"
      />
    </div>
  )
}
```

## Source

```tsx title="components/blog-card.tsx"
import * as React from "react"
import { ArrowUpRight } from "lucide-react"

import { cn } from "@/lib/utils"

export interface BlogCardProps extends Omit<React.ComponentProps<"article">, "title"> {
  title: string
  excerpt: string
  category: string
  author: string
  readTime: string
  href?: string
  /** Tailwind gradient utilities for the cover, e.g. "from-indigo-500 to-pink-500" */
  coverClassName?: string
}

function initials(name: string) {
  return name
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 2)
    .map((part) => part[0]?.toUpperCase())
    .join("")
}

export function BlogCard({
  title,
  excerpt,
  category,
  author,
  readTime,
  href = "#",
  coverClassName = "from-indigo-500 via-violet-500 to-pink-500",
  className,
  ...props
}: BlogCardProps) {
  return (
    <article
      className={cn(
        "group relative flex w-full max-w-sm flex-col overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-sm transition-all duration-300",
        "hover:-translate-y-1 hover:shadow-lg motion-reduce:transition-none motion-reduce:hover:translate-y-0",
        "has-[a:focus-visible]:ring-4 has-[a:focus-visible]:ring-ring/40",
        className
      )}
      {...props}
    >
      <div className={cn("relative h-40 bg-gradient-to-br", coverClassName)} aria-hidden>
        <div className="absolute -right-8 -top-8 size-32 rounded-full bg-white/20 blur-2xl" />
        <div className="absolute -bottom-10 left-6 size-28 rounded-full bg-black/10 blur-2xl" />
        <span className="absolute left-4 top-4 rounded-full bg-background/90 px-2.5 py-1 text-xs font-medium text-foreground backdrop-blur">
          {category}
        </span>
      </div>
      <div className="flex flex-1 flex-col gap-3 p-5">
        <h3 className="text-lg font-semibold leading-snug tracking-tight">
          <a
            href={href}
            className="outline-none after:absolute after:inset-0 after:content-['']"
          >
            {title}
          </a>
        </h3>
        <p className="line-clamp-3 text-sm text-muted-foreground">{excerpt}</p>
        <div className="mt-auto flex items-center justify-between pt-2">
          <div className="flex items-center gap-2.5">
            <span className="flex size-8 items-center justify-center rounded-full bg-secondary text-xs font-semibold text-secondary-foreground">
              {initials(author)}
            </span>
            <div className="leading-tight">
              <p className="text-sm font-medium">{author}</p>
              <p className="text-xs text-muted-foreground">{readTime}</p>
            </div>
          </div>
          <ArrowUpRight
            className="size-5 text-muted-foreground transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-1 group-hover:text-foreground motion-reduce:transition-none"
            aria-hidden
          />
        </div>
      </div>
    </article>
  )
}
```
