Sass Placeholder Selectors

Beginner
⏱️ 12 min read
📚 Updated: Jul 2026
🎯 5 Examples
%selector

What You’ll Learn

Sass placeholder selectors start with %. They work like classes for @extend, but unused placeholders never appear in the CSS—perfect for optional library styles. Five compiled examples follow.

01

%name

Silent selector

02

@extend

Opt-in CSS

03

Omit rules

No clutter

04

Libraries

No forced HTML

05

vs class

When to choose

06

Practice

5 examples

What Are Placeholder Selectors?

A placeholder looks and acts a lot like a class selector, but it starts with % (for example %toolbelt) and is not included in the CSS output by itself.

  • Any complex selector (the parts between commas) that contains a placeholder is omitted.
  • A whole style rule is omitted when every selector in its list contains a placeholder.
  • Placeholders become useful when another selector @extends them.
  • Unlike extending a class, unused placeholders do not leave dead CSS behind.
💡
Beginner tip

Think of % as a “silent class”: styles live in Sass until a real selector opts in with @extend. Your HTML never needs a class="%toolbelt"—that is not valid CSS anyway.

📝 Basic Syntax

styles.scss
.alert:hover, %strong-alert {
  font-weight: bold;
}

%strong-alert:hover {
  color: red;
}
// Only .alert:hover survives in CSS until something @extends %strong-alert

The second rule is dropped entirely because its only selector contains a placeholder. The first rule keeps .alert:hover and drops the %strong-alert branch.

🔗 Extending Placeholders

Placeholders exist to be extended. Library authors can ship reusable style chunks without requiring a fixed HTML class name. Consumers write their own classes and @extend %name.

