Chapter 14: Sass Selector
Sass Selector Functions — the tools inside the sass:selector module. This is one of the more advanced and powerful parts of Sass, especially useful when you’re writing libraries, component systems, dynamic classes, or need to programmatically manipulate selectors (append, nest, replace, check relationships, etc.).
In 2026 (Dart Sass 1.97+ reality), selector manipulation is done exclusively through the sass:selector module — old global functions like selector-nest(), selector-append() still work in many setups but throw deprecation warnings and will eventually be removed. Always use the module style.
1. Why Selector Functions Exist & When You Actually Need Them
Most Sass users never touch sass:selector — nesting, &, BEM naming, and basic interpolation cover 90% of cases.
But you reach for sass:selector when you want to:
- Build dynamic selector utilities (e.g., add pseudo-classes to many selectors at once)
- Create modifier systems (.btn–large, .card–hoverable)
- Nest programmatically (not just statically with {})
- Check selector relationships (is-superselector?, is-in-selector?)
- Replace / unify / parse selectors in themes or legacy code migration
- Generate very complex selectors safely (avoiding invalid CSS)
It’s basically Sass giving you access to its own internal selector engine.
2. Loading the Module (Mandatory in Modern Sass)
|
0 1 2 3 4 5 6 7 |
@use 'sass:selector' as sel; // or @use 'sass:selector'; → selector.nest(...) |
We’ll use the sel. alias in examples — short and clear.
Important format note: Almost all functions return a selector value, which internally is a comma-separated list of complex selectors (each complex selector is a space-separated list of compound selectors as unquoted strings). You usually interpolate them with #{…} to use in rules.
3. The Main sass:selector Functions (Practical 2026 List)
Here are the most commonly used ones with real examples.
A. selector.nest($selectors…) / sel.nest(…)
→ Nests selectors like static nesting would do (but programmatically)
This is the closest thing to “dynamic nesting”.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
@debug sel.nest(".parent", "&:hover"); // → ".parent:hover" @debug sel.nest(".card", ".title", "&:hover"); // → ".card .title:hover" @debug sel.nest(".menu", "li", "> a"); // → ".menu li > a" |
Real usage – add pseudo to many components:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@use 'sass:selector' as sel; $components: ".btn", ".card", ".alert", ".modal"; @each $comp in $components { #{$comp} { // normal styles... #{sel.nest($comp, "&:hover")} { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(black, 0.12); } #{sel.nest($comp, "&:active")} { transform: scale(0.98); } } } |
B. selector.append($selectors…) / sel.append(…)
→ Appends selectors without space (great for BEM modifiers, pseudo-elements)
|
0 1 2 3 4 5 6 7 8 9 |
@debug sel.append(".btn", "--large"); // ".btn--large" @debug sel.append(".btn", ":disabled"); // ".btn:disabled" @debug sel.append(".card", "__title"); // ".card__title" @debug sel.append(".input", "-error"); // ".input-error" |
Real usage – BEM modifier generator:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@use 'sass:selector' as sel; @mixin modifier($base, $mod) { #{sel.append($base, "--#{$mod}")} { @content; } } .card { padding: 1.5rem; @include modifier(&, "featured") { border: 2px solid #6c5ce7; } @include modifier(&, "compact") { padding: 1rem; } } |
C. selector.extend($selector, $extendee) / sel.extend(…)
→ Like @extend, but programmatic (adds extenders to a selector)
|
0 1 2 3 4 5 6 7 |
@debug sel.extend(".alert", ".alert-success"); // → ".alert, .alert-success" |
Use case: Rare, mostly for meta-programming or legacy CSS migration.
D. selector.replace($selector, $original, $replacement)
→ Replaces one simple selector with another
|
0 1 2 3 4 5 6 7 |
@debug sel.replace(".old-theme .card", ".old-theme", ".new-theme"); // → ".new-theme .card" |
Use case: Theme prefix swapping in large codebases.
E. selector.unify($selector1, $selector2)
→ Finds the most specific selector that matches elements selected by both
|
0 1 2 3 4 5 6 7 |
@debug sel.unify(".parent .child", ".parent:hover"); // → ".parent:hover .child" |
Use case: Combining media-query/context selectors safely.
F. selector.is-superselector($super, $sub)
→ true if $super matches everything $sub matches (and possibly more)
|
0 1 2 3 4 5 6 7 8 |
@debug sel.is-superselector(".parent", ".parent .child"); // true @debug sel.is-superselector(".btn", ".btn--primary"); // true @debug sel.is-superselector(".btn--primary", ".btn"); // false |
Use case: Validation in mixins, avoid over-specificity.
G. selector.parse($selector)
→ Parses a string into selector value (useful for debugging)
|
0 1 2 3 4 5 6 7 |
@debug sel.parse(".main aside:hover, .sidebar p"); // → complex internal list representation |
Use case: Mostly debugging or meta code.
H. Other useful ones (less common but powerful)
- selector.simple-selectors($selector) → list of simple selectors in a compound selector
- selector.is-in($needle, $haystack) → whether $needle is contained in $haystack
- selector.pseudo($selector) → extracts pseudo-classes/elements
4. Real-World Combined Example – Dynamic Pseudo & Modifier System
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
@use 'sass:selector' as sel; @mixin add-state($state) { #{sel.append(&, $state)} { @content; } } @mixin add-pseudo($pseudo) { #{sel.nest(&, "&" + $pseudo)} { @content; } } .btn { padding: 0.8rem 1.6rem; background: #6c5ce7; color: white; @include add-state(":disabled") { opacity: 0.6; cursor: not-allowed; } @include add-pseudo(":hover") { background: color.adjust(#6c5ce7, $lightness: -12%); } @include add-state("--large") { padding: 1.2rem 2.4rem; font-size: 1.125rem; } } |
This kind of mixin + selector function combo is very common in 2026 component libraries (Headless UI style, design-system helpers).
5. Quick Tips & Gotchas (Dart Sass 2026)
- Always interpolate selector function results: #{sel.nest(…)}
- Parent selector & works in many functions (especially nest and append)
- Selector values cannot be used directly as strings — must interpolate
- Avoid overusing — most cases are better solved with static nesting + &
- Performance: fine for normal usage; very complex selector generation can slow compile slightly
- Deprecations: move away from old globals (selector-nest(…) → sel.nest(…))
- Debugging tip: @debug sel.nest(…) shows internal structure
You’ve now got selector manipulation unlocked — this is advanced Sass territory that separates good stylesheets from great, scalable libraries.
Next — want to combine sass:selector + maps + lists to build a full dynamic component modifier system? Or go into sass:meta for even deeper meta-programming? Or start putting everything together in a small project structure?
Just tell me — we’re getting really powerful now! 🚀
