Codeskill

Learn to code, step by step

SCSS in component libraries and boundaries

Using SCSS inside component libraries – React, Vue, Web Components, or plain HTML partials – without leaking implementation details or fighting encapsulation.

Publish CSS, not SCSS (usually)

Library consumers expect a stable CSS file they can link or import. Shipping raw SCSS forces every consumer to run Sass, pin compatible versions, and resolve your partial paths. Compile before publish unless your audience is explicitly SCSS-only internal teams.

// Authoring: src/components/button.scss
@use '../tokens' as *;

.btn {
  padding: $space-sm $space-md;
  &--primary { background: $color-primary; }
}

// Published: dist/button.css (compiled)
// package.json "style": "dist/index.css"

Some design-system packages ship both: compiled CSS for most users, SCSS entry for teams that want to re-theme at compile time. Document which path you support.

CSS custom properties as the public API

Expose theming through custom properties on the component root, not Sass variables consumers cannot reach at runtime:

// _button.scss - internal Sass tokens compile to defaults
.btn {
  --btn-bg: #{$color-primary};
  --btn-padding-x: #{$space-md};
  background: var(--btn-bg);
  padding: $space-sm var(--btn-padding-x);
}

// Consumer override - no Sass required
.sidebar .btn {
  --btn-bg: #333;
}
/* Compiled output */
.btn {
  --btn-bg: #0066cc;
  --btn-padding-x: 1rem;
  background: var(--btn-bg);
  padding: 0.5rem var(--btn-padding-x);
}

Sass variables are author-time. Custom properties are the contract with host pages and shadow boundaries.

Shadow DOM and scoped styles

Web Components with shadow DOM do not see global CSS unless you construct stylesheets or use :host. Compiled component CSS often ships as a string injected per component. SCSS still helps author that string – compile each component partial to a CSS file, then import into JS:

// card.scss - scoped selectors for shadow root
:host {
  display: block;
  padding: var(--card-padding, 1rem);
}

.card__title {
  font-size: 1.125rem;
}

Global design tokens can pierce the boundary via inherited custom properties on :host or explicit var(--token) references defined on the host element in light DOM.

BEM and prefix discipline

Prefix class names with the library namespace to avoid clashes with app CSS:

$ns: 'cs-'; // codeskill / your-lib prefix

.#{$ns}btn {
  /* ... */
}

.#{$ns}card {
  &__body { /* .cs-card__body */ }
}

Configure prefix once in tokens. Consumers grep for cs- instead of fighting generic .btn rules from Bootstrap and your library at once.

Side-effect imports and tree-shaking

One big index.css is simple but ships unused component styles. Per-component CSS files let bundlers include only what JS imports. SCSS authoring can still centralise tokens; compilation emits separate CSS chunks per component.

Boundaries checklist

  • Public: class names, custom properties, documented variants
  • Private: Sass variables, mixins, internal partial paths
  • Semver: breaking class renames or removed custom properties = major bump
  • Do not rely on app-global selectors (.container, bare a tags) inside library components

Try it

  • Refactor one component to expose two custom properties instead of Sass-only variables.
  • Add a namespace prefix to all classes in a small component set.
  • Decide what your library would publish: CSS only, or CSS plus SCSS entry – write one paragraph explaining why.
PreviousDocumentation and living style guides