Tools

Instagram Card

A versatile, responsive Instagram-style card component for showcasing user profiles with customizable photo galleries and multiple layout options.

Last updated on

A responsive Instagram-style card component that displays user profiles with configurable photo layouts, avatar support, and multiple size variants.

SA
Sarah Anderson

Travel photographer and content creator

Sarah Anderson photo 1
Sarah Anderson photo 2
Sarah Anderson photo 3
Sarah Anderson photo 4

Installation

npx shadcn@latest add @grenish/instagram-card

This requires the @grenish registry in your components.json. See the installation guide for setup.

Manual Dependencies

If you prefer manual installation, add the base components:

npx shadcn@latest add card avatar button aspect-ratio item

Usage

import InstagramCard from "@/components/tools/instagram-card";

export default function Demo() {
  return (
    <InstagramCard
      profileName="Sarah Anderson"
      description="Travel photographer and content creator"
      avatarSrc="https://github.com/shadcn.png"
      photos={[
        "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
        "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
        "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
        "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
      ]}
    />
  );
}
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
  import { Button } from "@/components/ui/button";
  import { Card, CardContent } from "@/components/ui/card";
  import {
    Item,
    ItemActions,
    ItemContent,
    ItemDescription,
    ItemMedia,
    ItemTitle,
  } from "@/components/ui/item";
  import { cn } from "@/lib/utils";
  import { InstagramLogoIcon } from "@phosphor-icons/react/dist/ssr";
  import Image from "next/image";
  
  // Types
  
  type InstagramCardSize = "sm" | "md" | "lg" | "xl";
  type InstagramPhotoLayout = 1 | 2 | 3 | 4;
  
  interface InstagramCardProps {
    avatarSrc?: string;
    className?: string;
    description?: string;
    photoLayout?: InstagramPhotoLayout;
    photos?: string[];
    profileName?: string;
    size?: InstagramCardSize;
  }
  
  interface SizeConfig {
    avatar: string;
    button: "icon-xs" | "icon-sm" | "icon" | "icon-lg";
    card: string;
    cardSize: "default" | "sm";
    contentPadding: string;
    description: string;
    gap: string;
    itemSize: "default" | "sm";
    mediaRadius: string;
    title: string;
  }
  
  interface InstagramCardHeaderProps {
    avatarSrc?: string;
    avatarClass: string;
    buttonSize: "icon-xs" | "icon-sm" | "icon" | "icon-lg";
    description?: string;
    descriptionClass: string;
    initials: string;
    itemSize: "default" | "sm";
    profileName: string;
    titleClass: string;
  }
  
  interface InstagramCardBodyProps {
    contentPadding: string;
    gap: string;
    mediaRadius: string;
    photoLayout: InstagramPhotoLayout;
    photos: string[];
    profileName: string;
  }
  
  // Config
  
  const sizeConfig: Record<InstagramCardSize, SizeConfig> = {
    sm: {
      avatar: "size-9",
      button: "icon-xs",
      card: "max-w-xs",
      cardSize: "sm",
      contentPadding: "px-4",
      description: "text-xs line-clamp-2",
      gap: "gap-1.5",
      itemSize: "sm",
      mediaRadius: "rounded-2xl",
      title: "text-sm",
    },
    md: {
      avatar: "size-10",
      button: "icon-sm",
      card: "max-w-sm",
      cardSize: "default",
      contentPadding: "px-5",
      description: "text-sm line-clamp-2",
      gap: "gap-2",
      itemSize: "default",
      mediaRadius: "rounded-[1.35rem]",
      title: "text-sm",
    },
    lg: {
      avatar: "size-12",
      button: "icon",
      card: "max-w-md",
      cardSize: "default",
      contentPadding: "px-6",
      description: "text-sm line-clamp-3",
      gap: "gap-2.5",
      itemSize: "default",
      mediaRadius: "rounded-[1.5rem]",
      title: "text-base",
    },
    xl: {
      avatar: "size-14",
      button: "icon-lg",
      card: "max-w-xl",
      cardSize: "default",
      contentPadding: "px-6",
      description: "text-sm line-clamp-3",
      gap: "gap-3",
      itemSize: "default",
      mediaRadius: "rounded-[1.75rem]",
      title: "text-base",
    },
  };
  
  const photoLayoutClass: Record<InstagramPhotoLayout, string> = {
    1: "grid-cols-1 grid-rows-1",
    2: "grid-cols-2 grid-rows-1",
    3: "grid-cols-[1.35fr_1fr] grid-rows-2",
    4: "grid-cols-2 grid-rows-2",
  };
  
  // Utilities
  
  function getInitials(name?: string): string {
    return name
      ?.trim()
      .split(" ")
      .filter(Boolean)
      .slice(0, 2)
      .map((word) => word[0])
      .join("")
      .toUpperCase() || "";
  }
  
  function getPhotoSpanClass(
    layout: InstagramPhotoLayout,
    index: number,
  ): string {
    return layout === 3 && index === 0 ? "row-span-2" : "";
  }
  
  // Components
  
  function InstagramCardHeader({
    avatarSrc,
    avatarClass,
    buttonSize,
    description,
    descriptionClass,
    initials,
    itemSize,
    profileName,
    titleClass,
  }: InstagramCardHeaderProps) {
    return (
      <Item size={itemSize} className="border-0 rounded-none bg-transparent py-0">
        <ItemMedia>
          <Avatar className={avatarClass}>
            <AvatarImage src={avatarSrc} alt={profileName} />
            <AvatarFallback>{initials}</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle className={titleClass}>{profileName}</ItemTitle>
          {description && (
            <ItemDescription
              className={cn(descriptionClass, "text-muted-foreground")}
            >
              {description}
            </ItemDescription>
          )}
        </ItemContent>
        <ItemActions>
          <Button
            variant="outline"
            size={buttonSize}
            aria-label="Open on Instagram"
          >
            <InstagramLogoIcon weight="duotone" />
          </Button>
        </ItemActions>
      </Item>
    );
  }
  
  function InstagramCardBody({
    contentPadding,
    gap,
    mediaRadius,
    photoLayout,
    photos,
    profileName,
  }: InstagramCardBodyProps) {
    const visiblePhotos = photos.slice(0, photoLayout);
  
    if (visiblePhotos.length === 0) return null;
  
    return (
      <CardContent className={contentPadding}>
        <div
          className={cn(
            "grid w-full overflow-hidden aspect-square",
            gap,
            mediaRadius,
            photoLayoutClass[photoLayout],
          )}
        >
          {visiblePhotos.map((photo, index) => (
            <div
              key={`${photo}-${index}`}
              className={cn(
                "relative min-h-0 overflow-hidden",
                getPhotoSpanClass(photoLayout, index),
              )}
            >
              <Image
                src={photo}
                alt={`${profileName} photo ${index + 1}`}
                fill
                className="object-cover"
                sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
              />
            </div>
          ))}
        </div>
      </CardContent>
    );
  }
  
  export default function InstagramCard({
    avatarSrc,
    className,
    description,
    photoLayout = 4,
    photos = [],
    profileName = "",
    size = "md",
  }: InstagramCardProps) {
    const config = sizeConfig[size];
    const initials = getInitials(profileName);
  
    return (
      <Card
        size={config.cardSize}
        className={cn(
          "w-full border border-border/60 bg-card/95 shadow-sm",
          config.card,
          className,
        )}
      >
        <InstagramCardHeader
          avatarSrc={avatarSrc}
          avatarClass={config.avatar}
          buttonSize={config.button}
          description={description}
          descriptionClass={config.description}
          initials={initials}
          itemSize={config.itemSize}
          profileName={profileName}
          titleClass={config.title}
        />
        <InstagramCardBody
          contentPadding={config.contentPadding}
          gap={config.gap}
          mediaRadius={config.mediaRadius}
          photoLayout={photoLayout}
          photos={photos}
          profileName={profileName}
        />
      </Card>
    );
  }

