Installation

Install the package

bash
npm install srcdev-hair-treatments

Import the styles

Two stylesheets ship in dist/: the component's own CSS, and a set of colour tokens it reads from. Import both once, globally, in your app's entry point or global CSS:

css
@import "srcdev-hair-treatments/dist/style.css";
@import "srcdev-hair-treatments/dist/tokens.css";

tokens.css defines the colour ramps the component uses: --brand-00 through --brand-10, plus --accent-* (the selected state), --success-*, --warning-* and --error-*. Skip this import and provide your own values instead if you want the widget themed to match your brand from the start. See Theming.

Copy the images

Hair type and colour swatches can show real photography — the package ships a full set of default images, but they need to be served from your own app's public/ directory, not read from node_modules. Add a script to your package.json and run it once after install:

json
"setup:assets": "cp -r node_modules/srcdev-hair-treatments/public/. public/"

Copy the whole folder, not just its images. It holds the images in public/images/treatment-consultant/ and the virtual try-on model in public/models/treatment-consultant/; copying only public/images/ leaves the model missing.

Wire it into a postinstall hook so it stays in sync on every npm install, picking up any new or changed images on a package update:

json
"postinstall": "npm run setup:assets"

See Theming → Images for overriding these with your own photography per option.

Register the plugin

createHairTreatments() is a Vue plugin that provides a default config to every TreatmentConsultant instance in your app. Passing a config here is optional — you can also pass one directly as a prop per-instance (see Configuration). It can also carry your license key so you don't have to repeat it per instance — see Licensing.

ts
import { createApp } from "vue"
import { createHairTreatments } from "srcdev-hair-treatments"
import App from "./App.vue"

const app = createApp(App)
app.use(createHairTreatments())
app.mount("#app")

Use the component

vue
<template>
  <TreatmentConsultant @complete="onComplete" />
</template>

<script setup lang="ts">
import { TreatmentConsultant } from "srcdev-hair-treatments"
import type { ConsultationSelections } from "srcdev-hair-treatments"

function onComplete(selections: ConsultationSelections) {
  console.log(selections)
}
<\/script>

Nuxt

Register the plugin under app/plugins/ (not a root-level plugins/ directory — Nuxt 4's default srcDir is app/, and a plugin outside it silently won't register):

ts
// app/plugins/hair-treatments.ts
import { createHairTreatments } from "srcdev-hair-treatments"

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(createHairTreatments())
})

Add both stylesheets to nuxt.config.ts's css array, and wrap the component itself in <ClientOnly> — TreatmentConsultant is not SSR-safe. Full details in SSR & ClientOnly.

Virtual try-on: security headers

Only needed if you turn on behaviour.experimentalTryOn (see Configuration). The try-on loads its MediaPipe runtime from cdn.jsdelivr.net and then asks for the visitor's camera. If your site sends a Content-Security-Policy, allow both the script and the file it downloads:

ts
// Content-Security-Policy: allow the MediaPipe wasm loader script and wasm binary
"script-src": ["'self'", "'wasm-unsafe-eval'", "https://cdn.jsdelivr.net" /* ...your other sources */],
"connect-src": ["'self'", "https://cdn.jsdelivr.net" /* ...your other sources */],

Some security setups also deny the camera outright with a Permissions-Policy of camera=(), which is the default in nuxt-security. Allow it for your own pages:

ts
// nuxt.config.ts, with nuxt-security
security: {
  headers: {
    permissionsPolicy: {
      camera: ["self"],
    },
  },
},

If you host the runtime files yourself via behaviour.tryOnWasmBaseUrl, allow your own host in place of jsDelivr.

Plain HTML — no build step

No Vue app, no bundler? A separate dist/embed.js bundle ships everything needed — Vue included — so a plain HTML page can embed the consultation with a <div>, a stylesheet link, and one <script> tag. Loaded from a CDN like jsDelivr, the URL is pinned to a package version, so there's nothing to cache-bust when a new version ships — just bump the version in the URL when you want it.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/srcdev-hair-treatments@1.5.1/dist/embed-style.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/srcdev-hair-treatments@1.5.1/dist/tokens.css" />

<div data-hair-treatments data-license-key="YOUR_LICENSE_KEY"></div>

<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/srcdev-hair-treatments@1.5.1/dist/embed.js"
></script>

Every element on the page matching [data-hair-treatments] is mounted automatically once the script loads — nothing else to write. The version above, 1.5.1, is read from this site's own installed dependency, so it's always current — copy it as-is, or replace it with whichever version you've pinned to yourself. See Licensing for where the license key comes from.

Pass configuration per-instance with a JSON-encoded data-config attribute — see Configuration for the shape it accepts:

html
<div
  data-hair-treatments
  data-license-key="YOUR_LICENSE_KEY"
  data-config="{"text": {"heading": "Find your perfect colour"}}"
></div>

There's no Vue app here for @complete/@change to hook into, so the embed bundle re-dispatches them as native CustomEvents on the mount element instead — listen with addEventListener:

js
document
  .querySelector("[data-hair-treatments]")
  .addEventListener("hair-treatments:complete", (event) => {
    console.log(event.detail) // ConsultationSelections
  })

Turning on the try-on in an embed? Its default behaviour.tryOnModelPath expects the model file copied by setup:assets, which an embed site has no build step to run. Point it at the copy on jsDelivr in data-config instead, pinned to the same version as the script:

html
<div
  data-hair-treatments
  data-config='{"behaviour":{"experimentalTryOn":true,"tryOnModelPath":"https://cdn.jsdelivr.net/npm/srcdev-hair-treatments@1.5.1/public/models/treatment-consultant/hair_segmenter.tflite"}}'
></div>