AgentSkillsCN

sfcc-sfra-scss

在 SFRA 商店前台中使用 SCSS 进行样式设计与主题定制的最佳实践。适用于被要求在 SFCC 中创建样式覆盖、进行主题设计、打造响应式布局,或进行 CSS 自定义时使用。

SKILL.md
--- frontmatter
name: sfcc-sfra-scss
description: Best practices for styling and theming SFRA storefronts using SCSS. Use when asked to create style overrides, theming, responsive layouts, or CSS customizations in SFCC.

SFRA SCSS Best Practices (AI-Agent Optimized)

Purpose: Provide a concise, implementation‑ready reference for styling and theming Salesforce B2C Commerce SFRA storefronts using SCSS. Optimized for automated reasoning, safe overrides, upgrade resilience, and performant output.


1. Core Principles (Never Skip)

PrincipleRuleWhy It ExistsAction Pattern
Cartridge OverlayNever edit app_storefront_basePreserve upgrade pathCreate mirror path in custom cartridge + import base first
Import First, Then OverrideAlways @import '~base/...'; at topKeeps base layout + utilitiesFile header snippet template
Single Source of TruthCentralize tokens in abstracts/_variables.scssGlobal theming + diffable changesImport base → redefine only changed vars
Shallow SpecificityMax selector depth 2–3Predictable cascadeUse BEM instead of nested HTML chains
Mobile‑FirstBase = smallest viewportLess override churnAdd enhancements with min-width media mixin
Immutable Generated CSSNever hand edit /static/default/cssPrevent driftRecompile via build scripts only
Deterministic BuildSame inputs ⇒ same CSSDebug + CI parityLock dependency versions

Red flag triggers for re‑evaluation: deep nesting (>3), repeated hardcoded colors, !important, duplicated breakpoint literals, editing compiled CSS, or missing initial @import.

AI Guardrail (fast reject): If asked to "change base SCSS" directly, respond with an override strategy instead of editing base source.


2. Minimal Override Workflow (Algorithm)

  1. Inspect element in browser → capture compiled CSS filename (e.g. product/detail.css) & selector(s).
  2. Locate source in base: app_storefront_base/cartridge/client/default/scss/<path>.scss.
  3. Recreate identical relative path in custom cartridge under cartridge/client/default/scss/.
  4. Add header: @import '~base/<path>'; (must be line 1; no preceding BOM/comment).
  5. Append scoped overrides (BEM, tokens, mixins only—no raw hex unless introducing new token).
  6. Run npm run compile:scss (or project alias) → deploy → verify cascade diff.
  7. If change is global (color, spacing, radius) prefer variable override in _variables.scss before component override.

Decision Tree:

GoalLocation to Change FirstFallback If Not Available
Color / Font / Spacing shift_variables.scssComponent partial override
Layout structureISML + classesNew component partial
Responsive tweakMixin breakpointsLocal media query (still via mixin)
Reusable visual patternNew mixinPlaceholder + @extend
Plugin overridevendors/ override partialScoped component patch

Plugin Extension Nuances (Observed from plugin_wishlists):

  • Keep plugin additions minimal: import base global first inside plugin global.scss (@import "~base/global";) then only plugin-specific partials.
  • Do NOT re-import variables inside multiple plugin partials unless you need local compilation context—prefer central import in the entry file to avoid duplication.
  • Avoid redefining spacing tokens mid-file (e.g. $spacer inside a feature file) unless intentionally creating a component-scoped token; document with a comment // local token.
  • Consolidate repeated keyframes: If multiple alerts use identical fade animation, define it once in a shared partial (e.g. components/_animations.scss) instead of inline per component.
  • When plugin needs base mixins (e.g. Bootstrap breakpoints) prefer @import "~base/global"; rather than directly importing bootstrap again to reduce risk of version drift.

3. Canonical SCSS Directory (7‑1 Adapted)

code
scss/
  abstracts/  (_variables.scss, _mixins.scss, _functions.scss)
  base/       (_reset.scss, _typography.scss, _base.scss)
  layout/     (_header.scss, _footer.scss, _navigation.scss, _grid.scss)
  components/ (_buttons.scss, _product-tile.scss, _carousel.scss, ...)
  pages/      (_home.scss, _checkout.scss, _pdp.scss, ...)
  vendors/    (_bootstrap-overrides.scss, _plugin-X.scss)
  global.scss  (imports only – no rules)

Import Order (global.scss):

  1. abstracts (variables → mixins → functions)
  2. vendors
  3. base
  4. layout
  5. components
  6. pages

