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

Blocks

Listing Detail Page

Blocks

Listing Detail Page React block for Tailwind CSS v4

KPI DashboardLogin Page
Live props
State
defaultFavorite
status
defaultMessageOpen
loading
Layout
galleryThumbnails
showMap
showSafety
stickyBar
Content
locale
currency
Appearance
tone
radius
Behavior
animateIn
Code
import { ListingDetailPage } from "@/components/blocks/listing-detail-page"

<ListingDetailPage />
  1. Home
  2. Components
  3. Blocks
  4. Listing Detail Page
Pro

Listing Detail Page React block for Tailwind CSS v4

v1.0.2·Updated in 1.9.0

A React listing detail page for a classifieds marketplace: photo gallery, price card, seller, map, contact actions and similar listings.

Swipe or arrow through the photos (thumbnails under the stage or beside it, a counter on phones); hover zooms with a mouse, a click, tap or Enter opens the full screen viewer and Escape closes it. The heart on the photo and Save in the price card save the listing together, at once; a rejected onFavoriteChange rolls both back with a toast. The share button uses the system share sheet where there is one, otherwise it copies the link (Link copied). Send message opens a dialog (a bottom sheet on phones) with the listing, quick replies as tags that fill the text, and the message field: Send or Cmd/Ctrl+Enter sends, an empty message shows its error, a failed send shows an alert and keeps the text; success turns the dialog into Message sent with Go to the conversation. Show phone shows the masked number, fetches the full one through onRevealPhone and fades it in as a tel: link (announced); the sticky bar's Call reveals and dials in one tap. Read more expands the description (clamped at 8 lines); URLs in it stay plain text. The map is read only with the approximate area; on touch a tap opens it full height. Report this listing opens a dialog with the reasons as radio cards and optional details. Similar listings are a swipeable rail with arrows in its header. Below 1024px the title, price and contact buttons sit under the photos, the seller card follows the description, and once those buttons scroll away a bar with the price, Call and Message stays at the bottom. Reserved shows a warning banner; sold shows a neutral banner, disables the contact buttons and puts the similar listings first. Sections rise in once on mount; reduced motion skips it.

Listing Detail Page is a Pro React block in the React blocks category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add listing-detail-page, together with Alert, Badge, Breadcrumb, Button, Carousel and 24 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 listing-detail-page

Listing Detail Page 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 ListingDetailPage from @/components/blocks/listing-detail-page.

// app/(market)/listing/[slug]/page.tsx (the shell lives in app/(market)/layout.tsx; mount <Toaster /> once there)
"use client"

import { useRouter } from "next/navigation"
import { ListingDetailPage } from "@/components/blocks/listing-detail-page"

export default function ListingPage() {
  const router = useRouter()
  return (
    <ListingDetailPage
      onMessage={(text) => fetch("/api/messages", { method: "POST", body: JSON.stringify({ listing: "l01", text }) })}
      onRevealPhone={() => fetch("/api/listings/l01/phone").then((response) => response.text())}
      onFavoriteChange={(favorite) => fetch("/api/favorites/l01", { method: favorite ? "PUT" : "DELETE" })}
      onOpenConversation={() => router.push("/messages?listing=l01")}
    />
  )
}

