API Reference

TreatmentConsultant props

PropTypeDefaultDescription
configDeepPartial<TreatmentConsultantConfig>{}Overrides merged with the defaults — see Configuration
styleClassPassthroughstring | string[][]Extra classes added to the widget's root element
licenseKeystringundefinedOverrides the plugin's key for this instance — see Licensing

TreatmentConsultant emits

EventPayloadFires when
changeConsultationSelectionsAny selection changes
completeConsultationSelectionsThe client reaches the Results step

createHairTreatments()

A Vue plugin factory. Installing it provides a default config to every TreatmentConsultant in the app — instance-level config props are merged on top of this.

ts
function createHairTreatments(
  config?: DeepPartial<TreatmentConsultantConfig> & { licenseKey?: string }
): Plugin

pages.docs.apiReference.createHairTreatments.licenseKeyField

The plugin's config is merged once, when the plugin is installed. To change configuration while the app runs, such as switching language, pass it through the component's config prop instead, which updates whenever its value changes. See Switching language.

TextConfig

Every user-facing string, grouped by where it's shown.

ts
interface TextConfig {
  header: {
    /** Brand/salon name shown in the built-in header. Empty string hides it. */
    brand: string
    /** Persistent title shown alongside the brand, e.g. "Colour Consultation" */
    title: string
  }
  navigation: {
    steps: string[]
    back: string
    next: string
    viewResults: string
    backToResults: string
  }
  progress: {
    /** Format string for the step counter. Use {step}, {total}, {label} as placeholders. */
    stepFormat: string
  }
  steps: {
    hairType: string
    naturalColour: string
    greyCoverage: string
    greyCoverageSubtitle: string
    desiredColour: string
    desiredColourSkipSublabel: string
    treatments: string
    treatmentsSubtitle: string
    applicationType: string
    applicationTypeSubtitle: string
    cut: string
    cutSubtitle: string
  }
  /** Hero content above each selection step, keyed by step name. */
  explainers: {
    hairType: ExplainerContent
    naturalColour: ExplainerContent
    desiredColour: ExplainerContent
    applicationType: ExplainerContent
    cut: ExplainerContent
    treatments: ExplainerContent
  }
  results: {
    title: string
    colourSection: string
    treatmentsSection: string
    processesRequired: string
    noColour: { label: string; note: string }
    noTreatment: { label: string; note: string }
    compatOk: string
    compatWarning: string
    suitability: Record<Suitability, { icon: string; label: string }>
  }
  summary: {
    hairType: string
    naturalColour: string
    greyCoverage: string
    /** Shown when the grey coverage question was skipped */
    noGreyCoverage: string
    dreamColour: string
    treatments: string
    noTreatment: string
    applicationType: string
    cut: string
    /** Shown when no cut was chosen */
    noCut: string
  }
  cta: {
    disclaimer: string
    bookLabel: string
    bookHref: string
    resetLabel: string
  }
  /** Small strings that don't belong to a single step or option. */
  labels: {
    /** The built-in "no cut today" choice on the cut step */
    cutSkipLabel: string
    cutSkipSublabel: string
    /** Shown on an option that isn't offered for the chosen hair type */
    hairTypeExcludedNote: string
    /** Shown on a treatment that conflicts with one already picked. Use {treatment} as a placeholder. */
    treatmentConflictFormat: string
    /** The extra "all shades" tab in the full shade catalogue */
    allShadesTab: string
  }
  /** Virtual try-on screen (behaviour.experimentalTryOn). */
  tryOn: {
    /** Accessible label for the try-on button in the header, shown on every step */
    triggerLabel: string
    /** The larger try-on button on the Dream Colour step */
    dreamColourCtaLabel: string
    title: string
    useCameraLabel: string
    uploadPhotoLabel: string
    loadingLabel: string
    unsupportedMessage: string
    cameraDeniedMessage: string
    closeLabel: string
    turnCameraOffLabel: string
    /** Colour strength slider */
    opacityLabel: string
    /** Edge softness slider */
    blurLabel: string
  }
  /** Colour advice on the results screen, keyed natural colour, then dream colour. */
  recommendations: Record<NaturalColourId, Partial<Record<DesiredColourId, RecommendationText>>>
  /** Used for any combination missing from recommendations */
  recommendationFallback: RecommendationText
  /** Added to the details when the hair type is curly or coily */
  texturedHairNote: string
  /** Added when the natural and dream shades are the same. Use {shade} as a placeholder. */
  sameShadeNoteFormat: string
  /** Per-option wording, keyed by list, then option id. See OptionTextConfig. */
  options?: OptionTextConfig
}

interface ExplainerContent {
  title: string
  body: string
  /** Falls back to the default hero image when omitted */
  image?: string
}

interface RecommendationText {
  method: string
  notes: string
  details: string[]
}

To translate these strings, or to use a ready-made language pack, see Languages.

OptionTextConfig

The shape of text.options: translatable wording for each option, keyed by list and then option id. Each field overrides the matching field on the option with that id. See Option labels for how it's applied.

ts
interface OptionTextConfig {
  hairTypes?: Partial<Record<HairTypeId, LabelOptionText>>
  naturalColours?: Partial<Record<NaturalColourId, LabelOptionText>>
  desiredColours?: Partial<Record<DesiredColourId, LabelOptionText>>
  /** Keyed by DesiredShadeOption.id */
  shades?: Record<string, LabelOptionText>
  /** Keyed by NaturalShadeOption.id */
  naturalShades?: Record<string, LabelOptionText>
  greyCoverageOptions?: Partial<Record<GreyCoverageId, DescribedOptionText>>
  treatments?: Partial<Record<TreatmentId, TreatmentOptionText>>
  applicationTypes?: Partial<Record<ApplicationTypeId, WarnableOptionText>>
  cuts?: Partial<Record<CutId, WarnableOptionText>>
}

