Blog Card
A reusable card component for displaying a summary of a blog post, designed to be flexible by showing or hiding elements based on the provided props.
Default Usage
This example shows the BlogCard
with all props utilized, including an image, category, read time, author, and date, inside a responsive grid.
Minimal Cards
The component is flexible. Here, the imageUrl
, author
, and date
props are omitted to create a more compact, text-only card.
Description Truncation
Use the truncateLines
prop to control text overflow. This is useful for creating a uniform layout when descriptions have varying lengths.
Installation
CLI
Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.
Manual
-
This component relies on the
Card
andBadge
components. Ensure you have installed them first:npx shadcn-ui@latest add card npx shadcn-ui@latest add badge
-
Copy and paste the utility functions from the Utilities section below into your
lib/utils.ts
file. -
Copy and paste the following component code into your project.
components/ui/blog-card.tsx import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardFooter, CardHeader, } from '@/components/ui/card'; import { cn, formatPostDate, formatReadTime } from '@/lib/utils'; import Image from 'next/image'; interface BlogCardProps { /** The main title of the blog post. */ title: string; /** A short summary or excerpt from the blog post. */ description: string; /** The URL for the main image of the post. */ imageUrl?: string; /** The category or tag associated with the post. */ category?: string; /** The estimated reading time in seconds. */ readTime?: number; /** The name of the post's author. */ author?: string; /** The publication date of the post. */ date?: Date; /** The number of lines to truncate the description to before showing an ellipsis. */ truncateLines?: number; } /** * A reusable card component for displaying a summary of a blog post. * It's designed to be flexible, showing or hiding elements based on the provided props. */ export const BlogCard: React.FC<BlogCardProps> = ({ imageUrl, category, readTime, title, description, author, date, truncateLines, }) => { const showMetadata = category || readTime; const showFooter = author || date; return ( <Card className='max-w-sm flex flex-col w-full overflow-hidden shadow-lg border p-3 rounded-3xl gap-3'> {imageUrl && ( <CardHeader className='p-0'> <div className='relative w-full h-56'> <Image src={imageUrl} alt={title} layout='fill' objectFit='cover' className='rounded-2xl' /> </div> </CardHeader> )} <CardContent className='p-3 flex-grow'> {showMetadata && ( <div className='flex items-center text-sm text-muted-foreground mb-4'> {category && ( <Badge className='text-sm bg-muted text-muted-foreground px-3 py-1 rounded-full'> {category} </Badge> )} {category && readTime && <span className='mx-2'>•</span>} {readTime && <span>{formatReadTime(readTime)}</span>} </div> )} <h2 className='text-2xl font-bold text-card-foreground mb-2 leading-tight'> {title} </h2> <p className={cn('text-muted-foreground', { 'overflow-hidden text-ellipsis [-webkit-box-orient:vertical] [display:-webkit-box]': truncateLines && truncateLines > 0, })} style={{ WebkitLineClamp: truncateLines, }} > {description} </p> </CardContent> {showFooter && ( <CardFooter className='flex justify-between items-center p-3'> {author && ( <div> <p className='text-sm text-muted-foreground'>Written by</p> <p className='font-semibold text-muted-foreground'> {author} </p> </div> )} {date && ( <div className={author ? 'text-right' : ''}> <p className='text-sm text-muted-foreground'>Posted on</p> <p className='font-semibold text-muted-foreground'> {formatPostDate(date)} </p> </div> )} </CardFooter> )} </Card> ); };
Utilities
The BlogCard
component uses the following helper functions for formatting dates and read times. Make sure to add them to your lib/utils.ts
file.
/**
* Formats a Date object into a string like "Apr 14, 2025".
* @param date - The Date object to format.
* @returns A formatted date string in the format "Month Day, Year".
*
* @example
* const myDate = new Date('2025-04-14T12:00:00Z');
* formatPostDate(myDate); // Returns "Apr 14, 2025"
*/
export const formatPostDate = (date: Date): string => {
// 'en-US' locale is used to ensure the month-day-year order.
// You can change this to other locales if needed.
return new Intl.DateTimeFormat('en-US', {
month: 'short', // e.g., "Apr"
day: 'numeric', // e.g., "14"
year: 'numeric', // e.g., "2025"
}).format(date);
};
/**
* Converts seconds into a human-readable string like "6 min read".
* @param seconds - The total number of seconds for the estimated read time.
* @returns A formatted string estimating the read time in minutes.
*
* @example
* formatReadTime(350); // Returns "6 min read"
* formatReadTime(360); // Returns "6 min read"
*/
export const formatReadTime = (seconds: number): string => {
// Calculate the minutes and round up to the nearest whole number
const minutes = Math.ceil(seconds / 60);
return `${minutes} min read`;
};
Usage
Here are a couple of examples demonstrating how to use the BlogCard
.
import { BlogCard } from '@/components/ui/blog-card';
const BlogPostPage = () => {
const longDescription =
'This is a much longer description that is intended to show how the truncation works. By setting the truncateLines prop, we can limit the amount of text shown before an ellipsis appears, creating a cleaner and more uniform look in a list of posts.';
return (
<div className='flex flex-wrap justify-center gap-8 p-4'>
{/* Example 1: Full-featured card */}
<BlogCard
title='Exploring the Alps'
description="A comprehensive guide to hiking and sightseeing in one of the world's most beautiful mountain ranges."
imageUrl='https://source.unsplash.com/random/800x600?mountain'
category='Travel'
readTime={300} // 5 minutes
author='Jane Doe'
date={new Date('2025-08-15')}
/>
{/* Example 2: Minimal card with description truncation */}
<BlogCard
title='A Quick Guide to React Hooks'
description={longDescription}
category='Programming'
readTime={180} // 3 minutes
truncateLines={3}
/>
</div>
);
};
export default BlogPostPage;
Props
Prop | Type | Default | Description |
---|---|---|---|
title | string | Req | The main title of the blog post. |
description | string | Req | A short summary or excerpt from the blog post. |
imageUrl | string | - | The URL for the main image of the post. |
category | string | - | The category or tag associated with the post. |
readTime | number | - | The estimated reading time in seconds. |
author | string | - | The name of the post's author. |
date | Date | - | The publication date of the post. |
truncateLines | number | - | The number of lines to truncate the description to before showing an ellipsis. |