npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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

  1. A banner button appears on your product page.
  2. The shopper clicks — outfits stream in as a modal overlay (default) or inline on the page (resultsDisplay="inline").
  3. Each card renders a category-aware canvas: clothing on the left, accessories/shoes arranged on the right.
  4. 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-piece

Import 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.css

Test locally before uploading:

npm run test:shopify
# → http://localhost:3000/examples/shopify/test-local.html

Mount 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:

  1. Shopper uploads a selfie (drag-and-drop or file picker)
  2. They pick gender, body type, skin tone, and hair colour
  3. Photo is uploaded to avatarUploadUrl — the same sparkle loader plays while the avatar is created
  4. 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