PandaProvider
Root of every widget: tenant public key, API origin, visitor session, auth dialog, user drawer and the theme (--panda-* tokens on a layout-neutral [data-panda-root]; @panda/ui variable aliases only inside widget roots…
Anatomy
Every piece of PandaProvider as it nests in code. A root holds the data and state; its parts and hooks only work inside it.
<PandaProvider>Mounted once in app/layout.tsx<PandaOpen />complete widgetSets how things open (open designs: listing, building, development, auth, account, collection, collectionPicker) for everything inside it,…
</PandaProvider>Installation
- 1npm install <tarball or @pandaidx/widgets>Terminalnpm install https://github.com/pandaidx/panda-widgets/releases/download/v1.14.1/pandaidx-widgets-1.14.1.tgz
- 2import "@pandaidx/widgets/styles.css" in app/layout.tsx
- 3Wrap the site in <PandaProvider publicKey apiUrl preset theme> (app/layout.tsx)
- 4In each page (Server Component) call the widget's server loader, then render the widget with its data
- 5Allow the images: next.config images.remotePatterns [{ protocol: "https", hostname: "**" }] (listing and blog photos come from several CDNs)
Usage
// app/layout.tsx (Server Component)import "@pandaidx/widgets/styles.css"import { PandaProvider } from "@pandaidx/widgets/client"export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en"><body><PandaProviderpublicKey={process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!}apiUrl={process.env.NEXT_PUBLIC_PANDA_API_URL}preset="template1"theme={{ colors: { primary: "#0b5fff" }, radius: { card: "12px" } }}searchPath="/properties">{children}</PandaProvider></body></html>)}
API reference
PandaProvider
Root of every widget: tenant public key, API origin, visitor session, auth dialog, user drawer and the theme (--panda-* tokens on a layout-neutral [data-panda-root]; @panda/ui variable aliases only inside widget roots and the overlay container). Mount once in the root layout. Fields follow ONE field definition, DESIGN.md components.field (height, heightSm, heightLg, radius, paddingX, paddingXLg, background, border, focusBorder, foreground, placeholder, fontSize, fontSizeSm, fontSizeLg, fontLg, iconSize, iconInset, textareaMinHeight, labelSize, labelWeight, labelTracking, checkboxRadius, variant, variantLg): every input, select, phone, textarea, date/time field, checkbox, radio, form field and the HomeValuation address bar of every widget reads it (filters use heightSm, size="lg" forms heightLg and variantLg, e.g. underline for conversational forms); declare the keys there, never restyle widget fields in app/globals.css. Control corners (inputs, selects, segmented tabs, checkboxes: the All Filters dialog, sign-in, forms) follow ONE token, the site's field radius: DESIGN.md rounded.input → --panda-radius-control (theme.radius.control for a deliberate one-off). There is no prop for it and nothing to style: change that single value (0px square, 6px soft); fields with input.variant: underline are always square, so use outline or filled to see it. Checkboxes take 0.625× of it (8px → 5px, 0px stays square); --panda-checkbox-radius on the widget root overrides only them. Buttons follow rounded.button, cards rounded.card, dialogs rounded.dialog. If the corners do not change, something outside the widget overrides the token: delete any [data-panda-widget] .rounded-* or button[role="checkbox"] rule in app/globals.css (the widgets already map their own radius scale onto the token, so such a rule only pins the corners), or, on a page under a PandaDefaultLook wrapper (it ignores DESIGN.md), set both control of PANDA_DEFAULT_THEME_RADIUS and --panda-host-radius-control of PANDA_DEFAULT_RADII to var(--ds-radius-input) in components/site/panda-default-look.tsx.
Import from @pandaidx/widgets/client
| Prop | Type | Default | Description |
|---|---|---|---|
| publicKey* | string | — | Tenant public key pk_<applicationId>_<signature> (safe in the browser). |
| apiUrl | string | "https://api.pandaidx.com" | Panda API origin. |
| children* | node | — | The site; widgets anywhere below read the provider. |
| preset | "default" | "template1" | "default" | Starting look of every widget: default (neutral Panda, rounded, solid pill buttons) or template1 (charcoal, square, serif headings, outlined uppercase buttons). |
| theme | object | — | Token overrides merged over the preset (see theme.input). Example: { colors: { primary: "#0b5fff" }, radius: { card: "16px" }, font: { heading: "var(--font-serif)" } }. |
| variant | "default" | "template1" | — | Deprecated alias of preset. |
| searchPath | string | "/search" | Host page of <PropertySearch>; the drawer opens saved searches there. |
| privacyLink | string | "/privacy" | Privacy link in the auth dialog. |
| termsLink | string | "/terms" | Terms link in the auth dialog. |
| companyName | string | — | Company name in the auth dialog consent copy (default: the account owner's name). |
| dashboardUrl | string | "https://app.pandaidx.com" | Dashboard origin (Google sign-in bridge, My Account). |
| mapTilesUrl | string | — | Map style.json URL for every widget map (default: bundled Panda style on OpenFreeMap tiles). |
| map | object | — | Look of every widget map (search, detail, buildings, valuation), on the bundled style. AI Builder sites set it in DESIGN.md / app/design-system.json map: { colors, layers, pins } (it reaches theme.map; the layout is managed); a host can pass it here. It paints the base map with the site palette and choose what shows. One map only: <SearchMap look={…} />. Price pins: the map.pin part variables (--panda-map-pin-bg/fg/active-bg/active-fg). Also preset (panda | minimal | streets), shape (the drawn / applied area: stroke, fill, fillOpacity, width, dash), banner (hint strip while drawing: bg, fg, height, radius) and controls (pills over the map: bg, fg, radius, shadow). Recipe: water and parks from DESIGN.md, layers: { pois: false, buildings: false } for a quiet map, { airports: true, transit: true } for a detailed one. |
| toaster | boolean | true | Renders the toast region widgets notify through (link copied, search saved). |
| locale | "en" | "es" | "br" | "it" | "ru" | "fr" | "sr" | "de" | "cn" | "pl" | "ro" | "tr" | "en" | Language of the widget copy (filters, listing facts, schools, buildings…): the 12 Panda website locales, same short codes (br = Portuguese (Brazil), cn = Chinese (Simplified)). Non-English dictionaries load on demand and fall back to English key by key. Match it to the page language (e.g. the /es/… routes of a multi-language site). |
| dictionary | object | — | Copy overrides in the website dictionary shape, winning over locale for the keys they set. Example: { filters: { beds: "Bedrooms" }, mls: { requestShowing: "Book a tour" } }. |
| listingSheetPath | string | "/mls/{id}/sheet" | Host route of the printable MLS sheet linked from every panda listing detail ({id} = listing path segment); false hides the MLS Sheet button. |
| profilePath | string | "/profile" | Agent profile page the agent card of the panda listing detail links to. |
| propertyDetail | object | — | Site-wide design of every Panda listing detail (PropertyDetail variant="panda", PropertyDetailRoot and the listing dialog the cards of PropertySearch / ListingGrid / AgentProfile open). Empty = the websites detail. PropertyDetail design and PropertySearch detail={{ design }} merge over it group by group. Example: { gallery: { variant: "grid" }, actions: { style: "icon", placement: "gallery" }, agent: { placement: "inline" }, sectionStyle: "flat" }. |
| open | object | — | How details open across the site, per domain: listing (cards, map pins, ?listing=), building (condo directory, ?condo=), development (new developments, ?project=). Example: { listing: { mode: "sheet", size: "md" }, development: { mode: "dialog" } }. A nested PandaProvider (e.g. a default-look wrapper) inherits it unless it sets its own; a widget's own open prop overrides both. Never build a hand-made dialog for a detail: set this instead. |
| authDialog | object | — | Arrangement and classes of the auth dialog every gated action opens (likes, Save Search, openAuth). By default it already follows the theme: dialog header (dialog.*), the Login / Sign Up pill track (auth.tabs part), fields (input.*, Continue with Google takes the field height and corners) and the submit (button.*). Example: { design: { tabs: false, providers: "top", dividerLabel: "or", labels: { loginTitle: "Welcome back" } }, classNames: { title: "text-3xl" } }. |
| buildingDetail | object | — | Site-wide design of every Panda building detail (BuildingDetail variant="panda" and the building dialog of CondoDirectory variant="panda" / PandaDirectoryRoot). Empty = the websites detail. Example: { gallery: { variant: "cover" } }. |
| propertyDetailTheme | object | — | Theme of every listing detail dialog when it must differ from this provider's: { preset, theme } as on the site's own PandaProvider. Use it on a nested provider that runs another look (e.g. the default search look) so the detail, and the dialogs it opens (Request Showing, Share), look the same wherever a card is opened. Empty = this provider's theme. |
| drawer | object | — | Visitor drawer (collections, saved searches, likes). |
PandaOpen
Sets how things open (open designs: listing, building, development, auth, account, collection, collectionPicker) for everything inside it, over <PandaProvider open>. AI Builder sites: the provider lives in the managed layout, so wrap the shell in app/site-shell.tsx — e.g. <PandaOpen value={{ account: { mode: "page", href: "/account" } }}> sends Collections / Saved / Likes / My Account to your /account page (render <PandaAccountRoot shell="page"> there).
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | object | — | Open designs by domain: { mode: "dialog" | "sheet" | "page" | "none", href, size, side, deepLink } (see PandaProvider open). |
| children* | node | — | The subtree (the site shell). |