logo

🚀 UI Package Integration Guide

The OV25-UI package provides a seamless way to integrate Orbital Vision's 3D product configurator directly into your existing website or application. Instead of manually using postMessage and an iframe, this package allows you to inject configurator components directly into your DOM elements.


📦 Installation

Install the package using npm:

npm i ov25-ui@latest

💡 Note: Always use @latest to ensure you have the most recent version with all the latest features and bug fixes.


🎯 Quick Start

The injectConfigurator function allows you to embed the configurator into your page. It has a variety of optional parameters that allow you to customize the configurator to your needs.

Minimal Example

import { injectConfigurator } from 'ov25-ui';
 
injectConfigurator({
  apiKey: 'your-api-key-here',
  productLink: 'range/12345',
  galleryId: '#product-gallery',
});
<div id="product-gallery"></div>

🎭 Choose Your Display Mode

The OV25-UI package offers multiple ways to display the configurator on your page. Snap2 Configurators can only be displayed in Dialog Mode.

Use this when you want the configurator to render directly on the page when it loads. The 3D viewer will be embedded inline within your specified element.

Inline Gallery Mode

Best for: Displaying specific product configurations without the option to customize.

injectConfigurator({
  apiKey: 'your-api-key',
  productLink: '12345',
  galleryId: '#product-gallery', // Configurator renders here on page load
});
<div id="product-gallery"></div>

If you also want to place variant controls on the page, you must specify variantsId. This will render the variant controls as a set of buttons. When controls are opened, the configurator will become fullscreen and options will be displayed.

Inline Gallery Mode with Variants

Best for: Product listing pages, catalog views, full variant and pricing integration.

injectConfigurator({
  apiKey: 'your-api-key',
  productLink: 'range/12345',
  galleryId: '#product-gallery', // Configurator renders here on page load
  variantsId: '#product-variants', // Variants will be placed here
});
  <div id="product-gallery"></div>
  <div id="product-variants"></div>

This mode displays variant controls directly within the main page layout, rather than opening as a fullscreen panel when clicked. The variant controls are rendered inline alongside the 3D configurator, providing a seamless user experience without modal overlays. The 3D configurator can still be fullscreened.

Inline Gallery Mode with Inline Variants

Best for: Product pages where you want variant controls always visible, e-commerce sites with custom layouts, and scenarios where fullscreen configurators are not desired.

injectConfigurator({
  apiKey: 'your-api-key',
  productLink: 'range/12345',
  galleryId: '#product-gallery', // Configurator renders here on page load
  variantsId: '#product-variants', // Variants will be placed here inline
  useInlineVariantControls: true // default: false
});
  <div id="product-gallery"></div>
  <div id="product-variants"></div>

Snap2 Dialog Mode (configureButtonId)

This mode is only available for Snap2 configurators. Your productLink must be of the form snap2/12345. Use this when you want to show a "Configure" button that opens the configurator in a large dialog overlay when clicked. The configurator doesn't start loading until the button is clicked.

Dialog Mode with Configure Button

Best for: Snap2 product configurators, when the configurator does not need to be loaded/displayed on initial page load.

injectConfigurator({
  apiKey: 'your-api-key',
  productLink: 'snap2/12345',
  configureButtonId: '#configure-btn', // Button renders here, opens dialog on click
});
<div id="configure-btn"></div>

🔄 Injecting Multiple Configurators

The injectMultipleConfigurators function allows you to embed multiple configurators on the same page. This is perfect for product comparison pages, catalog views, or any scenario where you need to display multiple products simultaneously.

Example 1: Multiple Standard Configurators

Ideal for product comparison pages with individual product configurators:

import { injectMultipleConfigurators } from 'ov25-ui';
 
injectMultipleConfigurators([
  {
    apiKey: 'your-api-key',
    productLink: '123',
    galleryId: '#gallery-1',
    priceId: { id: '#price-1', replace: true },
    nameId: { id: '#name-1', replace: true },
  },
  {
    apiKey: 'your-api-key',
    productLink: '456',
    galleryId: '#gallery-2', 
    priceId: { id: '#price-2', replace: true },
    nameId: { id: '#name-2', replace: true },
  }
]);
<div class="configurators-grid">
  <div class="configurator-card">
    <div id="gallery-1" class="gallery-container"></div>
    <div class="product-info">
      <div id="name-1" class="product-name"></div>
      <div id="price-1" class="product-price"></div>
    </div>
  </div>
  
  <div class="configurator-card">
    <div id="gallery-2" class="gallery-container"></div>
    <div class="product-info">
      <div id="name-2" class="product-name"></div>
      <div id="price-2" class="product-price"></div>
    </div>
  </div>
