vetir-style-this-piece
v2.0.3
Published
AI-powered outfit suggestions React component. Contact [email protected] for API token, free usage, and integration support.
Maintainers
Readme
vetir-style-this-piece
A React component that adds AI outfit suggestions and Virtual Try-On to any product page.
Works with Next.js, Vite, Create React App, Shopify Liquid themes, or any React 16.8+ project.
What it does
- A banner button appears on your product page.
- The shopper clicks — outfits stream in as a modal overlay (default) or inline on the page (
resultsDisplay="inline"). - Each card renders a category-aware canvas: clothing on the left, accessories/shoes arranged on the right.
- Each card can show a Try On button. The shopper sees themselves wearing the full outfit directly on the card — no page navigation.
Get API access
Email [email protected] for:
- Partner API token (
token) - Stylist API base URL (
apiBaseUrl) - Try-on endpoint URLs (
tryOnUrl,tryOnVideoUrl) - Avatar upload endpoint URL (
avatarUploadUrl) - Avatar fetch endpoint URL (
avatarFetchUrl) — optional, pre-fills the manage-avatar form
Install
npm install vetir-style-this-pieceImport the stylesheet once in your app entry file:
// Next.js: pages/_app.js or app/layout.js
// Vite / CRA: src/main.jsx or src/index.js
import "vetir-style-this-piece/style.css";Shopify (no React app)
Shopify themes use Liquid, not React. Build the standalone bundle and upload to theme assets:
npm run build:shopify
# → dist/vetir-stp.js + dist/vetir-stp.cssTest locally before uploading:
npm run test:shopify
# → http://localhost:3000/examples/shopify/test-local.htmlMount on the product page via Liquid:
{{ 'vetir-stp.css' | asset_url | stylesheet_tag }}
<script src="{{ 'vetir-stp.js' | asset_url }}" defer></script>
<div id="vetir-stp-root"></div>
<script>
VetirSTP.mount(document.getElementById('vetir-stp-root'), {
productId: '{{ product.metafields.custom.vetir_product_id }}',
userId: window.__vetirUserId,
token: '{{ settings.vetir_stp_token }}',
apiBaseUrl: 'https://api.vetirapp.com/s1/',
resultsDisplay: 'inline',
});
</script>| Guide | Use for |
|---|---|
| shopify_integration.md | Full step-by-step — build, upload, Liquid, metafields, cart, Marissa styling |
| SHOPIFY.md | Quick reference after setup |
| INTEGRATION_GUIDE.md | All props (StyleThisPieceProps) |
Minimum working example
import StyleThisPiece from "vetir-style-this-piece";
function ProductPage({ product, userId }) {
return (
<StyleThisPiece
productId={product.id}
userId={userId}
token={process.env.NEXT_PUBLIC_STP_TOKEN}
apiBaseUrl={process.env.NEXT_PUBLIC_STP_API_BASE_URL}
/>
);
}Banner appears; clicking it streams outfits into a centered modal overlay. No other setup needed.
Add Virtual Try-On
<StyleThisPiece
productId={product.id}
userId={userId}
token={process.env.NEXT_PUBLIC_STP_TOKEN}
apiBaseUrl={process.env.NEXT_PUBLIC_STP_API_BASE_URL}
showTryOn
tryOnUrl={process.env.NEXT_PUBLIC_STP_TRYON_URL}
tryOnVideoUrl={process.env.NEXT_PUBLIC_STP_TRYON_VIDEO_URL}
avatarUploadUrl={process.env.NEXT_PUBLIC_STP_AVATAR_UPLOAD_URL}
avatarGender="Female"
/>Each outfit card gets a Try On button. When clicked:
- An animated sparkle loader with cycling status text appears on that card only
- Result image fills the card canvas
- Action bar: Outfit (back) | ▶ (play video)
- Each card has independent state — trying card 1 doesn't affect card 2
First-time shoppers — avatar upload
If the shopper has no photo yet, the try-on API returns a statusCode 400 (avatar not found). When avatarUploadUrl is set, the library opens a built-in Manage Your Avatar modal:
- Shopper uploads a selfie (drag-and-drop or file picker)
- They pick gender, body type, skin tone, and hair colour
- Photo is uploaded to
avatarUploadUrl— the same sparkle loader plays while the avatar is created - Try-on retries automatically on the same outfit
Shoppers can also open this modal any time via the Manage Avatar button at the bottom of the results section (modal or inline).
Pass avatarFetchUrl to load existing avatar data when the modal opens (GET {avatarFetchUrl}/{userId}).
Results display — modal (default)
By default, results open in a modal overlay (portal to document.body). The modal closes when the shopper clicks the backdrop or the × button, which also resets the outfit list.
<StyleThisPiece
{...requiredProps}
modalPosition="center" // 'center' (default) | 'right' | 'left'
modalBackground="#ffffff" // any CSS colour — defaults to white
/>| modalPosition | Behaviour |
|---|---|
| 'center' | Centered overlay, fade-in animation, max-width 1020px |
| 'right' | Slides in from the right edge, full viewport height |
| 'left' | Slides in from the left edge, full viewport height |
Results display — inline
Render results directly in the page instead of a modal overlay. The banner is replaced by the outfit carousel once generation starts — useful when you want results to live inside your PDP layout.
<StyleThisPiece
productId={product.id}
userId={userId}
token={process.env.NEXT_PUBLIC_STP_TOKEN}
apiBaseUrl={process.env.NEXT_PUBLIC_STP_API_BASE_URL}
resultsDisplay="inline"
resultsTitle="Style This Piece"
resultsDescription="AI-curated outfits built around your selection."
inlineContainerStyle={{
border: "1px solid #e0e0e0",
marginTop: 16,
}}
/>Combine with inline outfit detail so clicking a card expands the detail panel below the carousel instead of opening another overlay:
<StyleThisPiece
{...requiredProps}
resultsDisplay="inline"
outfitDetailDisplay="inline"
showTryOn
tryOnUrl={process.env.NEXT_PUBLIC_STP_TRYON_URL}
avatarUploadUrl={process.env.NEXT_PUBLIC_STP_AVATAR_UPLOAD_URL}
onAddToBag={(product) => addToCart(product)}
/>| Prop | Default | Description |
|---|---|---|
| resultsDisplay | 'modal' | 'modal' — portal overlay; 'inline' — in-page, replaces banner |
| outfitDetailDisplay | 'modal' | 'modal' — detail overlay; 'inline' — panel below carousel |
| inlineContainerStyle | — | Style on the wrapper around inline results |
| inlineContainerClassName | — | Class on the wrapper around inline results |
When avatarUploadUrl is set, the Manage Avatar footer CTA appears below inline results as well as in the modal.
Headless mode — your own trigger UI
Don't want the built-in banner? Hide it with showBanner={false} and drive everything from your own button via a ref. Calling generate() hits the streaming API and the results modal opens automatically when the first outfits arrive.
import { useRef, useState } from "react";
import StyleThisPiece, { StyleThisPieceHandle } from "vetir-style-this-piece";
function ProductPage({ product, userId }) {
const stpRef = useRef<StyleThisPieceHandle>(null);
const [loading, setLoading] = useState(false);
return (
<>
<button onClick={() => stpRef.current?.generate()} disabled={loading}>
{loading ? "Generating…" : "Style this look"}
</button>
<StyleThisPiece
ref={stpRef}
showBanner={false}
onLoadingChange={setLoading}
onError={(msg) => toast.error(msg)}
productId={product.id}
userId={userId}
token={process.env.NEXT_PUBLIC_STP_TOKEN}
apiBaseUrl={process.env.NEXT_PUBLIC_STP_API_BASE_URL}
/>
</>
);
}Ref API (StyleThisPieceHandle)
| Method | Description |
|---|---|
| generate() | Calls the streaming API. Modal opens automatically on first results (inline results render in place in inline mode). Returns a promise that settles when the stream ends. |
| openModal() | Re-opens the results modal (modal mode; no-op until outfits exist). |
| closeModal() | Closes the modal / clears inline results and resets the outfit list. |
| openAvatarModal() | Opens the avatar management modal (requires avatarUploadUrl). Works even before any outfits are generated. |
Headless props
| Prop | Default | Description |
|---|---|---|
| showBanner | true | Set false to render no banner — trigger via ref instead |
| onError | — | Called with the error message when generation fails (config or stream error) |
| onLoadingChange | — | Called with true/false as the generation loading state changes |
The ref also works with the banner visible — e.g. trigger generation from a second location on the page, or open the avatar manager from your account menu.
Manage Avatar only
Render just an avatar management button — no banner, no outfit generation. Useful for account or profile pages.
<StyleThisPiece
userId={userId}
token={process.env.NEXT_PUBLIC_STP_TOKEN}
showAvatarOnly
avatarUploadUrl={process.env.NEXT_PUBLIC_STP_AVATAR_UPLOAD_URL}
avatarFetchUrl={process.env.NEXT_PUBLIC_STP_AVATAR_FETCH_URL}
manageAvatarText="Manage Avatar"
onAvatarUploaded={(url) => console.log("Avatar saved:", url)}
/>Outfit canvas layout
Each card renders product images in a category-aware absolute layout:
- Clothing (tops, bottoms, dresses) — positioned on the left; tops stacked above bottoms when both are present
- Accessories (belts, watches, earrings) — top-right
- Bags — mid-right
- Shoes — bottom-right
This works automatically from categoryId / subCategoryId / categoryName fields on each OutfitProduct. All items are scaled and centred to fit within the card — nothing clips or overflows.
Customisation examples
Modal position and colour
<StyleThisPiece
{...requiredProps}
modalPosition="right"
modalBackground="#1a1a1a"
/>Custom CTA copy and colours
<StyleThisPiece
{...requiredProps}
bannerText="Discover complete looks featuring this item."
ctaText="Get Styled"
bannerStyle={{ background: "#fff", borderColor: "#444" }}
ctaStyle={{ background: "#d4af37", color: "#000" }}
/>Card CTA button
<StyleThisPiece
{...requiredProps}
cardCtaText="Shop The Look"
onCardCta={(outfit) => openLookModal(outfit)}
/>Custom Try-On loading text
The loading overlay shows an animated sparkle loader (shared by image try-on, video try-on, and avatar creation) above the cycling status text below. Replace the text with your own:
<StyleThisPiece
{...requiredProps}
showTryOn
tryOnUrl={...}
tryOnLoadingTexts={["Analysing your style…", "Selecting pieces…", "Creating your look…"]}
tryOnVideoLoadingTexts={["Generating your video…", "Rendering frames…", "Finishing up…"]}
/>Restyle the loader itself via CSS — resize .stp-tryon-sparkle-loader or recolour the stars with .stp-tryon-sparkle svg path { fill: #yourcolor; } (defaults to near-black #212427).
Item tile style override
<StyleThisPiece
{...requiredProps}
itemStyle={{ borderRadius: 4, background: '#f5f5f5' }}
/>Outfit detail — click a card canvas
Clicking an outfit card canvas opens a built-in Outfit Details panel (modal by default). Set outfitDetailDisplay="inline" to show it below the carousel instead.
<StyleThisPiece
{...requiredProps}
onAddToBag={(product) => addToCart(product)}
addToBagText="Add To Bag"
outfitDetailProps={{
title: "Outfit Details",
itemsTitle: "Items in this outfit",
gridCols: 2,
}}
/>Set showOutfitDetail={false} and use onOutfitDetailOpen to render your own detail UI.
Avatar modal customisation
<StyleThisPiece
{...requiredProps}
avatarUploadUrl={process.env.NEXT_PUBLIC_STP_AVATAR_UPLOAD_URL}
avatarFetchUrl={process.env.NEXT_PUBLIC_STP_AVATAR_FETCH_URL}
avatarModalProps={{
title: "Manage Your Avatar",
ctaText: "Save Avatar",
defaultAvatarFemaleUrl: "https://your-cdn.com/default-female.png",
defaultAvatarMaleUrl: "https://your-cdn.com/default-male.png",
}}
resultsAvatarText="Manage Avatar"
/>Props reference
Required
| Prop | Type | Description |
|---|---|---|
| productId | string | Product to style. |
| userId | string | Logged-in or guest user ID. |
| token | string | Partner API key. |
| apiBaseUrl | string | Stylist API base URL. |
Display
| Prop | Default | Description |
|---|---|---|
| resultsDisplay | 'modal' | 'modal' — portal overlay; 'inline' — in-page results |
| outfitDetailDisplay | 'modal' | 'modal' — detail overlay; 'inline' — panel below carousel |
| inlineContainerStyle / inlineContainerClassName | — | Wrapper around inline results |
| modalPosition | 'center' | 'center' | 'right' | 'left' — where the results modal appears |
| modalBackground | '#ffffff' | Background colour of the modal panel |
| showAvatarOnly | false | Render only a Manage Avatar CTA (no banner/outfits) |
| manageAvatarText | 'Manage Avatar' | Label for showAvatarOnly button |
| resultsAvatarText | 'Manage Avatar' | Label for footer CTA in results |
| zIndex | 9000 | Base z-index for results modal (detail +100, avatar +200) |
Outfit generation
| Prop | Default | Description |
|---|---|---|
| gender | 'female' | 'female' or 'male' |
| country | — | Region for pricing |
| resultsTitle | 'Style This Piece' | Heading in the results modal |
| resultsDescription | — | Subheading in the results modal |
Banner
| Prop | Default | Description |
|---|---|---|
| bannerText | Default copy | Text shown next to the CTA |
| ctaText | 'Style this Piece' | CTA button label |
| showCtaIcon | true | Show/hide the sparkle icon |
| ctaIcon | — | Replace sparkle — URL string or ReactNode |
| bannerStyle / ctaStyle | — | Inline styles |
| bannerClassName / ctaClassName | — | Extra class names |
| bannerTextStyle / bannerTextClassName | — | Style/class for banner text |
| sectionStyle / sectionClassName | — | Style/class for outer wrapper |
Outfit detail
| Prop | Default | Description |
|---|---|---|
| showOutfitDetail | true | Open detail panel when card canvas is clicked |
| onOutfitDetailOpen | — | Called on card click (even if showOutfitDetail is false) |
| outfitDetailDisplay | 'modal' | 'modal' or 'inline' (below carousel) |
| outfitDetailProps | — | Text/layout overrides for the detail panel — see OutfitDetailCustomization below |
| onAddToBag | — | Called when Add To Bag is clicked on a product |
| addToBagText | 'Add To Bag' | Add To Bag button label |
| addToBagIcon / addToBagStyle | — | Add To Bag button customisation |
outfitDetailProps (OutfitDetailCustomization) — common fields:
| Field | Default | Description |
|---|---|---|
| title | "Outfit Details" | Panel heading |
| itemsTitle | "Items in this outfit" | Items section heading |
| gridCols | 2 | Number of columns in the items grid |
| canvasShare | 0.4 | Fraction of panel width for outfit canvas (0–1); items grid takes the rest |
| stackBreakpoint | 900 | Viewport width (px) below which layout stacks vertically |
| titleStyle / itemsTitleStyle | — | Inline styles for headings |
| itemStyle / itemBrandStyle / itemNameStyle | — | Inline styles per product row |
Card
| Prop | Default | Description |
|---|---|---|
| onCardCta | — | Called when card button clicked. Button only renders when set. |
| cardCtaText | — | Card button label, e.g. "Shop The Look" |
| cardStyle / cardClassName | — | Style/class for each card's outer wrapper |
| cardWidth | 310 | Fixed card width in px |
| cardCanvasHeight | 300 | Fixed canvas height in px |
| renderResultsHeader | — | () => ReactNode — custom JSX above the carousel |
| renderCardExtra | — | (outfit) => ReactNode — custom JSX inside each card |
| resultsStyle | — | Style on the .stp-results wrapper |
| resultsTitleStyle / resultsTitleClassName | — | Style/class for results heading |
| resultsDescriptionStyle / resultsDescriptionClassName | — | Style/class for results description |
Canvas layout
| Prop | Description |
|---|---|
| carouselStyle / carouselClassName | Style/class for the carousel container |
| itemStyle | Inline style applied to each product image tile inside the canvas |
Try-On
| Prop | Default | Description |
|---|---|---|
| showTryOn | false | Show Try On button on each card. Requires tryOnUrl + token. |
| tryOnUrl | — | Try-on image endpoint URL (from your API provider) |
| tryOnVideoUrl | — | Try-on video (GIF) endpoint URL (optional) |
| onTryOn | — | Override Try On handler |
| tryOnText | 'Try On' | Try On button label |
| tryOnStyle / tryOnClassName | — | Style/class for Try On button |
| tryOnIcon | Scan-frame SVG | Icon inside the Try On button |
| outfitIcon | Hanger SVG | Icon for the action bar "Outfit" button |
| outfitButtonText | 'Outfit' | Label for the action bar "Outfit" button |
| videoPlayIcon / videoPauseIcon | SVGs | Icons for video play/pause |
| tryOnLoadingTexts | Default strings | Cycling text while image generates |
| tryOnVideoLoadingTexts | Default strings | Cycling text while video generates |
| avatarUploadUrl | — | Avatar endpoint. Required for upload modal on first try-on. |
| avatarFetchUrl | — | GET endpoint to pre-fill the avatar form (GET {url}/{userId}) |
| avatarGender | 'Female' | Default gender sent to avatar upload API |
| avatarModalProps | — | Text/style overrides for the avatar modal — see AvatarModalCustomization type |
avatarModalProps includes defaultAvatarFemaleUrl / defaultAvatarMaleUrl to override the built-in preview placeholders shown before a selfie is uploaded.
Named exports
import {
StyleThisPiece, // default — all-in-one component
AvatarUploadModal, // standalone avatar upload/manage modal
StyleThisPieceBanner, // just the trigger banner
StyleThisPieceResults, // just the results carousel
StyleThisPieceOutfitCard, // individual outfit card
DefaultCloseIcon, // built-in × icon (used in modals)
DefaultManageAvatarIcon, // built-in gear icon for Manage Avatar CTA
DefaultAddToBagIcon, // built-in bag icon for Add To Bag
useStyleThisPieceOutfits, // hook — outfit streaming
useTryOn, // hook — try-on state per card
tryOnOutfit, // raw API call — try-on image
tryOnOutfitVideo, // raw API call — try-on video
AvatarNotFoundError, // thrown when try-on API returns statusCode 400
} from "vetir-style-this-piece";Types: StyleThisPieceHandle (ref API for headless mode), AvatarModalCustomization, OutfitDetailCustomization, OutfitProduct, StyleThisPieceProps, and more.
How it works
Modal mode (default)
[ Banner on product page ]
↓ click
[ Results modal opens (portal to document.body) ]
[ Title + carousel of outfit cards ]
[ Manage Avatar footer (if avatarUploadUrl set) ]
↓ click card canvas
[ Outfit detail modal (or inline panel if outfitDetailDisplay="inline") ]
↓ "Try On" (if showTryOn)
[ No avatar? → Manage Your Avatar modal ]
↓
[ Try-on image on card ] → [ Outfit | ▶ Video ]
↓ close modal (× or backdrop)
[ Back to banner — outfit list resets ]Inline mode (resultsDisplay="inline")
[ Banner on product page ]
↓ click
[ Banner replaced by inline results carousel ]
[ Manage Avatar footer ]
↓ click card canvas
[ Outfit detail panel below carousel (if outfitDetailDisplay="inline") ]Troubleshooting
| Problem | Fix |
|---|---|
| No styles | Add import 'vetir-style-this-piece/style.css' to your app entry file |
| Banner error: "X is required" | Pass the missing productId / userId / token / apiBaseUrl |
| Try On failing | Verify tryOnUrl and tryOnVideoUrl — contact [email protected] |
| Avatar upload modal not shown | Pass avatarUploadUrl — modal opens on API 400 or via Manage Avatar footer |
| Manage Avatar footer missing | Pass avatarUploadUrl — footer shows in modal and inline results |
| Avatar form not pre-filled | Pass avatarFetchUrl — library calls GET {url}/{userId} on modal open |
| Results still open in modal | Set resultsDisplay="inline" for in-page results |
| Modal position wrong | Use modalPosition: 'center', 'right', or 'left' (modal mode only) |
| Product tiles overlap or clip | Ensure API returns categoryId / subCategoryId on each OutfitProduct |
| No card button | Add both onCardCta and cardCtaText |
| Duplicate React error | Add bundler alias: 'react': path.resolve('./node_modules/react') |
Contact
[email protected] — API tokens, try-on URLs, integration support
More examples: USAGE.md | Full props: INTEGRATION_GUIDE.md
