SASS에서의 상속

SASS에서의 상속

이 글은 SASS에서의 상속에 대해 설명합니다.

실제 예제를 통해 SASS의 상속 개념을 설명하겠습니다.

YouTube Video

SASS에서의 상속

SASS에서의 상속(@extend)은 한 셀렉터의 스타일을 중복 없이 다른 셀렉터에 적용할 수 있게 하는 메커니즘입니다. 동일한 스타일이 하나로 '결합'되어 여러 요소에 적용되므로 결과 CSS의 중복이 줄어듭니다. 하지만 잘못 사용할 경우 의도치 않은 셀렉터 결합이 발생할 수 있습니다.

기초: @extend 사용 방법

아래는 .btn--primary.btn의 스타일을 상속받는 기본 예시입니다. @extend는 대상 셀렉터를 확장하는 지시어입니다.

 1// Base button styles
 2.btn {
 3  padding: 0.5rem 1rem;
 4  border-radius: 4px;
 5  border: 1px solid #ccc;
 6  background: white;
 7  color: #333;
 8}
 9
10/* Primary button extends the base .btn */
11.btn--primary {
12  @extend .btn;
13  background: #007bff;
14  color: white;
15}
  • @extend를 사용하면 .btn--primary.btn의 기본 스타일을 상속하고 필요한 부분만 덮어쓸 수 있습니다.

생성된 CSS

 1.btn, .btn--primary {
 2  padding: 0.5rem 1rem;
 3  border-radius: 4px;
 4  border: 1px solid #ccc;
 5  background: white;
 6  color: #333;
 7}
 8
 9.btn--primary {
10  background: #007bff;
11  color: white;
12}

베스트 프랙티스: 플레이스홀더(%placeholder) 사용

플레이스홀더 셀렉터(%name)는 CSS로 출력되지 않는 셀렉터입니다. 여러 컴포넌트 간에 상속을 통해 공통 스타일을 안전하게 공유하고 싶을 때 특히 널리 사용됩니다.

 1// %placeholder will not be output directly
 2%card-base {
 3  padding: 1rem;
 4  border-radius: 6px;
 5  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 6  background: #fff;
 7}
 8
 9/* Components extend the placeholder */
10.card {
11  @extend %card-base;
12  border: 1px solid #eee;
13}
14
15.panel {
16  @extend %card-base;
17  border: 1px solid #ddd;
18}
  • .card.panel 모두 %card-base에서 상속을 받아 공통 스타일을 공유하면서 각자의 차별화도 할 수 있습니다.

생성된 CSS

 1.card, .panel {
 2  padding: 1rem;
 3  border-radius: 6px;
 4  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 5  background: #fff;
 6}
 7
 8.card {
 9  border: 1px solid #eee;
10}
11
12.panel {
13  border: 1px solid #ddd;
14}

다중 상속 (여러 @extend 사용)

여러 개의 플레이스홀더나 클래스를 동시에 상속할 수 있습니다. 스타일의 재사용성이 향상되지만, 어떤 규칙이 어떤 셀렉터와 결합되는지 추적하는 것이 중요합니다.

 1%btn-base {
 2  display: inline-block;
 3  padding: 0.5rem 1rem;
 4  border-radius: 3px;
 5}
 6
 7%btn-large {
 8  padding: 0.75rem 1.5rem;
 9  font-size: 1.125rem;
10}
11
12/* Composite button that extends both placeholders */
13.btn--lg {
14  @extend %btn-base;
15  @extend %btn-large;
16  background: #222;
17  color: #fff;
18}
  • 아래는 버튼이 'base'와 'size' 두 개의 플레이스홀더를 상속하는 예시입니다.
  • .btn--lg%btn-base%btn-large 모두를 상속하여 기본 레이아웃과 큰 사이즈를 결합합니다.

생성된 CSS

1.btn--lg {
2  display: inline-block;
3  /* %btn-large overrides the padding from %btn-base */
4  padding: 0.75rem 1.5rem;
5  border-radius: 3px;
6  font-size: 1.125rem;
7  background: #222;
8  color: #fff;
9}

@extend의 동작(병합 메커니즘) 및 '셀렉터 폭발' 주의점