</div>

Example 2: Multiple Configurators with Variants

Similar to previous example, but each configurator has its own variant selection menu. This can be done with inlinex variants or with the fullscreen variant panel.

import { injectMultipleConfigurators } from 'ov25-ui';
 
injectMultipleConfigurators([
  {
    apiKey: 'your-api-key',
    productLink: 'range/123',
    galleryId: '#gallery-1',
    variantsId: '#variants-1',
    priceId: { id: '#price-1', replace: true },
    nameId: { id: '#name-1', replace: true },
    logoURL: 'https://your-logo.com/logo.png',
    addToBasketFunction: () => console.log('Add to basket - Range 1'),
    buyNowFunction: () => console.log('Buy now - Range 1'),
  },
  {
    apiKey: 'your-api-key',
    productLink: 'range/456',
    galleryId: '#gallery-2',
    variantsId: '#variants-2', 
    priceId: { id: '#price-2', replace: true },
    nameId: { id: '#name-2', replace: true },
    logoURL: 'https://your-logo.com/logo.png',
    addToBasketFunction: () => console.log('Add to basket - Range 2'),
    buyNowFunction: () => console.log('Buy now - Range 2'),
  }
]);
<div class="configurators-grid">
  <div class="configurator-card">
    <div id="gallery-1" class="gallery-container"></div>
    <div class="product-info">
      <div id="name-1" class="product-name"></div>
      <div id="price-1" class="product-price"></div>
    </div>
    <div class="variants-container">
      <div id="variants-1"></div>
    </div>
  </div>
  
  <div class="configurator-card">
    <div id="gallery-2" class="gallery-container"></div>
    <div class="product-info">
      <div id="name-2" class="product-name"></div>
      <div id="price-2" class="product-price"></div>
    </div>
    <div class="variants-container">
      <div id="variants-2"></div>
    </div>
  </div>
</div>

Example 3: Multiple Snap2 Configurators

Perfect for showcasing different Snap2 product configurations with configure buttons:

import { injectMultipleConfigurators } from 'ov25-ui';
 
injectMultipleConfigurators([
  {
    apiKey: 'your-api-key',
    productLink: 'snap2/123',
    configureButtonId: '#configure-1',
    logoURL: 'https://your-logo.com/logo.png',
    hidePricing: true,
  },
  {
    apiKey: 'your-api-key',
    productLink: 'snap2/456',
    configureButtonId: '#configure-2',
    logoURL: 'https://your-logo.com/logo.png',
    hidePricing: true,
  }
]);
<div class="product-grid">
  <div class="product-card">
    <img src="sofa-image.jpg" />
    <button id="configure-1">Configure 1</button>
  </div>
  
  <div class="product-card">
    <img src="sofa-image-2.jpg" />
    <button id="configure-2">Configure 2</button>
  </div>
</div>

Key Benefits of Multiple Configurators

  • Product Comparison: Display multiple products side-by-side for easy comparison
  • Range Showcases: Show different product ranges with their variants
  • Catalog Views: Create rich product catalogs with interactive 3D previews

Important Notes

  • Each configurator requires unique element IDs to avoid conflicts
  • For snap2 configurators only the first configurator will be preloaded. The other configurators will be loaded when the configure button is clicked.

🔧 Configuration Options

The injectConfigurator function accepts a comprehensive options object:

Required Parameters

ParameterTypeDescription
apiKeystring | () => stringYour Orbital Vision API key
productLinkstring | () => stringThe ID of the product, range, or snap2 configuration you want to display. In the format 12345 or range/12345 or /snap2/678.
galleryIdstring | { id: string, replace: boolean }Selector for 3D gallery container - not needed if configureButtonId is provided.
configureButtonIdstring | { id: string, replace: boolean }Only for Snap2 configurators (galleryId must be of the form snap2/12345). Selector for configure button element - not needed if galleryId is provided.