styles.scss
%toolbelt {
  box-sizing: border-box;
  padding: 16px 0;
  width: 100%;

  &:hover { border: 2px solid rgba(#000, 0.5); }
}

.action-buttons {
  @extend %toolbelt;
  color: #4285f4;
}
📚
App vs library

Official guidance: for a stylesheet used only in your own app, extending an existing class is often fine. Prefer placeholders when styles may go unused, or when you do not want to dictate HTML class names.

⚖️ Placeholders vs Class Selectors

%placeholder.class
Unused outputOmitted from CSSStill emitted
HTMLNot used in markupCan appear in HTML
Best forLibraries / optional packsApp UI classes
Activation@extend %nameClass in HTML or @extend .name

⚡ Quick Reference

GoalCode
Define a placeholder%card { padding: 1rem; }
Opt in from a class.tile { @extend %card; }
Nest states%btn { &:hover { … } }
Chain placeholders%card--lg { @extend %card; }
Leave unused styles outDefine %lib-x and never extend it

Examples Gallery

Each example is verified with Dart Sass. Open View Compiled CSS for output.

📚 Getting Started

See what gets omitted—and what @extend brings back.

Example 1 — Placeholders Are Omitted from CSS

Mixed selector lists drop only the placeholder branches.

styles.scss
.alert:hover, %strong-alert {
  font-weight: bold;
}

%strong-alert:hover {
  color: red;
}

How It Works

%strong-alert never reaches the browser. The pure-placeholder %strong-alert:hover rule disappears completely.

Example 2 — Share Styles with @extend %toolbelt

Two button groups opt into the same silent base, including nested &:hover.

styles.scss
%toolbelt {
  box-sizing: border-box;
  border-top: 1px rgba(#000, .12) solid;
  padding: 16px 0;
  width: 100%;

  &:hover { border: 2px rgba(#000, .5) solid; }
}

.action-buttons {
  @extend %toolbelt;
  color: #4285f4;
}

.reset-buttons {
  @extend %toolbelt;
  color: #cddc39;
}

How It Works

Sass merges both classes into the placeholder’s selector list. Nested &:hover becomes .reset-buttons:hover, .action-buttons:hover. Extra color rules stay on each class.

📈 Utilities, Chains & Unused Code

Reusable silent helpers and dead-code-free libraries.

Example 3 — Silent Utility Placeholder

A flex-center helper with no HTML class of its own.

styles.scss
%center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero {
  @extend %center;
  min-height: 40vh;
}

.modal {
  @extend %center;
  position: fixed;
  inset: 0;
}

How It Works

%center never appears as a class name. Only .hero and .modal show up—sharing one compiled rule for the flex centering.

Example 4 — Chain Placeholders

Build a richer silent base that extends another placeholder.

styles.scss
%card {
  border-radius: 8px;
  padding: 1rem;
  background: #fff;
}

%card--shadow {
  @extend %card;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

.product-card {
  @extend %card--shadow;
  border: 1px solid #e0e0e0;
}

.profile-card {
  @extend %card;
  border: 1px solid #90caf9;
}

How It Works

Extending %card--shadow pulls in %card as well. Both cards share the base styles; only the product card gets the shadow.

Example 5 — Unused Placeholders Leave No CSS

Define optional library styles that stay silent until extended.

styles.scss
%unused-lib-style {
  outline: 2px dashed red;
}

.button {
  padding: 0.5rem 1rem;
  background: #036;
  color: #fff;
}

How It Works

Nothing extends %unused-lib-style, so it contributes zero bytes. That is the main win over defining unused .unused-lib-style classes.

🚀 Real-World Use Cases

  • Component libraries — ship optional skins consumers @extend.
  • Silent utilities — shared layout helpers without utility class spam in HTML.
  • Design system tokens as rules — group borders, radii, and shadows under %.
  • Theme packs — include many placeholders; only extended ones cost CSS weight.
  • Avoiding API lock-in — let users choose their own class names in markup.

🧠 How Compilation Works

1

Collect % rules

Parse placeholder selectors like silent classes.

Parse
2

Apply @extend

Merge real selectors into placeholder selector lists.

Extend
3

Drop leftovers

Remove any complex selector that still contains %.

Omit
4

Emit clean CSS

Only real selectors reach the stylesheet.

⚠️ Common Pitfalls

  • Expecting % in HTML — placeholders are Sass-only; use real classes in markup.
  • Overusing @extend — deep extend graphs can surprise you; mixins are often clearer for parameterized styles.
  • Forgetting nothing extends it — then zero CSS is emitted (usually good—unless you meant to include it).
  • Extending across media queries carelessly@extend has limits across contexts; see the @extend page.
  • Using placeholders for every app class — if the class is already in your HTML, a normal class may be simpler.

💡 Best Practices

✅ Do

  • Use % for optional / library style chunks
  • Pair placeholders with clear @extend at call sites
  • Nest &:hover (and friends) inside placeholders when needed
  • Prefer placeholders when unused CSS must stay out of the bundle
  • Document which placeholders a package expects consumers to extend

❌ Don’t

  • Put % names in HTML class attributes
  • Replace every mixin with @extend blindly
  • Force library users into rigid class names when a placeholder would do
  • Assume unused .classes are free—they still emit CSS
  • Build huge extend chains that are hard to reason about

Key Takeaways

🚫 01

% is silent

no CSS alone

Core
🔗 02

@extend %

opt-in styles

Extend
📚 03

Libraries

no forced HTML

API
⚖️ 04

vs .class

app vs lib

Choose
🗑️ 05

Unused = 0

no dead CSS

Size

❓ Frequently Asked Questions

Selectors that start with % (for example %toolbelt). They look and behave much like class selectors, but they are not written to the CSS output unless another selector extends them.
Unused placeholders leave no CSS behind. Classes always emit. Placeholders also avoid forcing library users to put a specific class name in their HTML—they @extend the placeholder from their own selectors.
Define styles under %name { ... }, then write .my-class { @extend %name; }. Sass merges .my-class into the placeholder’s selector list in the compiled CSS.
Only the complex selectors that do not contain a placeholder are emitted. Example: .alert:hover, %strong-alert { } compiles to just .alert:hover { } unless something extends %strong-alert.
Official Sass guidance: if the stylesheet is only for your own app and a real class already exists in the HTML, extending that class is often simpler. Prefer placeholders when writing reusable libraries or optional style packs.
Yes. %toolbelt { &:hover { ... } } works. Extenders inherit those nested rules too, so .action-buttons:hover appears in the output when you @extend %toolbelt.
Did you know?

Placeholder selectors are sometimes called “silent classes.” The % syntax was designed so stylesheets can share rules through @extend without polluting the final CSS when a chunk goes unused—especially helpful in large Sass libraries.

Conclusion

Placeholder selectors give you extendable style chunks that stay out of CSS until something opts in. Use them for libraries and optional packs; use ordinary classes when your app already owns the HTML class names.

Continue with Sass Nesting or @extend.

Next: Sass Nesting

Nest selectors to mirror HTML—keep nests shallow.

Sass Nesting →

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