SFRA Reality Snapshot: The core global.scss in app_storefront_base imports: variables, skin/skinVariables, bootstrap custom, overrides, utilities, icon libraries, then individual component partials. AI agents should treat this as the authoritative ordering when composing custom global.scss overlays: always import base global first in plugin/global contexts, then plugin additions.

Rule: No CSS selectors in global.scss; only @import.


4. Naming & Specificity Strategy (BEM + SFRA Conventions)

AspectRuleExample
BlockSemantic, domain concept.product-tile
Element__ double underscore.product-tile__price
Modifier-- double hyphen.product-tile--featured
State (JS)is- prefix (transient).is-loading
Utilityu- + purpose.u-hidden

Guidelines:

  • Avoid HTML element chaining: prefer .mini-cart__count not header .mini-cart span.count.
  • Never exceed 3 levels of specificity (block + element + modifier/state).
  • Do not style IDs; reserve for scripting anchor only if necessary.
  • Existing SFRA partials sometimes nest deeper (e.g. .product-tile .tile-body .price .tiered .value). When extending these, do NOT replicate entire nesting; extract just the block root with a modifier class you control (e.g. .product-tile--compact).
  • For legacy classes not following BEM (e.g. .wishlistTile), retain original naming but annotate exception with justification comment to aid future refactor and silence lint selectively—in code: /* stylelint-disable-line */.

5. Token & Theme Management

Override pattern (abstracts/_variables.scss):

scss
@import '~base/variables'; // must stay first
// Theme – Colors
$primary: #0B5FFF; // Brand blue
$secondary: #00A676;
$body-bg: #F8F9FA;
$body-color: #212529;
// Typography
$font-family-sans-serif: 'Inter', 'Helvetica Neue', Arial, sans-serif;
$font-family-serif: 'Merriweather', Georgia, serif;
// Radii / Spacing
$border-radius: 2px;
$btn-padding-y: 0.5rem;
$btn-padding-x: 1.125rem;

Rules:

  • Only override variables you intentionally change.
  • Introduce new tokens with consistent naming ($color-*, $space-*, $z-*).
  • Never redefine variables in component partials—centralize.
  • If a plugin must create a small local scalar (e.g. $spacer for a one-off layout tweak), prefix with component context when possible ($wishlist-spacer) to avoid collision with global tokens.
  • Avoid re-importing base variables in each partial—imports are concatenated; repeated imports increase compile time and risk inconsistent overrides if order diverges.

6. Reuse Mechanisms (Decision Matrix)

NeedUseWhyAvoid If
Static property bundle reused widely%placeholder + @extendSingle output selector groupNeeds argumentization
Dynamic pattern w/ params@mixinFlexible + readableOutput duplication causes bloat
Global theming value$variableSingle update pointRepresents multi-rule semantic block
Conditional computed value@functionReturns scalar for calc chainsCan be plain variable

Responsive Mixin:

scss
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
) !default;

@mixin mq($bp) {
  @if map-has-key($breakpoints, $bp) {
    @media (min-width: map-get($breakpoints, $bp)) { @content; }
  } @else { @warn 'Unknown breakpoint: #{$bp}'; }
}

Usage:

scss
.product-tile { width: 100%; @include mq(md) { width: 50%; } }

Bootstrap Breakpoints Interop: SFRA base uses Bootstrap's media-breakpoint-* mixins. When generating code referencing base breakpoints, prefer those existing mixins (@include media-breakpoint-down(md) { ... }) if the base already depends on Bootstrap, instead of introducing a parallel custom map to avoid divergence. Use the custom $breakpoints + mq() pattern only if project intentionally decouples from Bootstrap.


7. Example Override (PDP Title)

File: product/detail.scss

scss
@import '~base/product/detail'; // inherit base

.product-detail__name {
  font-family: $font-family-serif;
  font-size: 2.4rem;
  color: $primary;
}

Compiled file resolved via assets.addCss('/css/product/detail.css') with cartridge path precedence—no ISML changes needed. AI Validation Step: After generation, assert snippet begins with base import and contains only new selectors or modified declarations—no duplication of entire base blocks.


8. Component Patterns Library (Sample Snippets)

Buttons (components/_buttons.scss):

scss
@import '~base/components/buttons';

