Skip to content

Instantly share code, notes, and snippets.

@mdo
Last active March 19, 2022 01:18
Show Gist options
  • Save mdo/c0db745a0db31df70e08 to your computer and use it in GitHub Desktop.
Save mdo/c0db745a0db31df70e08 to your computer and use it in GitHub Desktop.
You can create the same set of components with HTML and (S)CSS in a handful of ways. Here's how the same set of buttons looks with base and modifier classes, as well as extends and placeholders. The goal is to measure the output—the total number of selectors and declarations. Personally, I consider fewest selectors to be more optimal (for you an…
// Original
//
// Markup:
//
// <button class="button">Button</button>
// <button class="button button-primary">Button</button>
// <button class="button button-danger">Button</button>
//
// Total selectors: 6
// Total declarations: 19
.button {
display: inline-block;
padding: .5rem 1rem;
font-size: 1rem;
color: #333;
background-color: #eee;
border: 1px solid darken(#eee, 10%);
&:hover {
cursor: pointer;
background-color: darken(#eee, 10%);
border-color: darken(#eee, 15%);
}
}
.button-primary {
color: #fff;
background-color: #0074d9;
border-color: darken(#0074d9, 10%);
&:hover {
background-color: darken(#0074d9, 10%);
border-color: darken(#0074d9, 15%);
}
}
.button-danger {
color: #fff;
background-color: #ff4136;
border-color: darken(#ff4136, 10%);
&:hover {
background-color: darken(#ff4136, 10%);
border-color: darken(#ff4136, 15%);
}
}
// Base class
//
// Markup:
//
// <button class="button button-default">Button</button>
// <button class="button button-primary">Button</button>
// <button class="button button-danger">Button</button>
//
// Total selectors: 8
// Total declarations: 20
.button {
display: inline-block;
padding: .5rem 1rem;
font-size: 1rem;
border: .1rem solid;
&:hover {
cursor: pointer;
}
}
.button-default {
color: #333;
background-color: #f5f5f5;
border-color: darken(#f5f5f5, 10%);
&:hover {
background-color: darken(#eee, 10%);
border-color: darken(#eee, 15%);
}
}
.button-primary {
color: #fff;
background-color: #0074d9;
border-color: darken(#0074d9, 10%);
&:hover {
background-color: darken(#0074d9, 10%);
border-color: darken(#0074d9, 15%);
}
}
.button-danger {
color: #fff;
background-color: #ff4136;
border-color: darken(#ff4136, 10%);
&:hover {
background-color: darken(#ff4136, 10%);
border-color: darken(#ff4136, 15%);
}
}
// Extend
//
// Markup:
//
// <button class="button-default">Button</button>
// <button class="button-primary">Button</button>
// <button class="button-danger">Button</button>
//
// Total selectors: 14
// Total declarations: 20
.button {
display: inline-block;
padding: .5rem 1rem;
font-size: 1rem;
border: .1rem solid;
&:hover {
cursor: pointer;
}
}
.button-default {
@extend .button;
color: #333;
background-color: #f5f5f5;
border-color: darken(#f5f5f5, 10%);
&:hover {
background-color: darken(#eee, 10%);
border-color: darken(#eee, 15%);
}
}
.button-primary {
@extend .button;
color: #fff;
background-color: #0074d9;
border-color: darken(#0074d9, 10%);
&:hover {
background-color: darken(#0074d9, 10%);
border-color: darken(#0074d9, 15%);
}
}
.button-danger {
@extend .button;
color: #fff;
background-color: #ff4136;
border-color: darken(#ff4136, 10%);
&:hover {
background-color: darken(#ff4136, 10%);
border-color: darken(#ff4136, 15%);
}
// }
// Extend w/ placeholder
//
// Markup:
//
// <button class="button-default">Button</button>
// <button class="button-primary">Button</button>
// <button class="button-danger">Button</button>
//
// Total selectors: 12
// Total declarations: 20
%button {
display: inline-block;
padding: .5rem 1rem;
font-size: 1rem;
border: .1rem solid;
&:hover {
cursor: pointer;
}
}
.button-default {
@extend %button;
color: #333;
background-color: #f5f5f5;
border-color: darken(#f5f5f5, 10%);
&:hover {
background-color: darken(#eee, 10%);
border-color: darken(#eee, 15%);
}
}
.button-primary {
@extend %button;
color: #fff;
background-color: #0074d9;
border-color: darken(#0074d9, 10%);
&:hover {
background-color: darken(#0074d9, 10%);
border-color: darken(#0074d9, 15%);
}
}
.button-danger {
@extend %button;
color: #fff;
background-color: #ff4136;
border-color: darken(#ff4136, 10%);
&:hover {
background-color: darken(#ff4136, 10%);
border-color: darken(#ff4136, 15%);
}
}
@davidkpiano
Copy link

So here's my (opinionated) take on this:

A component is essentially a rule set containing its characteristic declarations. In Sass, it can @extend or be @extended by other components, which is to say that it shares traits with the component it is @extending (and can override extended declarations).

A component can also have variations, in which it is not a completely new component, but rather is the same component with added declarations. A good example of this would be stylistic changes on a component regarding colors or drop shadows; it's the same component both functionally and structurally, despite having a different skin.

Here's a pragmatic example of two different but related components:

%button {
  // ... button structure
}

%anchor-button {
  @extend %button;
  text-decoration: none;
  line-height: 1;
}

The above placeholder declarations say:

I have a separate anchor-button component that has the same (inherited) structure as a button component, with a couple of tweaks.

This is useful, and makes sense as separate components, since a %button can have <button>this structure</button> and an %anchor-button can have <a href="#" class="button">this structure</a>, for example. They're truly two different, but related, components.

With your example, classes that are purely related to skin/theme are conflated with the notion of separate components, as if a .button-danger was a different (albeit related) component, even though I'm sure that .button-danger only adds style to an existing %button component.

So is the use of @extend appropriate there? In my opinion, no. There is no .button-danger component; there is a %button component that has a variation of .button-danger.

TL;DR: @extend doesn't seem appropriate here. I'd recommend using it for establishing relationships between separate components, not within the same component.

@mdo
Copy link
Author

mdo commented Feb 4, 2015

I'd recommend avoiding conflating relationships between components entirely. One class (or set of classes) should do one thing really well. That kind of cross pollination leads to often unexpected dependencies, lack of clarity around which component to use where, etc.

I see what you mean though, and that's what I want to point out—don't use @extend when it doesn't make sense to. In most cases, in so far as I've tried them, extends just lead to more trouble. I'd like to create some more tests to test that though.

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