public
Last active

This gist describes a new feature we're experimenting with for Sass 3.2: placeholder selectors. They do not get generated into your output, but they can be used like a class and extended like one.

  • Download Gist
0_silent_selector_grid.scss
SCSS
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
$gutter: 10px;
$grid-unit: 60px;
 
%clearfix {
*zoom: 1;
&:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
}
 
%column {
@extend %clearfix;
float: left;
margin-right: $gutter;
&.last {
margin-right: 0;
}
}
 
@for $i from 1 through 9 {
.span-#{$i} {
@extend %column;
width: $grid-unit * $i + $gutter * ($i - 1);
}
}
1_output.css
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
.span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9 { *zoom: 1; }
.span-1:after, .span-2:after, .span-3:after, .span-4:after, .span-5:after, .span-6:after, .span-7:after, .span-8:after, .span-9:after { content: "\0020"; display: block; height: 0; clear: both; overflow: hidden; visibility: hidden; }
 
.span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9 { float: left; margin-right: 10px; }
.last.span-1, .last.span-2, .last.span-3, .last.span-4, .last.span-5, .last.span-6, .last.span-7, .last.span-8, .last.span-9 { margin-right: 0; }
 
.span-1 { width: 60px; }
.span-2 { width: 130px; }
.span-3 { width: 200px; }
.span-4 { width: 270px; }
.span-5 { width: 340px; }
.span-6 { width: 410px; }
.span-7 { width: 480px; }
.span-8 { width: 550px; }
.span-9 { width: 620px; }

Gosh that's awesome

Someday I swear Sass will unify those two .span-1, ..., .span-9 selectors. But for now features like the placeholder selector are more important than optimizations like that.

Very cool feature, but I'd hate to see this implemented with the %. I'd much prefer a @virtual keyword:

@virtual clearfix { ... }

Or something along those lines....

Overloading symbols is problematic as CSS's use of symbols is a moving target.

Great, even better syntax than @silent; !

CSS will always be a moving target (They could add an @virtual directive with different semantics), fortunately, as a preprocessor we can adjust to that and probably faster than the browsers can implement.

@jlong That was basically our original design (see nex3/sass#236). The biggest downside to that approach is that it's ambiguous whether it should/does make the given selector virtual everywhere or just for that ruleset; if it's global, you get this unexpected wide-ranging effect from a small bit of code, whereas if it's local, it gets very awkward to use the placeholder in multiple places. @virtual or @silent also feel like more complex and verbose ways of declaring what are ultimately just placeholder selectors. It seems like cutting out the middleman and directly expressing what we're trying to express is correct.

I'm also reasonably confident that CSS won't use % in a conflicting way any time soon.

All that said, this syntax isn't set in stone; we're open to being convinced to go back to a directive approach, or even a new approach no one's mentioned yet.

I have been over this in my head many times and I do agree % is a solid choice.

At first I thought there must be a way to allow @mixins to be @extended. No new keywords, no new syntax, just good ol' Sass.

However, cascading was then mentioned on Twitter. You could still get that with a keyword based syntax:

/* @virtual, @silent or just */ 
@mixin button {
  border-radius: 10px;

  #page & a {
    color: red;
  }
}

.my-button { @extend button; }

however once defined, a ruleset cannot be updated, e.g., if I were to want button within #main to be different I can't have it like so. It seems % can deal with that as well:

// _library.scss
//
%button { .. }
.big %button { .. }

// my_file.scss
//
@import 'library';

.small %button a { .. }

.my-button { @extend %button; }

and there you have it, inherited and extended.

Cool feature!

+1 for %

This has been pushed, by the way, and is released in 3.2.0.alpha.49.

Very interesting thread.

@StanAngelhoff, why not think of these like "open" classes (much like Ruby)? To expand on your example:

// _library.scss
//
@virtual button { .. 
  .big & { .. }
}

// my_file.scss
//
@import 'library';

@virtual button { .small & { .. }  }
.my-button { @extend button; }

In my mind this is analogous @media bubbling.

Especially for the Scss syntax, I much prefer the keyword approach as it keeps the names clean. This is one of the things I love about Ruby. Even variables (with the exception of instance variables) aren't cluttered up with a prefix symbol like PHP. Most of the time it's just not needed. Especially when you have proper scoping. (OT: Which is also why we need namespaces in Sass.)

For the name of the keyword, I'd vote not to overload "@mixin" as while these two concepts are similar they actually aren't the same. Mixins can utilize parameters and logic. It would be ideal if we could find a noun for this keyword that is similar to @mixin. (@virtual is more of an adjective, but the way we tend to use the word @mixin in Sass, it is more like a noun so we need something parallel to this.)

@placeholder is an interesting concept, but seems more descriptive of the "placeholder selector" concept that Chris advocates. I think of these more like virtual classes in object oriented programming.

@abstract could be used like a noun, but like @virtual it's really more of an adjective.

@module could be used for this. However, I tend to think we will want to use that in the future for namespaces. But maybe modules and namespaces are actually similar enough that overlap here could be useful?

@module FancyButtons {
  @module Matte { .. }
  @module Shiny { .. }
  @module Metallic { .. }
}

.my-button { @extend FancyButtons.Metallic; }

