We use cookies to understand how this site is used. You can accept or reject them — see our Cookie Policy for details.

Theming

Every override targets the .treatment-consultant class (or any ancestor of it) with CSS custom properties — no build-time theming step needed.

Colour ramps

The component reads four semantic 11-step ramps (00 light through 10 dark): --brand-*, --success-*, --warning-* and --error-*. Import the defaults from srcdev-hair-treatments/dist/tokens.css, or supply your own to match your brand:

css
:root {
  --brand-00: oklch(98% 0.0086 195);
  --brand-01: oklch(94% 0.0342 195);
  /* ...through */
  --brand-10: oklch(25% 0.1216 195);

  --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:

css
:root {
  --brand-00: var(--teal-00);
  --brand-01: var(--teal-01);
  /* ...through */
  --brand-09: var(--teal-09);
  --brand-10: oklch(25% 0.0896 195);
}

Images

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:

FieldTypeUsed on
imagestringHairTypeOption, ColourOption (naturalColours), DesiredColourOption, DesiredShadeOption, NaturalShadeOption
image2xstringDesiredShadeOption, 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:

ts
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.

Fonts

PropertyUsed forDefault
--treatment-consultant-font-bodyBody text, sublabels, notessystem-ui, sans-serif
--treatment-consultant-font-headingStep titles, Results title, section titlesGeorgia, Cambria, "Times New Roman", Times, serif
--treatment-consultant-font-labelProgress status, option labelsSame as font-body
css
.treatment-consultant {
  --treatment-consultant-font-body: "My Font", sans-serif;
  --treatment-consultant-font-heading: "My Heading Font", serif;
}

Other tokens

PropertyUsed for
--treatment-consultant-max-inline-sizeOverall width cap on the widget (default 1216px)
--treatment-consultant-checked-surface-colourBackground of the checkmark badge on a selected option
--treatment-consultant-checked-stroke-colourCheckmark 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-sizeWidth of the scroll-edge fade hinting more tabs, shown in granular shade catalog mode (Premium)

Option buttons & swatches

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:

css
.treatment-consultant {
  /* Option card button */
  --option-gap: var(--_spacing-sm);
  --option-border-width: 1px;
  --option-border-color: var(--_border-active);
  --option-border-radius: var(--_border-radius);
  --option-outline-width: 2px;
  --option-outline-color: var(--_outline-default); /* transparent — invisible until hover */
  --option-outline-offset: 0;
  --option-background-color: var(--_surface-active);
  --option-padding-block: var(--_spacing-lg);
  --option-padding-inline: var(--_spacing-lg);
  --option-font-size: var(--_font-size-md);
  --option-font-weight: 400;

  /* Hover / focus-visible */
  --option-border-width-hover: 1px;
  --option-border-color-hover: var(--_border-active);
  --option-outline-width-hover: 2px;
  --option-outline-color-hover: var(--_outline-active);
  --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-active);
  --option-swatch-outline-width: 2px;
  --option-swatch-outline-color: var(--_outline-default); /* 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.

The outline colour defaults are theme-driven rather than fixed values — --_outline-default is transparent, so the ring is invisible at rest and only shows on hover/focus, where it switches to --_outline-active. See Themes below for where the theme comes from.

Themes

behaviour.theme switches the component's colour treatment as a whole, on top of any customisation — set it via the behaviour config config. Default "default".

ValueDescription
"default"The existing dark, brand-coloured canvas with light text.
"monochrome-light"White canvas, foreground darkening from white through greys to near-black. Fully desaturated regardless of any --brand-* customisation.
"monochrome-dark"Same dark-canvas/light-text layout as "default", with the brand hue removed.
ts
const config = { behaviour: { theme: "monochrome-light" } };

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.

Sharp corners

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.

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

Sizing & your page's root font-size

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:

css
.treatment-consultant { --_unit: 0.0625rem; }  /* 1px at a default 16px root */

SSR & ClientOnly

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>:

vue
<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.