CSS 유틸 클래스와 관심사 분리

옥탑방 개발자·2021년 7월 12일
2

이 글은 https://adamwathan.me/css-utility-classes-and-separation-of-concerns/
사이트의 글을 의역한 것입니다.
tailwind css 를 개발한 개발자가 왜 tailwind 를 만들게 되었는지 그 의도를 밝힌 글입니다. 좀 오래된 글이지만, 큰 깨우침을 주는 글이라 의역해서 번역해 봅니다. 원문과 다소 다를 수 있으니, 원문을 반드시 확인해 보시기 바랍니다.

지난 몇 년 동안 내가 CSS를 작성하는 방식은 "Semantic" 접근 방식에서 "functional CSS" 접근 방식으로 변화해 왔다.

이 "functional CSS" 접근 방식이 뭔지 설명하면 많은 개발자들이 본능적으로 거부감을 갖는다. 그래서 내가 왜 이 접근방식을 선택하게 되었는지 그 과정을 설명하고 그 과정에서 얻은 교훈과 통찰력을 공유하고자 한다.

1단계: "Semantic" CSS

CSS를 잘 설계하는 패턴중에 하나가 "관심사 분리" 이다.

이 패턴의 주요 아이디어는 컨텐츠에 대한 정보는 HTML에, 스타일에 대한 정보는 CSS에 있어야 한다는 것이다.

아래 HTML을 살펴보자.

<p class="text-center">
    Hello there!
</p>

.text-center 클래스가 있다. 이 클래스는 컨텐츠를 중앙에 배치하라는 의도로 만들었다. 이것은 스타일 정보를 HTML에 넣은 것이기 때문에 "관심사 분리"에 위배된다.

그렇기 때문에 아래와 같이 다시 작성하라고 권고한다.

<style>
.greeting {
    text-align: center;
}
</style>

<p class="greeting">
    Hello there!
</p>

이런 접근 방식의 기가막힌 예가 바로 CSS ZEN GARDEN 사이트이다. 컨텐츠와 디자인의 관심사를 분리하면 css 교체만으로 드라마틱 하게 다른 사이트를 만들어 낼 수 있다.

내가 아래와 같이 작업을 해보았다.

  1. 새로운 UI에 필요한 마크업을 작성했다.
<div>
  <img src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  <div>
    <h2>Adam Wathan</h2>
    <p>
      Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
    </p>
  </div>
</div>
  1. 의미를 주기 위해 class를 추가했다. (.author-bio)
- <div>
+ <div class="author-bio">
    <img src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
    <div>
      <h2>Adam Wathan</h2>
      <p>
        Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
      </p>
    </div>
  </div>
  1. 내용을 꾸며주기 위해 CSS/Less/Sass에 스타일을 걸었다.
.author-bio {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
  > img {
    display: block;
    width: 100%;
    height: auto;
  }
  > div {
    padding: 1rem;
    > h2 {
      font-size: 1.25rem;
      color: rgba(0,0,0,0.8);
    }
    > p {
      font-size: 1rem;
      color: rgba(0,0,0,0.75);
      line-height: 1.5;
    }
  }
}

그렇게 해서 나온 결과는 아래와 같다.

이 접근 방식은 매우 직관적이었기 때문에, 한동안 내가 HTML과 CSS를 장석하기 위해 즐겨 사용하던 방식이었다.

그런데, 뭔가 조금 기분이 나빠지기 시작했다.

나는 "내 관심사를 분리"했지만 여전히 내 CSS와 HTML 사이에 매우 분명한 결합이 있었다.(분리와 결합은 서로 모순되는 단어이지 않은가?)
대부분의 경우 내 CSS는 내 HTML 태그의 구조를 그대로 따라갔다.

내 HTML은 스타일에 상관없이 작성할 수 있었지만, CSS는 HTML에 매우 의존적이었다.

내 관심사는 완벽하게 분리되지 않았다.

2단계: HTML 구조에서 스타일 분리하기

이 결합의 문제를 해결하기 위한 방법을 찾던중, HTML에 더 많은 클래스를 추가하면 HTML에 덜 의존적인 CSS를 만들 수 있다는 주장을 알게 되었다.

가장 잘 알려진 것은 BEM(Block Element Modifier) 이라는 방법이다.

BEM 의 방법을 사용해 위 내용을 다시 작성하면 아래와 같다.

<div class="author-bio">
  <img class="author-bio__image" src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  <div class="author-bio__content">
    <h2 class="author-bio__name">Adam Wathan</h2>
    <p class="author-bio__body">
      Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
    </p>
  </div>
</div>

...그리고 CSS는 다음과 같을 것이다:

.author-bio {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.author-bio__image {
  display: block;
  width: 100%;
  height: auto;
}
.author-bio__content {
  padding: 1rem;
}
.author-bio__name {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.author-bio__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

이것은 나에게 엄청난 발전처럼 느껴졌다.
내 HTML은 여전히 "Semantic"하게 보였고, 스타일에 대한 내용도 담지 않았고,
CSS는 html의 구조에 의존해서 작성하지 않아도 되었기 때문이다.

그런데 다시 딜레마에 빠졌다.

유사한 구성 요소 다루기

사이트에 새로운 기능을 추가해야 한다고 가정해 보자. 기사 미리보기를 card layout 으로 표시하는 것이다.

이 기사 미리보기 카드의 상단에는 풀 블리드 이미지, 아래에는 패딩된 콘텐츠 섹션, 굵은 제목과 그 아래 작은 본문 텍스트가 있다고 가정해 보자.

저자 약력과 똑같이 보이도록 작성할 수 있다.

관심사를 분리하면서 이 문제를 처리하는 가장 좋은 방법이 무엇일까?

.author-bio 클래스를 그대로 적용하고 싶지만, 그러면 Semantic 하지 않다.
그래서 어쩔 수 없이 .article-preview 라는 클래스를 추가한다.

그렇게 해서 나온 결과는 아래와 같다.

<div class="article-preview">
  <img class="article-preview__image" src="https://i.vimeocdn.com/video/585037904_1280x720.webp" alt="">
  <div class="article-preview__content">
    <h2 class="article-preview__title">Stubbing Eloquent Relations for Faster Tests</h2>
    <p class="article-preview__body">
      In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.
    </p>
  </div>
</div>

그렇다면 CSS는 어떻게 처리해야 할까?

옵션 1: 스타일 복제

첫번째 방법은 .author-bio 스타일 을 복제하고 클래스 이름을 바꾸는 것입니다.

.article-preview {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.article-preview__image {
  display: block;
  width: 100%;
  height: auto;
}
.article-preview__content {
  padding: 1rem;
}
.article-preview__title {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.article-preview__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

이것은 작동은 하지만 DRY(Don't Repeat Yourself)를 위반하는 것이다.
이런 복제가 늘어날 수록 디자인에 일관성이 유지되기 어렵다.(다른 패딩 또는 글꼴 색상)

옵션 2: @extend 를 이용해 클래스 확장하기

두번째 방법은 @extend 기능을 이용해 클래스를 확장하는 것이다.

.article-preview {
  @extend .author-bio;
}
.article-preview__image {
  @extend .author-bio__image;
}
.article-preview__content {
  @extend .author-bio__content;
}
.article-preview__title {
  @extend .author-bio__name;
}
.article-preview__body {
  @extend .author-bio__body;
}

@extend 를 css에 사용하는 것은 권장하지 않은 방법이긴 하지만, 이것으로 우리 문제는 완벽하게 해결할 수 있는 것처럼 보인다. HTML에 스타일을 넣지도 않았고, CSS는 재활용되었고, 의미기반도 살릴 수 있다.

그런데.. 한 가지 옵션을 더 살펴보자.

옵션 3: 콘텐츠에 무관한 컴포넌트 만들기

위의 .author-bio 클래스와 .article-preview 클래스는 Semantic 관점에서는 공통점이 없는 클래스이다. 하나는 저자 약력이고 하나는 뉴스 기사 미리보기에 관한 것이다.

그러나 우리가 이미 보았듯, 이들은 디자인 관점에서는 많은 공통점을 갖고 있다.

그래서 의미기반이 아닌 디자인 관점에서 새로운 컴포넌트를 만들고 그 컴포넌트를 재활용할 수도 있지 않을까?

그래서 .media-card 라는 클래스를 아래와 같이 만들었다.

.media-card {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.media-card__image {
  display: block;
  width: 100%;
  height: auto;
}
.media-card__content {
  padding: 1rem;
}
.media-card__title {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.media-card__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

... 그럼 저자 약력을 나타내는 HTML은 아래와 같이 작성할 수 있다.

<div class="media-card">
  <img class="media-card__image" src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">Adam Wathan</h2>
    <p class="media-card__body">
      Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
    </p>
  </div>
</div>

... 또 기사 미리보기에 대한 HTML은 아래와 같이 작성할 수 있다.

<div class="media-card">
  <img class="media-card__image" src="https://i.vimeocdn.com/video/585037904_1280x720.webp" alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">Stubbing Eloquent Relations for Faster Tests</h2>
    <p class="media-card__body">
      In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.
    </p>
  </div>
</div>

이런 접근 방식은 CSS 중복을 제거하지만 다시금 "관심사 분리"를 섞어 버렸다는 비난을 받을 수 있다.

HTML에 기술한 내용은 이 두 콘텐츠가 모두 media-card 라는 클래스로 지정해버렸다.
만약 기사 미리보기의 모양을 변경하지 않고 작성자 약력의 모양을 변경하려면 어떻게 해야 할까?

이전에는 CSS 변경만으로 가능했지만, 이제는 HTML을 편집해야 한다. 신성모독 이라고?!

그러나 잠시 그 이면에 대해 생각해 보자.

동일한 스타일이 필요한 새로운 유형의 콘텐츠 를 추가해야 하는 경우 어떻게 해야 할까?

  1. "Semantic" 접근 방식을 사용하여 새 HTML을 작성하고, "hook" 가 되는 클래스를 지정해 css 파일을 열어서 클래스를 만들고 기존 css(혹은 sass) 의 스타일을 복사해서 넣거나 @extend 나 @mixin을 사용해 기존 css 를 재사용한다.

  2. 콘텐츠에 무관한 .media-card 클래스를 사용 하여 새 HTML만 작성한다. 스타일시트를 손댈 필요가 전혀 없다.

정말로 "관심사 분리"를 하고 싶다면, 그 방법은 1번이 아니라 2번이어야 하지 않을까?

'관심사 분리'는 허수아비

"관심의 분리"라는 관점에서 HTML과 CSS의 관계를 생각하면 둘의 관계는 선과 악처럼 명확해 보인다.

관심사가 두개의 파일에 온전히 분리가 있거나(선!) 그렇지 않는 것이다 (악!) .

이것은 HTML과 CSS을 대하는 올바른 방법이라고 볼 수 없다.

그러한 관점 대신에, 의존성의 방향 에 대해 생각해보자.

HTML과 CSS를 작성하는 방법에는 두 가지가 있다.

1. "관심사 분리": HTML에 의존한 CSS 작성

  • 콘텐츠에 기반해 HTML의 엘리먼트에 클래스 이름(예: .author-bio)을 넣어준다.
  • HTML은 독립적입니다. 이 페이지가 어떻게 보일지에 대해서는 HTML은 관여하지 않는다. 그냥 .author-bio 처럼 클래스 이름만 넣어준다.
  • 반면에 CSS는 독립적이지 않습니다. HTML이 노출하기로 결정한 클래스를 알아야 하고 HTML 스타일을 지정하기 위해 해당 클래스를 대상으로 지정해야 한다.
  • 이 모델에서 HTML은 스타일을 변경할 수 있지만 CSS는 재사용할 수 없다.

2. "관심사 섞기": CSS에 의존한 HTML 작성

  • UI에서 반복되는 패턴(예: .media-card) 이후에 내용에 구애받지 않는 방식으로 클래스 이름을 지정하면 CSS가 HTML의 종속성으로 취급됩니다.
  • CSS는 독립적이다. 어떤 콘텐츠에 적용되는지는 상관하지 않고 마크업에 적용할 수 있는 구성 요소 집합만 노출한다.
  • HTML은 독립적이지 않다. CSS에서 제공한 클래스를 사용하고 있으며 원하는 디자인을 달성하는 데 필요한 클래스를 결합할 수 있도록 어떤 클래스가 존재하는지 알아야 한다.
  • 이 모델에서 CSS는 재사용할 수 있지만 HTML은 스타일을 변경할 수 없다.

CSS Zen Garden은 첫 번째 방법으로 접근한 예이고, Bootstrap 이나 Bulma 와 같은 UI 프레임워크 는 두 번째 방식을 취한 것이다.

둘 다 본질적으로 "틀린" 것이 아니다. 각자 처한 상황에 따라 무엇이 우선이고 중요한지에 따라 결정했을 뿐이다.

작업 중인 프로젝트에서 스타일을 변경할 수 있는 HTML과 재사용 가능한 CSS 중 어느 것이 더 가치가 있는가?

재사용성 선택

나에게 전환점은 Nicolas Gallagher의 About HTML semantics and front-end architecture를 읽었을 때엿다.

여기서 그가 말한 요점을 되풀이하지는 않겠지만, 두말할 필요도 없이 나는 내가 작업하는 프로젝트에 재사용 가능한 CSS를 조합해서 사용하는 방법이 올바른 선택이라는 것을 확신할 수 있었다.

3단계: 콘텐츠에 구애받지 않는 CSS 컴포넌트

이 시점에서 내 목표는 내 콘텐츠를 기반으로 하는 클래스를 만드는 것을 명시적으로 피하는 것이 아니라 가능한 한 재사용 가능한 방식으로 모든 이름을 지정하는 것 이었다.

그 결과 다음과 같은 클래스 이름이 생성되었다.

  • .card
  • .btn, .btn--primary,.btn--secondary
  • .badge
  • .card-list, .card-list-item
  • .img--round
  • .modal-form, .modal-form-section

등등...

재사용 가능한 클래스를 만드는 데 집중하기 시작했을 때 다른 점을 발견했습니다.

컴포넌트에 내용이 많을 수록 혹은 내용이 구체적일 수록 재사용하기가 어려웠다.

다음은 직관적인 예입니다.

몇 개의 양식 섹션이 있고 하단에 제출 버튼이 있는 양식을 작성 중이라고 가정해 보겠습니다.

모든 양식 내용을 .stacked-form구성 요소의 일부로 생각 하면 제출 버튼에 다음과 같은 클래스를 제공할 수 있습니다 .stacked-form__button.

<form class="stacked-form" action="#">
  <div class="stacked-form__section">
    <!-- ... -->
  </div>
  <div class="stacked-form__section">
    <!-- ... -->
  </div>
  <div class="stacked-form__section">
    <button class="stacked-form__button">Submit</button>
  </div>
</form>

하지만 같은 방식으로 스타일을 지정해야 하는 양식의 일부가 아닌 다른 버튼이 사이트에 있을 수 있습니다.

.stacked-form__button해당 버튼에 클래스를 사용하는 것은 의미가 없습니다. 스택 형식의 ​​일부가 아닙니다.

이 두 버튼은 모두 해당 페이지의 기본 작업이므로 구성 요소의 공통점을 기반으로 버튼 이름을 지정 .btn--primary하고 .stacked-form__접두사를 완전히 제거하는 이라고 하면 어떻게 될까요?

  <form class="stacked-form" action="#">
    <!-- ... -->
    <div class="stacked-form__section">
-     <button class="stacked-form__button">Submit</button>
+     <button class="btn btn--primary">Submit</button>
    </div>
  </form>

이제 이 쌓인 형태가 떠다니는 카드에 있는 것처럼 보이길 원한다고 가정해 보겠습니다.

한 가지 접근 방식은 수정자를 만들어 다음 형식에 적용하는 것입니다.

- <form class="stacked-form" action="#">
+ <form class="stacked-form stacked-form--card" action="#">
    <!-- ... -->
  </form>
그러나 이미 .card클래스 가 있는 경우 기존 카드와 스택 형식을 사용하여 이 새 UI를 구성 하지 않겠 습니까?

+ <div class="card">
    <form class="stacked-form" action="#">
      <!-- ... -->
    </form>
+ </div>

이 접근 방식 .card을 사용하면 모든 콘텐츠의 집 .stacked-form이 될 수 있고 모든 컨테이너 내부에서 사용할 수 있는 의견 이 없습니다.

우리는 구성 요소에서 더 많은 재사용을 얻고 있으며 새로운 CSS를 작성할 필요가 없었습니다.

하위 구성 요소에 대한 구성
스택 양식의 맨 아래에 다른 버튼을 추가해야 하고 기존 버튼에서 약간 떨어져 있기를 원한다고 가정해 보겠습니다.

<form class="stacked-form" action="#">
  <!-- ... -->
  <div class="stacked-form__section">
    <button class="btn btn--secondary">Cancel</button>
    <!-- Need some space in here -->
    <button class="btn btn--primary">Submit</button>
  </div>
</form>

한 가지 접근 방식은 , 와 같은 새 하위 구성 요소를 만들고 .stacked-formfooter각 버튼에 와 같은 추가 클래스를 추가하고 .stacked-formfooter-item후손 선택기를 사용하여 여백을 추가하는 것입니다.

  <form class="stacked-form" action="#">
    <!-- ... -->
-   <div class="stacked-form__section">
+   <div class="stacked-form__section stacked-form__footer">
-     <button class="btn btn--secondary">Cancel</button>
-     <button class="btn btn--primary">Submit</button>
+     <button class="stacked-form__footer-item btn btn--secondary">Cancel</button>
+     <button class="stacked-form__footer-item btn btn--primary">Submit</button>
    </div>
  </form>

CSS는 다음과 같습니다.

.stacked-form__footer {
  text-align: right;
}
.stacked-form__footer-item {
  margin-right: 1rem;
  &:last-child {
    margin-right: 0;
  }
}

그러나 어딘가의 subnav 또는 헤더에서 이와 동일한 문제가 발생한다면 어떻게 될까요?

.stacked-form__footer외부를 재사용할 수 없으므로 .stacked-form헤더 내부에 새 하위 구성요소를 만들 수 있습니다.

  <header class="header-bar">
    <h2 class="header-bar__title">New Product</h2>
+   <div class="header-bar__actions">
+     <button class="header-bar__action btn btn--secondary">Cancel</button>
+     <button class="header-bar__action btn btn--primary">Save</button>
+   </div>
  </header>

...하지만 이제 우리는 .stacked-formfooter새로운 .header-baractions구성 요소 를 구축하는 데 들인 노력을 복제해야 합니다 .

이것은 콘텐츠 중심 클래스 이름을 사용하면서 처음에 겪었던 문제와 매우 흡사합니다. 그렇죠?

이 문제를 해결하는 한 가지 방법은 재사용하기 쉽고 합성을 사용 하는 완전히 새로운 구성 요소를 찾는 것 입니다.

어쩌면 우리는 다음과 같은 것을 만들 수 있습니다 .actions-list.

.actions-list {
  text-align: right;
}
.actions-list__item {
  margin-right: 1rem;
  &:last-child {
    margin-right: 0;
  }
}

이제 .stacked-formfooter및 .header-baractions구성 요소를 완전히 제거 하고 대신 .actions-list두 상황 모두에서 사용할 수 있습니다.

<!-- Stacked form -->
<form class="stacked-form" action="#">
  <!-- ... -->
  <div class="stacked-form__section">
    <div class="actions-list">
      <button class="actions-list__item btn btn--secondary">Cancel</button>
      <button class="actions-list__item btn btn--primary">Submit</button>
    </div>
  </div>
</form>

<!-- Header bar -->
<header class="header-bar">
  <h2 class="header-bar__title">New Product</h2>
  <div class="actions-list">
    <button class="actions-list__item btn btn--secondary">Cancel</button>
    <button class="actions-list__item btn btn--primary">Save</button>
  </div>
</header>

그러나 이러한 작업 목록 중 하나를 왼쪽 정렬하고 다른 하나는 오른쪽 정렬해야 한다면 어떻게 될까요? 우리는 어떻게해야합니까 .actions-list--left및 .actions-list--right수정?

4단계: 콘텐츠에 구애받지 않는 구성요소 + 유틸리티 클래스

항상 이러한 구성 요소 이름을 생각해 내기 위해 노력하는 것은 지겹습니다.

와 같은 수정자를 만들 때 .actions-list--left단일 CSS 속성을 할당하기 위해 완전히 새로운 구성 요소 수정자를 만드는 것입니다. 그것은 이미 left이름에 포함되어 있으므로 어떤 식 으로든 "의미론적"인 사람을 속일 수 없습니다.

왼쪽 정렬 및 오른쪽 정렬 수정자가 필요한 다른 구성 요소가 있는 경우 이에 대한 새 구성 요소 수정자도 생성할까요?

이것은 우리가 죽이기로 결정했을 때 우리가 직면 한 동일한 문제로 돌아 오기 .stacked-formfooter및 .header-baractions단일로 교체를 .actions-list:

우리는 복제보다 합성을 선호합니다.

따라서 두 개의 작업 목록이 있는 경우 하나는 왼쪽으로 정렬해야 하고 다른 하나는 오른쪽으로 정렬해야 하는 경우 구성으로 이 문제를 어떻게 해결할 수 있을까요?

정렬 유틸리티
컴포지션에서 이 문제를 해결하려면 원하는 효과를 제공하는 재사용 가능한 새 클래스를 컴포넌트에 추가할 수 있어야 합니다.

우리는 이미 우리의 modifers 전화 거라고 .actions-list--left과 .actions-list--right같은 새로운 클래스 뭔가를 호출하지 않을 이유가 없다, 그래서, .align-left그리고 .align-right:

.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
이제 컴포지션을 사용하여 스택 양식 버튼을 왼쪽 정렬할 수 있습니다.

<form class="stacked-form" action="#">
  <!-- ... -->
  <div class="stacked-form__section">
    <div class="actions-list align-left">
      <button class="actions-list__item btn btn--secondary">Cancel</button>
      <button class="actions-list__item btn btn--primary">Submit</button>
    </div>
  </div>
</form>

... 그리고 헤더 버튼을 오른쪽 정렬:

<header class="header-bar">
  <h2 class="header-bar__title">New Product</h2>
  <div class="actions-list align-right">
    <button class="actions-list__item btn btn--secondary">Cancel</button>
    <button class="actions-list__item btn btn--primary">Save</button>
  </div>
</header>

두려워하지마
HTML에서 "left" 및 "right"라는 단어를 보는 것이 불편하다면 이 시점에서 UI에서 시각적 패턴의 이름을 따서 명명된 구성 요소를 오랫동안 사용해 왔다는 것을 기억하십시오.

.stacked-form"의미론적"인 척하는 것은 없습니다 .align-right. 둘 다 마크업의 표시에 영향을 주는 방식에 따라 이름이 지정되었으며 특정 표시 결과를 달성하기 위해 마크업에서 해당 클래스를 사용하고 있습니다.

CSS 종속 HTML을 작성 중입니다. 우리는에서 우리의 양식을 변경하려면 .stacked-formA를 .horizontal-form, 우리는 마크 업이 아닌 CSS에서 그것을 할.

쓸모없는 추상화 삭제
이 솔루션의 흥미로운 점은 .actions-list구성 요소가 이제 기본적으로 쓸모가 없다는 것입니다. 이전에는 내용을 오른쪽에 정렬하기만 하면 됩니다.

삭제합시다:

  • .actions-list {
  • text-align: right;
  • }
    .actions-listitem {
    margin-right: 1rem;
    &:last-child {
    margin-right: 0;
    }
    }
    하지만 지금은 없는 것이 조금 이상 .actions-list
    item합니다 .actions-list. .actions-list__item구성 요소 를 만들지 않고 원래 문제를 해결할 수 있는 다른 방법이 있습니까?

돌이켜 생각해보면 우리가 이 컴포넌트를 만든 이유는 두 버튼 사이에 약간의 여백을 추가하기 위해서였습니다. .actions-list일반적이고 상당히 재사용이 가능하기 때문에 버튼 목록에 대한 꽤 괜찮은 은유였습니다. 그러나 "액션"이 아닌 항목 사이에 동일한 양의 간격이 필요한 상황이 있을 수 있습니다. 맞습니까?

재사용 가능한 이름은 다음과 같을 수 있습니다 .spaced-horizontal-list. .actions-list실제로 스타일 지정이 필요한 것은 자식뿐이기 때문에 실제 구성 요소를 이미 삭제했습니다 .

스페이서 유틸리티
아이들에게만 스타일링이 필요한 경우 멋진 의사 선택기를 사용하여 그룹으로 스타일을 지정하는 대신 독립적으로 아이들의 스타일을 지정하는 것이 더 간단할까요?

요소 옆에 공백을 추가하는 가장 재사용 가능한 방법은 "이 요소는 옆에 공백이 있어야 합니다"라고 말하는 클래스입니다.

우리는 이미 .align-left및 와 같은 .align-right유틸리티를 추가했습니다. 올바른 마진을 추가하기 위해 새 유틸리티를 만들면 어떨까요?

.mar-r-sm요소의 오른쪽에 약간의 여백을 추가하기 위해 와 같은 새 유틸리티 클래스를 만들어 보겠습니다 .

  • .actions-list__item {
  • margin-right: 1rem;
  • &:last-child {
  • margin-right: 0;
  • }
  • }
  • .mar-r-sm {
  • margin-right: 1rem;
  • }
    이제 양식과 헤더가 어떻게 생겼는지 보여줍니다.
Cancel Submit

New Product

Cancel Save
의 전체 개념 .actions-list은 어디에도 없으며 CSS는 더 작고 클래스는 더 많이 재사용할 수 있습니다.

5단계: 유틸리티 기반 CSS

이 기능을 클릭하면 얼마 지나지 않아 다음과 같이 필요한 일반적인 시각적 조정을 위한 전체 유틸리티 클래스 제품군을 구축할 수 있었습니다.

  • 텍스트 크기, 색상 및 두께
  • 테두리 색상, 너비 및 위치
  • 배경색
  • Flexbox 유틸리티
  • 패딩 및 여백 도우미

이것에 대한 놀라운 점은 당신이 그것을 알기도 전에 새로운 CSS를 작성하지 않고도 완전히 새로운 UI 구성 요소를 구축할 수 있다는 것입니다.

내 프로젝트에서 이런 종류의 "제품 카드" 구성 요소를 살펴보세요.

내 마크업은 다음과 같습니다.

<div class="card rounded shadow">
    <a href="..." class="block">
        <img class="block fit" src="...">
    </a>
    <div class="py-3 px-4 border-b border-dark-soft flex-spaced flex-y-center">
        <div class="text-ellipsis mr-4">
            <a href="..." class="text-lg text-medium">
                Test-Driven Laravel
            </a>
        </div>
        <a href="..." class="link-softer">
            @icon('link')
        </a>
    </div>
    <div class="flex text-lg text-dark">
        <div class="py-2 px-4 border-r border-dark-soft">
            @icon('currency-dollar', 'icon-sm text-dark-softest mr-4')
            <span>$3,475</span>
        </div>
        <div class="py-2 px-4">
            @icon('user', 'icon-sm text-dark-softest mr-4')
            <span>25</span>
        </div>
    </div>
</div>

여기에 사용된 클래스의 수로 인해 처음에는 당황할 수 있지만 유틸리티에서 구성하는 대신 실제 CSS 구성 요소로 만들고 싶었습니다. 무엇이라고 부를까요?

구성 요소는 하나의 컨텍스트에서만 사용할 수 있기 때문에 콘텐츠별 이름을 사용하고 싶지 않습니다.

아마 이런 것?

.image-card-with-a-full-width-section-and-a-split-section { ... }
물론 웃기지는 않습니다. 대신 이전에 이야기한 것처럼 더 작은 구성 요소로 구성하고 싶을 것입니다.

그 구성 요소는 무엇입니까?

글쎄, 아마도 그것은 카드에 보관되어 있습니다. 모든 카드에 그림자가 있는 것은 아니므로 .card--shadowed수정자를 사용하거나 .shadow모든 요소에 적용 할 수 있는 유틸리티를 만들 수 있습니다. 더 재사용할 수 있을 것 같으니 그렇게 합시다.

우리 사이트의 일부 카드에는 모서리가 둥근 것이 없지만 이 카드는 있습니다. 우리는 그것을 만들 수 .card--rounded있지만 사이트에 때때로 같은 금액으로 반올림되는 다른 요소가 있으며 이는 카드가 아닙니다. rounded유틸리티는 더 재사용 될 것이다.

맨 위에 있는 이미지는 어떻습니까? 아마도 .img--fitted그것은 카드를 채우는 것과 같은 것일까요? 사이트에는 부모 너비에 맞춰야 하는 몇 가지 다른 부분이 있으며 항상 이미지는 아닙니다. 그냥 .fit도우미가 더 나을 수도 있습니다.

...내가 어디로 가는지 알 수 있습니다.

재사용성에 중점을 두고 그 흔적을 충분히 따른다면 재사용 가능한 유틸리티에서 이 구성 요소를 구축하는 것이 자연스러운 목적지입니다.

강제 일관성
작고 구성 가능한 유틸리티를 사용할 때의 가장 큰 이점 중 하나는 팀의 모든 개발자가 항상 고정된 옵션 집합에서 값을 선택한다는 것입니다.

HTML의 스타일을 지정하고 "이 텍스트는 좀 더 어두워야 합니다." 라고 생각한 다음 darken()기본 기능을 조정하기 위해 몇 번이나 도달 $text-color했습니까?

아니면 "이 글꼴은 조금 더 작아야 합니다"라고font-size: .85em 말하고 작업 중인 구성 요소에 추가 하시겠습니까?

임의의 값이 아니라 상대적인 색상이나 상대적인 글꼴 크기를 사용하고 있기 때문에 "올바른" 일을 하고 있는 것처럼 느껴집니다.

그러나 구성 요소에 대해 텍스트를 10% 어둡게 하기로 결정하고 다른 사람이 해당 구성 요소에 대해 12% 어둡게 하면 어떻게 될까요? 당신이 그것을 알기도 전에 스타일시트에 402개의 고유한 텍스트 색상이 생깁니다 .

이것은 스타일을 지정하는 방식이 새로운 CSS를 작성하는 모든 코드베이스에서 발생합니다.

GitLab : 402개의 텍스트 색상, 239개의 배경색, 59개의 글꼴 크기
버퍼 : 124개의 텍스트 색상, 86개의 배경색, 54개의 글꼴 크기
HelpScout : 198개의 텍스트 색상, 133개의 배경 색상, 67개의 글꼴 크기
Gumroad : 91가지 텍스트 색상, 28가지 배경 색상, 48가지 글꼴 크기
스트라이프 : 189개의 텍스트 색상, 90개의 배경색, 35개의 글꼴 크기
GitHub : 163개의 텍스트 색상, 147개의 배경색, 56개의 글꼴 크기
ConvertKit : 128개의 텍스트 색상, 124개의 배경색, 70개의 글꼴 크기
이는 작성하는 CSS의 모든 새 덩어리가 빈 캔버스이기 때문입니다. 원하는 값을 사용하는 것을 막을 수는 없습니다.

변수나 믹스인을 통해 일관성을 시도하고 적용할 수 있지만 새로운 CSS의 모든 라인은 여전히 ​​새로운 복잡성의 기회입니다 . 더 많은 CSS를 추가한다고 해서 CSS가 더 단순해지지는 않습니다.

대신 스타일을 지정하는 솔루션이 기존 클래스 를 적용하는 것이라면 빈 캔버스 문제가 갑자기 사라집니다.

약간 어두운 텍스트를 음소거하고 싶습니까? .text-dark-soft클래스를 추가합니다 .

글꼴 크기를 조금 더 작게 만들어야 하나요? .text-sm클래스를 사용합니다 .

프로젝트의 모든 사람이 선별된 제한된 옵션 집합에서 스타일을 선택하면 CSS가 프로젝트 크기에 따라 선형적으로 커지는 것을 멈추고 무료로 일관성을 얻을 수 있습니다.

여전히 구성 요소를 만들어야 합니다.
내 의견이 정말 완고하게 기능하는 CSS 옹호자들과 약간 다른 영역 중 하나는 유틸리티 만으로 무언가를 구축해야 한다고 생각하지 않는다는 것 입니다.

Tachyons (환상적인 프로젝트임) 와 같은 인기 있는 유틸리티 기반 프레임워크를 살펴보면 순수한 유틸리티에서 균일한 버튼 스타일을 생성하는 것을 볼 수 있다.

Button Text

이것을 한개씩 분석해 보면....

  • f6 : 글꼴 크기 눈금에서 여섯 번째 글꼴 크기 (Tachyons에서는 .875rem).
  • br3 : border-radius 세번째 값. (.5rem)
  • ph3 : 수평 패딩 세번째 값 (1rem)
  • pv2 : 수직 패딩 두번째 값 (.5rem)
  • white : 흰색 텍스트
  • bg-purple : 보라색 배경
  • hover-bg-light-purple : 마우스 오버 시 연보라색 배경 사용

동일한 클래스 조합을 가진 여러 버튼이 필요한 경우, Tachyons이 권장하는 방법은 CSS를 통해 변경하는 것이 아니라 템플릿을 통해 추상화를 만드는 것이다.

예를 들어 Vue.js 를 사용하는 경우 다음과 같이 컴포넌트를 만들 수 있다.

<ui-button color="purple">Save</ui-button>

... ui-button 은 아래와 같이 정의한다.

<template>
  <button class="f6 br3 ph3 pv2" :class="colorClasses">
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: ['color'],
  computed: {
    colorClasses() {
      return {
        purple: 'white bg-purple hover-bg-light-purple',
        lightGray: 'mid-gray bg-light-gray hover-bg-light-silver',
        // ...
      }[this.color]
    }
  }
}
</script>

이것은 많은 프로젝트에 대한 훌륭한 접근 방식이지만 여전히 템플릿 기반 구성 요소를 만드는 것보다 CSS 구성 요소를 만드는 것이 더 실용적인 사용 사례가 많이 있다고 생각 합니다.

내가 작업하는 프로젝트의 .btn-purple경우 사이트의 모든 작은 위젯을 템플릿화하는 것보다 7가지 유틸리티를 묶는 새 클래스 를 만드는 것이 일반적으로 더 간단합니다 .

...하지만 먼저 유틸리티를 사용하여 빌드하십시오.

내가 CSS 유틸리티 우선 접근 방식이라고 부르는 이유 는 유틸리티에서 가능한 모든 것을 구축하려고 하고 반복 패턴이 나타날 때만 추출하기 때문입니다.

당신이 사용하는 경우 더 적은을 당신의 전처리, 당신은 유지 mixin로 클래스를 기존 사용할 수 있습니다. 즉, 이 .btn-purple구성 요소 를 만드는 데 편집기에서 약간의 다중 커서 마법사가 필요합니다.

불행히도 모든 유틸리티 클래스에 대해 별도의 믹스인을 생성하지 않고는 Sass 또는 Stylus에서 이 작업을 수행할 수 없으므로 약간 더 많은 작업이 필요합니다.

물론 구성 요소의 모든 단일 선언이 유틸리티에서 나오는 것이 항상 가능한 것은 아닙니다. 부모 위로 마우스를 가져갈 때 자녀의 재산을 변경하는 것과 같은 요소 간의 복잡한 상호 작용은 유틸리티에서만 수행하기 어렵습니다. 따라서 최선의 판단을 사용하고 더 간단하다고 생각되는 모든 작업을 수행하십시오.

더 이상 성급한 추상화 없음

CSS에 대한 구성 요소 우선 접근 방식을 취한다는 것은 재사용되지 않더라도 구성 요소를 생성한다는 의미입니다. 이 조기 추상화는 스타일시트에서 많은 팽창과 복잡성의 원인입니다.

예를 들어 탐색 모음을 사용하십시오. 앱에서 기본 탐색에 대한 마크업을 몇 번이나 다시 작성합니까?

내 프로젝트에서는 일반적으로 한 번만 수행합니다. 내 기본 레이아웃 파일에서.

유틸리티로 먼저 빌드하고 걱정스러운 중복이 표시될 때만 구성 요소 를 추출한다면 아마도 navbar 구성 요소를 추출할 필요가 없을 것입니다.

대신 탐색 모음이 다음과 같이 보일 수 있습니다.

<nav class="bg-brand py-4 flex-spaced">
  <div><!-- Logo goes here --></div>
  <div>
    <!-- Menu items go here -->
  </div>
</nav>

추출할 가치가 있는 것은 없습니다.

이것이 바로 인라인 스타일이 아닙니까?

이 접근 방식을 보면 HTML 요소에 스타일 태그를 던지고 필요한 속성을 추가하는 것과 같다고 생각하기 쉽지만 제 경험으로는 매우 다릅니다.

인라인 스타일을 사용하면 선택하는 값에 대한 제약이 없습니다.

하나의 태그는 될 수 있고 font-size: 14px, 다른 하나는 될 수 있고 , 다른 하나는 될 수 있고 font-size: 13px, 다른 하나는 될 font-size: .9em수 있습니다 font-size: .85rem.

모든 새 구성 요소에 대해 새 CSS를 작성할 때 직면하는 것과 동일한 빈 캔버스 문제입니다.

유틸리티는 다음을 선택하도록 강요합니다.

이것은 text-sm또는 text-xs?

py-3또는 를 사용해야 합니까 py-4?

내가 원 text-dark-soft하거나 text-dark-faint?

원하는 값을 고를 수는 없습니다. 선별된 목록에서 선택해야 합니다.

380개의 텍스트 색상 대신 10 또는 12로 끝납니다.

내 경험에 따르면 처음에는 직관적이지 않은 것처럼 들릴 수 있지만 유틸리티 우선적으로 구축하는 것이 구성 요소 우선적으로 작업하는 것보다 더 일관된 모양의 디자인으로 이어집니다 .

어디서 부터 시작할까요?

이런 접근 방식에 관심이 있다면, 아래의 프레임워크를 살펴볼만 한다.

최근에 나는 유틸리티 기반 작업과 반복 패턴에서 구성 요소 추출이라는 아이디어를 중심으로 Tailwind CSS 라는 오픈 소스 PostCSS 프레임워크를 오픈했다.

들어와서 살펴보고 싶다면, Tailwind CSS 웹사이트로 이동 하여 살펴보기 바란다.

profile
게으른 개발자....

0개의 댓글