- Working examples: badge.css, status-light.css
- Main rules: 01_component-css
- Custom property decisions: 02_custom-properties
- PR self-check: 03_component-css-pr-checklist
:host {
display: inline-block;
}
* {
box-sizing: border-box;
}
.swc-Badge {
--_swc-badge-border-width: token('border-width-200');
min-block-size: var(--swc-badge-height, token('component-height-100'));
background: var(
--swc-badge-background-color,
token('accent-background-color-default')
);
}
:host([size='s']) {
--swc-badge-height: token('component-height-75');
}
:host([variant='positive']) {
--swc-badge-background-color: token('positive-background-color-default');
}
.swc-Badge--magenta:where(.swc-Badge--subtle) {
--swc-badge-background-color: token('magenta-background-color-default');
}/*
- visual styles are incorrectly on :host
- legacy --mod-* indirection is preserved
- hardcoded values instead of token()
- selector specificity is too high
*/
:host {
padding: 8px;
background: var(--mod-badge-background, var(--swc-badge-background-color));
}
.swc-Badge.swc-Badge--large.swc-Badge--primary {
padding: 16px;
}Put layout-participation styles on :host (display, inline-size, min-*/max-*, position, custom property definitions). Put visual styles on .swc-ComponentName or an internal part.
Two non-obvious cases to flag explicitly:
paddingon:host— feels like layout but is visual spacing; move it to the internal class.cursor: pointer— do not set it anywhere; the project relies on browser defaults.
Also check that display: flex or display: grid on :host is actually laying out direct children of :host, not internal children already wrapped inside a container element. Flex/grid properties (flex: 1 1 auto, align-self) only activate when their immediate parent is the flex/grid container — if the element is inside a wrapper div, the flex context must be on that wrapper, not on :host.
:host:has() is unreliable across browsers. Safari and Firefox do not consistently support :has() relative to a shadow host boundary. Move all :has() selectors to the internal wrapper: .swc-Component:has(...) instead of :host:has(...). Custom properties cascade identically either way. See 01_component-css#state-implementation-patterns.
→ See 01_component-css
Follow the prescribed stylesheet order to manage specificity and reduce selector conflicts. → See 01_component-css#rule-order
Use token() for design token values. Use --swc-* only for intentionally exposed override points, and --_swc-* for internal/private properties. Do not keep old --mod-* chains.
Every exposed --swc-* property must be documented with a @cssprop JSDoc tag on the primary component class export (the SWC layer class, not the core base). Storybook picks these up automatically and surfaces them in the API docs panel.
/**
* @cssprop --swc-button-height - Block size of the button. Defaults to the medium component height token.
* @cssprop --swc-button-border-radius - Corner radius. Defaults to half the component height (pill shape).
*/
export class Button extends ButtonBase { … }→ See 02_custom-properties
Use :host([size="..."]) and :host([variant="..."]) for consumer-settable attributes that should expose a customization surface. Use .swc-ComponentName--modifier for:
- Implementation details that should not expose overrides (non-semantic color variants, static color, geometric modifiers)
- Derived states — visual modes computed from slot content or internal logic, not set by consumers (e.g. icon-only layout derived from
hasIcon && !hasLabel). These must never appear as a host attribute; apply them viaclassMapin the template.
// ✅ Derived state as a class modifier — not a consumer attribute
class=${classMap({ 'swc-Button': true, 'swc-Button--iconOnly': this.hasIcon && !this.hasLabel })}→ See 01_component-css#when-to-use-classes-vs-attributes
When migrating block padding, use component-padding-vertical-{scale} (S2). Do not carry forward component-top-to-text-{scale} or component-bottom-to-text-{scale} from S1/Spectrum CSS — those were offset hacks to compensate for glyph positioning in the old typeface. Adobe Clean VF corrected the glyph position, so the offsets no longer produce visually centered text in S2 components.
→ See 01_component-css#vertical-spacing-tokens
Keep selector specificity at or below (0,1,0). If you need a compounded selector, use :where() on the extra class instead of stacking specificity. Don't use higher-specificity selectors to "win."
→ See 01_component-css#managing-specificity and 05_anti-patterns
Only add @media (forced-colors: active) if browser defaults are not conveying correct semantic intent, and always put it at the end of the component stylesheet.
Semantic HTML elements (<button>, <input>, <a>) get correct forced-colors treatment automatically — ButtonText, focus Highlight, disabled GrayText — without any CSS override. Only non-semantic elements (a decorative <div> or a <span> using background-color as a visual indicator) need explicit overrides. Do not carry over forced-colors rules from 1st-gen Spectrum CSS without first verifying the 2nd-gen component uses non-semantic markup that requires them.