Project Card
Portfolio project cards in three layout styles — classic (inset image with metadata rows), minimal (text only), and horizontal (side-by-side image).
Classic
Inset image with title/year header, description, and divider-separated metadata rows. Best for portfolio grids.
API Reference
| Prop | Type |
|---|---|
| title | string |
| year | string |
| description | string |
| location | string |
| workType | string |
| image | string |
| href | string |
Basic Usage
import { ProjectCard } from "@/extensions/features/portfolio";
<ProjectCard
title="Project name"
year="2026"
category="Branding"
image="/photo.jpg"
href="/projects/slug"
/>Source
src/features/features/portfolio/ProjectCard.tsx
import {
Card,
CardActionArea,
CardImage,
CardContent,
Divider,
Heading,
Image,
Text,
} from "@/ui";
interface ProjectCardProps {
title: string;
year: string;
description: string;
location: string;
workType: string;
image: string;
href: string;
}
export function ProjectCard({
title,
year,
description,
location,
workType,
image,
href,
}: ProjectCardProps) {
return (
<Card variant="outlined" color="primary">
<CardActionArea href={href}>
<CardContent padding="sm">
<div className="flex items-start justify-between gap-4">
<Heading as="h3" size="xs" color="primary">
{title}
</Heading>
<Text size="xs" color="primary" subtle className="shrink-0 pt-0.5">
{year}
</Text>
</div>
</CardContent>
<CardImage rounded="xl" className="mx-4 aspect-4/3">
<Image
src={image}
alt={title}
fill
className="object-cover transition-transform duration-500 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</CardImage>
<CardContent>
<div className="pb-3">
<Text size="sm" color="primary" subtle className="line-clamp-2 leading-relaxed">
{description}
</Text>
</div>
<Divider />
<div className="flex items-center justify-between py-3">
<Text size="xs" color="primary" subtle>
Location
</Text>
<Text size="xs" color="primary">{location}</Text>
</div>
<Divider />
<div className="flex items-center justify-between py-3">
<Text size="xs" color="primary" subtle>
Type of work
</Text>
<Text size="xs" color="primary">{workType}</Text>
</div>
</CardContent>
</CardActionArea>
</Card>
);
}Minimal
Solid primary card with oversized year as a typographic element. Inverted color scheme. Best for featured highlights or hero grids.
API Reference
| Prop | Type |
|---|---|
| title | string |
| year | string |
| description | string |
| location | string |
| workType | string |
| href | string |
Basic Usage
import { ProjectCardMinimal } from "@/extensions/features/portfolio";
<ProjectCardMinimal
title="Project name"
year="2026"
href="/projects/slug"
/>Source
src/features/features/portfolio/ProjectCardMinimal.tsx
import {
Card,
CardActionArea,
CardContent,
Heading,
Text,
} from "@/ui";
interface ProjectCardMinimalProps {
title: string;
year: string;
description: string;
location: string;
workType: string;
href: string;
}
export function ProjectCardMinimal({
title,
year,
description,
location,
workType,
href,
}: ProjectCardMinimalProps) {
return (
<Card variant="solid" color="primary">
<CardActionArea href={href}>
<CardContent padding="lg">
<Text
as="span"
size="xs"
color="primary-contrast"
subtle
className="font-mono uppercase tracking-widest"
>
{workType}
</Text>
<Text
as="span"
color="primary-contrast"
subtle
className="block text-7xl font-extralight leading-none tracking-tighter"
>
{year}
</Text>
<Heading as="h3" size="sm" color="primary-contrast" className="mt-4">
{title}
</Heading>
<Text size="sm" color="primary-contrast" subtle className="mt-2">
{description}
</Text>
<div className="mt-6 flex items-center gap-2 border-t border-primary-contrast/15 pt-4">
<Text
as="span"
size="xs"
color="primary-contrast"
subtle
className="font-mono uppercase tracking-widest"
>
{location}
</Text>
</div>
</CardContent>
</CardActionArea>
</Card>
);
}Horizontal
Editorial split layout with structured metadata on the left and image on the right. Best for featured projects or detail rows.
API Reference
| Prop | Type |
|---|---|
| title | string |
| year | string |
| description | string |
| location | string |
| workType | string |
| image | string |
| href | string |
Basic Usage
import { ProjectCardHorizontal } from "@/extensions/features/portfolio";
<ProjectCardHorizontal
title="Project name"
year="2026"
category="Branding"
image="/photo.jpg"
href="/projects/slug"
/>Source
src/features/features/portfolio/ProjectCardHorizontal.tsx
import {
Card,
CardActionArea,
CardImage,
CardContent,
Divider,
Heading,
Image,
Text,
} from "@/ui";
interface ProjectCardHorizontalProps {
title: string;
year: string;
description: string;
location: string;
workType: string;
image: string;
href: string;
}
export function ProjectCardHorizontal({
title,
year,
description,
location,
workType,
image,
href,
}: ProjectCardHorizontalProps) {
return (
<Card variant="outlined" color="primary">
<CardActionArea href={href}>
<div className="grid sm:grid-cols-2">
<CardContent className="flex flex-col justify-between p-6">
<div>
<Text
as="span"
size="xs"
color="primary"
subtle
className="font-mono uppercase tracking-widest"
>
{year}
</Text>
<Heading as="h3" size="sm" color="primary" className="mt-3">
{title}
</Heading>
<Text size="sm" color="primary" subtle className="mt-2">
{description}
</Text>
</div>
<div className="mt-6">
<Divider color="primary" />
<div className="flex items-center justify-between py-3">
<Text size="xs" color="primary" subtle>
Location
</Text>
<Text size="xs" color="primary">
{location}
</Text>
</div>
<Divider color="primary" />
<div className="flex items-center justify-between py-3">
<Text size="xs" color="primary" subtle>
Type of work
</Text>
<Text size="xs" color="primary">
{workType}
</Text>
</div>
</div>
</CardContent>
<CardImage>
<Image
src={image}
alt={title}
width={600}
height={600}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
sizes="(min-width: 640px) 50vw, 100vw"
/>
</CardImage>
</div>
</CardActionArea>
</Card>
);
}