@use Modules vs. @extend: Managing Placeholder Inheritance Across Boundaries
0 reputation · 27 May 2020, 17:59 UTC
0 reputation · 27 May 2020, 17:59 UTC
Dart Sass 2.0+ deprecates @import in favor of @use with explicit namespaces. While @use prevents global namespace collisions and enables @forward for curated APIs, it introduces a hard constraint: @extend only resolves within the same module scope. A %placeholder defined in _a.scss cannot be extended from _b.scss when _a is loaded via @use.
Teams migrating from @import must choose between:
@use fully and refactoring all cross-module @extend chains — often by duplicating placeholders or replacing them with mixins or CSS custom properties.@import (or using @use '...' as * as a shim) to preserve existing @extend behavior, accepting deprecation warnings and future removal.No automated migration path exists for cross-module @extend references. The sass-migrator tool converts syntax but cannot rewrite selector inheritance across module boundaries. Source maps and duplicate CSS output improve with @use, yet the refactoring effort for placeholder-heavy codebases remains unbounded.
What criteria should drive the decision to refactor @extend versus defer migration? Are there patterns that minimize placeholder duplication while staying within @use semantics? How do teams validate that no silent @extend failures remain after switching module systems?
The decision to refactor @extend chains during a migration to @use depends on the scale of your design system and your tolerance for technical debt. Because @extend resolution is confined to the module scope, cross-module placeholders silently fail to produce CSS output.
@import dependencies create unpredictable global collisions for consumers.@use improves compilation performance and source map accuracy, which outweighs the manual effort of refactoring placeholders.@import chain is stable, the risk of introducing regression during a massive @extend refactor may exceed the benefit of removing warnings.To maintain the benefits of @use without duplicating placeholder definitions, transition from inheritance (@extend) to composition (@mixin).
Replace %placeholder with a @mixin. While @extend groups selectors in the CSS, a mixin copies the properties. This is the standard approach for module-based Sass.
// _shared.scss
@mixin base-button {
padding: 10px;
border-radius: 4px;
}
// _component.scss
@use 'shared';
.btn-primary {
@include shared.base-button;
background: blue;
}
For values that change frequently but the structure remains the same, move the shared logic to CSS variables defined at the :root. This removes the need for Sass-level inheritance entirely for those specific properties.
Since Dart Sass does not always throw a hard error for unresolvable @extend directives across modules, you must verify the output manually or via tooling:
@import version against the @use version. Any missing selectors in the new version indicate a failed @extend.--verbose to check for warnings regarding unresolvable placeholders.Diagnostic Requirement: Are you using @use '...' as * as a temporary shim? This allows @extend to work by flattening the namespace, but it defeats the primary purpose of module encapsulation.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 28 May 2020, 05:18 UTC
A practical guardrail: add a post-build step that parses compiled CSS for selectors that should have been extended but appear without the expected rules. Since cross-module @extend silently drops the extending selector in Dart Sass 2.0+, a simple diff against a known-good baseline (or a snapshot test) catches regressions that deprecation warnings miss when @extend is used dynamically via interpolation. Pair this with sass --verbose --quiet-deps 2>&1 | grep -i extend in CI to surface both static and runtime gaps.