Features

  • Responsive Design: Adapts seamlessly across all screen sizes
  • Multiple Size Variants: Small, medium, large, and extra-large options
  • Flexible Layouts: 1, 2, 3, or 4 photo grid arrangements
  • Custom Images: Support for custom profile avatars and gallery photos
  • Accessible: Built with semantic HTML and ARIA labels
  • Type-Safe: Fully typed with TypeScript interfaces
  • Modular Components: Organized subcomponents for maintainability

Props

Prop

Type

Size Variants

Small

SA
Sarah Anderson

Travel photographer

Sarah Anderson photo 1
Sarah Anderson photo 2
Sarah Anderson photo 3
Sarah Anderson photo 4
<InstagramCard
  size="sm"
  profileName="Sarah Anderson"
  description="Travel photographer"
  photos={[
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
    "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
    "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
    "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
  ]}
/>

Medium (Default)

SA
Sarah Anderson

Travel photographer and content creator

Sarah Anderson photo 1
Sarah Anderson photo 2
Sarah Anderson photo 3
Sarah Anderson photo 4
<InstagramCard
  size="md"
  profileName="Sarah Anderson"
  description="Travel photographer and content creator"
  photos={[
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
    "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
    "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
    "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
  ]}
/>

Large

SA
Sarah Anderson