🔑 apiKey: You can create a new API key in your dashboard at app.orbital.vision/dashboard/api-keys. Select the Product Configurator Access type.

💡 productLink: You can find the product or range IDs in your dashboard at app.orbital.vision/dashboard/products.

🖼️ galleryId: If provided, the configurator will show on page load, in the location given by the CSS selector.

🔘 configureButtonId: Only for Snap2 configurators (galleryId must be of the form snap2/12345). If provided, the configurator will not render on page load. The "Configure" button will be rendered in the location given by the CSS selector. When the "Configure" button is clicked, it will open a large dialog and load the configurator.

Optional Parameters

ParameterTypeDefaultDescription
configurationUuidstringundefinedFor snap2 configurators only. If given, the configurator will load with the saved configuration. configurationUuid can be got by clicking "Share" after customising a snap2 configurator.
imagesstring[]undefinedArray of product image URLs
deferThreeDbooleanfalseShows first carousel image while 3D scene loads in background
showOptionalbooleantrueShows 'Optional' in the header if an option has a 'None' option.
priceIdstring | { id: string, replace: boolean }undefinedSelector for price display element.
nameIdstring | { id: string, replace: boolean }undefinedSelector for product name element.
variantsIdstring | { id: string, replace: boolean }undefinedSelector for variant selection menu. Without this, user cannot change variants.
useInlineVariantControlsbooleanfalseIf true, variant controls will display inline within the main page rather than opening as a fullscreen panel.
swatchesIdstring | { id: string, replace: boolean }undefinedSelector for swatch book widget.
carouselIdstring | { id: string, replace: boolean } | trueundefinedSelector for product carousel.
addToBasketFunction() => voidundefinedCallback when user clicks "Add to Basket"
buyNowFunction() => voidundefinedCallback when user clicks "Buy Now"
addSwatchesToCartFunction(swatches, swatchRulesData) => voidundefinedCallback to add selected swatches to cart
logoURLstringundefinedURL of logo displayed in header of configurator side panel
mobileLogoURLstringundefinedMobile-specific logo URL
cssStringstringundefinedOptional custom CSS string to apply to all configurator UI.
hidePricingbooleanfalseIf true, pricing will be hidden from the UI.

🎨 Element Selectors

Element selectors can be either a simple string or a configuration object:

Simple Selector

galleryId: '#my-gallery' // Renders inside the target element

Advanced Selector with Replacement

galleryId: {
  id: '#my-gallery',
  replace: true  // Completely replaces the target element
}

⚠️ Replace Mode: When replace: true is set, the target element will be completely replaced with the configurator component. Use this when you want the configurator to take over the entire element.


🎨 CSS Customization

All Configurator elements are encapsulated in a shadow DOM. To style them, you can pass your CSS into the cssString parameter. The configurator components use unique CSS IDs and classes, making customization straightforward. You can easily style individual elements by targeting their specific selectors: Some elements have data attributes indicating their state.

/* Example: Customize the 3D gallery container */
#ov25-gallery {
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
 
/* Example: Style variant selection buttons */
.ov25-variant-option {
  border-radius: 8px;
  transition: all 0.3s ease;
}
 
/* Example: Customize price display */
.ov25-price-display {
  font-family: 'Your Custom Font', sans-serif;
  color: #your-brand-color;
}
 
/* Example: Highlight variant when it is selected */
.ov25-default-variant-card[data-selected="true"] {
  background: lightgray;
}

🏗️ Component Types

The main 3D configurator interface.

galleryId: '#product-gallery'

2. Price Display

Shows the current product price based on selected options.

priceId: '#price-display'

3. Product Name

Displays the current product name.

nameId: '#product-name'

4. Variant Selection Menu

Interactive menu for selecting product variants (colors, materials, etc.).

variantsId: '#variant-selector'

Image carousel for product photos. The carousel combines your custom images with OV25 product images, displaying your images first in the array.

// Standard carousel - replaces the target element
carouselId: '#image-carousel'
 
// Auto-positioning carousel - positions underneath the iframe automatically
carouselId: true

