Sass selector.extend() Function

Intermediate
⏱️ 12 min read
📚 Updated: Jul 2026
🎯 5 Examples
sass:selector · Dart Sass 1.23+

What You’ll Learn

selector.extend() belongs to the built-in sass:selector module. It rewrites a selector the same way the @extend rule would—keeping the original and adding extended forms. This page covers the three arguments, vs selector.replace, and five compiled examples.

01

Concept

@extend as a function

02

Module

@use "sass:selector"

03

Returns

Selector value

04

Args

selector, extendee, extender

05

vs replace

Keep vs swap

06

Practice

5 examples

What Is selector.extend()?

Official docs: extends $selector as with the @extend rule. It returns a copy of $selector modified as if this ran:

styles.scss
#{$extender} {
  @extend #{$extendee};
}

In plain terms: wherever $extendee appears in $selector, Sass keeps that match and adds forms that use $extender instead. If $selector never contains $extendee, the value is returned unchanged.

  • Three arguments: the selector to rewrite, what to extend from, and what extends it.
  • Placeholder selectors are allowed; parent selectors (&) are not.
  • See also selector.replace() when you want a swap instead of an add-on.
💡
Beginner tip

Think of sharing styles: a.disabled extended by .link becomes a.disabled, .disabled.link—both keep the .disabled piece.

📝 Syntax

styles.scss
@use "sass:selector";

// Recommended
selector.extend($selector, $extendee, $extender)

// Legacy global name
selector-extend($selector, $extendee, $extender)

Parameters

ParameterTypeRequiredDescription
$selectorSelectorYesSelector to rewrite with @extend-style unification.
$extendeeSelectorYesThe piece being extended (what @extend would target).
$extenderSelectorYesThe selector that extends $extendee.

Return value

TypeResult
SelectorExtended selector list (or the original if $extendee was absent)

📦 Loading sass:selector

styles.scss
// Recommended — namespaced (Dart Sass 1.23+)
@use "sass:selector";
$sel: selector.extend("a.disabled", "a", ".link");
// a.disabled, .disabled.link

// Optional — bring members into scope
@use "sass:selector" as *;
$sel: extend("a.disabled", "a", ".link");

// Legacy global
$sel: selector-extend("a.disabled", "a", ".link");

🎨 Official Patterns

CallResult
extend("a.disabled", "a", ".link")a.disabled, .disabled.link
extend("a.disabled", "h1", "h2")a.disabled (unchanged)
extend(".guide .info", ".info", ".content nav.sidebar").guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar

The third sample shows intelligent unification: complex extenders can produce more than one new complex selector, not just a simple class swap. Dart Sass may print compound classes in a different simple-selector order (for example .disabled.link); matching is the same.

⚖️ extend vs replace

HelperTypical result for a.disabled / a.linkUse when
selector.extenda.disabled, .disabled.linkKeep the original and add the extender form
selector.replace.disabled.linkSwap the original piece out completely

🛠 Compatibility

This is about Sass compilers, not browsers. The rewrite runs at compile time.

Implementationextend
Dart SassYes — prefer selector.extend (module since 1.23.0)
LibSassLegacy selector-extend may exist; no @use "sass:selector"
Ruby SassLegacy selector-extend may exist; no @use "sass:selector"

Prefer current Dart Sass so the module API matches the official docs.

⚡ Quick Reference

GoalCode
Import selector@use "sass:selector";
Share a compoundselector.extend("a.disabled", "a", ".link")
No match (safe)selector.extend("a.disabled", "h1", "h2") → unchanged
Use as a selector#{selector.extend(…)} { … }
Swap instead of addselector.replace(…)

Examples Gallery

Each example uses selector.extend at compile time (Dart Sass 1.23+). Open View Compiled CSS for verified output.

📚 Getting Started

Official docs samples, then real CSS rules.

Example 1 — Official Extend Samples

Match found, no match, and complex unification.

styles.scss
@use "sass:selector";

.probe {
  --link: #{selector.extend("a.disabled", "a", ".link")};
  --miss: #{selector.extend("a.disabled", "h1", "h2")};
  --guide: #{selector.extend(".guide .info", ".info", ".content nav.sidebar")};
}

How It Works

When $extendee is missing, nothing changes. When it is present, Sass keeps the original and adds unified extender forms.

Example 2 — Build Rule Selectors

Interpolate the extended result into a real style rule.

styles.scss
@use "sass:selector";

#{selector.extend(".btn.primary", ".btn", ".cta")} {
  background: teal;
}

How It Works

.btn is extended by .cta, so both .btn.primary and .primary.cta share the same declarations.