Travel photographer and content creator

Sarah Anderson photo 1
Sarah Anderson photo 2
Sarah Anderson photo 3
Sarah Anderson photo 4
<InstagramCard
  size="lg"
  profileName="Sarah Anderson"
  description="Travel photographer and content creator"
  photos={[
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
    "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
    "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
    "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
  ]}
/>

Extra Large

SA
Sarah Anderson

Travel photographer and content creator

Sarah Anderson photo 1
Sarah Anderson photo 2
Sarah Anderson photo 3
Sarah Anderson photo 4
<InstagramCard
  size="xl"
  profileName="Sarah Anderson"
  description="Travel photographer and content creator"
  photos={[
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
    "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
    "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
    "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
  ]}
/>

Photo Layouts

The component supports four distinct photo layout arrangements, automatically adjusting spacing and dimensions.

Layout 1 — Single Photo

Single full-width photo display.

SA
Solo Artist

Showcase single masterpiece

Solo Artist photo 1
<InstagramCard photoLayout={1} />

Layout 2 — Two Photos

Side-by-side photo arrangement.

DF
Dual Focus

Compare two moments

Dual Focus photo 1
Dual Focus photo 2
<InstagramCard photoLayout={2} />

Layout 3 — Three Photos

Mixed grid with one larger photo on the left spanning two rows.

MG
Mixed Gallery

Feature plus secondary photos

Mixed Gallery photo 1
Mixed Gallery photo 2
Mixed Gallery photo 3
<InstagramCard photoLayout={3} />

Layout 4 — Four Photos (Default)

2×2 grid layout showcasing four photos equally.

FG
Full Gallery

Complete photo showcase

Full Gallery photo 1
Full Gallery photo 2
Full Gallery photo 3
Full Gallery photo 4
<InstagramCard photoLayout={4} />

Examples

Custom Profile Information

ER
Elena Rodriguez

Product designer & creative director

<InstagramCard
  profileName="Elena Rodriguez"
  description="Product designer & creative director"
  avatarSrc="https://github.com/shadcn.png"
/>
TC
Travel Chronicles

Exploring the world one frame at a time

Travel Chronicles photo 1
Travel Chronicles photo 2
Travel Chronicles photo 3
Travel Chronicles photo 4
<InstagramCard
  profileName="Travel Chronicles"
  description="Exploring the world one frame at a time"
  photoLayout={4}
  photos={[
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1200",
    "https://images.unsplash.com/photo-1488646953014-85cb44e25828?q=80&w=1200",
    "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=1200",
    "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1200",
  ]}
/>

Large Size with Mixed Layout

CS
Creative Studio

Design, photography, and visual storytelling

Creative Studio photo 1
Creative Studio photo 2
Creative Studio photo 3
<InstagramCard
  size="lg"
  photoLayout={3}
  profileName="Creative Studio"
  description="Design, photography, and visual storytelling"
/>

On this page