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

PropType
titlestring
yearstring
descriptionstring
locationstring
workTypestring
imagestring
hrefstring

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

PropType
titlestring
yearstring
descriptionstring
locationstring
workTypestring
hrefstring

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

PropType
titlestring
yearstring
descriptionstring
locationstring
workTypestring
imagestring
hrefstring

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>
  );
}