📈 Practical Patterns

Mixins, replace contrast, and alternate class loops.

Example 3 — Share Styles Mixin

Wrap extend so callers pass selector, extendee, and extender.

styles.scss
@use "sass:selector";

@mixin share-styles($selector, $extendee, $extender) {
  #{selector.extend($selector, $extendee, $extender)} {
    color: inherit;
  }
}

@include share-styles("a.disabled", "a", ".link");

How It Works

Same official first sample, packaged as a reusable mixin for design-system helpers.

Example 4 — Extend vs Replace

Same arguments, different intent.

styles.scss
@use "sass:selector";

.compare {
  --extend: #{selector.extend("a.disabled", "a", ".link")};
  --replace: #{selector.replace("a.disabled", "a", ".link")};
}

How It Works

extend keeps a.disabled. replace drops it and leaves only .disabled.link.

Example 5 — Alternate Class Loop

Extend one base selector with several alternate class names.

styles.scss
@use "sass:selector";

$base: ".card.featured";
$alts: ".promo", ".highlight";

@each $alt in $alts {
  #{selector.extend($base, ".card", $alt)} {
    border-width: 2px;
  }
}

How It Works

Each alternate extends .card while keeping .featured on both the original and the new class.

🚀 Real-World Use Cases

  • Shared state styles — let .link.disabled share rules with a.disabled.
  • Design-system aliases — map alternate class names onto a canonical compound.
  • Mixin APIs — accept selector strings and emit extended rule lists.
  • Complex layouts — unify nested extenders like the official guide/info sample.
  • Safe defaults — no-op when $extendee is absent (returns the original).

🧠 How Compilation Works

1

Pass three selectors

Call selector.extend($selector, $extendee, $extender).

Source
2

Sass applies @extend logic

Keeps $extendee matches and adds unified $extender forms.

Compile
3

Interpolate into rules

Use #{…} as the selector for a style block.

Emit
4

CSS ships

Browsers only see finished lists like a.disabled, .disabled.link.

⚠️ Common Pitfalls

  • Confusing with replace — extend keeps the original; replace swaps it out.
  • Parent selectors& is not allowed in these arguments.
  • Wrong argument order — remember selector, then extendee, then extender.
  • Forgetting interpolation — wrap results in #{…} to use them as rule selectors.
  • Expecting CSS @extend — this is a Sass function result, not a browser feature.

💡 Best Practices

✅ Do

  • Use @use "sass:selector" and selector.extend()
  • Use it when you want to keep the original selector
  • Prefer replace when a full swap is clearer
  • Pass clean selectors without parent &
  • Prefer Dart Sass 1.23+

❌ Don’t

  • Pass parent selectors (&)
  • Assume every miss is an error—unchanged return is intentional
  • Confuse this function with the @extend at-rule syntax alone
  • Expect browsers to evaluate the call
  • Rely on LibSass for the module API

Key Takeaways

Knowledge Unlocked

Five things to remember about selector.extend()

Rewrite selectors with @extend logic—keep the original and add extender forms.

5
Core concepts
📦 02

Module

sass:selector

@use
🔗 03

Result

original + extended

Output
📚 04

Miss

returns as-is

Detail
05

Alt

replace to swap

Compare

❓ Frequently Asked Questions

selector.extend($selector, $extendee, $extender) returns a copy of $selector rewritten as if $extender { @extend $extendee; } ran. It keeps matches of $extendee and adds forms that use $extender. Example: extend("a.disabled", "a", ".link") becomes a.disabled, .disabled.link.
extend keeps the original selector and adds extended variants (a.disabled, .disabled.link). replace swaps $original for $replacement and drops the original match (.disabled.link only).
Official docs: if $selector does not contain $extendee, the function returns $selector unchanged.
No. Arguments may contain placeholder selectors, but not parent selectors.
Yes. selector-extend($selector, $extendee, $extender) is the legacy global name. Prefer selector.extend after @use "sass:selector".
Dart Sass 1.23+. LibSass and Ruby Sass do not load sass:selector with @use; they may still offer the legacy global name.
Did you know?

Official Sass docs describe selector.extend as returning a copy of $selector modified with an @extend rule—so you can use the same unification engine without writing the at-rule in place.

Conclusion

selector.extend() rewrites selectors with @extend-style unification: keep the original, add extender forms, and leave the value alone when $extendee is missing. Prefer selector.replace when you need a full swap, and avoid parent selectors.

Continue with selector.is-superselector() or the Sass introduction.

Next: compare selectors

Learn selector.is-superselector() to check if one selector covers another.

selector.is-superselector() →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful