Skip to content

I Built an SLDS 2 Skill. It Was Actually SLDS 1.

Reworking a Claude Code skill for Salesforce Lightning Web Components turned up a mislabeled design-system version, a linter that lies by omission, and a lesson in hedging honestly.

Matt Dennis

The skill’s description field said “SLDS 2 styling hooks.” The org it was meant for runs SLDS 1. That’s the kind of mismatch that doesn’t throw an error — it just makes an agent confidently generate CSS that’s wrong in a way nobody notices until a button renders unstyled in production.


I’d had Claude repackage a reference doc I’d built for generating Lightning Web Components — Salesforce’s component framework — into a Claude Code skill. The doc was solid: sourced from Salesforce’s own sf-skills agent-skills repo, the LWC developer guide, and the SLDS docs. I just hadn’t checked which version of SLDS it was actually describing. Neither had the doc.


A Skill You Have to Ask For

Most of my Claude Code skills auto-trigger. Mention the right keyword in conversation and the relevant skill loads itself; no ceremony required. This one needed the opposite behavior. LWC work is occasional, not a daily driver, and I didn’t want “Lightning” or “component” showing up in some unrelated conversation and pulling in three thousand words about styling hooks.


There’s no toggle for that. SKILL.md frontmatter supports exactly two fields, name and description, and the description is the entire routing mechanism — Claude reads it to decide whether a skill is relevant to the current conversation. So “invoke-only” isn’t a mode, it’s a sentence: the description says, in as many words, use ONLY when the user explicitly says “/lwc-design-guide” and do NOT auto-trigger on casual mentions. It works because Claude actually reads and follows it, not because anything is enforced.


The Downgrade That Wasn’t

Once the skill existed, I asked for the actual fix: rework it for SLDS 1, since that’s the design system my org runs, not the newer SLDS 2 (Salesforce’s internal codename was Cosmos, in case you’ve seen that in release notes and wondered). I expected this to be a pruning exercise — strip out whatever was SLDS 2-only, ship a smaller doc.


Instead the research inverted the premise. Salesforce’s own Compare SLDS Versions doc states it plainly: component-level styling hooks — the --slds-c-* custom properties that let you retheme a single lightning-button instance without touching its shadow DOM — are fully supported in SLDS 1 and not yet supported in SLDS 2. The original guide leaned on --slds-c-* hooks constantly, because that’s the correct, surgical way to restyle one base component. It had been SLDS 1-flavored the entire time. The “SLDS 2” label on the description field was simply wrong, and by accident, the guide underneath it was more right than its own header claimed.


That’s a strange kind of bug. Not “the code doesn’t work” — the code was fine. The label was lying about which world the code lived in.


Two Things That Don’t Exist on SLDS 1

Two pieces of the original guide didn’t survive contact with the actual version split.


Dark mode. It ships with SLDS 2’s Cosmos theme exclusively — there’s no SLDS 1 equivalent, beta or otherwise. The original doc had a whole section on writing hooks so dark mode “works for free.” I deleted it. Building toward a feature the runtime doesn’t have isn’t defensive coding, it’s dead code with better production values.


The SLDS linter. @salesforce-ux/slds-linter is the tool Salesforce ships to catch hardcoded colors and deprecated tokens — the guide’s original validation step was npx @salesforce-ux/slds-linter@latest lint <path>, zero violations as the bar. Its own README states it’s built specifically for SLDS 2 migration rules. No version flag, no SLDS 1 mode. Point it at correct SLDS 1 code that uses --slds-c-* component hooks and it’ll flag the hooks as violations, because from the linter’s perspective those hooks shouldn’t exist. I swapped the validation step for a grep pass hunting hardcoded hex values and raw pixel counts instead — cruder, but it doesn’t punish you for using the feature you’re supposed to be using.


What I Didn’t Verify, and Said So

The harder problem was the global color hooks — --slds-g-color-surface-1, -on-surface-2, -accent-container-1, the semantic roles that make a card’s background and its text agree with each other. One Salesforce doc implied these were an SLDS 2 addition, layered on top of SLDS 1’s older hook set. A page hosted directly on the SLDS 1 site documented the same hooks as if they’d always been there. Official sources, in apparent disagreement, about whether a var(--slds-g-color-surface-1, #fff) in a component running under an SLDS 1 org theme actually resolves to a real value or silently falls through to the fallback every time.


I didn’t resolve that by picking the more convenient answer. The guide now says so directly: the hook tables are carried over from the earlier draft, spot-checked against the SLDS 1 site rather than re-verified line by line, and anyone using an unfamiliar hook should confirm it against v1.lightningdesignsystem.com before shipping. That sentence costs nothing to write and it’s the difference between a reference doc and a reference doc that gets trusted the next time it’s wrong about something smaller.


This is the same discipline the guide preaches to whoever’s writing the actual components — its own anti-patterns file has a rule against inventing hook names by pattern, because --slds-g-font-scale-2 exists and --slds-g-font-size-2 doesn’t, and the two are indistinguishable if you’re guessing. Writing “unverified, check before use” instead of asserting a plausible-sounding fact is the same rule applied one level up, to the document instead of the CSS.


Why the Label Matters More Than the Content

Salesforce ships SLDS releases on a schedule the way most platforms ship dependency updates — regularly, incrementally, without asking whether every consumer of the design system tracked the change. A skill that’s technically accurate but versioned wrong doesn’t fail loudly. It generates a component that compiles, passes the linter it was told to run, looks fine in the preview — and quietly breaks the moment someone opens it in the org where it actually has to live. The failure mode isn’t a stack trace, it’s a designer three weeks later asking why the button doesn’t match anything else on the page.


The guide is public now, at github.com/grqg-dev/lwc-design-guide, version-pinned and honest about the parts it didn’t get to verify. The mislabeled one never got that lucky twice.