Configuration

TreatmentConsultant takes a single optional config prop — a deep-partial version of TreatmentConsultantConfig. Anything you don't provide falls back to sensible defaults, so most integrations only need to override a handful of fields.

Prefer to point and click? The config builder writes a starting config for you.

vue
<TreatmentConsultant
  :config="{
    behaviour: { autoAdvance: true },
    text: { cta: { bookHref: `/book-appointment` } },
  }"
/>

Merge behaviour

The three top-level shapes in TreatmentConsultantConfig merge differently:

KeyMerge strategy
hairTypes, naturalColours, desiredColours, treatments, applicationTypes, cuts, greyCoverageOptions, shades, naturalShadesReplaced wholesale — provide the full array if you override any of these
textDeep-merged — override only the keys you need
behaviourShallow-merged

Behaviour

KeyTypeDescription
autoAdvancebooleanAutomatically move to the next step once a selection is made
allowMultipleTreatmentsbooleanLet clients select more than one add-on treatment
showCutStepbooleanShow or hide the Cut step entirely
showTreatmentsStepbooleanShow or hide the Style & Treatments step entirely
currencystringReserved for future use: the currency symbol for price display. Nothing in the consultation shows prices yet, so setting it has no visible effect today. Default "£"
swatchVariant"circle" | "square" | "portrait" | "landscape"Shape applied to every colour/image swatch (and its option button) across the whole wizard. Default "circle"
swatchLayout"grid" | "scroll-mobile" | "scroll-tablet"Layout of the swatch steps (Hair Type, Natural Colour, Dream Colour). "grid": a wrapping grid at every width. "scroll-mobile" / "scroll-tablet": a single horizontally scrolling row below 640px / 1024px of the component's own width. See Swatch scroll row. Default "grid"
dreamColourMode"basic" | "granular""granular" switches Dream Colour to a tabbed catalog of individual shades (config.shades) — requires a Premium license, see Granular shade catalog mode below. Default "basic"
naturalColourMode"basic" | "granular"Same as dreamColourMode, for the Natural Colour step (config.naturalShades) — also requires a Premium license. Default "basic"
theme"default" | "monochrome-light" | "monochrome-dark" | "blush" | "champagne" | "plum" | "ink"Switches the component's colour treatment: your brand colours, two monochromes, or one of four preset colourways. See Themes. Default "default"
sharpCornersbooleanSquares off every rounded corner (cards, buttons, panels, the license watermark). Deliberately circular/pill shapes are unaffected. Default false
fullBleedHeaderbooleanThe built-in header runs edge to edge of the widget, flush with its top, with square corners. Set false to inset it by the canvas padding like every other row. Default true
fullBleedHerobooleanThe step hero (the image and explainer above each selection step) runs edge to edge, sitting flush under the header when that is full width too. Set false to inset it. Default true
experimentalTryOnbooleanExperimental. Adds the virtual colour try-on, see Virtual try-on. Default false
tryOnShowShadeImagesbooleanThe try-on palette shows each shade's photo, like the granular Dream Colour grid. Set false for flat colour swatches. Default true
tryOnModelPathstringURL the try-on loads its hair segmentation model from. Default "/models/treatment-consultant/hair_segmenter.tflite", the copy made by the setup:assets script
tryOnWasmBaseUrlstringBase URL for the try-on's MediaPipe runtime files. Defaults to a pinned jsDelivr URL; change it only if you host those files yourself

Wizard steps

The wizard has 7 internal step indices. Application Type is auto-skipped when the client picks "none" for dream colour; Cut and Treatments can each be turned off entirely via behaviour above.

StepNotes
Hair Type
Natural Colour
Dream Colour
Application TypeAuto-skipped when dream colour is "none"
CutSkippable via behaviour.showCutStep
TreatmentsSkippable via behaviour.showTreatmentsStep
Results

Text

Every user-facing string in the widget lives under text — step headings, navigation labels, the results screen, the summary sidebar, and the final call-to-action. Override just the keys you need; anything else falls back to the English defaults. See the TextConfig reference for the full shape.

To show the widget in another language, see Languages.

Languages

The widget doesn't detect or translate anything by itself. Every string it shows, including option labels and colour advice, comes from text, in English by default, so showing it in another language means supplying that text. Translation is included on every plan.

Built-in language packs

The package ships ready-made packs that translate every string in the widget. Each pack is a DeepPartial<TextConfig>, so it goes straight into text.

LanguageImport
Frenchimport { fr } from "srcdev-hair-treatments/locales/fr"
Germanimport { de } from "srcdev-hair-treatments/locales/de"
US Englishimport { enUS } from "srcdev-hair-treatments/locales/en-US"

