Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Media

Image Gallery

Media

Image Gallery React component for Tailwind CSS v4

Image CropperImage Lightbox
Live props
Layout
layout
gap
aspectRatio
targetRowHeight240px
Appearance
radius
showCaptions
hoverZoom
skeleton
Filters
filters
filterVariant
defaultFilter
Behavior
lightbox
Loading
hasMore
loadMode
loading
Code
import { ImageGallery } from "@/components/ui/image-gallery"

<ImageGallery />
  1. Home
  2. Components
  3. Media
  4. Image Gallery
Pro

Image Gallery React component for Tailwind CSS v4

v1.0.0·Added in 1.14.0

A React image gallery with masonry, cropped grid and justified rows layouts, tag filters, a lightbox and load more.

Photos fade in over a shimmering placeholder that already has their shape, so nothing jumps while they load. Masonry keeps each photo's shape and fills the shortest column, so the order still reads left to right; grid crops every photo to one ratio; rows packs justified rows like a photos app, every row one height and edge to edge. Tap a filter chip and the photos that leave fade out while the rest glide into their new places on a spring (a plain fade under reduced motion); on a phone the chips sit in one row you can swipe sideways. Tap a photo, or press Enter on it, and it grows into the full-screen lightbox, then flies back into its tile on close. The photos are one Tab stop: Left / Right move to the previous / next photo, Up / Down to the one above or below, Home / End to the first and last. Captions show on hover and focus with a mouse and stay visible on touch screens. With hasMore a Load more button appears (it spins while the next page loads), or with loadMode inView the next page loads as you scroll near the end. Phones get two columns by default.

Image Gallery is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add image-gallery, together with Image Lightbox, Toggle Group, Skeleton, Button, useControllableState and 3 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add image-gallery

Image Gallery is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import ImageGallery from @/components/ui/image-gallery.

import { ImageGallery, type GalleryImage } from "@/components/ui/image-gallery"

const photos: GalleryImage[] = [
  { src: "/photos/brooklyn-loft.jpg", alt: "Loft living room", width: 1600, height: 1000, caption: "Loft, Brooklyn", tags: ["Interiors"] },
  { src: "/photos/glacier-trail.jpg", alt: "Mountain trail", width: 1000, height: 1500, caption: "Glacier National Park", tags: ["Travel"] },
  { src: "/photos/road-bike.jpg", alt: "Steel road bike", width: 1200, height: 1200, caption: "Road bike", tags: ["Gear"] },
]

export function Portfolio() {
  return <ImageGallery images={photos} layout="masonry" filters showCaptions="hover" />
}

