Last active
March 19, 2022 01:18
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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%); | |
} | |
} |
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
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@extend
ed by other components, which is to say that it shares traits with the component it is@extend
ing (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:
The above placeholder declarations say:
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.