The built-in text is British English. The US English pack only carries the strings that differ: American spellings such as color, gray and -ize, plus salon vocabulary such as bangs for fringe. Use it like any other pack.

If your site is only ever shown in one language, pass the pack in your plugin config:

ts
import { createHairTreatments } from "srcdev-hair-treatments"
import { fr } from "srcdev-hair-treatments/locales/fr"

createHairTreatments({
  licenseKey: "YOUR_LICENSE_KEY",
  text: fr,
})

A pack leaves out settings that belong to your site rather than to a language: text.header.brand, text.cta.bookHref and the icons in text.results.suitability. Whatever you set for those stays in place when the language changes. To change a string the pack does set, spread the pack and override on top:

ts
text: {
  ...fr,
  cta: { ...fr.cta, bookLabel: "Prendre rendez-vous" },
}

Switching language

Plugin config is read once, when your app starts, so it can't follow a language switcher. Keep your fixed setup (license key, options, theme) in the plugin, and pass the pack through the component's config prop from a computed that follows your site's locale. The prop is merged on top of the plugin config and updates whenever it changes:

vue
<script setup lang="ts">
import { TreatmentConsultant } from "srcdev-hair-treatments"
import { fr } from "srcdev-hair-treatments/locales/fr"
import { de } from "srcdev-hair-treatments/locales/de"
import { enUS } from "srcdev-hair-treatments/locales/en-US"

const { locale } = useI18n()
const localePacks = { fr, de, "en-US": enUS }
type LocalePackCode = keyof typeof localePacks

const consultantConfig = computed(() => {
  const pack =
    localePacks[locale.value as LocalePackCode] ??
    localePacks[locale.value.split("-")[0] as LocalePackCode]
  return pack ? { text: pack } : {}
})
</script>

<template>
  <TreatmentConsultant :config="consultantConfig" />
</template>

Only pass text through the prop. Option arrays replace wholesale, so an array passed there would replace the plugin's list and bring back any options you removed. Language packs never contain arrays, so they're always safe to pass this way. The example tries the exact locale code first, so a regional pack like "en-US" wins, then just its first part, so "fr" and "fr-FR" both show French. A language without a pack, "en-GB" included, stays in British English.

The config builder writes both halves for you: tick more than one language under Languages.

Your own translations

For a language without a pack, or to use your own wording, supply the strings yourself, for example from your own i18n messages, using the same prop pattern as above. Most of text is plain strings, listed in the TextConfig reference. A good starting point is to copy defaultConfig.text, or an existing pack, and translate it. Two parts need more care: option labels and colour recommendations.

Option labels

Hair types, colours, shades, treatments, cuts and the other options are translated under text.options, keyed by list and then option id. Don't pass translated copies of the option arrays:

ts
const consultantConfig = computed(() => ({
  text: {
    options: {
      hairTypes: { straight: { label: t("hair.straight") } },
      treatments: {
        perm: { label: t("perm.label"), description: t("perm.description"), notes: [t("perm.note")] },
      },
      cuts: {
        fringe: { warnings: { coily: { note: t("fringe.coilyNote"), detail: t("fringe.coilyDetail") } } },
      },
    },
  },
}))
ListsTranslatable fields
hairTypes, naturalColours, desiredColours, shades, naturalShadeslabel
greyCoverageOptionslabel, description
treatmentslabel, description, notes
applicationTypes, cutslabel, description, warnings

Each field you supply replaces that option's own value, and anything you leave out keeps it. text.options only ever changes wording: it never adds, removes or reorders options, and ids that aren't in the list are ignored. notes replaces a treatment's notes as a whole list, while warnings merges per hair type and per field.

Full shape: OptionTextConfig.

Colour recommendations

The colour advice on the results screen comes from text.recommendations, keyed by natural colour and then dream colour, each with a method, notes and details. It deep-merges like the rest of text, so when translating, supply every combination: any you leave out stay in English. Which combinations are rated suitable is decided by the widget itself; only the wording can be changed.

Three more strings complete the results text: recommendationFallback (used for any combination without an entry), texturedHairNote (added for curly and coily hair) and sameShadeNoteFormat (added when the natural and dream shades match, with {shade} standing in for the shade name).

Option arrays

hairTypes, naturalColours, desiredColours, treatments, applicationTypes, cuts, greyCoverageOptions, shades and naturalShades define the selectable options at each step. These replace the defaults entirely when provided, so a partial override isn't possible — supply the complete list. See the option type reference for each shape.

