Cookies
We use cookies to understand how this site is used. You can accept or reject them: see our Cookie Policy for details.
Every override targets the .treatment-consultant class (or any ancestor of it) with CSS custom properties — no build-time theming step needed.
The component reads semantic 11-step ramps (00 light through 10 dark): --brand-*, --success-*, --warning-* and --error-*, plus an optional --accent-* ramp for the selected state, which falls back to --brand-* when you don't define it. Import the defaults from srcdev-hair-treatments/dist/tokens.css, or supply your own to match your brand:
:root {
--brand-00: oklch(98% 0.0086 195);
--brand-01: oklch(94% 0.0342 195);
/* ...through */
--brand-10: oklch(25% 0.1216 195);
/* Optional: the selected state. Falls back to --brand-* */
--accent-00: oklch(98% 0.0072 85);
/* ... */
--accent-10: oklch(25% 0.1024 85);
--success-00: oklch(98% 0.0086 157);
/* ... */
--warning-00: oklch(98% 0.0086 50);
/* ... */
--error-00: oklch(98% 0.0086 30);
/* ... */
}This is the same idea GuideMyHair's own marketing site uses to theme the widget — in app/assets/styles/main.css, --brand-00 through --brand-10 are aliased to the site's own teal design-system ramp instead of hand-written OKLCH values, replacing the package's default warm-orange brand colour entirely:
:root {
--brand-00: var(--teal-00);
--brand-01: var(--teal-01);
/* ...through */
--brand-09: var(--teal-09);
--brand-10: oklch(25% 0.0896 195);
}Hair type, colour, and granular shade options can each show a real photo instead of (or alongside) a flat colour swatch. The package ships a full set of default images — one per hair type, plus the entire default shades / naturalShades catalog — under public/images/treatment-consultant/, copied into your own app's public/ directory by the setup:assets script (see Installation). Nothing renders from node_modules directly — every image path the component uses at runtime is one your app itself serves.
Every option type with visual swatches carries the same optional fields:
| Field | Type | Used on |
|---|---|---|
image | string | HairTypeOption, ColourOption (naturalColours), DesiredColourOption, DesiredShadeOption, NaturalShadeOption |
image2x | string | DesiredShadeOption, NaturalShadeOption only — a 2x-density variant rendered via srcset for crisp high-DPI rendering |
Both are optional and fall back gracefully when omitted: a hair type option without image falls back to its ASCII pattern text; a colour/shade option without image falls back to a flat swatch in its colour; a shade without image2x just renders image at every density (no broken-image risk from a guessed URL). To override an image, replace the field with a path to your own asset — same shape as the defaults, no special API:
import { defaultConfig } from "srcdev-hair-treatments";
const config = {
hairTypes: defaultConfig.hairTypes.map((t) =>
t.id === "curly" ? { ...t, image: "/images/my-salon/curly.webp" } : t
),
};See Option types for the full shape of each option, and Granular shade catalog mode for where shades / naturalShades come from.
| Property | Used for | Default |
|---|---|---|
--treatment-consultant-font-body | Body text, sublabels, notes | system-ui, sans-serif |
--treatment-consultant-font-heading | Step titles, Results title, section titles | Georgia, Cambria, "Times New Roman", Times, serif |
--treatment-consultant-font-label | Progress status, option labels | Same as font-body |
.treatment-consultant {
--treatment-consultant-font-body: "My Font", sans-serif;
--treatment-consultant-font-heading: "My Heading Font", serif;
}| Property | Used for |
|---|---|
--treatment-consultant-max-inline-size | Overall width cap on the widget (default 1216px) |
--treatment-consultant-checked-surface-colour | Background of the checkmark badge on a selected option |
--treatment-consultant-checked-stroke-colour | Checkmark icon colour |
--treatment-consultant-emerald | "Great" suitability / "ok" compatibility badge on the Results step |
--treatment-consultant-amber | "Possible" suitability badge |
--treatment-consultant-orange | "Difficult" suitability badge, colour-compatibility warning badges |
--treatment-consultant-red | "Not recommended" suitability badge |
--shade-tabs-fade-size | Width of the scroll-edge fade hinting more tabs, shown in granular shade catalog mode (Premium) |
--swatch-scroll-visible | Cards visible per row in the swatch scroll row (default 2.5 below 640px, 3.5 from 640px). Keep a .5 so the next card always peeks in. See Swatch scroll row. |
--treatment-consultant-header-block-size | Height of the built-in header banner (default auto, sized to its content) |
--treatment-consultant-header-background | Header banner background. Takes any background value, such as url(...) center / cover for an image |
--treatment-consultant-hero-block-size | Height of the step hero banner from a 768px widget width up (default 320px) |
--treatment-consultant-hero-scrim-colour | Gradient behind the text overlaid on the hero image (default #000) |
--treatment-consultant-hero-text-colour | Colour of the text overlaid on the hero image (default #fff) |
--treatment-consultant-tryon-controls-gap | Spacing between the virtual try-on sliders, tabs and palette (default 0) |
--treatment-consultant-tryon-controls-margin-block-end | Space below the try-on controls (default 0) |
The virtual try-on opens in its own dialog at the end of <body>, outside the widget's place on your page. That dialog carries the .treatment-consultant class and any classes you pass the widget, so overrides set on .treatment-consultant or :root reach it, but tokens set on a wrapper element around the widget don't. For the same reason, write overrides in a plain style block rather than <style scoped>: scoped styles reach the wizard but miss the try-on. If a block has to be scoped, wrap the selector, for example :global(.treatment-consultant.my-theme).
Every selectable card — hair type, colour, application, cut, treatment, and the Results summary cards — is an OptionButton, optionally paired with an OptionSwatch for colour/image steps. Both expose a full set of custom properties, each with a matching -hover variant for the hover/focus-visible state that falls back to its resting counterpart unless overridden:
.treatment-consultant {
/* Option card button */
--option-gap: var(--_spacing-sm);
--option-border-width: 1px;
--option-border-color: var(--_border);
--option-border-radius: var(--_border-radius);
--option-outline-width: 2px;
--option-outline-color: var(--_option-ring); /* transparent — invisible until hover */
--option-outline-offset: 0;
--option-background-color: var(--_surface);
--option-padding-block: var(--_spacing-lg);
--option-padding-inline: var(--_spacing-lg);
--option-font-size: var(--_font-size-sm); /* drops to --_font-size-xs below a 768px component width unless set */
--option-font-weight: 400;
/* Label overlaid on square/portrait/landscape swatches */
--option-label-scrim-colour: #000;
--option-label-overlay-colour: contrast-color(var(--option-label-scrim-colour)); /* #fff where unsupported */
/* Hover / focus-visible */
--option-border-width-hover: 1px;
--option-border-color-hover: var(--_border);
--option-outline-width-hover: 2px;
--option-outline-color-hover: var(--_ring);
--option-outline-offset-hover: 0;
/* Colour/image swatch (hair type, colour steps, results summary) */
--option-swatch-size: 100px;
--option-swatch-border-width: 1px;
--option-swatch-border-color: var(--_border);
--option-swatch-outline-width: 2px;
--option-swatch-outline-color: var(--_option-ring); /* transparent — invisible until hover */
--option-swatch-outline-offset: 0px;
--option-swatch-img-scale: 1;
/* Swatch hover / focus-visible, triggered by the parent option button */
--option-swatch-border-width-hover: 1px;
--option-swatch-outline-width-hover: 2px;
--option-swatch-img-scale-hover: 1;
}Swatch shape is controlled separately, via behaviour.swatchVariant (circle / square / portrait / landscape) — see Configuration → behaviour.
Any swatch shape other than circle shows the option label over the bottom of the image instead of below it. --option-label-scrim-colour sets the gradient behind it, and --option-label-overlay-colour the text colour, which picks black or white against the scrim where the browser supports it.
The outline colour defaults are theme-driven rather than fixed values: --_option-ring is transparent, so the ring is invisible at rest and only shows on hover/focus, where it switches to --_ring. See Themes below for where the theme comes from.
behaviour.theme switches the component's colour treatment as a whole. Set it via the behaviour config config. Default "default".
A theme is a canvas plus an accent. The accent marks what the client picked (the selected option's outline and tint, and the check badge) and is kept separate from the success green, which only ever means a good match. In "default" the canvas comes from your --brand-* tokens and the accent from --accent-*, falling back to --brand-*, so both follow your branding. For a one-off accent without a full ramp, override --treatment-consultant-checked-surface-colour and --treatment-consultant-checked-stroke-colour instead (see Other tokens).
| Value | Description |
|---|---|
"default" | Dark canvas from your --brand-* tokens, selected state from --accent-*. |
"monochrome-light" | White canvas, foreground darkening from white through greys to near-black. No hue anywhere, selected state included (selections show in near-black), regardless of any --brand-* customisation. |
"monochrome-dark" | Same dark-canvas/light-text layout as "default", with every hue removed. Selections show in white. |
"blush" | Light: dusty pink canvas with a rose accent. |
"champagne" | Light: ivory canvas with an antique gold accent. |
"plum" | Dark: aubergine canvas with a rose accent. |
"ink" | Dark: navy canvas with a brass accent. |
const config = { behaviour: { theme: "champagne" } };The four colourway presets ("blush", "champagne", "plum", "ink") use their own built-in ramps, so they look the same whatever your --brand-* / --accent-* tokens are. If you build your own colours, keep the canvas away from green and teal (they compete with the success colour) and from red, orange and amber (they compete with the warning and error colours).
Both monochrome themes are built on a dedicated chroma-0 grey ramp (--mono-00…--mono-10, shipped in srcdev-hair-treatments/dist/tokens.css) rather than --brand-*, so they stay fully desaturated even if you've customised your app's brand ramp.
Upgrading from 1.4 or earlier: the selected state used to share the success green. If you tuned --success-* to restyle selections, move that to --accent-*.
Set behaviour.sharpCorners to true to square off every rounded corner — option cards, buttons, panels, the license watermark. Deliberately circular/pill shapes (the circle swatch, the checkmark badge, progress dots/bars) keep their own radius and are unaffected. Default false.
const config = { behaviour: { sharpCorners: true } };No html { font-size: 62.5% } reset is needed to use this component. It sizes itself via a local --_unit custom property (default 1px) instead of raw rem, so it renders at the same intended pixel sizes regardless of your app's root font-size. If you'd rather have it scale with your page's root font-size or the user's browser text-zoom setting, opt in explicitly:
.treatment-consultant { --_unit: 0.0625rem; } /* 1px at a default 16px root */TreatmentConsultant is a plain client-side Vue component — it has no server-rendering support. In a Nuxt app (or any SSR framework), rendering it during SSR will 500 on a production build. Wrap it in <ClientOnly>:
<ClientOnly>
<TreatmentConsultant />
</ClientOnly>This is a hard requirement, not a workaround for a bug that'll be fixed later — always wrap it when embedding on a server-rendered page.