afv-library/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md
Kenam e850eb1a25
W-21831072: Add skill: uplifting-components-to-slds2 (#152)
Add skill: uplifting components to slds2

Made-with: Cursor
2026-03-31 21:03:28 +05:30

16 KiB

Color Hooks Decision Guide

This guide helps you choose the correct SLDS 2 color hook. Use it when replacing hardcoded colors or when the linter suggests multiple options.

BEFORE choosing any hook: Always inspect the element's context in this order:

  1. Markup (.html for LWC, .cmp for Aura) — search for the CSS class. Check parent containers, nesting depth, ARIA attributes, interactive role.
  2. JavaScript — if not in markup, search the JS/TS files for dynamic class insertion (classList.add, template literals, conditional class bindings).

CSS alone is never enough — always determine context from markup or JS before choosing a hook.

Table of Contents

Hook Selection Priority

Always try semantic hooks first. ~85-90% of color decisions should use semantic hooks. System and palette hooks are last resorts, not alternatives.

1. SEMANTIC HOOKS (try first, ~85-90% of decisions)
   surface-*, accent-*, error/warning/success/info/disabled-*
   • Accessibility built-in  • Theme-aware  • Dark mode ready

2. SYSTEM HOOKS (5-10%, only when no semantic family fits)
   *-base-*
   • Manual accessibility required  • No semantic meaning

3. PALETTE (data viz & decorative only, <5%)
   palette-*
   • Manual accessibility  • Non-semantic color only

If you find yourself reaching for a system hook, re-check whether a semantic hook applies — surface, accent, or feedback families cover most cases.

Decision flow:

  1. Page/overlay/container background? → Surface hooks
  2. Brand/interactive? → Accent hooks
  3. Error/warning/success/info/disabled state? → Feedback hooks
  4. Edge case with no semantic fit? → System hooks
  5. Data viz or decorative? → Palette hooks

Visual color density rule (85-5-10): ~85% of UI surface area should be neutral grays/whites (surface hooks), ~5% accent/feedback colors, ~10% maximum expressive colors.

"Color ≠ Semantic Meaning"

Scenario Wrong Choice Right Choice Why
Red text but no error class --slds-g-color-error-1 --slds-g-color-palette-red-50 Color for emphasis, not error state
Blue that's not clickable --slds-g-color-accent-2 --slds-g-color-palette-cloud-blue-50 Decorative blue, not interactive
Green in data chart --slds-g-color-success-1 --slds-g-color-palette-green-50 Data point, not success state
Orange highlight span --slds-g-color-warning-1 --slds-g-color-palette-orange-90 Visual emphasis, not warning

Use system hooks if the element matches a semantic meaning. If it explicitly does not, use the palette equivalent (e.g., --slds-g-color-palette-red-40). Evaluate background-color first, as it informs the correct foreground color pair.


Surface Family

Core Question

Is this element a page foundation, an overlay (modal/popover/dropdown), or does it sit within the content flow on an existing surface?

Characteristic Use surface-* Use surface-container-*
Creates new stacking context Yes No
Elevated/overlays other content Yes No
Exists within page's content flow No Yes
Sits on top of an existing surface No Yes

Hook Types

Hook Type Pattern Use For
Surface --slds-g-color-surface-1 / -2 / -3 Pages, modals, popovers, overlays
Container --slds-g-color-surface-container-1 / -2 / -3 Cards, buttons, panels on existing surfaces
On Surface --slds-g-color-on-surface-1 / -2 / -3 Foreground (text, icons) on any surface or container
Inverse --slds-g-color-surface-inverse-1 / -2 Dark backgrounds on light themes (hero banners, inverted headers)

Surface Numbering (Light → Dark, NOT States)

Surface variants are an aesthetic progression, not interaction states:

Variant Description Typical Use
surface-1 Lightest (white) Clean base; cards/containers stand out against it
surface-2 Light gray Softer separation; avoids harsh white backgrounds
surface-3 Medium gray Additional depth; rare in practice

The same numbering applies to surface-container-* (1=lightest, 3=darkest).

On-Surface Emphasis Levels

On-surface variants represent content emphasis, not surface pairing:

Variant Emphasis Use For
on-surface-1 Low (de-emphasized) Captions, placeholder text, secondary content
on-surface-2 Medium (standard) Body text, labels, filled input fields
on-surface-3 High (maximum weight) Page titles, component headings, primary content

All three on-surface variants can appear on the same surface background. Choose by content importance, not by matching the surface number.

Markup Nesting Depth → Hook Choice

DOM Position Hook Why
Page/app wrapper (<body> or root) surface-1 Foundation canvas
Card/panel directly on page surface-container-1 First container on a surface
Sub-panel inside a card surface-container-2 Nested container
Modal / popover / dropdown surface-1 Creates NEW stacking context — resets depth

State Progression

The starting variant depends on which one matches the original default color — don't assume -1:

Default Hover When
surface-container-1 surface-container-2 Default bg is white/near-white
surface-container-2 surface-container-3 Default bg is light gray (~#f4f4f4)

Edge Cases

Scenario Hook Why
Full-page card (IS the page background) surface-* Acts as page surface, not a container
Slide-out panel overlaying content surface-* Creates new stacking context
Slide-out panel in page flow (no overlay) surface-container-* Sits within existing surface
Nested modals surface-1 for each Each modal creates its own stacking context
Dropdown over page content surface-1 Overlay, not a container

Inverse Hooks

Use when hardcoded dark-blue backgrounds (#032d60, #03234d) appear on light themes. Pair with --slds-g-color-on-surface-inverse-* — do NOT pair with regular on-surface-*.

Warning: CSS Class Names Are Irrelevant

An element named .card-container might use surface-* if it's the page-level background, or surface-container-* if it's a card. Always base the decision on structural DOM position, not naming conventions.

Surface variants (1-2-3) are a light-to-dark aesthetic progression, NOT functional states.


Accent Family

Core Question

Is this element interactive or expressing brand identity?

Hook Types

Hook Type Pattern Use For
Accent accent-1 / -2 / -3 Links, clickable text, interactive icons
Container accent-container-1 / -2 / -3 Brand button backgrounds
Border border-accent-1 / -2 / -3 High-emphasis brand borders
On Accent on-accent-1 / -2 / -3 Text on brand backgrounds

accent-1 may not meet 4.5:1 WCAG contrast on non-white backgrounds. accent-2 passes on all standard surfaces — use it as the safe default for text links. Go up one for hover: accent-2accent-3.

Quick Decision

  1. Link or clickable text → accent-2
  2. Brand button background → accent-container-1
  3. Text on brand background → on-accent-1
  4. Brand border → border-accent-1 (use only when design explicitly requires brand-colored outline — default to neutral border-2 for most borders)

Accent vs Surface

Element Type Surface Accent
Non-interactive card surface-container-* Never
Clickable card surface-container-* (bg) accent-2 (text)
Primary button Never accent-container-*

Feedback Family

Core Question

Does the markup/ARIA context indicate a specific state?

Hook Types

Type Use For
error-* / error-container-* / on-error-* / border-error-* Invalid inputs, errors, destructive actions
warning-* / warning-container-* / on-warning-* / border-warning-* Cautions, alerts
success-* / success-container-* / on-success-* / border-success-* Confirmations, valid states
info-* / info-container-* / on-info-* Tips, help badges
disabled-* / disabled-container-* / on-disabled-* / border-disabled-* Inactive elements

Quick Decision

  1. aria-invalid attribute → error-*
  2. Class with "error"/"invalid" → error-*
  3. Class with "success"/"valid" → success-*
  4. [disabled] attribute → disabled-*
  5. role="alert" → check alert type

Variant Availability

Type Has -2? Why
Error Yes Destructive buttons need hover states
Success Yes Success buttons need hover states
Warning No (only -1) No warning buttons — only static alerts
Info No (only -1) No info buttons — only static badges
Disabled Yes Different visual weights

Palette Family

Core Question

Is this color for data visualization or decoration without semantic meaning?

Use for: chart data series, decorative gradients, non-semantic colored elements — NOT standard UI.

Hook Pattern: --slds-g-color-palette-{color}-{grade}

Grade Scale: 0 (darkest) to 100 (lightest). Example: --slds-g-color-palette-cloud-blue-50.

Cool Tones (Recommended): cloud-blue, indigo, purple, violet Warm Tones (Use with caution): green, orange, hot-orange, red

Warm tones risk confusion with feedback colors — green looks like success, red looks like error, orange looks like warning. Only use palette hooks when the color explicitly does NOT carry semantic meaning.

Palette → Semantic Conversion

If existing code uses palette hooks but the element is actually interactive or semantic, convert to the appropriate semantic family:

Element Role Palette Hook (Before) Semantic Hook (After)
Clickable icon/link palette-*-blue-* accent-*
Brand button bg palette-*-* accent-container-*
Error indicator palette-red-* error-*
Success indicator palette-green-* success-*
Chart data point palette-* Keep palette (correct usage)
Decorative element palette-* Keep palette (correct usage)

Check markup context: if the element has click handlers, href, role="button", or brand intent, it belongs in a semantic family.

Accessibility

Palette hooks do NOT guarantee accessible contrast. Verify manually:

  • 50-point rule: 50 grade points between bg and text (4.5:1 WCAG)
  • 40-point rule: 40 grade points between bg and UI element (3:1 WCAG)

Color vision deficiency: Never use red+green or blue+purple as sole differentiators. Always pair with patterns, labels, or shapes.


System Family

Core Question

Have all semantic and palette options been exhausted?

Need Try First System Fallback Only If
Background surface-* neutral-base-95 Custom component, no semantic fit
Interactive accent-* brand-base-50 Legacy exact match required
Error state error-* error-base-50 Special non-standard requirement

System hooks require manual accessibility testing. Never use as a first choice.


Choosing the Numbered Variant

Do not default to -1. Numbered variants represent a light-to-dark progression. Pick the variant whose actual rendered value best matches the original hardcoded value.

  1. Look at the original hardcoded value
  2. Compare to the rendered values of available variants
  3. Pick the closest match, not the lowest number
/* Original: #014486 — very dark navy */
/* WRONG: defaulting to -1 */
border-color: var(--slds-g-color-border-accent-1, #014486);
/* RIGHT: -3 is darkest, closest to #014486 */
border-color: var(--slds-g-color-border-accent-3, #014486);
/* Original: rgb(243,242,242) — very light gray */
/* container-1 ≈ #fff, container-2 ≈ #f4f4f4, container-3 ≈ #f3f2f2 */
/* RIGHT: container-3 is closest */
background-color: var(--slds-g-color-surface-container-3, rgb(243,242,242));

When an element has interactive states, variants progress sequentially from the starting match:

Start At Hover Active
-1 -2 -3
-2 -3

Background-Foreground Pairing Rules

If background uses... Then text/fill MUST use...
surface-* or surface-container-* on-surface-*
surface-inverse-* or surface-container-inverse-* on-surface-inverse-*
accent-container-* on-accent-*
error-container-* on-error-*
warning-container-* on-warning-*
success-container-* on-success-*
info-container-* on-info-*
disabled-container-* on-disabled-*

Never mix families — e.g., don't use on-accent-* on a surface-container-* background.

Border Color Decision

Context Hook
Interactive element (default) border-2 (primary choice for most borders)
Decorative divider border-1
Validation state border-[state]-1
Disabled border-disabled-1

Applied Examples — Color Context Investigation

Context from Class Name

/* Before — recordPage.css */
.headerBackground {
    background: var(--lwc-colorBackgroundAlt);
}

Decision: From class name headerBackground, this is a header container on another surface → surface-container-1.

/* After */
.headerBackground {
    background: var(--slds-g-color-surface-container-1, var(--lwc-colorBackgroundAlt));
}

Context from Component Name

/* Before — defaultOrgSharingSettingsPanelFooter.css */
.THIS {
    background-color: t(colorBackground);
}

Decision: .THIS alone isn't enough. Component name says panel footer → container on a panel surface → surface-container-2.

/* After */
.THIS {
    background-color: var(--slds-g-color-surface-container-2, var(--lwc-colorBackground));
}

Context from Deep Investigation (Markup + JS)

/* Before — floatingPanelContent.css */
.main-body {
    background-color: var(--lwc-colorBackgroundAlt);
}

Investigation:

  • Component name floatingPanelContent is ambiguous
  • .main-body not found in markup — it's a computed class
  • JS shows the class is applied alongside slds-popover* classes

Decision: Main background of a popover → surface element → surface-1.

/* After */
.main-body {
    background-color: var(--slds-g-color-surface-1, var(--lwc-colorBackgroundAlt));
}

Border Color — Semantic Fit Over Math

/* Before */
border: 1px solid #dddbda;

Decision: Mathematically closest might be palette-neutral-90, but border-1 is still light grey, fits the design intention, and fits the semantic system better.

/* After */
border: var(--slds-g-sizing-border-1, 1px) solid var(--slds-g-color-border-1, #dddbda);

Pairing Examples

Card on page surface:

.product-card {
  background-color: var(--slds-g-color-surface-container-1, #ffffff);
  color: var(--slds-g-color-on-surface-1, #2e2e2e);
}

Brand button:

.primary-action {
  background: var(--slds-g-color-accent-container-1, #066afe);
  color: var(--slds-g-color-on-accent-1, #ffffff);
}

Error alert (role="alert" + error class):

.error-message {
  background: var(--slds-g-color-error-container-1, #fddde3);
  color: var(--slds-g-color-on-error-1, #b60554);
  border: 1px solid var(--slds-g-color-border-error-1, #b60554);
}

Linter vs Agent Decision-Making

Linters suggest hooks by color similarity — they match hex values regardless of meaning. Agents must choose by semantic family — inspect markup (.html/.cmp), ARIA attributes, and element purpose. Always prioritize semantic correctness over color matching.