Props

  • images*
    GalleryImage[]

    { src, alt, width?, height?, caption?, tags?, thumbnail?, srcSet?, sizes?, id? }. Pass width and height so each tile reserves its shape before the file loads; without them a photo uses aspectRatio until it loads.

  • layoutdefault "masonry"
    "masonry" | "grid" | "rows"

    masonry keeps every photo's shape in balanced columns, grid crops them to aspectRatio, rows fills justified rows of one height computed from the photo ratios.

  • columnsdefault "{ base: 2, md: 3, lg: 4 }"
    number | { base?: number; sm?: number; md?: number; lg?: number; xl?: number }

    Masonry and grid columns, 1 to 6, per breakpoint (sm 640px, md 768px, lg 1024px, xl 1280px). A number is one count everywhere. Two on phones, three on tablets and four on desktops by default.

  • gapdefault "md"
    "none" | "sm" | "md" | "lg"

    Space between photos: 0, 4, 8 or 16px.

  • aspectRatiodefault "1/1"
    string | number

    The grid layout's crop, as "4/3", "4:3" or a number. Masonry and rows use it only for photos without width and height until they load.

  • targetRowHeightdefault 240
    number

    Rows layout: the height each row aims for in px. Rows stretch a little to fill the width; below 640px they aim for 60% of it.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Tile corners: 0, 8, 12 or 16px; full falls back to lg like every surface.

  • showCaptionsdefault "hover"
    "hover" | "always" | "none"

    Captions over the bottom of each photo: on hover and keyboard focus (always visible on touch screens), always, or never. The lightbox shows them either way.

  • hoverZoomdefault true
    boolean

    The photo zooms in 4% inside its tile on hover.

  • skeletondefault "shimmer"
    "shimmer" | "pulse" | "none"

    The placeholder under each photo until it loads.

  • filtersdefault false
    boolean | string[]

    Tag chips above the photos with an All chip first. true collects the tags from the images in order; an array sets which tags show and their order.

  • filterVariantdefault "outline"
    "outline" | "soft" | "ghost"

    The look of the filter chips (the toggle group's variants).

  • filter
    string

    The active tag, controlled; "" shows all. Tags that are not in the chips count as all.

  • defaultFilterdefault ""
    string

    The tag active on the first render, uncontrolled. The playground remounts the gallery when it changes.

  • onFilterChange
    (filter: string) => void

    Called with the new tag, or "" for All.

  • lightboxdefault true
    boolean

    A tap or Enter opens the full-screen ImageLightbox at that photo, zooming out of the tile. Off, tiles are buttons only when onImageClick is set.

  • lightboxProps
    Omit<ImageLightboxProps, "images" | "open" | "onOpenChange" | "index" | "onIndexChange" | "getOrigin">

    Passed to the lightbox: loop, zoom, download, share, showThumbnails, labels and the rest.

  • onImageClick
    (image: GalleryImage, index: number) => void

    Called when a photo is tapped or opened with the keyboard, before the lightbox opens. index is its place among the visible photos.

  • hasMoredefault false
    boolean

    More photos exist: shows the Load more button (and, with loadMode inView, loads them as the end scrolls into view).

  • loadModedefault "button"
    "button" | "inView"

    button waits for a tap on Load more; inView calls loadMore when the end of the gallery comes within 400px of the screen, and keeps the button as a fallback.

  • loadMore
    () => void | Promise<unknown>

    Fetches the next page. Return a promise and the button spins until it settles; never called twice at once.

  • loadingdefault false
    boolean

    The next page is on its way (for a loadMore that does not return a promise): the button spins and in-view loading pauses.

  • labels
    Partial<ImageGalleryLabels>

    Every string: gallery, filters, all, loadMore, loading, empty, emptyFiltered, error, results(count) for the live region and openImage(index, total, alt) for each tile.

  • classNames
    Partial<Record<"filters" | "layout" | "item" | "tile" | "image" | "skeleton" | "caption" | "empty" | "footer" | "loadMore", string>>

    Extra classes for the inner parts: the chip row, the layout container, each list item, the tile button, the photo, its placeholder, the caption, the empty state and the load more row and button.

  • className
    string

    Extra classes for the root, merged last.

Parts

  • Masonry

    component

    The balanced masonry layout on its own, for any keyed children: cards, testimonials, notes. Each child goes to the shortest column in order and glides to its new place when the set changes; the server renders css columns, so the first paint already has the right columns. Takes div props.

    • columnsdefault "{ base: 2, md: 3, lg: 4 }"
      number | { base?: number; sm?: number; md?: number; lg?: number; xl?: number }

      Columns per breakpoint, 1 to 6.

    • gapdefault "md"
      "none" | "sm" | "md" | "lg" | number

      A step of the gap scale (0 / 4 / 8 / 16px) or px.

    • animatedefault true
      boolean

      Items glide on a spring when children come and go; off under reduced motion.

    • classNames
      Partial<Record<"item", string>>

      Extra classes for the wrapper around each child.

  • GalleryImage

    type

    One photo: { id?, src, alt, width?, height?, caption?, tags?, thumbnail?, srcSet?, sizes?, downloadName? }.

  • ImageGalleryLabels

    type

    The strings the gallery shows and announces.

  • DEFAULT_IMAGE_GALLERY_LABELS

    constant

    The English labels, to spread and override for another language.

Examples

Masonry with filters

Tag chips with an All chip; photos that stay glide into place while the rest fade out.

Justified rows

Rows of one height that fill the width, computed from each photo's ratio.

Square grid

Cropped to 1:1 with square corners: three columns on phones, six on desktops.

Load on scroll

loadMode inView fetches the next page as the end scrolls near; the button stays as a fallback.

Masonry for any content

The Masonry part on its own, balancing review cards of any height.

Pick a photo

lightbox off with onImageClick: the tiles become a picker for a listing cover.

Dependencies

motionlucide-reactimage-lightboxtoggle-groupskeletonbuttonuse-controllable-stateuse-media-querymotionui-config

Files

  • components/ui/image-gallery.tsx

FAQ

Is the React Image Gallery component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Image Gallery?

Run npx wingo-ui@latest add image-gallery. It also adds the Wingo UI items it builds on (Image Lightbox, Toggle Group, Skeleton, Button, useControllableState and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/image-gallery.

How does Image Gallery behave on phones?

Tap a filter chip and the photos that leave fade out while the rest glide into their new places on a spring (a plain fade under reduced motion); on a phone the chips sit in one row you can swipe sideways.

See also

Image LightboxProduct galleryCarouselToggle GroupSkeleton

More React media components

PDF ViewerStep PlayerQR CodePowerPoint ViewerVideo PlayerImage Cropper
All 17 React media components
PreviousImage CropperNext Image Lightbox

16 photos