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 hidden or 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
@extendor 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:
The above placeholder declarations say:
This is useful, and makes sense as separate components, since a
%buttoncan have<button>this structure</button>and an%anchor-buttoncan 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-dangerwas a different (albeit related) component, even though I'm sure that.button-dangeronly adds style to an existing%buttoncomponent.So is the use of
@extendappropriate there? In my opinion, no. There is no.button-dangercomponent; there is a%buttoncomponent that has a variation of.button-danger.TL;DR:
@extenddoesn't seem appropriate here. I'd recommend using it for establishing relationships between separate components, not within the same component.