부모 선택자 &: 중첩의 핵심

부모 선택자 &: 중첩의 핵심

Sass에서 선택자를 중첩해서 쓸 때, 밖의 선택자를 다시 가리키기 위해 쓰는 특별한 선택자가 있어요. 바로 부모 선택자(parent selector) &입니다. 이걸 쓰면 바깥 선택자를 더 복잡한 방식으로 재사용할 수 있어요. 가상 클래스를 붙이거나, 부모 앞에 선택자를 추가하는 식으로요. 중첩된 규칙에서 &의 위치에 바깥 선택자가 들어가고, 중첩이 기본으로 하는 방식 대신 그렇게 치환됩니다.

출처: Sass: Parent Selector (공식 문서)

기본 사용

부모 선택자는 여러 방식으로 쓸 수 있어요. 바깥 선택자에 가상 클래스를 붙이거나, 특정 맥락에서의 스타일을 정의하거나, 가상 클래스 선택자의 인자로도 쓸 수 있습니다.

.alert {
  // The parent selector can be used to add pseudo-classes to the outer
  // selector.
  &:hover {
    font-weight: bold;
  }

  // It can also be used to style the outer selector in a certain context, such
  // as a body set to use a right-to-left language.
  [dir=rtl] & {
    margin-left: 0;
    margin-right: 10px;
  }

  // You can even use it as an argument to pseudo-class selectors.
  :not(&) {
    opacity: 0.8;
  }
}
.alert:hover {
  font-weight: bold;
}
[dir=rtl] .alert {
  margin-left: 0;
  margin-right: 10px;
}
:not(.alert) {
  opacity: 0.8;
}

부모 선택자는 h1 같은 타입 선택자로 치환될 수 있기 때문에, 타입 선택자가 올 수 있는 복합 선택자의 맨 앞에서만 허용돼요. 예를 들어 span&은 쓸 수 없습니다.

접미사 붙이기

부모 선택자로 바깥 선택자에 접미사를 추가할 수도 있어요. BEM처럼 규칙적인 클래스 이름을 쓰는 방법론에서 특히 유용합니다. 바깥 선택자가 클래스·ID·요소 선택자처럼 영숫자 이름으로 끝나기만 하면, 부모 선택자에 텍스트를 이어붙일 수 있어요.

.accordion {
  max-width: 600px;
  margin: 4rem auto;
  width: 90%;
  font-family: "Raleway", sans-serif;
  background: #f4f4f4;

  &__copy {
    display: none;
    padding: 1rem 1.5rem 2rem 1.5rem;
    color: gray;
    line-height: 1.6;
    font-size: 14px;
    font-weight: 500;

    &--open {
      display: block;
    }
  }
}
.accordion {
  max-width: 600px;
  margin: 4rem auto;
  width: 90%;
  font-family: "Raleway", sans-serif;
  background: #f4f4f4;
}
.accordion__copy {
  display: none;
  padding: 1rem 1.5rem 2rem 1.5rem;
  color: gray;
  line-height: 1.6;
  font-size: 14px;
  font-weight: 500;
}
.accordion__copy--open {
  display: block;
}

중첩 밖에서

일반 CSS에서처럼 중첩 규칙 밖에서도 부모 선택자를 쓸 수 있어요. 이 경우엔 브라우저가 처리하도록 그대로 일반 CSS로 출력되고, 브라우저는 이를 스코핑 루트로 해석합니다. 중첩 밖의 부모 선택자에는 접미사를 붙일 수 없어요 — 붙일 대상이 없으니까요.

SassScript 안에서

부모 선택자는 SassScript 안에서도 쓸 수 있어요. 현재 부모 선택자 값을 선택자 함수와 같은 형식(쉼표로 구분된 선택자 리스트, 그 안의 공백으로 구분된 복합 선택자, 그 안의 따옴표 없는 문자열)으로 돌려주는 특별한 표현식입니다.

.main aside:hover,
.sidebar p {
  parent-selector: &;
  // => ((unquote(".main") unquote("aside:hover")),
  //     (unquote(".sidebar") unquote("p")))
}
.main aside:hover,
.sidebar p {
  parent-selector: .main aside:hover, .sidebar p;
}

& 표현식을 어떤 스타일 규칙 밖에서 쓰면 null을 돌려줍니다. null은 거짓(falsy)이므로, 이걸 이용해 믹스인이 스타일 규칙 안에서 호출됐는지 아닌지를 쉽게 알아낼 수 있어요.

@mixin app-background($color) {
  #{if(sass(&): '&.app-background'; else: '.app-background')} {
    background-color: $color;
    color: rgba(#fff, 0.75);
  }
}

@include app-background(#036);

.sidebar {
  @include app-background(#c6538c);
}
.app-background {
  background-color: #036;
  color: rgba(255, 255, 255, 0.75);
}

.sidebar.app-background {
  background-color: #c6538c;
  color: rgba(255, 255, 255, 0.75);
}

고급 중첩

&는 평범한 SassScript 표현식처럼 쓸 수 있어요. 함수에 넘기거나 보간(interpolation)에 넣을 수 있고, 심지어 다른 선택자 안에서도요. 선택자 함수나 @at-root 규칙과 함께 쓰면 선택자를 아주 강력하게 중첩할 수 있습니다.

예를 들어 바깥 선택자 그리고 요소 선택자에 모두 일치하는 선택자를 쓰고 싶다면, &를 사용자 선택자와 결합하는 selector.unify() 함수를 쓰는 믹스인을 만들 수 있어요.

@use "sass:selector";

@mixin unify-parent($child) {
  @at-root #{selector.unify(&, $child)} {
    @content;
  }
}

.wrapper .field {
  @include unify-parent("input") {
    /* ... */
  }
  @include unify-parent("select") {
    /* ... */
  }
}
.wrapper input.field {
  /* ... */
}

.wrapper select.field {
  /* ... */
}

Sass가 선택자를 중첩할 때는 어떤 보간이 선택자 생성에 쓰였는지 알 수 없어요. 그래서 &를 SassScript 표현식으로 썼더라도 바깥 선택자를 안쪽 선택자에 자동으로 더해요. 그렇기 때문에 @at-root 규칙을 명시적으로 써서 바깥 선택자를 포함하지 말라고 알려줘야 합니다.

더 알아보기