This is actually the approach that Ruby takes with modules. (Note: I use capitalization, instead of a fancy symbol prefix to indicate that I'm referring to a module. This could be a convention rather than a requirement.)

The interesting thing about using the @module keyword as more of a namespace is that it can also be used with mixins for interesting results:

@module FancyButtons {
  @mixin button-base($bg-color) { .. }
  @module Red { @mixin button-base(red); }
  @module Green { @mixin button-base(green); }
}

@module EvenFancierButtons {
  @extend FancyButtons;
  // Redefine the button base mixin for different results
  @mixin button-base($bg-color) { .. }
}

.my-button { @extend EvenFancierButtons.Red; }

I'm not totally sure that @module is the right name here, but it does open up some interesting options.

Cool, was waiting for it ! Thanks

@jlong: comparing virtual @-placeholder rulesets and @-media bubbling is an interesting thought, it makes sense. The module approach sounds pretty neat as well, it deserves a separate issue on GH?

@jlong Thanks for the feedback. We agree wholeheartedly that Sass needs a module system. There is some interesting thinking here on that subject and we need to start deciding what the module system's requirements are so that it can be implemented for Sass 4.0.

The feature that we want to build is exactly what the name "placeholder" implies. It's like a template for a set of selectors using an "abstract class" but the similarity with OOP ends at the definition of "abstact". The module system is where much of the concepts of OOP will end up (encapsulation, polymorphism, inheritance). This is a stylesheet and whether or not you like [sigils](http://en.wikipedia.org/wiki/Sigil_(computer_programming\)) the fact remains that CSS uses them for ids and classes and so I do not feel we are out of line introducing one to indicate a placeholder in a selector. Keeping them terse makes them no less cumbersome to use than a class and they immediately stand out to a reader that the selector is an abstraction that must be understood before it can be used.

I do not see any correlation whatsoever between @media bubbling and this feature.

@chriseppstein I saw it like so:

button {
  border-radius: 10px;
  @media print { border-radius: 0; }
}

@virtual button {
  border-radius: 10px;
}

.print {
  @virtual button {
    border-radius: 0;
  }
}

.my-button { @extend button; }

==>

button { border-radius: 10px; }
@media print {
  button { border-radius: 0; }
}

.my-button { border-radius: 10px; }
.print .my-button { border-radius: 0; }

nope. still don't get it. it's ok. I don't think there's an analogy to be drawn here. If you're connecting these dots, I think you're missing something.

@chriseppstein All analogies break down at some point. And similarity is in the eye of the beholder. :)

Well I think if you form a mental model of this feature with an association to media bubbling it will cause you to reason incorrectly about it. So no, I don't think there's an eye of the beholder thing here. Having helped defined the concept means I can say this. Whether you like the concept we've defined or think another concept would be better is certainly a topic for further debate.

@jlong Your module example definitely has some interesting ideas, but it also has some aspects that I'm pretty wary of. What you're proposing is, like Chris said, basically a Sassy take on an object system, complete with multiple inheritance and overriding. While these systems are very powerful, they also bring along a huge amount of complexity. In order to understand them, the user has to wrap their head around a number of fairly abstract concepts.

Although in some sense placeholder selectors are a pretty radical idea on their own -- we certainly don't add new syntax lightly -- they have the undeniable virtue of being easy to explain and understand. Where your module proposal has a lot of extra functionality, placeholder selectors are just "extendable selectors that don't get rendered," and that's exactly what's needed for the specific use case we're addressing.

I guess my point is just that I don't want to overreach and add a more complex feature that I'm not sure is necessary, rather than a simpler feature that neatly solves the problem at hand.

@chriseppstein What do you think we are missing? @StanAngeloff's example seems to illustrate the concept of the cascade and the similarity to @media bubbling well.

@nex3 I guess I just favor a more explicit syntax for this rather than a symbolic syntax. My module proposal is only to illustrate where you might take this if you venture in that direction. It would not need to be implemented all at once or in the same way.

The heart of my proposal for placeholder selectors is to use a keyword that is a noun. As demonstrated above you can still achieve the same cascade semantics as the symbolic method. Why not be more explicit? That seems to be the approach Scss favors.

@jlong I think you're missing how a placeholder selector can be used in any selector, not just as the core definition of a class and not just in a nested block of selectors. This is one of their strengths and what makes them exactly like classes (except for not being output). This is a new primitive concept in sass and as such it is more explicit than the original @silent/placeholder proposal I made, where one line in one file could cause a single class to become a placeholder and no indicator of this would be visible when reading the source. A more verbose syntax that would be analogous to what we've implemented here would be .foo::placeholder or placeholder(.foo).

@chriseppstein I may be missing something. Can you show me a good example of "how a placeholder selector can be used in any selector, not just as the core definition of a class"? I'm struggling to understand why the keyword syntax can't do the same thing.

"This is a new primitive concept in sass and as such it is more explicit than the original @silent/placeholder proposal I made, where one line in one file could cause a single class to become a placeholder and no indicator of this would be visible when reading the source."

@silent .button { ... }
.button { ... } // more rules for button, also silent because of the directive above
.my-button { @extend .button }

Is this what you mean?

If so, your original proposal makes a class selector silent and I agree that this is problematic. I propose a named group of rules -- not a selector.

@jlong In general, I prefer explicit syntax over symbolic syntax as well. The fact that placeholder selectors are obscure and not self-documenting is certainly their greatest shortcoming. However, in this case, the various proposed explicit syntaxes all have pretty significant downsides relative to placeholder selectors, and I think that those concerns outweigh the explicitness.

Specifically, proposals of the form @silent .foo { ... } require a lot of repetitive silencing and are contrary to the usual semantics of directives. Your @module syntax is as I mentioned complex to wrap one's head around; it's still not clear to me how you'd use it to provide for example a library of extendable classes. The most convincing factor for me was that in the end, all of the other proposals were ultimately just indirect ways of expressing "this selector should be a placeholder," which placeholder selectors express directly.

@jlong, yes, that is what I mean. And it's one of the reasons we didn't end up merging that changeset. A named group of rules is a mixin. but at the point that it's in the document it needs to have a selector to be addressable by extend.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.