Blog Components
Blog layouts compose from shared components located in src/layouts/blogs/components/. These components handle the index grid and individual post rendering.
Component Directory
src/layouts/blogs/components/
├── body/
│ └── default/
│ ├── IndexBody.astro # Post listing grid
│ ├── PostBody.astro # Single post content
│ └── styles.css
│
├── cards/
│ └── default/
│ ├── PostCard.astro # Individual post card
│ └── styles.css
│
└── common/
└── index-styles.css # Shared index styles
IndexBody
The IndexBody component renders the blog index page with a grid of post cards.
File: src/layouts/blogs/components/body/default/IndexBody.astro
Props
interface Props {
dataPath: string; // Path to blog folder
postsPerPage?: number; // Max posts to show (default: 10)
}
Internal Logic
IndexBody loads posts internally:
---
import PostCard from '../../cards/default/PostCard.astro';
import { loadContent } from '@loaders/data';
interface Props {
dataPath: string;
postsPerPage?: number;
}
const { dataPath, postsPerPage = 10 } = Astro.props;
const posts = await loadContent(dataPath, {
pattern: '*.{md,mdx}',
sort: 'date',
order: 'desc',
});
---
<div class="blog-index">
<header class="blog-index__header">
<h1>Blog</h1>
<p>Latest posts and updates</p>
</header>
<div class="blog-index__grid">
{posts.slice(0, postsPerPage).map(post => (
<PostCard
title={post.data.title}
description={post.data.description}
date={post.data.date}
author={post.data.author}
tags={post.data.tags}
href={`/blog/${post.slug}`}
image={post.data.image}
/>
))}
</div>
</div>
Usage in Layout
---
// IndexLayout.astro
import IndexBody from '../../components/body/default/IndexBody.astro';
interface Props {
dataPath: string;
postsPerPage?: number;
}
const props = Astro.props;
---
<IndexBody {...props} />
Customization
The grid layout is CSS-based:
.blog-index__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
}
PostBody
The PostBody component renders a single blog post with metadata.
File: src/layouts/blogs/components/body/default/PostBody.astro
Props
interface Props {
title: string;
description?: string;
date?: string;
author?: string;
tags?: string[];
content: string;
}
Structure
---
interface Props {
title: string;
description?: string;
date?: string;
author?: string;
tags?: string[];
content: string;
}
const { title, description, date, author, tags, content } = Astro.props;
const formattedDate = date
? new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
: null;
---
<article class="blog-post">
<header class="blog-post__header">
<h1 class="blog-post__title">{title}</h1>
<div class="blog-post__meta">
{author && <span class="blog-post__author">{author}</span>}
{formattedDate && <time>{formattedDate}</time>}
</div>
</header>
<div class="blog-post__content">
<Fragment set:html={content} />
</div>
{tags && tags.length > 0 && (
<footer class="blog-post__tags">
{tags.map(tag => <span class="tag">{tag}</span>)}
</footer>
)}
</article>
Usage in Layout
---
// PostLayout.astro
import PostBody from '../../components/body/default/PostBody.astro';
interface Props {
title: string;
description?: string;
date?: string;
author?: string;
tags?: string[];
content: string;
}
const props = Astro.props;
---
<PostBody {...props} />
PostCard
The PostCard component renders an individual post preview card.
File: src/layouts/blogs/components/cards/default/PostCard.astro
Props
interface Props {
title: string;
description?: string;
date?: string;
author?: string;
tags?: string[];
href: string;
image?: string;
}
Structure
---
interface Props {
title: string;
description?: string;
date?: string;
author?: string;
tags?: string[];
href: string;
image?: string;
}
const { title, description, date, author, tags, href, image } = Astro.props;
const formattedDate = date
? new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
: null;
---
<a href={href} class="post-card">
{image && (
<div class="post-card__image">
<img src={image} alt="" loading="lazy" />
</div>
)}
<div class="post-card__content">
<h3 class="post-card__title">{title}</h3>
<div class="post-card__meta">
{formattedDate && <time>{formattedDate}</time>}
{author && <span>by {author}</span>}
</div>
{description && (
<p class="post-card__description">{description}</p>
)}
{tags && tags.length > 0 && (
<div class="post-card__tags">
{tags.slice(0, 3).map(tag => (
<span class="tag">{tag}</span>
))}
</div>
)}
</div>
</a>
Styling
.post-card {
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
background: var(--color-bg-secondary);
transition: transform 0.2s, box-shadow 0.2s;
}
.post-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.post-card__image img {
width: 100%;
height: 200px;
object-fit: cover;
}
.post-card__content {
padding: 1.5rem;
}
Creating Custom Components
Custom Card Style
Create a new card variant:
mkdir -p src/layouts/blogs/components/cards/compact/
---
// cards/compact/PostCard.astro
interface Props {
title: string;
date?: string;
href: string;
}
const { title, date, href } = Astro.props;
---
<a href={href} class="compact-card">
<span class="compact-card__title">{title}</span>
{date && <time>{date}</time>}
</a>
<style>
.compact-card {
display: flex;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid var(--color-border);
}
</style>
Using in Layout
---
// Your custom IndexLayout
import PostCard from '../../components/cards/compact/PostCard.astro';
---
Component Composition Example
A full blog layout style composition:
---
// blog_style2/IndexLayout.astro
import { loadContent } from '@loaders/data';
// Use compact cards
import PostCard from '../../components/cards/compact/PostCard.astro';
// Custom styles
import './index-styles.css';
interface Props {
dataPath: string;
}
const { dataPath } = Astro.props;
const posts = await loadContent(dataPath, {
pattern: '*.{md,mdx}',
sort: 'date',
order: 'desc',
});
---
<div class="blog-list-style">
<h1>Blog</h1>
<ul class="post-list">
{posts.map(post => (
<li>
<PostCard
title={post.data.title}
date={post.data.date}
href={`/blog/${post.slug}`}
/>
</li>
))}
</ul>
</div>
Style Imports
When using components, import their styles:
---
import IndexBody from '../../components/body/default/IndexBody.astro';
// Import component styles
import '../../components/common/index-styles.css';
import '../../components/cards/default/styles.css';
---
Or import all styles in a central location:
/* blog_style1/styles.css */
@import '../../components/common/index-styles.css';
@import '../../components/cards/default/styles.css';
@import '../../components/body/default/styles.css';