Props

  • listing
    ListingDetail

    The listing (LISTING_DETAIL_SAMPLE.listing: l01, Trek FX 2 city bike, $280 negotiable, Portland Pearl District). ListingData without attributes plus images (ProductGalleryImage[]), description (plain text, blank lines split paragraphs), attributes (DescriptionListItem[]), views, favorites, adId, place { city, area, point, radiusKm }, courier { name, price } and the breadcrumb path.

  • seller
    SellerData

    The seller card (LISTING_DETAIL_SAMPLE.seller: Daniel Reed, 4.8 from 126 reviews, replies within an hour). Its name labels the contact buttons.

  • sellerListings
    ListingData[]

    The seller's other listings in the seller card, up to three (LISTING_DETAIL_SAMPLE.sellerListings).

  • similar
    ListingData[]

    The similar listings rail (LISTING_DETAIL_SAMPLE.similar, 8 listings around Portland). An empty array hides the section.

  • favorite
    boolean

    Saved to favorites, controlled; pair with onFavoriteChange.

  • defaultFavoritedefault false
    boolean

    Saved to favorites at first, uncontrolled.

  • onFavoriteChange
    (favorite: boolean) => (void | Promise<unknown>)

    A heart was toggled. The change shows at once; a rejected promise rolls it back and shows a toast.

  • onMessage
    (text: string) => (void | Promise<unknown>)

    Sends the dialog's message. A promise drives the Send spinner; a rejection shows its message in the dialog. Default: a pretend 900 ms request.

  • onRevealPhone
    () => (string | Promise<string>)

    Fetches the full number when Show phone or Call is pressed. Default: resolves "(503) 555-0142" after 500 ms.

  • onCall
    (phone: string) => void

    Replaces the tel: link once the number is shown (the demo shows a toast).

  • onShare
    () => void

    The share buttons. Default: navigator.share where available, else copies the listing link and shows Link copied.

  • onReport
    (report: ListingReport) => (void | Promise<unknown>)

    Sends the report dialog (ListingReport = { reason, details }). A rejection shows an alert in the dialog. Default: a pretend 900 ms request, then a thank you toast.

  • onOpenConversation
    () => void

    Go to the conversation after a sent message. Default: navigates to /messages?listing=<id> through the link provider.

  • statusdefault "active"
    "active" | "reserved" | "sold"

    reserved: a warning banner, contact still allowed. sold: a neutral banner, contact disabled, grey photos and similar ads first.

  • galleryThumbnailsdefault "bottom"
    "start" | "bottom" | "none"

    Where the gallery's thumbnails sit on wide screens; phones swipe with a counter.

  • showMapdefault true
    boolean

    The Location section with the map.

  • showSafetydefault true
    boolean

    The Stay safe note under the seller.

  • stickyBardefault true
    boolean

    Below 1024px, the bottom bar with the price, Call and Message once the contact buttons scroll away.

  • defaultMessageOpendefault false
    boolean

    Opens the message dialog on mount.

  • labels
    Partial<ListingDetailPageLabels>

    Every visible string (DEFAULT_LISTING_DETAIL_PAGE_LABELS, English), counted ones as functions; nested gallery, lightbox, price, favorite, listingCard, carousel, sellerCard, map, stickyBar and breadcrumbLabels for the composed items. LISTING_DETAIL_PAGE_LABELS_EN is the same set under its older name.

  • localedefault "en-US"
    string

    Intl locale for prices, counts and dates.

  • currencydefault "USD"
    string

    The currency of amounts without their own (the demo converts the sample to EUR).

  • now
    Date | number

    The clock for Today at 2:32 PM. Default: useNow() (the date while hydrating); the demo passes Sep 24, 2026, 4:40 PM.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Send message, the sticky bar, the map pin, the quick replies and the hearts (neutral hearts are red).

  • radiusdefault "md"
    "sm" | "md" | "lg"

    The surfaces: gallery, price card, seller card, map and listing cards 12 / 16 / 24px.

  • loadingdefault false
    boolean

    The gallery, the texts, the price card, the seller and the similar ads as skeletons of their real shape.

  • animateIndefault true
    boolean

    Sections rise in once when the page mounts on the client (never over server HTML, never with reduced motion).

  • linkComponent
    (props: LinkComponentProps) => ReactNode

    The router link for the breadcrumb, the seller and the listing cards. Default: the provider's (the shell's), then <a>.

  • className
    string

    Classes for the root.

  • classNames
    Partial<Record<"breadcrumb" | "banner" | "gallery" | "summary" | "attributes" | "description" | "location" | "report" | "aside" | "priceCard" | "seller" | "safety" | "similar" | "stickyBar", string>>

    Classes for the sections.

