Overview
The PortfolioPreview component creates interactive preview cards for portfolio projects. It displays a project image with an overlaid title badge and links to the full project page. The component includes smooth view transitions and hover effects for an engaging user experience.
Props
project
CollectionEntry<'work'>
required
A content collection entry from the work collection. This object contains:
slug: The project’s URL slug
data.title: The project title
data.img: The project image path
data.img_alt: Alternative text for the image (optional)
Usage
Basic Usage
import { getCollection } from 'astro:content';
import PortfolioPreview from '../components/PortfolioPreview.astro';
const projects = await getCollection('work');
<PortfolioPreview project={projects[0]} />
With Grid Layout
import Grid from '../components/Grid.astro';
import PortfolioPreview from '../components/PortfolioPreview.astro';
const projects = await getCollection('work');
<Grid variant="offset">
{projects.map((project) => (
<li>
<PortfolioPreview project={project} />
</li>
))}
</Grid>
Multiple Projects
import { getCollection } from 'astro:content';
import PortfolioPreview from '../components/PortfolioPreview.astro';
const allProjects = (await getCollection('work'))
.sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf())
.slice(0, 4);
<section>
{allProjects.map((project) => (
<PortfolioPreview project={project} />
))}
</section>
Real-World Examples
Homepage Featured Projects (src/pages/index.astro):
const projects = (await getCollection('work'))
.sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf())
.slice(0, 4);
<section>
<Grid variant="offset">
{projects.map((project) => (
<li>
<PortfolioPreview project={project} />
</li>
))}
</Grid>
</section>
All Work Page (src/pages/work.astro):
const projects = (await getCollection('work'))
.sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf());
<section>
<Grid>
{projects.map((project) => (
<li>
<PortfolioPreview project={project} />
</li>
))}
</Grid>
</section>
Component Structure
The component renders as a clickable card with:
- Container: An
<a> tag linking to /work/{slug}
- Title Badge: Overlaid on the top-left with project name
- Image: Full-size background image with object-fit cover
- View Transitions: Smooth animations when navigating to project detail
Styling Features
- Subtle gradient background (
--gradient-subtle)
- Rounded corners (0.75rem on mobile, 1.5rem on desktop)
- Box shadow that enhances on hover
- Title badge with semi-transparent dark background
- Responsive heights (11rem on mobile, 22rem on desktop)
- Lazy loading and async decoding for optimal performance
View Transitions
The component uses Astro’s view transitions for smooth page navigation:
<span transition:name={`title-${slug}`}>{data.title}</span>
<img transition:name={`img-${slug}`} src={data.img} alt={data.img_alt || ''} />
These transitions create a seamless morphing effect when clicking through to the project detail page.
Accessibility
- Semantic
<a> element for keyboard navigation
- Alt text support for images
- Fallback empty string for missing alt text
- Hover and focus states for interactive feedback
- Proper link structure for screen readers
Images use loading="lazy" and decoding="async" for optimal performance. Make sure your project images are optimized before adding them to the work collection.
Content Collection Structure
The component expects work entries with this structure:
// src/content/config.ts
const work = defineCollection({
schema: z.object({
title: z.string(),
img: z.string(),
img_alt: z.string().optional(),
// ... other fields
}),
});