Skip to content

Instantly share code, notes, and snippets.

@chriseppstein
Forked from nex3/extend.md
Created April 25, 2010 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriseppstein/378797 to your computer and use it in GitHub Desktop.
Save chriseppstein/378797 to your computer and use it in GitHub Desktop.

Often in user interface design, there are cases where an element should have all of the properties of another element, and then possibly some unique properties unto itself.

The common approach to dealing with this has been to adjust the markup of a document such that the element has the qualities of both elements.

For instance, imagine if we have a design for a normal error and also for a serious error. We might write our markup like so:

<div class="error seriousError">
  Oh No! You've been Hacked!
</div>

And our styles like so:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  border-width: 3px;
}

Unfortunately, this creates a maintenance burden because you have to remember to apply both classes in conjunction with each other -- failure to do so will result in a display bug that is hard to catch.

Now, with mixins, we could simplify our markup such that only one class must be used in the markup:

@mixin error {
  border: 1px #f00;
  background-color: #fdd;      
}
.error {
  @include error;
}
.seriousError {
  @include error;
  border-width: 3px;
}

Unfortunately, mixins can cause stylesheet bloat when large blocks of properties are being included. Additionally, the mixin approach used here fails to accurately represent that all uses of .seriousError are equivalent to using .error. Consider the case where we have more specific styles to apply:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  border-width: 3px;
}
.error.intrusion {
  background-image: url("/image/hacked.png");
}

In this case, it should be clear that mixins are not accurately representing the structure and intent of the design. With @extend, we can let the stylesheet compiler do the heavy lifting for us:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}
.error.intrusion {
  background-image: url("/image/hacked.png");
}

Which will be compiled to:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  border-width: 3px;
}
.error.intrusion, .seriousError.intrusion {
  background-image: url("/image/hacked.png");
}

As you can see, @extend works by adding additional selectors to the extended rules. Anywhere .error appears in the stylesheet, .seriousError will appear as well.

If the extended selector appears as part of a more complex selector, the extending selector will be merged in appropriately. Continuing the previous example:

.error {
  a {
    font-weight: bold;
    color: #a00;
  }

  &:hover {
    background-color: #ff0;
  }
}

is compiled to:

.error a, .seriousError a {
  font-weight: bold;
  color: #a00; }

.error:hover, .seriousError:hover {
  background-color: #ff0; }

When merging selectors, @extend is smart enough to avoid unnecessary duplication; .foo.foo gets translated to .foo. In addition, it won't produce selectors that can't match anything, like #foo#bar.

Extending Complex Selectors

It's possible to extend any simple selector sequence, such as .special.cool, a:hover, or a.user[href^="http://"]. For example:

.hoverlink {@extend a:hover}

// This matches a:hover, so it's extended
a.user:hover {text-decoration: underline}

// This does not match a:hover, so it's not extended
a {color: blue}

is compiled to:

a.user:hover, .user.hoverlink {
  text-decoration: underline; }

a {
  color: blue; }

Nested Selectors

Nested selectors, such as .foo .bar or .foo + .bar, currently can't be extended. However, it is possible for nested selectors themselves to use @extend. For example:

#fake-links .link {@extend a}

a {
  color: blue;
  &:hover {text-decoration: underline}
}

is compiled to

a, #fake-links .link {
  color: blue; }
  a:hover, #fake-links .link:hover {
    text-decoration: underline; }

Warning: if a nested selector is merged into another nested selector, this can result in a very large amount of output, since all possible sequences of selectors must be used. It's highly recommended that you be careful to avoid this when using @extend with nested selectors. For example:

.foo .bar {@extend .bang}
.baz .bang {color: blue}

is compiled to:

.baz .bang, .baz .foo .bar, .foo.baz .bar, .foo .baz .bar {
  color: blue; }
@kennethreitz
Copy link

@extend looks amazing! is this a sass feature?

@chriseppstein
Copy link
Author

Yep. it's part of sass 3 :)

@kennethreitz
Copy link

Hmmm, might have to finally give that a try now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment