Creative Tim UICreative Tim UI

Shadcn Gallery

PreviousNext

A collection of image gallery components for displaying photos in various grid layouts, perfect for showcasing portfolios, product images, or visual content.

gallery-photo-1
gallery-photo-2
gallery-photo-3
gallery-photo-4
gallery-photo-5
gallery-photo-6
gallery-photo-7
gallery-photo-8
gallery-photo-9
const IMAGES = [
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1737608734653-d1eaad541d46?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
]

export function GalleryDemo() {
  return (
    <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
      {IMAGES.map((imageLink, index) => (
        <div key={index}>
          <img
            className="h-40 w-full max-w-full rounded-lg object-cover object-center"
            src={imageLink}
            alt={`gallery-photo-${index + 1}`}
          />
        </div>
      ))}
    </div>
  )
}

Installation

pnpm dlx @creative-tim/ui@latest add gallery

Usage

import GalleryDemo from "@/components/gallery-demo"
<GalleryDemo />

Examples

A gallery with a large featured image display and thumbnail navigation below, allowing users to click on thumbnails to change the main displayed image, perfect for product showcases or detailed image viewing.

Featured gallery image
gallery-image-1
gallery-image-2
gallery-image-3
gallery-image-4
gallery-image-5
"use client"

import * as React from "react"

const IMAGES = [
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1737608734653-d1eaad541d46?auto=format&fit=crop&q=80&w=400&h=400",
]

export function FeaturedImageGallery() {
  const [active, setActive] = React.useState(IMAGES[0])

  return (
    <div className="grid gap-4">
      <div>
        <img
          className="h-auto w-full max-w-full rounded-lg object-cover object-center md:h-[480px]"
          src={active}
          alt="Featured gallery image"
        />
      </div>
      <div className="grid grid-cols-5 gap-4">
        {IMAGES.map((imgelink, index) => (
          <div key={index}>
            <img
              onClick={() => setActive(imgelink)}
              src={imgelink}
              className="h-20 w-full max-w-full cursor-pointer rounded-lg object-cover object-center transition-opacity hover:opacity-80"
              alt={`gallery-image-${index + 1}`}
            />
          </div>
        ))}
      </div>
    </div>
  )
}

A tabbed gallery that organizes images into different categories or sections, enabling users to switch between different image sets using tabs, ideal for filtering or categorizing visual content.

image-photo-1
image-photo-2
image-photo-3
image-photo-4
image-photo-5
image-photo-6
"use client"

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

const IMAGES = [
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1737608734653-d1eaad541d46?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
]

const DATA = [
  {
    label: "HTML",
    value: "html",
    images: IMAGES,
  },
  {
    label: "React",
    value: "react",
    images: IMAGES,
  },
  {
    label: "Vue",
    value: "vue",
    images: IMAGES,
  },
  {
    label: "Angular",
    value: "angular",
    images: IMAGES,
  },
  {
    label: "Svelte",
    value: "svelte",
    images: IMAGES,
  },
]

export function GalleryWithTab() {
  return (
    <Tabs defaultValue="html" className="w-full">
      <TabsList className="w-full">
        {DATA.map(({ label, value }) => (
          <TabsTrigger key={value} value={value} className="flex-1">
            {label}
          </TabsTrigger>
        ))}
      </TabsList>
      {DATA.map(({ value, images }) => (
        <TabsContent
          key={value}
          value={value}
          className="grid grid-cols-2 gap-4 md:grid-cols-3"
        >
          {images.map((imageLink, index) => (
            <div key={index}>
              <img
                className="h-40 w-full max-w-full rounded-lg object-cover object-center"
                src={imageLink}
                alt={`image-photo-${index + 1}`}
              />
            </div>
          ))}
        </TabsContent>
      ))}
    </Tabs>
  )
}

A Pinterest-style masonry layout gallery that displays images in multiple columns with varying heights, creating an organic and visually interesting grid that maximizes space utilization.

gallery-photo-1
gallery-photo-2
gallery-photo-3
gallery-photo-3
gallery-photo-4
gallery-photo-5
gallery-photo-6
gallery-photo-7
gallery-photo-8
gallery-photo-9
gallery-photo-10
gallery-photo-11
const IMAGES = [
  {
    src: "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-64",
  },
  {
    src: "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-48",
  },
  {
    src: "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-72",
  },
  {
    src: "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-56",
  },
  {
    src: "https://images.unsplash.com/photo-1737608734653-d1eaad541d46?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-52",
  },
  {
    src: "https://images.unsplash.com/photo-1571028634586-ae87c1a42432?w=700&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8YmxhY2slMjBhbmQlMjB3aGl0ZXxlbnwwfHwwfHx8MA%3D%3D",
    height: "h-60",
  },
  {
    src: "https://images.unsplash.com/photo-1499428665502-503f6c608263?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    height: "h-80",
  },
  {
    src: "https://images.unsplash.com/photo-1532517308734-0565178471d2?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    height: "h-44",
  },
  {
    src: "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-68",
  },
  {
    src: "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-56",
  },
  {
    src: "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-64",
  },
  {
    src: "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
    height: "h-52",
  },
]

export function MasonryGridGallery() {
  return (
    <div className="grid grid-cols-2 gap-3 md:grid-cols-4 md:gap-4">
      <div className="grid gap-3 md:gap-4">
        {IMAGES.slice(0, 3).map((image, index) => (
          <div
            key={index}
            className="group relative overflow-hidden rounded-lg shadow-md transition-all duration-300 hover:shadow-xl"
          >
            <img
              className={`${image.height} h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110`}
              src={image.src}
              alt={`gallery-photo-${index + 1}`}
            />
            <div className="absolute inset-0 bg-black/0 transition-colors duration-300 group-hover:bg-black/10" />
          </div>
        ))}
      </div>
      <div className="grid gap-3 md:gap-4">
        {IMAGES.slice(3, 6).map((image, index) => (
          <div
            key={index}
            className="group relative overflow-hidden rounded-lg shadow-md transition-all duration-300 hover:shadow-xl"
          >
            <img
              className={`${image.height} h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110`}
              src={image.src}
              alt={`gallery-photo-${index + 3}`}
            />
            <div className="absolute inset-0 bg-black/0 transition-colors duration-300 group-hover:bg-black/10" />
          </div>
        ))}
      </div>
      <div className="grid gap-3 md:gap-4">
        {IMAGES.slice(6, 9).map((image, index) => (
          <div
            key={index}
            className="group relative overflow-hidden rounded-lg shadow-md transition-all duration-300 hover:shadow-xl"
          >
            <img
              className={`${image.height} h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110`}
              src={image.src}
              alt={`gallery-photo-${index + 6}`}
            />
            <div className="absolute inset-0 bg-black/0 transition-colors duration-300 group-hover:bg-black/10" />
          </div>
        ))}
      </div>
      <div className="grid gap-3 md:gap-4">
        {IMAGES.slice(9, 12).map((image, index) => (
          <div
            key={index}
            className="group relative overflow-hidden rounded-lg shadow-md transition-all duration-300 hover:shadow-xl"
          >
            <img
              className={`${image.height} h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110`}
              src={image.src}
              alt={`gallery-photo-${index + 9}`}
            />
            <div className="absolute inset-0 bg-black/0 transition-colors duration-300 group-hover:bg-black/10" />
          </div>
        ))}
      </div>
    </div>
  )
}

A simple two-by-two grid gallery layout that displays four images in a compact square format, perfect for showcasing a small selection of featured images or creating visual balance.

gallery-photo-1
gallery-photo-2
gallery-photo-3
gallery-photo-4
const IMAGES = [
  "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1661327930345-9c6714b603b3?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1578508637199-240a8f25eff6?auto=format&fit=crop&q=80&w=400&h=400",
  "https://images.unsplash.com/photo-1559745482-57bfa9ca5a8a?auto=format&fit=crop&q=80&w=400&h=400",
]

export function QuadGallery() {
  return (
    <div className="grid grid-cols-2 gap-2">
      {IMAGES.map((imageLink, index) => (
        <div key={index}>
          <img
            className="h-40 w-full max-w-full rounded-lg object-cover object-center md:h-60"
            src={imageLink}
            alt={`gallery-photo-${index + 1}`}
          />
        </div>
      ))}
    </div>
  )
}

Best Practices

  • Use appropriate image sizes and formats for optimal performance
  • Provide meaningful alt text for accessibility
  • Consider lazy loading for galleries with many images
  • Use responsive grid layouts that adapt to different screen sizes
  • Implement image optimization techniques to reduce load times
  • Consider adding lightbox functionality for full-screen image viewing
  • Use consistent aspect ratios when possible for a cleaner appearance
  • Provide clear visual feedback for interactive elements like thumbnails

Gallery components provide flexible and visually appealing ways to display image collections. They're ideal for portfolios, product showcases, photo galleries, and any interface where visual content needs to be organized and presented effectively.