Cookies
We use cookies to understand how this site is used. You can accept or reject them: see our Cookie Policy for details.
| Prop | Type | Default | Description |
|---|---|---|---|
config | DeepPartial<TreatmentConsultantConfig> | {} | Overrides merged with the defaults — see Configuration |
styleClassPassthrough | string | string[] | [] | Extra classes added to the widget's root element |
licenseKey | string | undefined | Overrides the plugin's key for this instance — see Licensing |
| Event | Payload | Fires when |
|---|---|---|
change | ConsultationSelections | Any selection changes |
complete | ConsultationSelections | The client reaches the Results step |
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.
function createHairTreatments(
config?: DeepPartial<TreatmentConsultantConfig> & { licenseKey?: string }
): Pluginpages.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.
Every user-facing string, grouped by where it's shown.
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.
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.
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 }>>>
}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.
// 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>The shapes behind each option array in TreatmentConsultantConfig.
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
}The payload shape for both the change and complete events.
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[]
}type Suitability = "great" | "possible" | "difficult" | "not-recommended"headerRenders 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 prop | Type | Description |
|---|---|---|
displayStep | number | Current step, 1-indexed, adjusted for skipped/hidden steps |
totalSteps | number | Total step count (6 or 7, depending on behaviour.showTreatmentsStep) |
displayStepLabel | string | Current step's label from text.navigation.steps |
progressLabel | string | The formatted string driving the built-in progress text (text.progress.stepFormat) |
<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.