interface LabelOptionText {
  label?: string
}

interface DescribedOptionText extends LabelOptionText {
  description?: string
}

interface TreatmentOptionText extends DescribedOptionText {
  /** Replaces the treatment's notes as a whole list */
  notes?: string[]
}

interface WarnableOptionText extends DescribedOptionText {
  /** Merged per hair type and per field over the option's own warnings */
  warnings?: Partial<Record<HairTypeId, Partial<{ note: string; detail: string }>>>
}

Locale packs

Ready-made translations, one subpath export per language. Each is a DeepPartial<TextConfig> covering every string in the widget. See Built-in language packs for how to use them.

ts
// srcdev-hair-treatments/locales/fr
export const fr: DeepPartial<TextConfig>

// srcdev-hair-treatments/locales/de
export const de: DeepPartial<TextConfig>

// srcdev-hair-treatments/locales/en-US
export const enUS: DeepPartial<TextConfig>

Option types

The shapes behind each option array in TreatmentConsultantConfig.

ts
type HairTypeId = "straight" | "wavy" | "curly" | "coily"

interface HairTypeOption {
  id: HairTypeId
  label: string
  pattern: string
  image?: string
}

type NaturalColourId =
  | "light-blonde" | "dark-blonde" | "light-brown" | "dark-brown"
  | "red" | "black" | "grey-white"

interface ColourOption {
  id: NaturalColourId
  label: string
  colour: string
  image?: string
  textDark?: boolean
}

type DesiredColourId =
  | "none" | "blonde" | "brown" | "red" | "black" | "grey-silver" | "vivid" | "balayage"

interface DesiredColourOption {
  id: DesiredColourId
  label: string
  colour?: string
  image?: string
  textDark?: boolean
}

type ApplicationTypeId = "all-over" | "balayage" | "highlights" | "lowlights" | "t-section"

interface ApplicationTypeOption {
  id: ApplicationTypeId
  label: string
  description?: string
  icon: string
}

type GreyCoverageId = "none" | "some" | "significant" | "predominantly-grey"

interface GreyCoverageOption {
  id: GreyCoverageId
  label: string
  description?: string
}

/** Individual named shade within a broad category — Premium-only "granular" catalog mode. */
interface DesiredShadeOption {
  id: string
  label: string
  desiredColourId: DesiredColourId
  colour?: string
  image?: string
  image2x?: string
}

interface NaturalShadeOption {
  id: string
  label: string
  naturalColourId: NaturalColourId
  colour?: string
  image?: string
  image2x?: string
}

type CutId = "none" | "trim" | "restyle" | "layers" | "fringe"

interface CutOption {
  id: CutId
  label: string
  description?: string
  icon: string
  excludedByHairTypes?: HairTypeId[]
  warnForHairTypes?: HairTypeId[]
  warnings?: Partial<Record<HairTypeId, { note: string; detail: string }>>
}

type TreatmentId =
  | "none" | "keratin-smoothing" | "brazilian-blowout" | "perm" | "relaxer"
  | "japanese-straightening" | "deep-conditioning" | "bond-repair" | "gloss"
  | "scalp-treatment" | "blowdry"

interface Treatment {
  id: TreatmentId
  label: string
  icon: string
  description?: string
  notes: string[]
  /** Show a colour-compatibility badge on the results screen */
  compatibility?: boolean
  /** "ok" = compatible same-day; "warning" = not same-day as colour */
  colourCompatibility?: "ok" | "warning"
  excludes?: TreatmentId[]
  /** Set to false to hide this treatment from the treatments step */
  display?: boolean
}

ConsultationSelections

The payload shape for both the change and complete events.

ts
interface ConsultationSelections {
  hairType: HairTypeId | null
  naturalColour: NaturalColourId | null
  /** Picked shade id in "granular" naturalColourMode; null in basic mode or before picking */
  naturalShade: string | null
  greyCoverage: GreyCoverageId | null
  desiredColour: DesiredColourId | null
  /** Picked shade id in "granular" dreamColourMode; null in basic mode or before picking */
  desiredShade: string | null
  applicationType: ApplicationTypeId | null
  cut: CutId | null
  treatments: TreatmentId[]
}

Suitability

ts
type Suitability = "great" | "possible" | "difficult" | "not-recommended"

Slots

header

Renders above the step content, inside the widget's root. By default this shows the built-in header (text.header.brand / .title plus a live progress label) — provide this slot to replace it with your own markup entirely. It's a scoped slot, so a custom step counter stays in sync automatically:

Slot propTypeDescription
displayStepnumberCurrent step, 1-indexed, adjusted for skipped/hidden steps
totalStepsnumberTotal step count (6 or 7, depending on behaviour.showTreatmentsStep)
displayStepLabelstringCurrent step's label from text.navigation.steps
progressLabelstringThe formatted string driving the built-in progress text (text.progress.stepFormat)
vue
<TreatmentConsultant>
  <template #header="{ displayStep, totalSteps, displayStepLabel }">
    <header class="my-consultation-header">
      <img class="my-logo" src="/my-logo.svg" alt="My Salon" />
      <span>Step {{ displayStep }} of {{ totalSteps }} — {{ displayStepLabel }}</span>
    </header>
  </template>
</TreatmentConsultant>

On every selection step the hero also shows the step label over its image, so a custom header that shows progress text as well will show it twice.