To translate option labels, don't rewrite these arrays. Use option labels instead, which leaves your chosen options untouched.

Granular shade catalog mode

Requires a Premium license. By default, Dream Colour and Natural Colour each show their 7-item broad-category grid. Setting behaviour.dreamColourMode and/or behaviour.naturalColourMode to "granular" switches that step to a tabbed catalog of individual named shades — config.shades for Dream Colour, config.naturalShades for Natural Colour — grouped into tabs derived from whatever category ids appear in the data. The two modes are independent.

On a Basic or Standard license (or no/invalid license), "granular" is silently treated as "basic" — no error, no locked/upsell UI. The same fallback applies on a Premium license if the relevant catalog is empty. Selecting a shade is functionally equivalent to selecting its broad category for suitability purposes; the specific shade is additionally tracked as ConsultationSelections.desiredShade / .naturalShade. See Licensing for how license tiers are enforced.

Swatch scroll row

ts
const config = { behaviour: { swatchLayout: "scroll-tablet" } };

By default the swatch steps (Hair Type, Natural Colour and Dream Colour, in both basic and granular modes) lay out as a wrapping grid. "scroll-mobile" turns them into a single horizontally scrolling, snapping row while the component is under 640px wide; "scroll-tablet" does the same under 1024px. The next card peeks in at the row's edge as the scroll cue, and the selected swatch is scrolled into view when a step opens or a shade tab changes. Steps whose cards carry descriptions (Application Type, Cut, Treatments) always stay a grid.

The breakpoints are the component's own width (container queries), not the viewport's, so an instance in a narrow sidebar scrolls even on a wide screen.

Setting behaviour.swatchLayout adds a swatch-layout-<name> class to the root element, which is what the CSS keys off. To opt in from markup instead, pass that class yourself:

vue
<TreatmentConsultant style-class-passthrough="swatch-layout-scroll-mobile" />

Tune how many cards fit in the visible row with --swatch-scroll-visible (default 2.5 below 640px and 3.5 from 640px). Keep a .5 so a card always peeks in:

css
.treatment-consultant {
  --swatch-scroll-visible: 1.5;
}

The breakpoint itself can't be a custom property, because container and media query conditions don't accept var(). That's why the layout is chosen with a config value or class rather than a CSS variable.

Virtual try-on (experimental)

Experimental. The virtual try-on lets a client preview Dream Colour shades on a live camera view of their own hair. It is off by default: set behaviour.experimentalTryOn to true to add a try-on button to the header on every step, plus a larger one on the Dream Colour step. While it's experimental, how it looks and works may change in any release.

ts
const config = { behaviour: { experimentalTryOn: true } };

Everything runs in the visitor's browser. Their camera feed is processed on their own device and never sent anywhere, including to GuideMyHair.

Tapping a shade previews it and picks it in one go. The try-on closes and returns to the Dream Colour step with that colour selected, recorded as a specific shade in ConsultationSelections.desiredShade when your plan includes the granular catalog, or as its broad colour otherwise. The client still presses Next to carry on: a try-on pick never moves the wizard forward by itself.

The try-on is available on every plan. Its palette always shows the full config.shades catalog, whatever your plan, as a preview of the shades a Premium license unlocks in the granular shade catalog. Set behaviour.tryOnShowShadeImages to false to show flat colour swatches instead of shade photos.

It needs a hair segmentation model file, which the setup:assets script copies into your app, or point behaviour.tryOnModelPath at your own copy. If your site sends a Content-Security-Policy or blocks camera access, those headers need to allow it too: see Installation: virtual try-on.

Its wording (button labels, slider labels and messages) lives under text.tryOn and merges like the rest of the text. See the TextConfig reference.

Grey coverage

A second, independent question shown beneath the Natural Colour swatches: config.greyCoverageOptions (default: None / Some / Significant / Mostly Grey). Tracked as ConsultationSelections.greyCoverage and shown as its own Results summary card, but — unlike hair type/natural colour/desired colour — it never feeds the suitability matrix and never triggers autoAdvance.

Header text & the header slot

text.header.brand (empty string hides it) and text.header.title set the built-in header shown above the step content — brand/salon name plus a persistent title, alongside a live progress label. To replace the header's markup entirely (a custom logo/progress bar), use the scoped #header slot instead — see the Slots reference.

By default the header and the step hero below it both run edge to edge of the widget. Set behaviour.fullBleedHeader or behaviour.fullBleedHero to false to inset either one instead.

Events

EventPayloadFires when
changeConsultationSelectionsAny selection changes
completeConsultationSelectionsThe client reaches the Results step