@extend는 일치하는 모든 셀렉터를 병합하여 출력하기 때문에 때로는 의도하지 않은 셀렉터 조합이 생길 수 있습니다.

아래 예시는 동일한 기본 클래스가 여러 곳에서 확장될 때 출력이 어떻게 증가할 수 있는지 보여줍니다.

 1/* Many components extend .utility */
 2/* A generic utility class */
 3.utility {
 4  margin-bottom: 1rem;
 5}
 6
 7/* Nested selectors that extend .utility */
 8.header {
 9  @extend .utility;
10  .title {
11    font-weight: bold;
12  }
13}
14
15.footer {
16  @extend .utility;
17  .note {
18    color: #888;
19  }
20}
21
22.article {
23  @extend .utility;
24  .content {
25    line-height: 1.6;
26  }
27}
28
29.sidebar {
30  @extend .utility;
31  .section {
32    padding: 1rem;
33  }
34}
  • 여러 컴포넌트에서 .utility를 상속하면 셀렉터가 하나로 병합되어, 대규모 프로젝트에서는 CSS가 비대해질 수 있습니다.

생성된 CSS

 1.utility,
 2.header,
 3.footer,
 4.article,
 5.sidebar {
 6  margin-bottom: 1rem;
 7}
 8
 9.header .title {
10  font-weight: bold;
11}
12
13.footer .note {
14  color: #888;
15}
16
17.article .content {
18  line-height: 1.6;
19}
20
21.sidebar .section {
22  padding: 1rem;
23}

@extend.class vs 요소(태그) 셀렉터 — 우선순위 및 부작용

@extend는 클래스뿐만 아니라 요소 셀렉터에도 적용할 수 있습니다. 하지만 요소를 확장하면 영향 범위가 넓어져, 규칙이 의도하지 않은 곳에 적용될 위험이 높아집니다.

아래는 요소 셀렉터를 확장했을 때 발생할 수 있는 영향을 보여주는 예시입니다.

 1/* Extending an element selector (not recommended) */
 2h1 {
 3  font-size: 2rem;
 4  margin-bottom: 0.5rem;
 5}
 6
 7/* If you extend h1, the resulting selector will include your class with h1 */
 8.title {
 9  @extend h1;
10  color: #333;
11}
12
13/* Output becomes:
14h1, .title { font-size: 2rem; margin-bottom: 0.5rem; }
15*/
  • 이 예시에서는 요소 셀렉터인 h1을 상속하면 .titleh1이 동일한 스타일로 병합됩니다.
  • 소규모의 경우에는 편리해 보일 수 있지만, 프로젝트가 커질수록 h1의 규칙이 예상치 못하게 .title과 합쳐져 스타일이 복잡해지고 유지보수성이 떨어질 수 있습니다. 따라서 스타일을 주로 클래스와 플레이스홀더 중심으로 설계하면 유지관리가 쉬워집니다.

생성된 CSS

1h1,
2.title {
3  font-size: 2rem;
4  margin-bottom: 0.5rem;
5}
6
7.title {
8  color: #333;
9}

@extend!optional의 사용 사례

@extend!optional을 지정하면, 상속 대상이 존재하지 않을 때 에러를 발생시키지 않을 수 있습니다. 이 기능은 라이브러리와 같은 코드나 플레이스홀더가 조건부로 정의되는 경우에 특히 유용합니다.

아래는 존재하지 않을 수 있는 클래스를 !optional로 안전하게 상속 시도하는 예시입니다.

1/* Try to extend a class that might not exist */
2.component {
3  @extend .maybe-existing !optional;
4  padding: 1rem;
5}
  • .maybe-existing이 존재하지 않으면 아무 일도 일어나지 않고 건너뜁니다. 확장을 안전하게 시도하고 싶을 때 이 기능을 사용할 수 있습니다.

생성된 CSS

1.component {
2  padding: 1rem;
3}

@extend와 믹스인(@mixin / @include) 비교