When carouselId: true is used, the carousel is automatically positioned underneath the configurator iframe. When a specific selector is provided, the carousel replaces that target element instead.


📨 Listening for Product Data

The configurator sends product information via postMessage events that you can listen for in your checkout functions. This allows you to access the current SKU and price information:

// Store current product data
let currentSku = null;
let currentPrice = null;
 
// Listen for SKU updates
window.addEventListener('message', (event) => {
  if (event.data.type === 'CURRENT_SKU') {
    const skuData = JSON.parse(event.data.payload);
    currentSku = skuData;
    console.log('SKU updated:', skuData);
  }
});
 
// Listen for price updates
window.addEventListener('message', (event) => {
  if (event.data.type === 'CURRENT_PRICE') {
    const priceData = JSON.parse(event.data.payload);
    currentPrice = priceData;
    console.log('Price updated:', priceData);
  }
});
 
injectConfigurator({
  // ... other options
  
  addToBasketFunction: () => {
    if (currentSku && currentPrice) {
      addToCart({
        sku: currentSku.skuString,
        skuMap: currentSku.skuMap,
        price: currentPrice.totalPrice,
        formattedPrice: currentPrice.formattedPrice,
        subtotal: currentPrice.subtotal,
        moneySaved: currentPrice.discount.formattedAmount,
        priceBreakdown: currentPrice.priceBreakdown
      });
    }
  },
  
  buyNowFunction: () => {
    if (currentSku && currentPrice) {
      redirectToCheckout({
        sku: currentSku.skuString,
        skuMap: currentSku.skuMap,
        price: currentPrice.totalPrice,
        formattedPrice: currentPrice.formattedPrice,
        subtotal: currentPrice.subtotal,
        moneySaved: currentPrice.discount.formattedAmount,
        priceBreakdown: currentPrice.priceBreakdown
      });
    }
  }
});

Message Data Types

CURRENT_SKU Message

{
  type: 'CURRENT_SKU',
  payload: string // JSON stringified object with the following structure:
}

Parsed payload structure:

{
  skuString: string;        // Combined SKU string (e.g., "RANGE001/PROD002/SEL003")
  skuMap: {                 // Individual SKUs by category
    Ranges?: string;
    Products?: string;
    [categoryName: string]: string;
  };
}

CURRENT_PRICE Message

{
  type: 'CURRENT_PRICE',
  payload: string // JSON stringified object with the following structure:
}

Parsed payload structure:

{
  formattedPrice: string;   // Formatted price string (e.g., "£1,234.56")
  totalPrice: number;       // Price in pence/cents (e.g., 123456)
  subtotal: number;         // Price before discounts in pence/cents (e.g., 129500)
  formattedSubtotal: string; // Formatted subtotal string (e.g., "£1,295.00")
  discount: {               // Discount information
    amount: number;         // Discount amount in pence/cents (e.g., 25900)
    formattedAmount: string; // Formatted discount amount (e.g., "£259.00")
    percentage: number;     // Discount percentage (e.g., 20)
  };
  priceBreakdown: Array<{   // Detailed breakdown by component
    category: string;       // Category name (e.g., "Ranges", "Products", etc.)
    name: string;          // Display name of the item
    sku: string;           // SKU for this component
    price: number;         // Price in pence/cents
    formattedPrice: string; // Formatted price string
  }>;
}

💡 Advanced Examples

Complete E-commerce Integration

import { injectConfigurator } from 'ov25-ui';
 
// Initialize the configurator
injectConfigurator({
  // Authentication
  apiKey: () => getApiKeyFromEnvironment(),
  productLink: () => getCurrentProductUrl(),
  
  // Component placement
  galleryId: { id: '#main-gallery', replace: true },
  priceId: '#price-container',
  nameId: '#product-title',
  variantsId: '#options-panel',
  carouselId: '#product-images',
  
  // E-commerce callbacks
  addToBasketFunction: () => {
    const currentConfig = getCurrentConfiguration();
    addToCart(currentConfig);
    showNotification('Added to basket!');
  },
  
  buyNowFunction: () => {
    const currentConfig = getCurrentConfiguration();
    redirectToCheckout(currentConfig);
  },
  
  // Branding
  logoURL: 'https://cdn.mystore.com/logo.png',
  mobileLogoURL: 'https://cdn.mystore.com/logo-mobile.png',
  
 
  // Performance
  deferThreeD: true,
  
  // Additional product images for the carousel
  images: [
    'https://cdn.mystore.com/product1.jpg',
    'https://cdn.mystore.com/product2.jpg',
    'https://cdn.mystore.com/product3.jpg'
  ]
});