Parts

  • DEFAULT_LISTING_DETAIL_PAGE_LABELS

    constant

    The English strings for every visible label, the default of labels. Type: ListingDetailPageLabels.

  • LISTING_DETAIL_PAGE_LABELS_EN

    constant

    The same English strings under their older name, kept for existing imports.

  • LISTING_DETAIL_SAMPLE

    constant

    The demo data in USD: the Trek FX 2 city bike listing, its seller, three seller listings and eight similar ones.

  • LISTING_DETAIL_SAMPLE_EN

    constant

    The same sample priced in EUR, converted at LISTING_DETAIL_USD_PER_EUR (the currency example).

  • LISTING_DETAIL_APARTMENT_SAMPLE

    constant

    A second sample: a 2-bedroom apartment in Portland listed by a real estate agency.

  • createListingDetailSample

    function

    Builds the bike sample: ({ currency?: "USD" | "EUR" }) => ListingDetailSample.

  • createListingDetailApartmentSample

    function

    Builds the apartment sample in USD or EUR, same options as createListingDetailSample.

  • LISTING_DETAIL_NOW

    constant

    The demo clock (Sep 24, 2026, 4:40 PM) as a timestamp, passed as now so relative dates stay stable.

  • LISTING_DETAIL_USD_PER_EUR

    constant

    The rounded dollars per euro rate (1.09) used to convert the EUR sample.

  • ListingDetailPageProps

    type

    Props of ListingDetailPage, including the div attributes of the root.

  • ListingDetail

    type

    The listing shape: ListingData plus images, description, attributes, views, place, courier and breadcrumb.

  • ListingReport

    type

    What onReport receives: { reason: string; details: string }.

Examples

Open

Message dialog

Quick replies fill the message; send it to see the Message sent view.

Open

Reserved

A warning banner; messaging and calling still work.

Open

Sold

A neutral banner, contact disabled and similar listings first.

Open

Apartment from an agency

Greenleaf Realty as a company seller, the price per sq ft, a side thumbnail rail and brand actions.

Open

Loading

Gallery, text, price card and seller as skeletons of their real shape.

Open

Euro prices

en-GB formatting and the same listing priced in euro.

Dependencies

motionlucide-reactalertbadgebreadcrumbbuttoncarouselcollapsibledescription-listdialogfavorite-buttonimage-lightboxlisting-cardlocation-mappriceproduct-galleryradio-groupseller-cardskeletonsticky-cta-bartagtextareatoastcommercemotionnavigationnumber-formatrelative-timero-locationstonesui-config

Files

  • components/blocks/listing-detail-page.tsx
  • components/blocks/listing-detail-page-data.ts

FAQ

Is the React Listing Detail Page block free?

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

How do I install Listing Detail Page?

Run npx wingo-ui@latest add listing-detail-page. It also adds the Wingo UI items it builds on (Alert, Badge, Breadcrumb, Button, Carousel and 24 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/listing-detail-page.

How does Listing Detail Page behave on phones?

Swipe or arrow through the photos (thumbnails under the stage or beside it, a counter on phones); hover zooms with a mouse, a click, tap or Enter opens the full screen viewer and Escape closes it.

See also

Marketplace ShellMarketplace Home PageMarketplace Search PageProduct gallerySeller cardListing card

More React blocks

Order History PagePost Listing WizardPricing PageProfile PageRegister PageSettings Page
All 26 React blocks

Articles using this component

  • Shadcn Ecommerce Template: Five Screens That Work on Phones

    Build it: screens and apps · Nov 6, 2026 · 11 min read

  • React Marketplace Template That Works Like an App

    Build it: screens and apps · Oct 16, 2026 · 15 min read

PreviousKPI DashboardNext Login Page