@extend@mixin은 목적이 겹칠 수 있으나 결과물과 사용법이 다릅니다.

  • @extend

    • 생성된 CSS는 셀렉터 병합으로 중복을 줄입니다.
    • 생성 후 셀렉터가 병합되기 때문에 의도치 않은 조합이 발생할 수 있습니다.
    • 파라미터를 전달할 수 없습니다 (플레이스홀더 조합으로 보완 가능).
  • @mixin / @include

    • 호출마다 스타일이 중복 출력됩니다 (중복된 결과 발생).
    • 파라미터를 전달할 수 있고, 조건문이나 루프 등 로직도 포함할 수 있습니다.
    • 출력은 더 예측 가능하지만 파일 크기가 증가합니다.

아래는 동일한 버튼 스타일을 @mixin@extend 모두로 구현한 비교 예시입니다.

 1/* Mixin approach */
 2@mixin btn-styles($bg, $color) {
 3  display: inline-block;
 4  padding: 0.5rem 1rem;
 5  background: $bg;
 6  color: $color;
 7  border-radius: 4px;
 8}
 9
10/* Use mixin */
11.btn {
12  @include btn-styles(white, #333);
13}
14
15.btn--primary {
16  @include btn-styles(#007bff, white);
17}
18
19/* Extend approach (shared placeholder) */
20%btn-base {
21  display: inline-block;
22  padding: 0.5rem 1rem;
23  border-radius: 4px;
24}
25
26.btn2 {
27  @extend %btn-base;
28  background: white;
29  color: #333;
30}
31
32.btn2--primary {
33  @extend %btn-base;
34  background: #007bff;
35  color: white;
36}
  • @mixin은 스타일을 유연하게 삽입할 수 있고, @extend는 출력을 효율적으로 결합하므로 상황에 맞게 적절한 방식을 사용하면 됩니다.

생성된 CSS

@mixin 결과 출력
 1.btn {
 2  display: inline-block;
 3  padding: 0.5rem 1rem;
 4  background: white;
 5  color: #333;
 6  border-radius: 4px;
 7}
 8
 9.btn--primary {
10  display: inline-block;
11  padding: 0.5rem 1rem;
12  background: #007bff;
13  color: white;
14  border-radius: 4px;
15}
@extend 결과 출력
 1.btn2,
 2.btn2--primary {
 3  display: inline-block;
 4  padding: 0.5rem 1rem;
 5  border-radius: 4px;
 6}
 7
 8.btn2 {
 9  background: white;
10  color: #333;
11}
12
13.btn2--primary {
14  background: #007bff;
15  color: white;
16}

실전 가이드라인

SASS의 상속 기능은 스타일 재사용성을 높이기 위한 강력한 기능입니다. 하지만 잘못 사용하면 스타일 병합이 복잡해져 유지보수성이 떨어질 수 있습니다. 아래는 상속을 안전하고 효율적으로 사용하기 위한 주요 포인트입니다.

  • 구조나 레이아웃처럼 순수하게 공통적인 컴포넌트 스타일에는 플레이스홀더를 사용하세요. 또한, 동적으로 파라미터를 지정해야 한다면 @mixin을 사용할 수 있습니다.
  • h1 등 HTML 요소를 직접 상속하는 것은 피해야 합니다. 예기치 않은 셀렉터 조합이 발생하여, 예상치 못한 CSS가 생성될 수 있습니다.
  • BEM 같은 네이밍 또는 명확한 접두어로 플레이스홀더 용도를 표시하면 안전한 관리가 가능합니다.
  • @extend는 같은 파일 내에서 사용하는 것이 더 안전합니다. 특히 대규모 프로젝트에서는 상속 관계를 추적하기 쉽게 각 컴포넌트 범위 내에서 상속을 설계하는 것이 바람직합니다.

요약

SASS의 @extend 기능은 공통 스타일을 효율적으로 재사용하고, 디자인 일관성을 보장하는 편리한 방법입니다. 하지만 셀렉터 조합이 쉽게 복잡해질 수 있으므로, 이 기능을 신중하게 제한된 범위에서 사용하는 것이 필요합니다. 공유 스타일은 플레이스홀더 셀렉터(%placeholder)로 묶고, 동적 파라미터가 필요한 부분에는 @mixin을 사용하면, 간단하고 유지관리가 쉬운 디자인을 유지할 수 있습니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video