.btn--pill { border-radius: 999rem; }
.btn--outline-secondary {
  color: $secondary; background: transparent; border: 1px solid $secondary;
  &:hover { background: $secondary; color: #fff; }
}

Product Tile (components/_product-tile.scss):

scss
@import '~base/components/product-tile';

.product-tile {
  border: 1px solid transparent; transition: box-shadow .2s;
  &:hover { box-shadow: 0 4px 12px rgba(0,0,0,.1); border-color: $gray-300; }
  &__price--sale { color: $danger; font-weight: 600; }
}

Refactor Tip: Instead of deeply nested overrides inside `.product-tile .tile-body .price .tiered .value`, target a purposeful modifier class (`.product-tile__price--tiered-value`) you add in ISML for long-term maintainability.

Checkout (pages/_checkout.scss):

scss
@import '~base/pages/checkout';
.checkout-nav .nav-link { background:$gray-200; color:$gray-600;
  &.active { background:$primary; color:#fff; }
}

9. Responsive Strategy

RuleImplementation
Mobile‑firstBase styles = small viewport
Progressive enhancement@include mq(md){...} not max-width overrides
Centralized breakpoints$breakpoints map only
Avoid pixel driftUse tokens, not ad‑hoc numbers

Anti‑Pattern: Desktop CSS first + mobile overrides via @media (max-width) – leads to override churn. Plugin Reality: Wishlist plugin currently mixes media-breakpoint-up and media-breakpoint-down usage; maintain consistency—choose mobile-first pattern (prefer -up for enhancements) and refactor legacy -down only when safe.


10. Accessibility & Inclusive Styling

ConcernChecklist
Color ContrastEnsure WCAG AA (> 4.5:1 body, 3:1 large text). Token review before merge.
Focus StatesVisible focus ring; never remove outline without replacement.
Reduced MotionGate large transitions with @media (prefers-reduced-motion: reduce)
Hidden ContentUse utility .u-visually-hidden for screen-reader only text; avoid display:none for needed ARIA live content.
Touch TargetsMinimum 44px height for interactive elements (buttons, filters).

Utility Example:

scss
.u-visually-hidden { position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }

Animation Respect: Provide a reduced-motion alternative when introducing keyframe animations (e.g. fading alerts). Centralize keyframes and wrap any large movement inside @media (prefers-reduced-motion: no-preference); fallback: immediate end state.


11. Performance Practices

OptimizationActionTooling
Critical CSSGenerate subset for key templates → inline via store-skin assetnpm i critical
Prune Unused CSSIntegrate PurgeCSS scanning ISML & JSPurgeCSS plugin
MinificationEnsure prod build via sgmf-scripts produces minified bundleCI check size
Source Maps (dev only)Enable for DX; disable prodSass compiler flag
Font StrategyUse WOFF2 + subsetting + font-display: swapFont tooling

Suggested CI Budget: Global CSS ≤ 250KB uncompressed (enterprise baseline) – fail build if regression > +15% week‑over‑week. Duplicate Import Scan: Add a CI grep step to detect repeated @import "~base/variables"; inside plugin component partials—emit warning to consolidate.


12. Linting & Quality Automation

Install:

code
npm i -D stylelint stylelint-config-standard-scss stylelint-declaration-strict-value

Config (.stylelintrc.json):

json
{
  "extends": "stylelint-config-standard-scss",
  "plugins": ["stylelint-declaration-strict-value"],
  "rules": {
    "selector-class-pattern": ["^([a-z][a-z0-9]*)(__(?:[a-z0-9]+))?(--[a-z0-9-]+)?$", {"message": "Use BEM: block__element--modifier"}],
    "max-nesting-depth": 2,
    "declaration-no-important": true,
    "scale-unlimited/declaration-strict-value": [["color", "background-color", "border-color", "font-size"], {"ignoreValues": ["inherit", "transparent", "currentColor"]}]
  }
}

Script:

json
"scripts": { "lint:scss": "stylelint 'cartridges/**/*.scss' --cache" }

Optional: pre‑commit hook with Husky + lint-staged.

AI Generation Rule Set: When asked to produce SCSS:

  1. Always start with required base import (unless file is abstracts/_variables.scss).
  2. Do not inline raw colors already expressible as existing tokens—prefer token reference.
  3. Replace any suggested !important with a cascade adjustment strategy comment.
  4. Provide at most one new local token; advise moving to global if reused >2 times.
  5. Add a 2-line header comment with PURPOSE + SAFE REMOVE classification.

13. Anti‑Patterns (Refactor Immediately)

SmellWhy DangerousFix
Editing compiled CSSLost on next buildModify SCSS source
Missing base importBreaks structural inheritanceAdd @import '~base/...'; line 1
Deep selector chainsSpecificity lock-inConvert to BEM blocks/elements
Repeated hex codesInconsistent themingPromote to variable
!important usageOverrides cascade disciplineReduce specificity / re-order imports
Inline media queries w/ literalsHard to refactorUse mq() mixin
Component partial modifies unrelated blockHidden couplingCreate new partial / utility
Re-importing base variables in every plugin partialBuild bloat / risk inconsistent overridesImport once at entry (e.g. plugin global.scss)
Inline duplicated keyframesLarger bundle & maintenance overheadCentralize in shared components/_animations.scss

14. Troubleshooting Matrix

SymptomLikely CauseDiagnosticResolution
Style not appliedNot in compiled CSSSearch compiled file for selectorCheck import order / rebuild
Base style lostMissing base importInspect diff of partialAdd import + revert overrides
Wrong theme color persistingVariable shadowed locallyGrep for variable redefinitionCentralize in _variables.scss
Breakpoint shift inconsistentHardcoded px valuesgrep -R "992px"Replace with token map
Layout jumps FOUCCritical CSS missingLighthouse traceImplement store-skin inline CSS
Repeated variable override ignoredLater import overrides earlierTrace import graph orderConsolidate variable overrides at earliest import point

15. Promptable Action Snippets (For AI Agents)

IntentPrompt Template
Create component override"Generate SFRA SCSS override for <component> (base path <path>). Include base import and BEM modifiers for states: loading, error."
Introduce new theme color"Add semantic token for highlight color with accessible contrast against $body-bg and update button hover style."
Refactor deep selectors"Refactor selector <selector> into BEM with max depth 2; preserve semantics."
Add responsive variant"Extend .product-tile to show 2 columns at md, 4 at lg via existing breakpoint mixin."
Performance audit"List top 10 heaviest SCSS partials by compiled size estimate and suggest consolidation."
Plugin extension"Generate wishlist plugin SCSS override importing '~base/global' then add toast animation using existing base token palette; avoid duplicating keyframes if fade already exists."

16. Migration Checklist (Before Deploy)

[] All overrides start with base import [] No direct edits to app_storefront_base [] Variables overridden centrally only [] Lint passes (no warnings in CI) [] No !important (or justified + documented) [] CSS bundle size budget respected [] Critical CSS updated for homepage + PDP [] Source maps disabled for production [] New utilities documented (inline comment header) [] No duplicate keyframe blocks with identical declarations [] Plugin global includes base global exactly once


17. Reference Quick Commands

code
# Compile SCSS
npm run compile:scss

# Lint SCSS
npm run lint:scss

# Find hardcoded colors (non-variable)
// in app_custom/cartridge/client/default/scss/abstracts/_variables.scss

# Estimate partial usage (appearance in compiled)
@import '~base/variables';

CI Grep Helpers (Optional):

code
grep -R "@import \"~base/variables\"" cartridges/plugin_* | sort
grep -R "@keyframes fade" cartridges/ | wc -l

18. Secure & Maintainable Patterns

ConcernGuidance
Multi-brand scalingAbstract brand diffs to dedicated _brand-<id>.scss imported conditionally via build flag
A/B testing CSSIsolate experiment layer in components/experiments/ with clear removal date comment
Plugin overridesKeep each plugin override in vendors/_<plugin>-overrides.scss with upstream version tag
Future SFRA upgradeDiff only variable + override partials; never fork base files wholesale

Header Annotation Block (optional for generated partial):

scss
// PURPOSE: Override of base product tile for brand visual refinement
// DEPENDS: ~base/components/product-tile
// SAFE REMOVE: Yes (reverts to base visual style)

Classification Legend:

  • SAFE REMOVE: Removing file reverts to acceptable base styling.
  • CONDITIONAL: Removal affects brand contract (document impact).
  • CRITICAL: Provides accessibility or functional styling (never remove without replacement).

19. When NOT to Override

ScenarioBetter Option
Structural HTML limitationAdjust ISML markup first
Repeated spacing hacksIntroduce spacing scale utility classes
Overriding grid internalsUse flexbox utilities / custom wrapper
One-off promotional layoutPage partial (pages/_promo-<slug>.scss) + sunset note
Behavior actually JS-driven (e.g. show/hide)Adjust JS or data attributes; keep CSS purely presentational

20. AI Generation Quick Checklist (Inline in Responses)

Provide along with any generated SCSS so humans can validate rapidly:

  1. Base Import Present? (Yes/No)
  2. Variable Overrides? (List or None)
  3. New Tokens Introduced? (List or None)
  4. Max Nesting Depth (#)
  5. Uses Existing Breakpoint Mixins? (Yes/No)
  6. Any Raw Hex Values? (List or None + justification)
  7. Keyframes Added? (Name or None; already exists?)
  8. Accessibility Considerations? (Focus/contrast/motion)
  9. Removable Classification (SAFE REMOVE / CONDITIONAL / CRITICAL)
  10. Lint‑Sensitive Areas (Exceptions with reason)

If any answer violates a stated rule, propose an auto-correction diff, not just a warning.