Dynamic Configuration

// Function-based configuration for dynamic values
injectConfigurator({
  apiKey: () => {
    // Fetch API key dynamically
    return localStorage.getItem('ov25-api-key') || 'fallback-key';
  },
  
  productLink: () => {
    // Generate product link based on current page
    const productLink = window.customPlaceToPutId;
    return `${productLink}`;
  },
  
  galleryId: '#configurator',
  
  addToBasketFunction: () => {
    // Custom basket logic
    window.dataLayer?.push({
      event: 'add_to_cart',
      product_id: getCurrentProductId()
    });
  },
  
  buyNowFunction: () => {
    // Direct checkout
    window.location.href = '/checkout?quick=true';
  },
  
  logoURL: 'https://mystore.com/logo.png'
});

📱 Responsive Design

The configurator components are designed to be responsive. For optimal mobile experience:

injectConfigurator({
  // ... other options
  
  // Provide mobile-specific logo
  mobileLogoURL: 'https://mystore.com/logo-mobile.png',
  
  // Defer 3D rendering on mobile for better performance
  deferThreeD: true
});

⚡ Performance Optimization

Deferred 3D Display

For better initial user experience, especially on mobile devices:

injectConfigurator({
  // ... other options
  deferThreeD: true  // Shows first carousel image while 3D scene loads in background
});

When deferThreeD is enabled, users see the first product image immediately while the 3D scene loads behind the scenes. Once a user interacts with customization options, the 3D scene is ready to display instantly.

Lazy Loading

The configurator automatically handles lazy loading of components. Components are only rendered when their target elements are found in the DOM.

Image Optimization

Provide optimized images for better performance:

images: [
  'https://cdn.mystore.com/product1-800w.webp',
  'https://cdn.mystore.com/product2-800w.webp'
]

🔍 Troubleshooting

Common Issues

Components not appearing:

  • Ensure target elements exist in the DOM before calling injectConfigurator
  • Check that selectors are correct (including # for IDs and . for classes)

API key errors:

  • Confirm your API key is valid and has the correct permissions
  • Check that your domain is authorized in the Orbital Vision dashboard

Performance issues:

  • Enable deferThreeD for mobile devices
  • Optimize your product images
  • Consider loading the configurator only when needed

Debug Mode

Enable console logging for debugging:

// The configurator will log warnings for missing elements
// Check browser console for detailed error messages

Example Implementation

<!DOCTYPE html>
<html>
<head>
  <title>My Product Page</title>
  <style>
    #product-gallery {
      width: 100%;
      height: 500px;
      background: #f5f5f5;
      border-radius: 8px;
    }
    
    #price-display {
      font-size: 24px;
      font-weight: bold;
      color: #333;
    }
  </style>
</head>
<body>
  <div id="product-gallery">
    <!-- Fallback content for SEO -->
    <p>3D Product Configurator Loading...</p>
  </div>
  
  <div id="price-display">
    Loading price...
  </div>
  
  <div id="variant-selector">
    <!-- Variant options will be injected here -->
  </div>
 
  <script type="module">
    import { injectConfigurator } from 'ov25-ui';
    
    document.addEventListener('DOMContentLoaded', () => {
      try {
        injectConfigurator({
          apiKey: 'your-api-key',
          productLink: 'https://your-product-url.com',
          galleryId: '#product-gallery',
          priceId: '#price-display',
          variantsId: '#variant-selector',
          addToBasketFunction: () => alert('Added to basket!'),
          buyNowFunction: () => alert('Buy now clicked!'),
          logoURL: 'https://your-logo.com/logo.png'
        });
      } catch (error) {
        console.error('Failed to initialize configurator:', error);
      }
    });
  </script>
</body>
</html>

Built with ❤️ by the Orbital Vision team