Sass Guidelines

분별 있고, 유지∙확장 가능한 Sass 작성을 위한 주관적인 스타일가이드.

You are viewing the Korean translation by Donghee Kim of the original Sass Guidelines from Hugo Giraudel.

This version is exclusively maintained by contributors without the review of the main author, therefore might not be completely up-to-date, especially since it is currently in version 1.1 while the English version is in version 1.3.

옵션 패널 열기

저자에 대해

제 이름은 Hugo Giraudel이며, 베를린에 거주하는 프랑스 출신의 프론트 엔드 개발자입니다. 2년이 넘게 Sass를 써왔고 SassDocSass-Compatibility 같은 Sass 관련 프로젝트의 저자입니다. CSS3 Pratique du Design Web이라는 제목의 (프랑스 어로 된) CSS 책을 쓰기도 했습니다.

주로 재미 삼아서 몇 개의 사스 라이브러리를 쓰기도 했고요: SassyJSON, SassyLists, SassySort, SassyCast, SassyMatrix, SassyBitwise, SassyIteratorsGenerators, SassyLogger, SassyStrings, SassyGradients.

기여하기

Sass Guidelines는 여가시간에 유지하는 무료 프로젝트입니다. 말할 것도 없이, 모든 최신정보를 반영하고, 기록하며, 관련성을 유지하는 것은 아주 많은 양의 일입니다. 이 스타일가이드가 여러분 맘에 들었다는 점은 이미 감사하게 생각하고 있습니다!

혹시 이 프로젝트에 기여하고 싶으시다면, 스타일가이드에 대해 트윗하고, 말을 퍼뜨리거나 GitHub 저장소에서 이슈나 풀-리퀘스트를 통해 작은 오타를 수정해주시면 정말 좋을 거예요!

시작에 앞서 마지막으로: 이 문서를 즐기셨다면, 혹은 이 문서가 여러분이나 여러분의 팀에 유용하다면, 후원을 고려해주시기 바랍니다!

Sass에 대해

Sass공식 문서에서 스스로를 이렇게 묘사하고 있습니다:

사스는 기초 언어에 힘과 우아함을 더해주는 CSS의 확장이다.

Sass의 궁극적인 목적은 CSS의 결함을 보정하는 것입니다. 우리 모두 알듯이, CSS는 세계 최고의 언어는 못 됩니다[citation needed]. 배우기엔 매우 간단한 반면, 금세 아주 지저분해질 수 있습니다. 특히 큰 프로젝트에서 더 그렇습니다.

Sass는 이런 상황에서, 초언어로서, 추가 기능과 유용한 도구를 제공하기 위해 CSS의 구문을 개선하는 역할을 합니다. 한편, Sass는 CSS 언어에 대해 보수적인 입장을 취하려 합니다.

핵심은 CSS를 완전한 기능을 갖춘 프로그래밍 언어로 바꾸지 않는 것입니다; Sass는 단지 CSS에게서 부족한 부분만을 돕길 원합니다. 때문에, Sass를 시작하는 것은 CSS를 배우는 것보다 더 어려울 게 없습니다: CSS 위에 몇 가지의 추가 기능을 더할 뿐이니까요.

그렇다고는 하나, 이 기능들을 사용하는 데에는 많은 방법들이 있습니다. 좋은 것도 있고, 나쁜 것도, 예외적인 것도 있죠. 이 가이드라인은 여러분에게 Sass 코드 작성에 대한 일관되고, 기록된 접근법을 제공할 것입니다.

참고

Ruby Sass 혹은 LibSass

Sass의 첫 번째 커밋은 8년 이상이 지난, 2006년 후반까지 거슬러 올라갑니다. 말할 것도 없이 그 이후로 아주 먼 길을 왔습니다. Ruby로 처음에 개발되었고, 여기저기서 다양한 포트들이 튀어나왔죠. 가장 성공적인, (C/C++로 쓰인) LibSass는 현재 원래의 Ruby 버전과의 완전한 호환에 근접해 있습니다.

2014년, Ruby 사스와 LibSass 팀은 더 나아가기 전에 두 가지 버전의 동기화를 기다리기로 결정했습니다. 그 이후로, LibSass는 형과의 기능 동기화를 위해 활발하게 버전들을 출시하고 있습니다. 남아있는 불일치들은 제가 Sass-Compatibility 프로젝트로 한데 모아 정리해두었습니다. 열거되지 않은 불일치를 알고 계시다면, 이슈를 여는 친절을 베풀어주시기 바랍니다.

여러분의 컴파일러를 선택하는 것으로 돌아가겠습니다. 사실, 이건 여러분의 프로젝트에 달려있습니다. Ruby on Rails 프로젝트라면, Ruby Sass를 사용하는 게 나을 겁니다. Ruby Sass가 이 경우에 완벽하게 적합하죠. 또한, Ruby Sass가 언제나 참조 구현이 될 것이며 기능에 있어 LibSass를 선도할 것이라는 점을 알아두십시오.

작업 흐름의 통합이 필요한 비(非)Ruby 프로젝트의 경우, 주로 감싸는 용도로 만들어져 있으므로 LibSass가 아마도 더 나은 생각일 것입니다. 그러니까 만약 Node.js를 사용하고 싶으시다면, 선택은 node-sass입니다.

참고

Sass 혹은 SCSS

Sass라는 이름의 의미에 관해 꽤 많은 혼동이 있습니다. 그럴 만한 이유가 있죠: Sass는 전처리기와 그 구문 두 가지를 의미합니다. 좀 불편하죠, 안 그런가요?

Sass는 처음에 들여쓰기의 감지를 그 핵심 특성으로 갖는 구문을 가리켰습니다. 얼마 지나지 않아, Sass를 유지하는 사람들은 Sassy CSS를 의미하는 SCSS라는 CSS 친화적인 구문을 제공함으로써 Sass와 CSS 사이의 차이를 좁히기로 결정했습니다. 모토는 이렇습니다: 만약 유효한 CSS라면, 유효한 SCSS이다.

그 이후로, Sass(전처리기)는 두 가지 다른 구문을 제공해 오고 있습니다: 들여쓰기 구문으로도 알려진 Sass(전부 대문자가 아닙니다, 제발), 그리고 SCSS. 둘은 정확히 동등한 기능을 갖고 있기 때문에 어느 것을 사용할지는 여러분에게 달렸습니다. 지금 시점에서는 이것은 순전히 미관상의 문제입니다.

Sass의 공백에 반응하는 구문은 중괄호, 세미콜론 그리고 다른 구두점 기호들을 없애기 위해 들여쓰기에 의존하며, 이는 간결하고 짧은 구문으로 이어집니다. 한편, SCSS는 주로 CSS에 올려진 작은 추가사항들이므로 배우기에 더 쉽습니다.

저 자신은 CSS와 더 비슷하고 대부분의 개발자들에게 더 친숙하기 때문에 SCSS를 Sass보다 더 선호합니다. 그런 이유로, SCSS가 이 가이드라인의 기본 구문입니다. 에서 Sass의 들여쓰기 구문으로 바꾸실 수 있습니다.

참고

기타 전처리기들

Sass 외에도 여러 전처리기들이 있습니다. 가장 만만찮은 경쟁자는 LESS일 것입니다. 유명한 CSS 프레임워크인 Bootstrap이 사용한 덕분에 널리 알려진 Node.js 기반의 전처리기죠. 너드스럽고, 제한 없는 버전의 LESS처럼 느껴지는 Stylus도 있습니다. 이것은 CSS를 거의 프로그래밍 언어로 바꿔놓기 때문에 원하는 것 대부분을 할 수 있습니다.

LESS나 다른 전처리기를 두고 왜 Sass를 선택하는가? 오늘날에도 여전히 합당한 질문입니다. 얼마 전까진, Sass가 애초에 Ruby로 만들어졌고 Ruby on Rails와 함께 잘 작동했기 때문에 Ruby 기반의 프로젝트에 Sass를 추천하곤 했습니다. 이제 LibSass가 오리지널 Sass를 (거의) 따라잡았기 때문에, 이건 더이상 적절한 조언이 아닙니다.

제가 Sass에서 좋아하는 점은 CSS에 대한 보수적인 접근입니다. Sass의 디자인은 강력한 원칙에 기반하고 있습니다: 디자인 접근법의 큰 부분은 다음과 같은 중심 팀의 믿음으로부터 나옵니다. a) 추가 기능을 더하는 것은 유용성에 의해 정당화되어야만 하는 복잡성의 비용을 가진다. b) 따로 떨어져있는 스타일 블록 하나만을 보는 것으로도 주어진 블록이 무엇을 하고 있는지 추론하는 것이 쉬워야 한다. 또한, Sass는 다른 전처리기에 비해 세부사항에 대한 훨씬 날카로운 관심을 갖고 있습니다. 제가 아는 한, 핵심 디자이너들은 간과하기 쉬운 모든 CSS 호환성에 대한 지원까지 신경 쓰고 전반적인 동작들이 모두 일관성을 유지하도록 합니다.

다시 말해서, Sass는 어떠한 논리적 용도도 지원하도록 의도되지 않은 언어에 엄청난 기능을 추가함으로써 저처럼 프로그래머가 되고 싶은 너드를 만족시키는 것을 겨냥한 전처리기가 아닙니다. Sass는 실제의 문제를 해결하고, CSS가 미흡한 지점에서 CSS에 유용한 기능을 제공하는 것을 돕기 위한 소프트웨어입니다.

전처리기 외에도, 우리는 후처리기 역시 언급해야 합니다. 이들은 주로 PostCSScssnext 덕분에, 지난 몇 달 동안 상당한 노출을 받았죠. 후처리기는 오직 새로 추가될 CSS 구문만을 제공한다는 점을 제외하면 전처리기와 거의 동일합니다.

지원되지 않는 CSS 기능에 대한 폴리필polyfill이라고 후처리기를 생각하시면 됩니다. 예를 들면, CSS 명세에 나와있는 대로 변수를 작성하고, 후처리기로 컴파일하면 모든 변수를 찾아 그 값으로 대체합니다. Sass가 하는 것처럼요.

후처리기의 배경에 있는 아이디어는 일단 브라우저가 새로운 기능을 지원하게 되면 (예를 들면 CSS 변수), 후처리기가 더이상 그것을 컴파일하지 않고 그 역할을 브라우저가 대체하도록 하는 것입니다.

내일의 문법을 오늘 제공한다는 것은 훌륭한 생각이긴 하나, 전 그래도 대부분의 작업에 Sass를 사용하는 것을 선호합니다. 그러나, 후처리기가 Sass보다 더 적합하다고 믿는 일부의 경우가 있습니다. 예를 들면 CSS 프리픽스 같은 경우입니다. 그러나 이 부분은 나중에 다시 이야기하도록 하겠습니다.

참고

서론

왜 스타일가이드가 필요한가

스타일가이드는 여러분의 코드에 대한 이상적인 상태를 제시하는 그저 읽기에 즐거운 문서가 아닙니다. 어떻게 그리고 왜 코드가 쓰여져야 하는지를 묘사하는, 프로젝트의 일생에서 핵심이 되는 문서입니다. 작은 프로젝트에서는 이것이 지나친 노력으로 보일 수 있습니다. 하지만 코드베이스를 깔끔하고 확장 가능하며 쉽게 관리할 수 있도록 유지하는 데 큰 도움이 됩니다.

말할 것도 없이, 더 많은 개발자들이 프로젝트에 참여할수록, 더 많은 코드 가이드라인이 필요합니다. 같은 맥락으로, 프로젝트가 클수록 스타일가이드는 더욱 필수품이 됩니다.

Harry RobertsCSS Guidelines에서 이에 대해 잘 서술하고 있습니다:

코딩 스타일가이드는 (시각적 스타일가이드 아님) 다음과 같은 팀을 위한 소중한 도구이다:

  • 상당 기간 동안 제품을 제작하고 유지하는 팀;
  • 다른 능력과 특기를 소유한 개발자들이 있는 팀;
  • 어느 때나 작업을 진행하고 있는 여러 명의 다른 개발자들이 있는 팀;
  • 정기적으로 직원을 충원하는 팀;
  • 개발자들이 들락날락하는 다수의 코드베이스를 가진 팀.

이 글에서 기대하지 말아야 할 것

중요한 것 먼저: 이것은 CSS 스타일가이드가 아닙니다. 이 문서는 CSS 클래스를 위한 작명 관례, 모듈 패턴과 CSS 세계에서의 ID 문제를 논하지 않을 것입니다. 이 가이드라인은 오직 Sass와 관련된 내용만을 다루는 것을 목표로 합니다.

또한, 이 가이드라인은 제 자신의 것이고 따라서 매우 치우친 견해를 갖고 있습니다. 방법론 모음집이나 제가 수년 동안 다듬고 해 온 조언이라고 생각하세요. 이 가이드라인은 몇몇 통찰력 있는 자료를 연결할 기회 또한 제공하니, 참고 자료 역시 꼭 확인하세요.

누구나 동의하다시피, 이것이 유일한 방법은 아니며, 여러분의 프로젝트에 맞을 수도 맞지 않을 수도 있습니다. 마음대로 골라서 여러분의 필요에 맞게 적용하세요. 우리가 이야기하듯이, 여러분 자동차의 연비는 다를 수 있습니다(역주: 상황에 따라 다르게 적용될 수 있습니다).

핵심 원칙

마지막에 가서, 여러분이 이 스타일가이드 전체로부터 얻기를 바라는 한 가지가 있다면, 그것은 Sass는 가능한 한 간단하게 유지되어야 한다는 것입니다.

Sass로 쓰인 bitwise operators, iterators and generators, a JSON parser 등 저의 실없는 실험들 덕분에 우리는 이 전처리기를 가지고 어떤 일을 할 수 있는지 잘 알고 있습니다.

한편, CSS는 간단한 언어입니다. Sass는 CSS를 작성하도록 의도되었으므로, 보통의 CSS보다 더 복잡해져선 안 됩니다. KISS 원칙 (Keep It Simple Stupid)이 핵심이며 어떤 상황에선 심지어 DRY 원칙 (Don’t Repeat Yourself)보다 우선할 수도 있습니다.

때로는, 코드를 유지가능하도록 만들기 위해 조금 반복하는 편이 더 낫습니다. 무거운 머리를 가진, 통제하기 힘들고, 불필요하게 복잡한 시스템을 제작하면 지나친 복잡성 때문에 유지관리가 완전히 불가능해질 수 있습니다.

또한, 한 번 더 Harry Roberts를 인용하자면, 실용주의가 완벽을 이깁니다. 어느 순간, 아마도 여러분은 여기에 설명된 규칙을 거스르는 스스로를 발견하실 겁니다. 만약 그것이 타당하다면, 만약 그것이 옳게 느껴진다면, 그렇게 하세요. 코드는 목적이 아니라 수단에 불과합니다.

참고

구문 & 서식

제 생각으로는, 스타일가이드가 가장 먼저 해야 할 일은 우리 코드가 어떻게 보이길 원하는지를 묘사하는 것입니다.

같은 프로젝트에서 여러 명의 개발자들이 CSS 작성에 참여할 때, 그들 중 하나가 자기만의 방식으로 일을 하기 시작하는 것은 시간 문제일 뿐입니다. 일관성을 고취하는 코드 가이드라인은 이것을 방지할 뿐만 아니라, 코드를 읽고 업데이트하는 데에도 도움을 줍니다.

대략, 우리가 원하는 것은 (뻔뻔스럽게도 CSS Guidelines에서 영감을 얻은) 다음과 같습니다:

  • 탭 대신 스페이스 두 칸 (2) 들여쓰기;
  • 이상적인 행의 너비는 80 글자;
  • 적절하게 쓰인 여러 행의 CSS 규칙;
  • 공백의 의미 있는 사용.
// Yep
.foo {
  display: block;
  overflow: hidden;
  padding: 0 1em;
}

// Nope
.foo {
    display: block; overflow: hidden;

    padding: 0 1em;
}
// Sass 들여쓰기 구문은 위의 코딩 기준을 강제하기 때문에 잘못된 방식으로 처리할
// 수 없습니다
.foo
  display: block
  overflow: hidden
  padding: 0 1em

이 절에서는 파일 구조에 대한 질문과는 씨름하지 않겠습니다. 그것은 다른 절의 주제입니다.

문자열

믿거나 말거나, 문자열은 CSS와 Sass 생태계에서 꽤 중요한 역할을 합니다. 대부분의 CSS 값들은 길이 혹은 (주로 따옴표가 없는) 문자열이기 때문에, Sass에서 문자열을 다룰 때는 어느 정도 가이드라인을 따르는 것이 사실 제법 중요합니다.

인코딩

문자 인코딩과 관련한 잠재적인 문제를 피하기 위해서는, 메인 스타일시트에서 @charset 지시어를 사용해 UTF-8 인코딩을 강제하는 것이 강력하게 권장됩니다. 이 지시어가 스타일시트의 가장 첫 번째 요소이고 어떤 종류의 문자도 앞에 오지 않도록 하세요.

@charset 'utf-8';
@charset 'utf-8'

따옴표

CSS에서 문자열은 따옴표로 둘러싸일 필요가 없습니다. 심지어 공백을 포함한 경우에도요. 예를 들면 font-family가 있습니다: 따옴표로 감쌌는지 여부는 CSS 파서에게 문제가 되지 않습니다.

이 때문에, Sass 역시 문자열이 따옴표로 둘러싸일 것을 요구하지 않습니다. 더 나아가 (그리고 여러분도 인정하듯이, 운이 좋게도) 따옴표로 둘러싸인 문자열은 그렇지 않은 쌍둥이와 정확히 동일합니다(예를 들면 'abc'abc와 정확히 동일합니다).

그렇기는 하나, 문자열이 따옴표에 둘러싸일 것을 요구하지 않는 언어들은 분명히 소수이고 따라서, Sass에서 문자열은 언제나 작은 따옴표(')로 감싸져야 합니다(qwerty 자판에서 작은 따옴표가 큰 따옴표보다 입력하기 쉬우므로). CSS의 사촌 JavaScript를 포함한 다른 언어와의 일관성 외에도, 이 선택에 대한 몇 가지 이유가 있습니다:

  • 색 이름은 따옴표가 없으면 색으로 취급되는데, 이는 심각한 문제로 이어질 수 있다;
  • 대부분의 구문 강조기는 따옴표 없는 문자열을 지원하지 못할 것이다;
  • 전반적인 가독성에 도움이 된다;
  • 문자열을 따옴표로 감싸지 않을 적절한 이유가 없다.
// Yep
$direction: 'left';

// Nope
$direction: left;
// Yep
$direction: 'left'

// Nope
$direction: left

CSS 값인 문자열

initial이나 sans-serif 같은 특정 CSS 값은 따옴표로 싸여서는 안됩니다. font-family: 'sans-serif' 같은 선언의 경우 CSS는 인용부호가 붙은 문자열이 아니라 식별자를 기대하고 있기 때문에 아무 경고도 없이 작동하지 않을 것입니다. 이 때문에, 그런 값들은 따옴표로 감싸지 않습니다.

// Yep
$font-type: sans-serif;

// Nope
$font-type: 'sans-serif';

// Okay I guess
$font-type: unquote('sans-serif');
// Yep
$font-type: sans-serif

// Nope
$font-type: 'sans-serif'

// Okay I guess
$font-type: unquote('sans-serif')

따라서, 우리는 앞의 예처럼 CSS 값(CSS 식별자)으로 사용될 문자열과 맵 키와 같은 Sass 자료 유형에 쓰일 문자열을 구별할 수 있습니다.

전자에는 따옴표를 붙이지 않지만, 후자는 작은 따옴표로 감쌉니다.

따옴표를 포함한 문자열

만약 문자열이 하나 혹은 여러 개의 작은 따옴표를 포함하고 있다면, 문자열 안에서 과도한 문자 이스케이프를 피하기 위해 대신 큰 따옴표(")로 문자열을 감싸는 것을 고려해 볼 수 있습니다.

// Okay
@warn 'You can\'t do that.';

// Okay
@warn "You can't do that.";
// Okay
@warn 'You can\'t do that.'

// Okay
@warn "You can't do that."

URL

URL 역시 위와 동일한 이유로 따옴표로 감싸여야 합니다:

// Yep
.foo {
  background-image: url('/images/kittens.jpg');
}

// Nope
.foo {
  background-image: url(/images/kittens.jpg);
}
// Yep
.foo
  background-image: url('/images/kittens.jpg')

// Nope
.foo
  background-image: url(/images/kittens.jpg)
참고

숫자

Sass에서 숫자는 단위가 없는 숫자에서부터 길이, 기간, 빈도, 각도 등에 이르기까지 모든 것을 포함하는 데이터 타입입니다. 덕분에 그런 단위들을 가지고 연산을 하는 것이 가능해집니다.

숫자는 1보다 작은 소수 앞에 앞장서는 영을 표기해야 합니다. 뒤따르는 영은 절대 표기하지 마세요.

// Yep
.foo {
  padding: 2em;
  opacity: 0.5;
}

// Nope
.foo {
  padding: 2.0em;
  opacity: .5;
}
// Yep
.foo
  padding: 2em
  opacity: 0.5

// Nope
.foo
  padding: 2.0em
  opacity: .5

단위

길이를 다룰 때, 0 값은 절대로 단위를 가져선 안 됩니다.

// Yep
$length: 0;

// Nope
$length: 0em;
// Yep
$length: 0

// Nope
$length: 0em

Sass에서 숫자와 관련해 제가 생각할 수 있는 가장 흔한 실수는 단위가 숫자에 안전하게 덧붙여질 수 있는 문자열이라고 생각하는 것입니다. 이게 그럴 듯하게 들리긴 하지만, 단위가 작동하는 방식은 분명히 아닙니다. 단위를 대수 기호라고 생각해보세요. 예를 들어 실제 세계에서, 5인치에 5인치를 곱하면 25 제곱 인치가 나옵니다. 똑같은 논리가 Sass에도 적용됩니다.

단위를 숫자에 붙이기 위해서는, 이 숫자에 1 단위를 곱해야 합니다.

$value: 42;

// Yep
$length: $value * 1px;

// Nope
$length: $value + px;
$value: 42

// Yep
$length: $value * 1px

// Nope
$length: $value + px

0 단위를 더하는 것도 역시 같은 결과를 내긴 하지만, 0 단위를 더하는 것은 약간 혼란스러울 수 있기 때문에 앞서 언급한 방법을 추천합니다. 사실, 숫자를 다른 호환되는 단위로 변환하려고 할 때, 0을 더하는 것은 효과가 없습니다.

$value: 42 + 0px;
// -> 42px

$value: 1in + 0px;
// -> 1in

$value: 0px + 1in;
// -> 96px
$value: 42 + 0px
// -> 42px

$value: 1in + 0px
// -> 1in

$value: 0px + 1in
// -> 96px

결국에는, 여러분이 달성하려고 하는 것이 무엇인지에 달려있습니다. 단위를 문자열로서 더하는 것은 좋은 방법이 아니라는 점을 명심하세요.

값의 단위를 제거하기 위해서는, 그 종류의 한 단위로 나누어야 합니다.

$length: 42px;

// Yep
$value: $length / 1px;

// Nope
$value: str-slice($length + unquote(''), 1, 2);
$length: 42px

// Yep
$value: $length / 1px

// Nope
$value: str-slice($length + unquote(''), 1, 2)

단위를 문자열로서 숫자에 덧붙이면 결과물은 문자열이 되며, 그 값으로 더이상 연산을 할 수 없습니다. 숫자의 숫자 부분을 단위에서 잘라내면 그 결과 역시 문자열이 됩니다. 이것은 여러분이 원하는 것이 아닙니다.

연산

최상위 숫자 계산은 언제나 괄호로 감싸져야 합니다. 이 요건은 가독성을 향상시킬 뿐만 아니라, Sass가 괄호 안의 수치를 계산하도록 강제함으로써 일부 예외적인 상황을 방지합니다.

// Yep
.foo {
  width: (100% / 3);
}

// Nope
.foo {
  width: 100% / 3;
}
// Yep
.foo
  width: (100% / 3)

// Nope
.foo
  width: 100% / 3

매직 넘버

“매직 넘버”는 익명의 숫자 상수를 일컫는 전통적인 프로그래밍 용어입니다. 기본적으로, 이 숫자는 어쩌다보니 맞아떨어지지만™ 어떤 논리적인 설명과도 관련되지 않은 임의의 숫자입니다.

말할 것도 없이 매직 넘버는 역병 같은 존재이며 무슨 수를 써서라도 피해야 합니다. 왜 매직넘버가 효과를 내는지에 대한 합리적인 설명을 찾을 수 없을 때는, 어떻게 거기에 도달했고 왜 효과를 낸다고 생각하는지를 설명하는 충분한 주석을 달아놓으세요. 무언가가 제대로 작동하는 이유를 모른다고 인정하는 것이 그래도 아무런 사전 정보 없이 알아내게 하는 것보다 다음 개발자에게 더 도움이 됩니다.

/**
 * 1. 매직 넘버. `.foo`의 상단을 부모에 맞춰 정렬시키기 위해 찾은 값 중 가장
 * 낮은 값이다. 가능하다면, 적절하게 고쳐야 할 것.
 */
.foo {
  top: 0.327em; /* 1 */
}
/**
 * 1. 매직 넘버. `.foo`의 상단을 부모에 맞춰 정렬시키기 위해 찾은 값 중 가장
 * 낮은 값이다. 가능하다면, 적절하게 고쳐야 할 것.
 */
.foo
  top: 0.327em /* 1 */
참고

색은 CSS 언어에서 중요한 위치를 차지하고 있습니다. 자연스럽게, Sass는 몇 가지의 강력한 함수을 제공함으로써 색 조작에 있어 소중한 동맹이 되었습니다.

색 서식

색을 가능한 한 간단하게 만들기 위한 제 조언은 색 서식에 대한 다음의 우선순위를 따르라는 것입니다:

  1. CSS 색 키워드;
  2. HSL 표기법;
  3. RGB 표기법;
  4. 16진법 표기법. 가급적 소문자와 가능한 경우 단축형으로.

우선, 키워드는 자명한 서식입니다. HSL 표기는 인간의 두뇌로 이해하기에 가장 쉬울 뿐만 아니라[citation needed] 스타일시트 작성자가 색상, 채도, 명도를 조정함으로써 색을 변경하는 일을 쉽게 만듭니다. RGB 역시 색이 청색, 녹색, 적색 중 어느 것에 가까운지 바로 보여주는 이점을 갖고 있지만 세 속성으로부터 색을 제조하는 일을 쉽게 만들어주진 않습니다. 마지막으로, 16진법은 인간의 머리로는 거의 해독이 불가능합니다.

// Yep
.foo {
  color: hsl(0, 100%, 50%);
}

// Also yep
.foo {
  color: rgb(255, 0, 0);
}

// Meh
.foo {
  color: #f00;
}

// Nope
.foo {
  color: #FF0000;
}

// Nope
.foo {
  color: red;
}
.foo
  color: hsl(0, 100%, 50%)

// Also yep
.foo
  color: rgb(255, 0, 0)

// Nope
.foo
  color: #f00

// Nope
.foo
  color: #FF0000

// Nope
.foo
  color: red

HSL이나 RGB 표기를 사용할 때, 쉼표(,) 뒤에는 언제나 스페이스 한 칸을 더하고 괄호((, ))와 내용 사이에는 스페이스를 넣지 마세요.

// Yep
.foo {
  color: rgba(0, 0, 0, 0.1);
  background: hsl(300, 100%, 100%);
}

// Nope
.foo {
  color: rgba(0,0,0,0.1);
  background: hsl( 300, 100%, 100% );
}
// Yep
.foo
  color: rgba(0, 0, 0, 0.1)
  background: hsl(300, 100%, 100%)

// Nope
.foo
  color: rgba(0,0,0,0.1)
  background: hsl( 300, 100%, 100% )

색과 변수

색을 한 번 이상 사용할 때는 색을 대변하는 의미 있는 이름을 붙여 변수에 저장하세요.

$sass-pink: hsl(330, 50%, 60%);
$sass-pink: hsl(330, 50%, 60%)

이제 이 변수를 언제든 원할 때마다 자유롭게 사용할 수 있습니다. 하지만, 만약 변수의 용도가 테마와 깊은 관련이 있다면, 변수를 그대로 사용하지 말라고 조언하겠습니다. 대신, 그 변수가 어떻게 사용되어야 하는지 설명하는 이름을 붙여 다른 변수에 저장하세요.

$main-theme-color: $sass-pink;
$main-theme-color: $sass-pink

이렇게 함으로써 테마 변경이 $sass-pink: blue 같은 사태를 초래하는 것을 방지할 수 있습니다.

색 명암 조절

lightendarken 두 함수는 HSL 공간에서 색의 명도를 증감하여 조정합니다. 기본적으로, 이들은 adjust-color 함수의 $lightness 매개 변수의 가명일 뿐입니다.

문제는, 이들 함수가 가끔 기대되는 결과를 제공하지 않는다는 것입니다. 반면에, mix 함수는 색을 whiteblack과 혼합함으로써 명암을 조절하는 좋은 방법입니다.

앞서 언급한 두 함수보다 mix를 사용하는 것의 이점은 색의 비율을 감소시킴에 따라 점진적으로 검은 색(혹은 흰 색)으로 나아간다는 점입니다. 반면 darkenlighten은 색을 순식간에 완전한 검은 색이나 흰 색으로 보내버릴 것입니다.

lighten/darken 과 mix 사이의 차이를 보여주는 삽화 by KatieK
lighten/darkenmix 사이의 차이를 보여주는 삽화 by KatieK

만약 매번 mix 함수를 쓰는 것을 원치 않으신다면, 두 가지 사용하기 쉬운 (Compass에 포함되어 있기도 한) tintshade 평션을 만들어 같은 일을 할 수 있습니다:

/// 색을 약간 밝게 한다
/// @access public
/// @param {Color} $color - 밝게 만들려는 색
/// @param {Number} $percentage - 반환될 색 내 `$color`의 백분율
/// @return {Color}
@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

/// 색을 약간 어둡게 한다
/// @access public
/// @param {Color} $color - 어둡게 만들려는 색
/// @param {Number} $percentage - 반환될 색 내 `$color`의 백분율
/// @return {Color}
@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}
/// 색을 약간 밝게 한다
/// @access public
/// @param {Color} $color - 밝게 만들려는 색
/// @param {Number} $percentage - 반환될 색 내 `$color`의 백분율
/// @return {Color}
@function tint($color, $percentage)
  @return mix($color, white, $percentage)

/// 색을 약간 어둡게 한다
/// @access public
/// @param {Color} $color - 어둡게 만들려는 색
/// @param {Number} $percentage - 반환될 색 내 `$color`의 백분율
/// @return {Color}
@function shade($color, $percentage)
  @return mix($color, black, $percentage)

scale-color 함수는 속성들이 이미 얼마나 높거나 낮은지를 고려함으로써 그 크기를 보다 유동적으로 변경하도록 디자인되었습니다. 이 함수는 mix 만큼이나 좋은 결과물과 함께 보다 명확한 호출 관례를 제공합니다. 그렇지만 비례 계수는 정확히 같지 않습니다.

참고

리스트

리스트는 Sass에서 배열에 상당하는 개념입니다. 리스트는 어떤 타입의 값이든(리스트도 포함. 이 경우 내포 리스트가 된다) 저장할 수 있게 의도된 (과 달리) 평면적인 데이터 구조입니다.

리스트는 다음의 가이드라인을 준수해야 합니다:

  • 한 줄 혹은 여러 줄;
  • 80자 줄에 안 들어갈 정도로 길면 반드시 여러 줄에 표기한다;
  • CSS 상에서 그대로 사용되지 않는 한, 언제나 쉼표로 분리한다;
  • 언제나 괄호로 감싼다;
  • 여러 줄인 경우 뒤따르는 쉼표를 붙이고, 한 줄인 경우 제외한다.
// Yep
$font-stack: ('Helvetica', 'Arial', sans-serif);

// Yep
$font-stack: (
  'Helvetica',
  'Arial',
  sans-serif,
);

// Nope
$font-stack: 'Helvetica' 'Arial' sans-serif;

// Nope
$font-stack: 'Helvetica', 'Arial', sans-serif;

// Nope
$font-stack: ('Helvetica', 'Arial', sans-serif,);
// Yep
$font-stack: ('Helvetica', 'Arial', sans-serif)

// Nope (not supported)
$font-stack: (
  'Helvetica',
  'Arial',
  sans-serif,
)

// Nope
$font-stack: 'Helvetica' 'Arial' sans-serif

// Nope
$font-stack: 'Helvetica', 'Arial', sans-serif

// Nope
$font-stack: ('Helvetica', 'Arial', sans-serif,)

리스트에 새로운 아이템을 추가할 때는, 언제나 제공된 API를 이용하세요. 수동으로 새로운 아이템을 추가하려고 하지 마세요.

$shadows: (0 42px 13.37px hotpink);

// Yep
$shadows: append($shadows, $shadow, comma);

// Nope
$shadows: $shadows, $shadow;
$shadows: (0 42px 13.37px hotpink)

// Yep
$shadows: append($shadows, $shadow, comma)

// Nope
$shadows: $shadows, $shadow
참고

Sass 3.3부터, 스타일시트 작성자는 맵을 정의할 수 있는데, 이는 연관 배열, 해쉬 혹은 JavaScript 오브젝트에 해당하는 Sass 용어입니다. 맵은 키를 모든 유형의 값과 연결하는 자료 구조입니다 (키는 어떤 자료 유형도 될 수 있습니다. 추천하지는 않지만 맵도 포함됩니다).

맵은 다음과 같이 작성되어야 합니다:

  • 콜론(:) 다음에 스페이스;
  • 여는 괄호 (()는 콜론(:)과 같은 줄에;
  • (99%의 경우에 해당하는) 문자열인 키는 따옴표로 감싼다;
  • 각각의 키/값 쌍은 새 줄을 차지한다;
  • 각 키/값 뒤에는 쉼표(,);
  • 추가, 제거 혹은 순서를 바꾸기 쉽도록 마지막 아이템 뒤에 따라오는 쉼표(,);
  • 닫는 괄호())는 새 줄을 차지한다;
  • 닫는 괄호())와 세미콜론(;) 사이에는 스페이스나 새 줄을 넣지 않는다.

보기:

// Yep
$breakpoints: (
  'small': 767px,
  'medium': 992px,
  'large': 1200px,
);

// Nope
$breakpoints: ( small: 767px, medium: 992px, large: 1200px );
// Yep
$breakpoints: ('small': 767px, 'medium': 992px, 'large': 1200px,)

// Nope
$breakpoints: ( 'small': 767px, 'medium': 992px, 'large': 1200px )

// Nope
$breakpoints: (small: 767px, medium: 992px, large: 1200px,)

// Nope (since it is not supported)
$breakpoints: (
  'small': 767px,
  'medium': 992px,
  'large': 1200px,
)

Sass 맵 디버그

어떤 미친 마법이 Sass 맵에서 일어나고 있는 건지 알 수 없어 헤매는 스스로를 발견하게 된다면, 아직 구원받을 수 있는 방법이 있으니 걱정하지 마십시오.

@mixin debug-map($map) {
  @at-root {
    @debug-map {
      __toString__: inspect($map);
      __length__: length($map);
      __depth__: if(function-exists('map-depth'), map-depth($map), null);
      __keys__: map-keys($map);
      __properties__ {
        @each $key, $value in $map {
          #{'(' + type-of($value) + ') ' + $key}: inspect($value);
        }
      }
    }
  }
}
=debug-map($map)
  @at-root
    @debug-map
      __toString__: inspect($map)
      __length__: length($map)
      __depth__: if(function-exists('map-depth'), map-depth($map), null)
      __keys__: map-keys($map)
      __properties__
        @each $key, $value in $map
          #{'(' + type-of($value) + ') ' + $key}: inspect($value)

맵의 깊이를 알고 싶으시면 아래 함수를 추가하세요. 위의 믹스인이 자동으로 값을 표시할 것입니다.

/// 맵의 최대 깊이를 계산한다
/// @param {Map} $map
/// @return {Number} `$map`의 최대 깊이
@function map-depth($map) {
  $level: 1;

  @each $key, $value in $map {
    @if type-of($value) == 'map' {
      $level: max(map-depth($value) + 1, $level);
    }
  }

  @return $level;
}
/// 맵의 최대 깊이를 계산한다
/// @param {Map} $map
/// @return {Number} `$map`의 최대 깊이
@function map-depth($map)
  $level: 1

  @each $key, $value in $map
    @if type-of($value) == 'map'
      $level: max(map-depth($value) + 1, $level)

  @return $level;
참고

CSS 규칙

이 부분은 모두가 알고 있는 내용의 복습이 되겠지만, 여기 CSS 규칙의 작성 방법이 있습니다 (적어도, CSS Guidelines을 포함한 대부분의 가이드라인에 따르면 이렇습니다):

  • 관련된 선택자는 같은 줄에; 관련 없는 선택자는 새 줄에;
  • 여는 중괄호({)는 마지막 선택자와 스페이스 한 칸의 간격을 둔다;
  • 각각의 선언은 저마다 새 줄을 차지한다;
  • 콜론(:) 뒤에는 스페이스 한 칸을 둔다;
  • 모든 선언의 끝은 세미콜론(;)으로 마무리한다;
  • 닫는 중괄호(})는 새 줄을 차지한다;
  • 닫는 중괄호(}) 뒤에 새 줄.

보기:

// Yep
.foo, .foo-bar,
.baz {
  display: block;
  overflow: hidden;
  margin: 0 auto;
}

// Nope
.foo,
.foo-bar, .baz {
    display: block;
    overflow: hidden;
    margin: 0 auto }
// Yep
.foo, .foo-bar,
.baz
  display: block
  overflow: hidden
  margin: 0 auto

// Nope
.foo,
.foo-bar, .baz
    display: block
    overflow: hidden
    margin: 0 auto

CSS와 관련된 가이드라인에 더해, 우리는 다음 사항들에 관심을 기울여야 합니다:

  • 지역 변수는 어떤 선언보다 먼저 선언되어야 하며, 새 줄 하나로 다른 선언들과 간격을 둔다;
  • @content가 없는 믹스인 호출은 다른 선언보다 앞에 위치한다;
  • 내포된 선택자는 언제나 새 줄 뒤에 온다;
  • @content를 가진 믹스인 호출은 내포된 선택자보다 뒤에 위치한다;
  • 닫는 중괄호(}) 앞에는 새 줄이 없어야 한다.

보기:

.foo, .foo-bar,
.baz {
  $length: 42em;

  @include ellipsis;
  @include size($length);
  display: block;
  overflow: hidden;
  margin: 0 auto;

  &:hover {
    color: red;
  }

  @include respond-to('small') {
    overflow: visible;
  }
}
.foo, .foo-bar,
.baz
  $length: 42em

  +ellipsis
  +size($length)
  display: block
  overflow: hidden
  margin: 0 auto

  &:hover
    color: red

  +respond-to('small')
    overflow: visible
참고

선언 정렬

전 CSS 선언을 정렬하는 문제 만큼 견해가 갈리는 주제를 별로 떠올릴 수가 없습니다. 구체적으로, 여기 두 파가 있습니다:

  • 알파벳 순서 고수하기
  • 유형 별로 정렬하기(position, display, colors, font, 기타 등등…).

두 가지 방법 모두 장단점이 있습니다. 우선, 알파벳순은 (적어도 로마자를 사용하는 언어에서는) 보편적인 만큼 한 속성을 다른 속성 앞에 정렬하는 문제가 논쟁거리가 못됩니다. 하지만, bottomtop 같은 속성들이 서로 붙어있지 않은 모습이 제겐 엄청나게 이상해보입니다. 왜 애니메이션이 디스플레이 유형보다 먼저 나와야 합니까? 알파벳순에는 이상한 점이 많이 있습니다.

.foo {
  background: black;
  bottom: 0;
  color: white;
  font-weight: bold;
  font-size: 1.5em;
  height: 100px;
  overflow: hidden;
  position: absolute;
  right: 0;
  width: 100px;
}
.foo
  background: black
  bottom: 0
  color: white
  font-weight: bold
  font-size: 1.5em
  height: 100px
  overflow: hidden
  position: absolute
  right: 0
  width: 100px

반면, 유형별로 속성을 정렬하는 것은 아주 타당합니다. 모든 폰트 관련 선언들이 한데 모이고, topbottom은 재결합하며 규칙들을 보면 마치 짧은 이야기를 읽는 느낌입니다. 그러나 Idiomatic CSS와 같은 관례를 고수하지 않는 한 이 방식은 여러가지로 해석될 수 있습니다. white-space는 어디로 가야 할까요: 폰트 혹은 디스플레이? overflow는 정확히 어디에 속할까요? 그룹 내에서 속성들의 순서는 어떻게 되어야 할까요(역설적이게도, 알파벳순으로 정렬할 수도 있겠죠)?

.foo {
  height: 100px;
  width: 100px;
  overflow: hidden;
  position: absolute;
  bottom: 0;
  right: 0;
  background: black;
  color: white;
  font-weight: bold;
  font-size: 1.5em;
}
.foo
  height: 100px
  width: 100px
  overflow: hidden
  position: absolute
  bottom: 0
  right: 0
  background: black
  color: white
  font-weight: bold
  font-size: 1.5em

유형별 정렬의 다른 흥미로운 하위 갈래로 Concentric CSS라는 것이 있는데, 이것 역시 꽤 많이 사용되는 듯 합니다. 기본적으로, Concentric CSS는 순서를 정의하기 위해 박스 모델에 의존합니다: 바깥쪽에서 출발해서, 안쪽으로 들어오게 되죠.

.foo {
  width: 100px;
  height: 100px;
  position: absolute;
  right: 0;
  bottom: 0;
  background: black;
  overflow: hidden;
  color: white;
  font-weight: bold;
  font-size: 1.5em;
}
.foo
  width: 100px
  height: 100px
  position: absolute
  right: 0
  bottom: 0
  background: black
  overflow: hidden
  color: white
  font-weight: bold
  font-size: 1.5em

저 스스로도 결정할 수가 없다는 점을 말씀드려야겠습니다. CSS Tricks에서의 최근 설문조사에 따르면 45% 이상의 개발자들이 유형별로, 14%가 알파벳순으로 선언을 정렬하는 것으로 나타났습니다. 또한, 완전히 임의로 정렬하는 39%의 개발자들도 있습니다. 저를 포함해서요.

개발자들의 CSS 선언 정렬 방식을 보여주는 도표
개발자들의 CSS 선언 정렬 방식을 보여주는 도표

이 때문에, 이 스타일가이드에서는 선택을 강요하지 않겠습니다. 여러분이 스타일시트 내내 일관성을 유지하기만 한다면, 맘에 드는 걸로 고르시면 됩니다(즉, 랜덤이 아닌 한).

최근의 연구는 (유형별 정렬을 이용하는) CSS Comb를 사용한 CSS 선언 정렬이 Gzip 압축 시 평균 파일 크기를 2.7% 줄인다는 결과를 보여줍니다. 그에 비해, 알파벳순으로 정렬했을 때는 1.3%가 줄었습니다.

참고

선택자 내포Nesting

Sass가 제공하는 기능 중 많은 개발자들에 의해 심하게 남용되고 있는 것 하나는 선택자 내포입니다. 선택자 내포는 짧은 선택자들을 서로 포개어 넣음으로써 긴 선택자를 산출해 내는 방식을 제안합니다.

일반적인 규칙

예로, 아래의 Sass는:

.foo {
  .bar {
    &:hover {
      color: red;
    }
  }
}
.foo
  .bar
    &:hover
      color: red

… 이런 CSS를 만들어냅니다:

.foo .bar:hover {
  color: red;
}

같은 방식으로, Sass 3.3부터는 현재 선택자 참조(&)를 이용해 고급 선택자를 생성하는 것이 가능합니다. 예를 들면:

.foo {
  &-bar {
    color: red;
  }
}
.foo
  &-bar
    color: red

… 위의 코드는 이런 CSS를 생성합니다:

.foo-bar {
  color: red;
}
.foo-bar
  color: red

이 방법은 종종 BEM 작명 관례와 함께 .block__element.block__modifier 선택자를 원래 선택자(이 경우엔 .block)에 기반하여 생성하는 데 사용됩니다.

반드시 그런 건 아닐 수도 있지만, 현재 선택자 참조(&)로 새로운 선택자를 생성하면 그 선택자 자체가 코드베이스에 존재하지 않기 때문에 검색을 할 수 없게 됩니다.

선택자 내포의 문제는 결과적으로 코드를 읽기 어렵게 만든다는 것입니다. 읽기 위해서는 들여쓰기의 단계를 바탕으로 산출되는 선택자를 마음속으로 계산해야 합니다; CSS가 어떤 모습이 될지 항상 명확한 것은 아닙니다.

선택자가 길어지고 현재 선택자(&)를 더 자주 인용할수록 더더욱 그러합니다. 어느 순간이 되면, 선택자를 파악하고 무슨 일이 일어나고 있는지 더이상 이해하기가 힘들어질 위험이 너무 커지기 때문에 무릅쓸 만한 가치가 없습니다.

그런 상황을 방지하기 위해, 우리는 가능한 한 선택자 내포를 피해야 합니다. 하지만, 이 규칙에는 분명 몇 가지의 예외가 있습니다.

예외

우선, 원래의 선택자 안에 가상 클래스와 가상 요소를 내포하는 것은 허용되며 나아가 추천할 만합니다.

.foo {
  color: red;

  &:hover {
    color: green;
  }

  &::before {
    content: 'pseudo-element';
  }
}
.foo
  color: red

  &:hover
    color: green

  &::before
    content: 'pseudo-element'

가상 클래스와 가상 요소에 선택자 내포를 사용하는 것은 (밀접하게 관련된 선택자를 다루므로) 타당할 뿐만 아니라, 한 컴퍼넌트에 관한 모든 것을 같은 장소에 유지하는 데 도움이 됩니다.

또한, .is-active 같은 컴퍼넌트에 독립적인 상태 클래스를 사용할 때, 컴퍼넌트의 선택자 아래에 내포하여 깔끔하게 정리하는 것에는 아무 문제가 없습니다.

.foo {
  // …

  &.is-active {
    font-weight: bold;
  }
}
.foo
  // …

  &.is-active
    font-weight: bold

마지막으로 짚어야 할 것으로, 요소를 꾸밀 때, 그것이 우연히 다른 특정 요소 안에 들어가있다면 그 컴퍼넌트에 관한 모든 것을 한 곳에 유지하기 위해 내포를 사용하는 것은 문제가 없습니다.

.foo {
  // …

  .no-opacity & {
    display: none;
  }
}
.foo
  // …

  .no-opacity &
    display: none

경험이 적은 개발자와 함께 일한다면, .no-opacity & 같은 선택자는 조금 이상해보일 수 있습니다. 혼란을 방지하기 위해, 이 이상한 구문을 명확한 API로 바꿔놓는 아주 짧은 믹스인을 만들 수 있습니다.

/// 간단한 선택자 내포 API를 제공하는 헬퍼 믹스인
/// @param {String} $selector - 선택자
@mixin when-inside($selector) {
  #{$selector} & {
    @content;
  }
}
/// 간단한 선택자 내포 API를 제공하는 헬퍼 믹스인
/// @param {String} $selector - 선택자
=when-inside($selector) {
  #{$selector} &
    @content
}

앞의 예시를 다시 쓰면 이렇게 됩니다:

.foo {
  // …

  @include when-inside('.no-opacity') {
    display: none;
  }
}
.foo
  // …

  +when-inside('.no-opacity')
    display: none

모든 것이 그렇듯이, 세부사항은 크게 상관이 없으며, 일관성이 핵심입니다. 선택자 내포에 충분한 확신이 있다면 선택자 내포를 사용하세요. 단지 여러분의 팀 전체가 그 선택에 동의하는지 확실히 하시면 됩니다.

참고

작명 관례

이 절에서는, 유지와 확장을 위한 최고의 CSS 작명 관례를 다루진 않을 것입니다; 그것은 여러분에게 달린 문제일 뿐만 아니라, Sass 스타일가이드의 범위를 벗어나는 것이기도 합니다. 전 CSS Guidelines가 추천하는 방법을 참고하시길 권하겠습니다.

Sass에서 이름을 붙일 수 있는 것들이 몇 가지 있는데, 코드베이스 전체가 일관되며 읽기 쉽도록 이름을 잘 짓는 것이 중요합니다:

  • 변수;
  • 함수;
  • 믹스인.

Sass 플레이스홀더는 이 목록에서 일부러 제외했습니다. 플레이스홀더는 보통의 CSS 선택자로 간주될 수 있고, 따라서 클래스와 똑같은 작명 패턴을 따를 수 있기 때문입니다.

변수, 함수, 믹스인에 관해서, 우리는 매우 CSS스러운 것을 고수할 것입니다: 하이픈으로 구분된 소문자, 그리고 무엇보다도 의미있는 이름이어야 합니다.

$vertical-rhythm-baseline: 1.5rem;

@mixin size($width, $height: $width) {
  // …
}

@function opposite-direction($direction) {
  // …
}
$vertical-rhythm-baseline: 1.5rem

=size($width, $height: $width)
  // …

@function opposite-direction($direction)
  // 
참고

상수

만약 여러분이 프레임워크 개발자나 라이브러리 작가라면, 아마 어떤 상황에서도 갱신되지 않아야 하는 변수를 다루게 될 것입니다: 상수. 불행히도 (혹은 다행히도?), 사스는 그런 개체를 정의할 어떤 방법도 제공하지 않기 때문에, 우리는 목적을 달성하기 위해 엄격한 작명 관례를 고수해야만 합니다.

많은 언어들의 경우처럼, 상수에 대해 저는 모두 대문자로 된 스네이크 케이스 변수를 권합니다. 이것이 매우 오래된 관례일 뿐만 아니라, 보통의 하이픈으로 연결된 소문자 변수와 잘 대비되기 때문입니다.

// Yep
$CSS_POSITIONS: (top, right, bottom, left, center);

// Nope
$css-positions: (top, right, bottom, left, center);
// Yep
$CSS_POSITIONS: (top, right, bottom, left, center)

// Nope
$css-positions: (top, right, bottom, left, center)
참고

네임스페이스

라이브러리나 프레임워크, 그리드 시스템 혹은 무엇이 됐든 Sass 코드를 배포할 생각이라면, 다른 사람의 코드와 충돌하지 않도록 모든 변수, 함수, 믹스인, 플레이스홀더를 네임스페이스 안에 넣는 것이 좋을 것입니다.

예를 들면, 세계 전역의 개발자들에 의해 사용될 Sassy Unicorn 프로젝트를 작업하고 있다면 (누가 아니겠어요, 그렇죠?), su-를 네임스페이스로 붙이는 걸 고려할 수 있을 겁니다. 이 정도면 어떤 명명 충돌도 방지할 수 있을 정도로 충분히 구체적이고 쓰기 괴롭지 않을 정도로 충분히 짧습니다.

$su-configuration: (  );

@function su-rainbow($unicorn) {
  // …
}
$su-configuration: (  )

@function su-rainbow($unicorn)
  // 

자동 네임스페이스는 단연 사스 4.0에서 곧 있을 @import 개선의 설계 목표입니다. 그것이 결실을 맺기에 가까워지면서, 수동 네임스페이스는 점점 유용성이 떨어질 것입니다: 결국에는, 수동 네임스페이스를 이용한 라이브러리는 실제로 사용하기가 더 어려워질 수도 있습니다.

참고

주석

CSS는 까다로운 언어입니다. 핵과 이상함으로 가득차 있죠. 이 때문에, CSS는 주석이 잔뜩 달려야 합니다. 특히 여러분이나 다른 누군가가 지금으로부터 6개월 혹은 1년 뒤에 읽고 수정해야 한다면요.이건-내가-쓴-게-아니야-오-신이시여-왜죠 같은 말을 하게 만들지 마세요.

CSS는 간단해질 수도 있지만, 그래도 주석의 여지가 많이 있습니다. 이들이 설명해야 할 것들은:

  • 파일의 구조와 역할;
  • 규칙의 목적;
  • 매직 넘버의 배후에 있는 생각;
  • CSS 선언에 대한 이유;
  • CSS 선언의 정렬;
  • 진행 방식의 배후에 있는 사고 과정.

그리고 다른 많은 이유들은 잊어버린 것 같습니다. 코드와 함께 바로 이어서 주석을 다는 데는 아주 적은 시간이 소요됩니다. 나중에 코드의 한 부분으로 돌아와서 주석을 다는 것은 완전히 비현실적일 뿐더러 극도로 짜증나는 일입니다.

주석 쓰기

이상적으로, 어느 것이든 CSS 규칙은 CSS 블록의 요점을 설명하는 C 스타일 주석을 앞세워야 합니다. 이 주석은 규칙의 특정 부분에 대해 번호를 붙인 설명도 제공합니다. 예를 들면:

/**
 * 너무 길어서 한 줄에 안 들어가는 문자열을 자르고 말줄임표를 붙이는 헬퍼 클래스.
 * 1. 줄바꿈을 방지하고, 한 줄로 유지되도록 강제한다.
 * 2. 줄 끝에 말줄임표를 붙인다.
 */
.ellipsis {
  white-space: nowrap; /* 1 */
  text-overflow: ellipsis; /* 2 */
  overflow: hidden;
}
/**
 * 너무 길어서 한 줄에 안 들어가는 문자열을 자르고 말줄임표를 붙이는 헬퍼 클래스.
 * 1. 줄바꿈을 방지하고, 한 줄로 유지되도록 강제한다.
 * 2. 줄 끝에 말줄임표를 붙인다.
 */
.ellipsis
  white-space: nowrap /* 1 */
  text-overflow: ellipsis /* 2 */
  overflow: hidden

기본적으로 첫눈에 명확하지 않은 것에는 전부 주석이 달려야 합니다. 너무 과한 문서화 같은 것은 없습니다. 주석을 너무 많이 다는 것은 불가능하다는 것을 기억하세요. 그러니 불길로 뛰어들어 가치가 있는 것에는 모두 주석을 붙이세요.

Sass에만 한정된 부분에 주석을 달 때는, C 스타일 블록 대신 Sass 인라인 주석을 사용하세요. 이것은 주석이 산출물에서는 물론이고, 개발하는 동안의 확장 모드에서도 보이지 않게 해 줍니다.

// 현재 모듈을 불러온 모듈 리스트에 더한다.
// 전역 변수를 업데이트하도록 하기 위해 `!global` 플래그가 필요함.
$imported-modules: append($imported-modules, $module) !global;
// 현재 모듈을 불러온 모듈 리스트에 더한다.
// 전역 변수를 업데이트하도록 하기 위해 `!global` 플래그가 필요함.
$imported-modules: append($imported-modules, $module) !global
참고

문서화

코드베이스 전역에서 사용되도록 만들어진 모든 변수, 함수, 믹스인, 플레이스홀더는 SassDoc을 이용하여 전역 API의 일부로서 문서화되어야 합니다.

/// 코드 베이스 전역에서 사용되는 버티컬 리듬 베이스라인.
/// @type Length
$vertical-rhythm-baseline: 1.5rem;
/// 코드 베이스 전역에서 사용되는 버티컬 리듬 베이스라인.
/// @type Length
$vertical-rhythm-baseline: 1.5rem

슬래시(/) 세 개가 필수입니다.

SassDoc은 두 가지 중요한 역할을 합니다:

  • 공개 혹은 비공개 API의 일부인 모든 것에 대해 주석 기반의 시스템을 이용하는 표준화된 주석을 강제한다;
  • SassDoc의 엔드포인트(CLI tool, Grunt, Gulp, Broccoli, Node…)를 이용하여 API 문서의 HTML 버전을 생성할 수 있다.
SassDoc에 의해 생성된 문서
SassDoc에 의해 생성된 문서

SassDoc으로 문서화된 믹스인의 예시입니다:

/// `width`와 `height`를 동시에 정의하도록 돕는 믹스인.
///
/// @author Hugo Giraudel
///
/// @access public
///
/// @param {Length} $width - 요소의 `width`
/// @param {Length} $height [$width] - 요소의 `height`
///
/// @example scss - 사용법
///   .foo {
///     @include size(10em);
///   }
///
///   .bar {
///     @include size(100%, 10em);
///   }
///
/// @example css - CSS 아웃풋
///   .foo {
///     width: 10em;
///     height: 10em;
///   }
///
///   .bar {
///     width: 100%;
///     height: 10em;
///   }
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}
/// `width`와 `height`를 동시에 정의하도록 돕는 믹스인.
///
/// @author Hugo Giraudel
///
/// @access public
///
/// @param {Length} $width - 요소의 `width`
/// @param {Length} $height ($width) - 요소의 `height`
///
/// @example scss - 사용법
///   .foo
///     +size(10em)
///
///   .bar
///     +size(100%, 10em)
///
/// @example css - CSS 아웃풋
///   .foo {
///     width: 10em;
///     height: 10em;
///   }
///
///   .bar {
///     width: 100%;
///     height: 10em;
///   }
=size($width, $height: $width)
  width: $width
  height: $height
참고

설계

CSS 프로젝트를 설계하는 것은 아마도 프로젝트의 일생에서 여러분이 해야 할 가장 어려운 일 중 하나일 것입니다. 구조를 일관되고 의미있게 유지하는 것은 더더욱 어렵습니다.

다행히도, CSS 전처리기를 사용함으로써 얻는 주된 장점 중 하나는 (CSS 지시어 @import와 달리) 성능에 영향을 주지 않고 코드베이스를 여러 파일로 분리할 수 있게 된다는 것입니다. Sass가 @import 지시어의 무거운 짐을 짊어진 덕분에 개발 단계에서 필요한 만큼 많은 파일을 사용하는 것이 완벽하게 안전하며, 생산 단계에서 모든 파일들이 하나의 스타일시트로 컴파일됩니다.

무엇보다도, 폴더의 중요성에 대해서는 아무리 강조해도 지나치지 않습니다. 심지어 작은 규모의 프로젝트에서조차 말입니다. 집에서도 모든 서류를 같은 박스에 넣지는 않는 법입니다. 폴더를 사용하겠죠: 집/아파트용, 은행용, 청구서용, 기타 등등. CSS 프로젝트를 구축할 때도 다르게 할 이유가 없습니다. 나중에 코드로 돌아왔을 때 찾아내기 쉽도록 코드베이스를 의미있는 분리된 폴더로 나누세요.

CSS 프로젝트를 위한 잘 알려진 설계 양식들이 많이 있습니다: OOCSS, Atomic Design, Bootstrap류, Foundation류… 이들 모두 훌륭하며, 장단점을 갖고 있습니다.

저 스스로는 Jonathan SnookSMACSS와 아주 비슷한 접근법을 사용하는데, 이것은 간단명료함을 유지하는 데 초점을 맞추고 있습니다.

저는 설계가 대부분의 경우 프로젝트에 한정되어 있다는 사실을 배웠습니다. 여러분의 필요에 맞는 시스템을 사용할 수 있도록 제시된 해법을 마음대로 폐기하거나 조정하세요.

참고

컴퍼넌트

작동하게 만드는 것과 좋게 만드는 것 사이에는 커다란 차이가 있습니다. 다시 한번 말하자면, CSS는 아주 엉망인 언어입니다[citation needed]. 더 적은 CSS를 가질수록, 더 즐거워집니다. 우리는 수메가바이트의 CSS 코드를 다루는 것을 원하지 않습니다. 스타일시트를 짧고 효율적으로 유지하기 위해서는 — 전혀 놀랍지 않게도 — 인터페이스를 컴퍼넌트의 모음이라고 여기는 것이 대개 좋은 생각입니다.

다음 요건들을 충족한다면 컴퍼넌트가 될 수 있습니다:

  • 단 한 가지 일만 한다;
  • 재사용이 가능하고 프로젝트 전체에 걸쳐 재사용된다;
  • 독립적이다.

예를 들면, 검색 폼은 컴퍼넌트로 취급되어야 합니다. 그것은 다른 위치, 다른 페이지에서, 다양한 상황에서 재사용이 가능해야 합니다. DOM에서의 위치(footer, sidebar, main content…)에 의존해서는 안 됩니다.

대부분의 인터페이스는 작은 컴퍼넌트들로 생각될 수 있으며 이러한 인식을 고수할 것을 강력히 추천합니다. 이는 전체 프로젝트에 필요한 CSS의 양을 줄일 뿐만 아니라 모든 것이 혼란스러운 난장판보다 유지를 더 쉽게 만들기도 합니다.

7-1 패턴

다시 설계로 돌아가볼까요? 저는 보통 제가 7-1 패턴 이라고 부르는 것을 사용합니다: 폴더 7개, 파일 1개. 기본적으로, 7개의 다른 폴더에 채워넣은 모든 부분 파일과, 이들을 불러들여 CSS 스타일시트로 컴파일하는 루트 레벨에 있는 하나의 파일(대개 main.scss)을 갖게 됩니다.

  • base/
  • components/
  • layout/
  • pages/
  • themes/
  • utils/
  • vendors/

그리고 물론:

  • main.scss
배경화면 by Julien He
배경화면 by Julien He

이상적으로, 우리는 이런 구조를 만들어 낼 수 있습니다:

sass/
|
|– base/
|   |– _reset.scss       # Reset/normalize
|   |– _typography.scss  # 타이포그래피 규칙
|   …                    # 기타.
|
|– components/
|   |– _buttons.scss     # 버튼
|   |– _carousel.scss    # 캐러셀
|   |– _cover.scss       # 커버
|   |– _dropdown.scss    # 드롭다운
|   …                    # 기타.
|
|– layout/
|   |– _navigation.scss  # 네비게이션
|   |– _grid.scss        # 그리드 시스템
|   |– _header.scss      # 헤더
|   |– _footer.scss      # 푸터
|   |– _sidebar.scss     # 사이드바
|   |– _forms.scss       # 폼
|   …                    # 기타.
|
|– pages/
|   |– _home.scss        # 홈 한정 스타일
|   |– _contact.scss     # 연락처 한정 스타일
|   …                    # 기타.
|
|– themes/
|   |– _theme.scss       # 디폴트 테마
|   |– _admin.scss       # 관리자 테마
|   …                    # 기타.
|
|– utils/
|   |– _variables.scss   # Sass 변수
|   |– _functions.scss   # Sass 함수
|   |– _mixins.scss      # Sass 믹스인
|   |– _helpers.scss     # 클래스 & 플레이스홀더 헬퍼
|
|– vendors/
|   |– _bootstrap.scss   # Bootstrap
|   |– _jquery-ui.scss   # jQuery UI
|   …                    # 기타.
|
|
`– main.scss             # 메인 Sass 파일

파일은 위에서 설명한 작명 관례를 따라 하이픈으로 구분됩니다.

Base 폴더

base/ 폴더는 프로젝트의 보일러플레이트 코드라고 부를 만한 것을 담습니다. 거기에선, 리셋 파일, 타이포그래피 규칙, 그리고 아마도 자주 사용되는 HTML 요소에 대한 표준 스타일을 정의하는 스타일시트(전 _base.scss라고 부릅니다)를 찾을 수 있을 것입니다.

  • _base.scss
  • _reset.scss
  • _typography.scss

Layout 폴더

layout/ 폴더에는 사이트나 어플리케이션의 레이아웃에 기여하는 모든 것들이 들어갑니다. 이 폴더는 사이트의 주요 부분(header, footer, navigation, sidebar…), 그리드 시스템 혹은 모든 폼의 스타일을 위한 스타일시트를 가질 수도 있습니다.

  • _grid.scss
  • _header.scss
  • _footer.scss
  • _sidebar.scss
  • _forms.scss
  • _navigation.scss

layout/ 폴더는 partials/라고 불릴 수도 있습니다. 이는 여러분이 선호하는 바에 달렸습니다.

Components 폴더

components/ 폴더에는 더 작은 컴퍼넌트들이 들어갑니다. layout/이 (전반적인 뼈대를 정의하는) 거시적인 폴더임에 반해, components/는 위젯에 초점을 둡니다. 이 폴더는 슬라이더, 로더, 위젯, 그리고 기본적으로 이들과 비슷한 것을 비롯해 온갖 구체적인 모듈들을 담습니다. 전체 사이트/어플리케이션이 주로 작은 모듈들로 구성되어야 하므로 components에는 대개 많은 파일들이 있습니다.

  • _media.scss
  • _carousel.scss
  • _thumbnails.scss

components/ 폴더는 선호에 따라 modules/라고 불릴 수도 있습니다.

Pages 폴더

만약 페이지에 한정된 스타일이 있다면, 그것은 pages/ 폴더 속, 페이지 이름을 딴 파일에 넣는 것이 좋습니다. 예를 들면, 홈페이지에만 한정된 스타일이 있어 pages/ 폴더 속 _home.scss 파일이 필요해지는 것은 드문 일이 아닙니다.

  • _home.scss
  • _contact.scss

이 파일들은 배포 과정에 따라, 산출되는 스타일시트에서 다른 파일과 병합되는 것을 피하기 위해 별도로 호출될 수 있습니다. 이것은 여러분이 결정할 문제입니다.

Themes 폴더

큰 사이트와 어플리케이션에서 여러 다른 테마들을 갖는 것은 흔히 있는 일입니다. 물론 테마를 다루는 다른 방법들도 있지만, 개인적으로는 themes/ 폴더에 전부 모아두는 것을 좋아합니다.

  • _theme.scss
  • _admin.scss

이것은 프로젝트에 달려있는 것으로 많은 프로젝트에서는 존재할지 않을 가능성이 큽니다.

Utils 폴더

utils/ 폴더는 프로젝트에서 사용되는 모든 Sass 도구와 헬퍼를 모읍니다. 모든 전역 변수, 함수, 믹스인, 플레이스홀더는 여기로 들어가야 합니다.

이 폴더에 대한 경험적 법칙은 그 자체만으로는 컴파일되었을 때 한 줄의 CSS도 산출하지 않아야 한다는 것입니다. 이것은 그저 Sass 헬퍼일 뿐입니다.

  • _variables.scss
  • _mixins.scss
  • _functions.scss
  • _placeholders.scss (frequently named _helpers.scss)

utils/ 폴더는 선호에 따라 helpers/, sass-helpers/ 혹은 sass-utils/로 불릴 수도 있습니다.

Vendors 폴더

그리고 마지막으로 잊지 말아야 할 것으로, 대부분의 프로젝트는 Normalize, Bootstrap, jQueryUI, FancyCarouselSliderjQueryPowered 등의 외부 라이브러리와 프레임워크에서 나오는 모든 CSS 파일을 담고 있는 vendors/ 폴더를 가집니다. 이들을 같은 폴더에다 치워두는 것은 “저기요, 이건 내가 한 게 아닙니다. 내 코드가 아니고, 나는 책임이 없습니다”라고 말하는 좋은 방법입니다.

  • _normalize.scss
  • _bootstrap.scss
  • _jquery-ui.scss
  • _select2.scss

만약 어느 벤더의 한 부분을 덮어써야 한다면, 덮어쓰는 벤더의 이름을 그대로 딴 파일들을 담는 여덟 번째 폴더를 만드는 것을 추천합니다.

예를 들면, vendors-extensions/_boostrap.scss는 Bootstrap의 기본 CSS 일부를 재선언하는 모든 CSS 규칙을 담고 있는 파일입니다. 이는 벤더 파일 자체를 편집하는 것을 피하기 위함입니다. 그건 대개 좋은 생각이 아니니까요.

Main 파일

(주로 main.scss로 이름이 붙는) 메인 파일은 전체 코드베이스에서 언더스코어로 시작하지 않는 유일한 Sass 파일이어야 합니다. 이 파일은 @import와 주석 외에는 아무 것도 포함하지 않아야 합니다.

파일들은 각자 자리 잡은 폴더에 따라 아래의 순서대로 하나하나 불러들여집니다:

  1. vendors/
  2. utils/
  3. base/
  4. layout/
  5. components/
  6. pages/
  7. themes/

가독성을 유지하기 위해, 메인 파일은 이 가이드라인을 준수해야 합니다:

  • @import 당 파일 하나;
  • 한 줄에 하나의 @import;
  • 같은 폴더로부터의 두 import 사이는 새 줄로 띄우지 않는다;
  • 한 폴더로부터의 마지막 import 다음에는 새 줄 하나로 간격을 둔다.
  • 파일 확장자와 앞에 붙는 언더스코어는 생략한다.
@import 'abstracts/variables';
@import 'abstracts/functions';
@import 'abstracts/mixins';
@import 'abstracts/placeholders';

@import 'vendors/bootstrap';
@import 'vendors/jquery-ui';

@import 'base/reset';
@import 'base/typography';

@import 'layout/navigation';
@import 'layout/grid';
@import 'layout/header';
@import 'layout/footer';
@import 'layout/sidebar';
@import 'layout/forms';

@import 'components/buttons';
@import 'components/carousel';
@import 'components/cover';
@import 'components/dropdown';

@import 'pages/home';
@import 'pages/contact';

@import 'themes/theme';
@import 'themes/admin';
@import vendors/bootstrap
@import vendors/jquery-ui

@import utils/variables
@import utils/functions
@import utils/mixins
@import utils/placeholders

@import base/reset
@import base/typography

@import layout/navigation
@import layout/grid
@import layout/header
@import layout/footer
@import layout/sidebar
@import layout/forms

@import components/buttons
@import components/carousel
@import components/cover
@import components/dropdown

@import pages/home
@import pages/contact

@import themes/theme
@import themes/admin

부분 파일을 불러오는 다른 합당한 방법도 있습니다. 밝은 면을 보자면, 이 방법은 파일을 보다 읽기 좋게 만듭니다. 반면, 수정하는 일은 약간 괴로워집니다. 어쨌든, 어느 것이 최고인지는 여러분이 결정하게 하겠습니다. 이건 별 문제가 안 되니까요. 이 방법으로 하면, 메인 파일은 이 가이드라인을 준수해야 합니다:

  • 폴더 당 하나의 @import;
  • @import 뒤에 줄 바꿈;
  • 각 파일은 한 줄을 차지한다;
  • 한 폴더로부터의 마지막 import 다음에는 새 줄 하나로 간격을 둔다;
  • 파일 확장자와 앞에 붙는 언더스코어는 생략한다.
@import
  'abstracts/variables',
  'abstracts/functions',
  'abstracts/mixins',
  'abstracts/placeholders';

@import
  'vendors/bootstrap',
  'vendors/jquery-ui';

@import
  'base/reset',
  'base/typography';

@import
  'layout/navigation',
  'layout/grid',
  'layout/header',
  'layout/footer',
  'layout/sidebar',
  'layout/forms';

@import
  'components/buttons',
  'components/carousel',
  'components/cover',
  'components/dropdown';

@import
  'pages/home',
  'pages/contact';

@import
  'themes/theme',
  'themes/admin';
@import
  vendors/bootstrap,
  vendors/jquery-ui

@import
  utils/variables,
  utils/functions,
  utils/mixins,
  utils/placeholders

@import
  base/reset,
  base/typography

@import
  layout/navigation,
  layout/grid,
  layout/header,
  layout/footer,
  layout/sidebar,
  layout/forms

@import
  components/buttons,
  components/carousel,
  components/cover,
  components/dropdown

@import
  pages/home,
  pages/contact

@import
  themes/theme,
  themes/admin

각 파일을 수동으로 불러오지 않기 위해서는, @import "components/*"와 같이 Sass @import에서 glob 패턴을 사용할 수 있게 해주는 sass-globbing이라는 Ruby Sass의 확장이 있습니다.

그렇지만 이것은 대개 원치 않는 방식인 알파벳순에 따라 파일을 불러들이기 때문에 전 추천하지 않습니다. 특히 소스의 순서에 의존하는 언어를 다룰 때는 더욱 그렇습니다.

Shame 파일

Harry Roberts, Dave Rupert, Chris Coyier에 의해 알려진 흥미로운 개념이 있습니다. 이는 모든 CSS 선언과 핵, 그리고 우리가 자랑스럽게 여기지 않는 것들을 수치 파일에 넣는 것으로 이루어집니다. 이 파일은, 극적이게도 _shame.scss라고 불리며, 스타일시트의 맨 끝에서, 다른 모든 파일들 다음으로 불러들여질 것입니다.

/**
 * Nav 한정성 해결.
 *
 * 누군가 헤더 코드에 ID를 사용해서 (`#header a {}`) 네비게이션 선택자
 * (`.site-nav a {}`)가 무효화됨. 헤더 부분 리팩터링할 시간이 날 때까지
 * !important로 덮어쓸 것.
 */
.site-nav a {
    color: #BADA55 !important;
}
/**
 * Nav 한정성 해결.
 *
 * 누군가 헤더 코드에 ID를 사용해서 (`#header a {}`) 네비게이션 선택자
 * (`.site-nav a {}`)가 무효화됨. 헤더 부분 리팩터링할 시간이 날 때까지
 * !important로 덮어쓸 것.
 */
.site-nav a
    color: #BADA55 !important
참고

반응형 웹 디자인과 브레이크포인트

반응형 웹 디자인은 이제 어디에서나 볼 수 있는 만큼 따로 소개해야 한다고 생각하진 않습니다. Sass 스타일가이드에 왜 RWD 섹션이 있는 거야?라고 자문하실지도 모르겠습니다. 사실 브레이크포인트의 사용을 쉽게 만들기 위해 할 수 있는 일들이 꽤 있고, 그래서 여기에 포함시키는 게 그리 나쁜 아이디어는 아닐 거라 생각했습니다.

브레이크포인트 이름 짓기

미디어 쿼리가 특정 기기에 의존해서는 안된다고 말해도 과언은 아닐 거라 생각합니다. 예를 들면, 아이패드나 블랙베리 폰을 특정해서 겨냥하는 것은 분명 나쁜 생각입니다. 디자인이 깨지고 다음 미디어 쿼리가 넘겨받기 전까지, 미디어 쿼리는 다양한 스크린 크기를 처리해야 합니다.

같은 이유로, 브레이크포인트는 기기의 이름이 아니라 보다 보편적인 것을 따라서 이름 지어져야 합니다. 특히 몇몇 폰은 이제 태블릿보다 크고, 몇몇 태블릿은 일부 작은 스크린의 컴퓨터보다 크기 때문입니다.

// Yep
$breakpoints: (
  'medium': (min-width: 800px),
  'large': (min-width: 1000px),
  'huge': (min-width: 1200px),
);

// Nope
$breakpoints: (
  'tablet': (min-width: 800px),
  'computer': (min-width: 1000px),
  'tv': (min-width: 1200px),
);
// Yep
$breakpoints: ('medium': (min-width: 800px), 'large': (min-width: 1000px), 'huge': (min-width: 1200px))

// Nope
$breakpoints: ('tablet': (min-width: 800px), 'computer': (min-width: 1000px), 'tv': (min-width: 1200px))

이런 견지에서 볼 때, 디자인이 특정 기기에 직접적으로 관련되지 않았다는 것을 명확히 밝히는 어떤 작명 관례도 괜찮습니다. 크기에 대한 감을 전해줄 수 있기만 하면 됩니다.

$breakpoints: (
  'seed': (min-width: 800px),
  'sprout': (min-width: 1000px),
  'plant': (min-width: 1200px),
);
$breakpoints: ('seed': (min-width: 800px), 'sprout': (min-width: 1000px), 'plant': (min-width: 1200px))

앞의 예시들은 브레이크포인트를 정의하기 위해 맵을 중첩해서 사용하고 있지만, 이건 여러분이 어떤 브레이크포인트 매니저를 사용하는가에 전적으로 달렸습니다. 유연성을 위해 맵을 포개어넣는 대신 문자열을 선택할 수도 있을 겁니다. (예를 들면 '(min-width: 800px)').

참고

브레이크포인트 매니저

일단 원하는 방식으로 브레이크포인트의 이름을 짓고 나면, 실제 미디어쿼리에서 사용할 방법이 필요합니다. 많은 방법들이 있지만 전 getter 함수로 읽을 수 있는 브레이크포인트 맵의 열혈 팬이라는 것을 밝혀야겠습니다. 이 시스템은 간단하면서도 효과적입니다.

/// 반응형 매니저
/// @access public
/// @param {String} $breakpoint - 브레이크포인트
/// @requires $breakpoints
@mixin respond-to($breakpoint) {
  $raw-query: map-get($breakpoints, $breakpoint);

  @if $raw-query {
    $query: if(
      type-of($raw-query) == 'string',
      unquote($raw-query),
      inspect($raw-query)
    );

    @media #{$query} {
      @content;
    }
  } @else {
    @error 'No value found for `#{$breakpoint}`. '
         + 'Please make sure it is defined in `$breakpoints` map.';
  }
}
/// 반응형 매니저
/// @access public
/// @param {String} $breakpoint - 브레이크포인트
/// @requires $breakpoints
=respond-to($breakpoint)
  $raw-query: map-get($breakpoints, $breakpoint)

  @if $raw-query
    $query: if(type-of($raw-query) == 'string', unquote($raw-query), inspect($raw-query))

    @media #{$query}
      @content

  @else
    @error 'No value found for `#{$breakpoint}`. '
         + 'Please make sure it is defined in `$breakpoints` map.'

확실히, 이건 아주 단순한 브레이크포인트 매니저입니다. 좀 더 많은 기능을 제공하는 브레이크포인트 매니저가 필요하다면, 바퀴를 새로 발명할 것 없이 Sass-MQ, Breakpoint, include-media와 같은 효과가 검증된 것을 사용하기를 권합니다.

참고

미디어 쿼리 사용법

얼마 전, 미디어 쿼리를 어디에 작성해야 하는지에 대한 치열한 논쟁이 있었습니다: 선택자 안으로 들어가야 하는가(Sass에서는 가능하므로), 아니면 철저하게 분리해야 하는가? 저는 선택자-속-미디어-쿼리 시스템의 열렬한 옹호자라는 점을 밝혀야겠습니다. 그 쪽이 컴퍼넌트 아이디어와 잘 어울린다고 생각하거든요.

.foo {
  color: red;

  @include respond-to('medium') {
    color: blue;
  }
}
.foo
  color: red

  +respond-to('medium')
    color: blue

위의 코드는 다음의 CSS를 만들어냅니다:

.foo {
  color: red;
}

@media (min-width: 800px) {
  .foo {
    color: blue;
  }
}

이 방식은 미디어 쿼리의 중복을 야기한다는 말을 들으실지도 모릅니다. 그것은 분명한 사실입니다. 그러나, 실험에 따르면 Gzip(혹은 그에 상당하는 것) 이후에는 문제가 되지 않습니다.

… 우리는 미디어 쿼리를 한 곳에 몰아넣는 것 혹은 산발적으로 배치하는 것이 성능에 영향을 미치는지에 대한 논의를 이어갔고, 압축 시, 최악의 경우 미세한 차이가 존재하며, 최선의 경우 기본적으로 차이가 존재하지 않는다는 결론에 이르렀다.
Sam Richards, Breakpoint에 관해

정말로 미디어 쿼리의 중복이 신경쓰인다면, 이 gem과 같은 도구를 이용해 합칠 수 있습니다. 하지만 CSS 코드를 옮길 때 발생할 수 있는 부작용에 대해 경고해야 할 것 같습니다. 여러분은 소스의 순서가 중요하다는 사실을 잘 알고 있습니다.

참고

변수

변수는 모든 프로그래밍 언어의 정수라고 할 수 있습니다. 복사를 반복하지 않고도 값을 재사용할 수 있게 해주죠. 가장 중요한 점은 값 수정을 매우 쉽게 만들어준다는 것입니다. 더이상 찾아서 바꾸기나 하나하나 고칠 필요가 없는 것입니다.

그러나 CSS는 우리의 달걀을 전부 담고 있는 커다란 바구니일 뿐입니다. 많은 언어들과는 다르게, CSS에는 실질적인 유효 범위가 없습니다. 이 때문에, 충돌을 목격할 위험을 무릅쓰고 변수를 추가할 때는 아주 주의를 기울여야 합니다.

타당한 상황에서만 변수를 만들라는 것이 제 조언입니다. 적절한 이유 없이는 변수를 선언하지 마세요. 도움이 되지 않을 것입니다. 새로운 변수는 다음의 기준이 충족될 때에만 생성되어야 합니다.

  • 값이 적어도 두 번 반복된다;
  • 값이 적어도 한 번은 수정될 가능성이 크다;
  • 값의 실현은 모두 변수와 관련되어 있다(즉, 우연에 의한 것이 아니라).

기본적으로, 절대 수정될 일이 없거나 한 군데에서만 사용되는 변수를 선언하는 것에는 아무 의미가 없습니다.

스코프

Sass의 변수 스코프는 수년 동안 변화해왔습니다. 아주 최근까지, 규칙과 다른 스코프 내에서의 변수 선언은 기본적으로 지역적이었죠. 하지만 같은 이름의 전역 변수가 이미 존재하는 경우, 지역적 지정은 전역 변수의 값을 바꾸어버렸습니다. 버전 3.4 이후로, Sass는 이제 스코프 개념에 적절하게 대응하며 대신 새로운 지역 변수를 생성합니다.

문서를 보면 전역 변수 가림shadowing에 대한 부분이 있습니다. 전역 스코프에 이미 존재하는 변수를 내부 스코프(선택자, 함수, 믹스인…)에서 선언할 때, 지역 변수가 전역 변수를 가린다고 말합니다. 기본적으로, 지역 변수가 지역 스코프 내에서는 우선시됩니다.

다음의 코드 스니펫은 변수 가림 개념을 설명하고 있습니다.

// 루트 수준에 전역 변수를 초기화합니다.
$variable: 'initial value';

// 전역 변수를 덮어쓰게 하는 믹스인을 만듭니다.
@mixin global-variable-overriding {
  $variable: 'mixin value' !global;
}

.local-scope::before {
  // 전역 변수를 가리는 지역 변수를 만듭니다.
  $variable: 'local value';

  // 믹스인 인클루드: 전역 변수를 덮어씁니다.
  @include global-variable-overriding;

  // 변수의 값을 출력합니다.
  // 전역 변수를 가리기 때문에, **지역** 변수의 값이 출력됩니다.
  content: $variable;
}

// 변수 가림이 없는 다른 선택자에서 변수를 출력합니다.
// 예상대로, **전역** 변수의 값이 출력됩니다.
.other-local-scope::before {
  content: $variable;
}
// 루트 수준에 전역 변수를 초기화합니다.
$variable: 'initial value'

// 전역 변수를 덮어쓰게 하는 믹스인을 만듭니다.
@mixin global-variable-overriding
  $variable: 'mixin value' !global

.local-scope::before
  // 전역 변수를 가리는 지역 변수를 만듭니다.
  $variable: 'local value'

  // 믹스인 인클루드: 전역 변수를 덮어씁니다.
  +global-variable-overriding

  // 변수의 값을 출력합니다.
  // 전역 변수를 가리기 때문에, **지역** 변수의 값이 출력됩니다.
  content: $variable

// 변수 가림이 없는 다른 선택자에서 변수를 출력합니다.
// 예상대로, **전역** 변수의 값이 출력됩니다.
.other-local-scope::before
  content: $variable

!default 플래그

라이브러리나 프레임워크, 그리드 시스템, 혹은 배포되어 외부의 개발자들에 의해 사용될 Sass 소품을 개발할 때는, 덮어쓰일 수 있도록 모든 환경설정 변수들을 !default 플래그를 붙여 정의하여야 합니다.

$baseline: 1em !default;
$baseline: 1em !default

이 덕분에, 개발자는 여러분의 라이브러리를 import하기 전에 재정의될 걱정 없이 자신의 $baseline 변수를 정의할 수 있습니다.

// 개발자 자신의 변수
$baseline: 2em;

// `$baseline`를 선언하는 라이브러리
@import 'your-library';

// $baseline == 2em;
// 개발자 자신의 변수
$baseline: 2em

// `$baseline`를 선언하는 라이브러리
@import your-library

// $baseline == 2em

!global 플래그

!global 플래그는 지역 스코프로부터 전역 변수를 정의할 때에만 사용되어야 한다. 루트 레벨에서 변수를 정의할 때, !global 플래그는 생략되어야 한다.

// Yep
$baseline: 2em;

// Nope
$baseline: 2em !global;
// Yep
$baseline: 2em

// Nope
$baseline: 2em !global

여러 개의 변수 혹은 맵

여러 개의 다른 변수들 대신 맵을 사용하는 것의 이점이 있습니다. 가장 중요한 것은 맵을 반복해서 순환하는 기능인데, 별개의 변수들로는 이것이 불가능합니다.

맵 사용의 또다른 장점은 사용이 편리한 API를 제공하는 작은 getter 함수를 만드는 기능입니다. 다음의 Sass 코드를 예로 들 수 있습니다:

/// Z-index 맵. 어플리케이션의 Z 레이어들을 한데 모음.
/// @access private
/// @type Map
/// @prop {String} 키 - 레이어 이름
/// @prop {Number} 값 - 키에 연결된 Z 값
$z-indexes: (
  'modal': 5000,
  'dropdown': 4000,
  'default': 1,
  'below': -1,
);

/// 레이어 이름으로부터 z-index 값을 가져온다.
/// @access public
/// @param {String} $layer - 레이어 이름
/// @return {Number}
/// @require $z-indexes
@function z($layer) {
  @return map-get($z-indexes, $layer);
}
/// Z-index 맵. 어플리케이션의 Z 레이어들을 한데 모음.
/// @access private
/// @type Map
/// @prop {String} 키 - 레이어 이름
/// @prop {Number} 값 - 키에 연결된 Z 값
$z-indexes: ('modal': 5000, 'dropdown': 4000, 'default': 1, 'below': -1,)

/// 레이어 이름으로부터 z-index 값을 가져온다.
/// @access public
/// @param {String} $layer - 레이어 이름
/// @return {Number}
/// @require $z-indexes
@function z($layer)
  @return map-get($z-indexes, $layer)

Extend

@extend 지시어는 몇 년 전 Sass를 아주 유명하게 만든 기능 중 하나임에 틀림이 없습니다. 상기하자면, 이 기능은 마치 선택자 B에도 연결된 것처럼 요소 A를 꾸미라고 Sass에게 말하는 것을 가능하게 합니다. 말할 것도 없이 이는 모듈식 CSS를 작성할 때 가치 있는 협력자가 될 수 있습니다.

그러나 이 기능을 사용하지 말라고 여러분에게 경고해야 할 것 같습니다. 기발한 만큼, @extend는 득보다 실이 더 많을 수 있는 매우 까다로운 개념입니다. 제대로 사용되지 않았을 경우 특히 그렇죠. 문제는, 선택자를 확장할 때, 전체 코드베이스에 정통하지 않고서야 이 질문들에 대답할 방법이 없다는 겁니다:

  • 내 현재 선택자가 어디에 첨부될 것인가?
  • 원치 않는 부작용이 초래될 수도 있는가?
  • 이 한 번의 확장으로 얼마나 큰 CSS가 생성되는가?

이런 점을 고려하면, 그 결과는 무해할 수도 있고 처참한 부작용을 초래할 수도 있습니다. 이 때문에, 제 첫 번째 조언은 @extend 지시어를 완전히 피하라는 것입니다. 너무 인정사정없이 들릴 수 있지만, 가장 중요한 것은 덕분에 두통과 골칫거리를 덜 수 있다는 점입니다.

그렇다고는 하나, 이런 말이 있죠:

절대 안 된다고 절대 말하지 말라.
— 듣자 하니, 비욘세는 아닌 누군가.

선택자의 확장이 유익하고 가치 있을 수 있는 상황도 있습니다. 그렇지만, 곤란에 처하지 않도록 이 규칙들을 항상 염두에 두세요:

  • 다른 모듈들에 걸치지 않게, 한 모듈 안에서 확장을 사용하라.
  • 오로지 플레이스홀더에만 확장을 사용하고, 실제 선택자에는 사용하지 말라.
  • 확장하는 플레이스홀더가 가능한 한 적게 존재하도록 하라.

확장은 @media 블록과는 제대로 작동하지 않는다는 점도 상기시켜 드려야겠군요. 알고 계시듯이, Sass는 미디어 쿼리 안에서 외부의 선택자를 확장할 수 없습니다. 시도할 경우, 컴파일러는 할 수 없는 일이라는 것을 알리며 충돌을 일으킵니다. 좋지 않은 일이죠. 특히 미디어 쿼리가 우리가 아는 거의 전부이기 때문에 더 그렇습니다.

.foo {
  content: 'foo';
}

@media print {
  .bar {
    // 이 코드는 듣지 않을 뿐더러 충돌을 일으킵니다.
    @extend .foo;
  }
}
.foo
  content: 'foo'

@media print
  .bar
    // 이 코드는 듣지 않을 뿐더러 충돌을 일으킵니다.
    @extend .foo

@media 안에서 외부의 선택자를 @extend할 수 없다.
같은 지시어 안에 있는 선택자만 @extend할 수 있다.

@extend는 속성을 중복하지 않고 선택자를 합치기 때문에 파일 크기와 관련해서 도움이 된다고들 말합니다. 사실이긴 하지만, Gzip으로 압축하게 되면 그 차이는 무시할 만한 정도입니다.

말인즉슨, 만약 Gzip(혹은 그에 상당하는 다른 방법)을 이용할 수 없는 경우, 자신이 뭘 하고 있는지 이해하는 한 @extend 접근법을 선택하는 것도 그렇게 나쁘진 않을 수도 있습니다.

요약하자면, 어떤 특정한 상황 아래가 아니라면 @extend 지시어를 사용하지 말라고 조언하겠습니다. 그러나 그것을 완전히 금하라고까진 않겠습니다.

참고

Mixins

믹스인은 Sass 언어 전체에서 가장 많이 사용되는 기능 중 하나이며, 재사용성과 DRY 컴퍼넌트의 핵심입니다. 그리고 거기엔 그럴 만한 이유가 있습니다: 믹스인은 작성자가 .float-left 같은 시맨틱하지 않은 클래스에 기대지 않고도 스타일시트 내내 재사용할 수 있는 스타일을 정의할 수 있게 해 줍니다.

믹스인은 CSS 규칙과 Sass 문서에서 허용되는 거의 모든 것을 포함할 수 있습니다. 심지어 함수처럼 매개변수를 취할 수도 있습니다. 말할 것도 없이 가능한 일은 끝이 없습니다

하지만 믹스인의 힘을 남용하지 말라고 경고해야만 할 것 같습니다. 다시 한 번 말하지만, 핵심은 간결성입니다. 거대한 로직을 가진 엄청나게 강력한 믹스인을 만들고 싶어질 수 있습니다. 이는 과설계over-engineering라고 하며 대부분의 개발자들이 이것 때문에 괴로워합니다. 여러분의 코드에 대해 너무 복잡하게 생각하지 말고, 무엇보다도 간단히 하세요. 만약 믹스인이 20줄을 넘어서게 되었다면, 더 작은 덩어리로 나뉘거나 완전히 수정되어야 합니다.

기본

믹스인은 아주 유용하며 아마 여러분도 사용하고 있을 겁니다. 대략적으로 이야기하자면, (우연이 아닌) 어떤 이유로 항상 같이 모습을 보이는 CSS 속성들의 그룹을 발견하게 되면, 그것들을 믹스인에 넣을 수 있습니다. 예를 들면 Nicolas Gallagher의 마이크로 클리어픽스 핵은 (매개변수 없는) 믹스인 안에 들어갈 만합니다.

/// 내부 float을 해제하는 헬퍼
/// @author Nicolas Gallagher
/// @link http://nicolasgallagher.com/micro-clearfix-hack/ Micro Clearfix
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}
/// 내부 float을 해제하는 헬퍼
/// @author Nicolas Gallagher
/// @link http://nicolasgallagher.com/micro-clearfix-hack/ Micro Clearfix
@mixin clearfix
  &::after
    content: ''
    display: table
    clear: both

다른 타당한 예로는 요소의 크기를 조절하는 믹스인이 있으며, widthheight를 동시에 정의합니다. 이는 코드 입력을 간단하게 만들 뿐만 아니라 쉽게 읽을 수 있도록 해 줍니다.

/// 요소 크기를 설정하는 헬퍼
/// @author Hugo Giraudel
/// @param {Length} $width
/// @param {Length} $height
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}
/// 요소 크기를 설정하는 헬퍼
/// @author Hugo Giraudel
/// @param {Length} $width
/// @param {Length} $height
=size($width, $height: $width)
  width: $width
  height: $height
참고

매개변수 리스트

믹스인에 들어가는 매개변수의 개수를 알 수 없을 때는, 리스트 대신 항상 arglist를 사용하세요. arglist는 임의의 수의 매개변수를 믹스인이나 함수에 전달할 때 암묵적으로 사용되는 Sass의 여덟 번째 데이터 유형이라고 생각할 수 있으며, ...이 그 특징입니다.

@mixin shadows($shadows...) {
  // type-of($shadows) == 'arglist'
  // …
}
=shadows($shadows...)
  // type-of($shadows) == 'arglist'
  // 

몇 개의 매개변수(3개 혹은 그 이상)를 취하는 믹스인을 만들 때, 하나하나 넘겨주는 것보다 쉬울 거라는 생각으로 매개변수들을 리스트나 맵으로 병합하기 전에 다시 생각해 보세요.

Sass는 사실 믹스인과 함수 선언에 재주가 있어서, 리스트나 맵을 함수/믹스인에 매개변수 리스트로 전달해 일련의 매개변수들로 읽히도록 할 수 있습니다.

@mixin dummy($a, $b, $c) {
  // …
}

// Yep
@include dummy(true, 42, 'kittens');

// Yep but nope
$params: (true, 42, 'kittens');
$value: dummy(nth($params, 1), nth($params, 2), nth($params, 3));

// Yep
$params: (true, 42, 'kittens');
@include dummy($params...);

// Yep
$params: (
  'c': 'kittens',
  'a': true,
  'b': 42,
);
@include dummy($params...);
=dummy($a, $b, $c)
  // …

// Yep
+dummy(true, 42, 'kittens')

// Yep but nope
$params: (true, 42, 'kittens')
$value: dummy(nth($params, 1), nth($params, 2), nth($params, 3))

// Yep
$params: (true, 42, 'kittens')
+dummy($params...)

// Yep
$params: ('c': 'kittens', 'a': true, 'b': 42,)
+dummy($params...)
참고

믹스인과 벤더 프리픽스

지원이 미비하거나 부분적으로 지원되는 CSS 속성을 위한 벤더 프리픽스 처리용 믹스인을 정의하는 것은 솔깃한 일일 수 있습니다. 그러나 그건 좋은 생각이 아닙니다. 우선, Autoprefixer를 사용할 수 있다면 Autoprefixer를 사용하세요. 여러분의 프로젝트에서 Sass 코드를 없애 주고, 항상 최신 정보를 반영하며, 프리픽스를 처리하는 데에는 여러분보다 훨씬 나을 것입니다.

불행하게도, Autoprefixer를 선택할 수 없는 상황도 있을 수 있죠. Bourbon이나 Compass를 사용하신다면, 둘 모두 벤더 프리픽스를 처리하는 믹스인들을 제공한다는 것을 알고 계실 겁니다. 그것들을 사용하세요.

만약 Autoprefixer를 사용할 수 없고 Bourbon이나 Compass도 사용할 수 없다면, 오직 그런 경우에만, 여러분 스스로 CSS 속성에 프리픽스를 붙이는 믹스인을 만들어 사용할 수 있습니다. 하지만. 바라건대 속성마다 하나씩 믹스인을 만들어 각 벤더를 수동으로 출력하진 마세요.

// Nope
@mixin transform($value) {
  -webkit-transform: $value;
  -moz-transform: $value;
  transform: $value;
}
// Nope
=transform($value)
  -webkit-transform: $value
  -moz-transform: $value
  transform: $value

영리한 방식으로 하세요.

/// 벤더 프리픽스를 산출하는 믹스인 헬퍼
/// @access public
/// @author HugoGiraudel
/// @param {String} $property - 프리픽스가 붙지 않은 CSS 속성
/// @param {*} $value - 가공되지 않은 CSS 값
/// @param {List} $prefixes - 산출할 프리픽스 리스트
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    -#{$prefix}-#{$property}: $value;
  }

  #{$property}: $value;
}
/// 벤더 프리픽스를 산출하는 믹스인 헬퍼
/// @access public
/// @author HugoGiraudel
/// @param {String} $property - 프리픽스가 붙지 않은 CSS 속성
/// @param {*} $value - 가공되지 않은 CSS 값
/// @param {List} $prefixes - 산출할 프리픽스 리스트
=prefix($property, $value, $prefixes: ())
  @each $prefix in $prefixes
    -#{$prefix}-#{$property}: $value

  #{$property}: $value

이 믹스인을 사용하는 것은 아주 간단합니다:

.foo {
  @include prefix(transform, rotate(90deg), ('webkit', 'ms'));
}
.foo
  +prefix(transform, rotate(90deg), ('webkit', 'ms'))

이것은 조악한 해결책이라는 점을 명심하세요. 예를 들면, Flexbox에 필요한 것과 같은 복잡한 폴리필은 처리하지 못합니다. 그런 면에서, Autoprefixer를 사용하는 것이 훨씬 나은 선택입니다.

참고

조건문

Sass가 @if@else 지시어를 통해 조건문을 제공한다는 것을 아마 알고 계실 겁니다. 여러분의 코드에 꽤 복잡한 논리를 갖고 있는게 아니라면, 일상적인 스타일시트에선 조건문이 필요하지 않습니다. 사실, 조건문이 존재하는 가장 큰 이유는 라이브러리와 프레임워크입니다.

어쨌든, 조건문을 필요로 하게 된다면, 다음의 가이드라인을 준수하세요:

  • 필요한 경우가 아니라면 괄호 없이;
  • @if 앞에는 빈 새 줄 하나;
  • 여는 중괄호({) 뒤에는 줄 바꿈;
  • @else 문은 이전의 닫는 중괄호(})와 같은 줄에;
  • 다음 줄이 닫는 중괄호(})가 아닌 한 마지막 닫는 중괄호(}) 뒤에는 빈 새 줄 하나.
// Yep
@if $support-legacy {
  // …
} @else {
  // …
}

// Nope
@if ($support-legacy == true) {
  // …
}
@else {
  // …
}
// Yep
@if $support-legacy
  // …
@else
  // …

// Nope
@if ($support-legacy == true)
  // …
@else
  // 

거짓 값을 테스트할 때는, falsenull 대신 언제나 not 키워드를 사용하세요.

// Yep
@if not index($list, $item) {
  // …
}

// Nope
@if index($list, $item) == null {
  // …
}
// Yep
@if not index($list, $item)
  // …

// Nope
@if index($list, $item) == null
  // 

언제나 변수 부분을 조건문의 왼쪽에, 기대되는 (혹은 기대되지 않는) 결과를 오른쪽에 놓으세요. 거꾸로 된 조건문은, 특히 경험이 적은 개발자들이 읽기에 더 어렵습니다.

// Yep
@if $value == 42 {
  // …
}

// Nope
@if 42 == $value {
  // …
}
// Yep
@if $value == 42
  // …

// Nope
@if 42 == $value
  // 

어떤 조건에 따라 다른 결과를 반환하는 함수 안에서 조건문을 사용할 때는, 반드시 함수가 조건문 블록 밖에서도 @return문을 갖도록 만드세요.

// Yep
@function dummy($condition) {
  @if $condition {
    @return true;
  }

  @return false;
}

// Nope
@function dummy($condition) {
  @if $condition {
    @return true;
  } @else {
    @return false;
  }
}
// Yep
@function dummy($condition)
  @if $condition
    @return true

  @return false;

// Nope
@function dummy($condition)
  @if $condition
    @return true
  @else
    @return false

반복문

Sass는 리스트 같은 복잡한 데이터 구조를 제공하기 때문에, 작성자에게 그 개체들을 반복할 수 있는 방법 역시 준다는 사실은 놀랍지 않습니다.

하지만, 대체로 반복문의 존재는 아마도 Sass에 속하지 않는 어느 정도 복잡한 논리를 암시합니다. 반복문을 사용하기 전에, 합당한지 그리고 실제로 문제를 해결해 주는지 확인하세요.

Each

@each 반복문은 분명 Sass가 제공하는 세 가지 반복문들 중에서 가장 많이 사용됩니다. 이 반복문은 리스트나 맵을 순환할 수 있는 깔끔한 API를 제공합니다.

@each $theme in $themes {
  .section-#{$theme} {
    background-color: map-get($colors, $theme);
  }
}
@each $theme in $themes
  .section-#{$theme}
    background-color: map-get($colors, $theme)

맵에서 반복할 때, 일관성을 강제하기 위해 언제나 $key$value를 변수 이름으로 사용하세요.

@each $key, $value in $map {
  .section-#{$key} {
    background-color: $value;
  }
}
@each $key, $value in $map
  .section-#{$key}
    background-color: $value

또한 가독성을 위해 이 가이드라인을 준수하세요:

  • @each 앞에 빈 새 줄;
  • 다음 줄이 닫는 중괄호(})가 아닌 한 닫는 중괄호(}) 뒤에 빈 새 줄.

For

@for 반복문은 CSS의 :nth-* 가상 클래스와 결합되었을 때 유용할 수 있습니다. 이 시나리오를 제외하고는, 무언가에 반복문을 사용해야만 한다면 @each 반복문을 선택하세요.

@for $i from 1 through 10 {
  .foo:nth-of-type(#{$i}) {
    border-color: hsl($i * 36, 50%, 50%);
  }
}
@for $i from 1 through 10
  .foo:nth-of-type(#{$i})
    border-color: hsl($i * 36, 50%, 50%)

일반적인 관례를 따라 항상 $i를 변수 이름으로 사용하고, 정말로 좋은 이유가 있는 게 아니라면 to 키워드를 사용하지 마세요: 언제나 through를 사용하세요. 많은 개발자들이 Sass가 이 조합을 제공한다는 사실조차 모릅니다; 사용 시 혼란을 초래할 수 있습니다.

또한 가독성을 위해 이 가이드라인을 준수하세요:

  • @for 앞에 빈 새 줄;
  • 다음 줄이 닫는 중괄호(})가 아닌 한 닫는 중괄호(}) 뒤에 빈 새 줄.

While

@while 반복문은 실제 Sass 프로젝트에서 전혀 용도가 없습니다. 특히 내부에서 반복문을 중단시킬 방법이 없기 때문입니다. 사용하지 마세요.

경고와 오류

Sass 개발자들에 의해 간과되는 기능이 하나 있다면, 그것은 동적으로 경고와 오류를 출력하는 기능입니다. 사실, Sass에는 표준 출력 시스템(CLI, compiling app…)에 내용을 표시하는 세 가지의 지시어가 있습니다:

  • @debug;
  • @warn;
  • @error.

@debug는 분명히 SassScript 디버그를 위해 의도된 것이므로 제쳐두겠습니다. 그건 지금 우리에게 중요한 게 아니니까요. 그럼 이제 하나는 컴파일러를 멈춰세우는 반면 다른 하나는 그렇지 않다는 점만 빼고 똑 닮은 @warn@error가 남았습니다. 무엇이 무엇인지 한 번 맞춰보세요.

Sass 프로젝트에는 경고와 오류의 여지가 많이 있습니다. 기본적으로 특정 유형의 매개변수를 기대하는 믹스인이나 함수는 뭔가가 잘못되었을 때 오류를 던지거나, 혹은 추정 시에는 경고를 표시합니다.

참고

경고

Sass-MQpx 값을 em으로 변환하려 시도하는 이 함수를 예로 들겠습니다:

@function mq-px2em($px, $base-font-size: $mq-base-font-size) {
  @if unitless($px) {
    @warn 'Assuming #{$px} to be in pixels, attempting to convert it into pixels.';
    @return mq-px2em($px + 0px);
  } @else if unit($px) == em {
    @return $px;
  }

  @return ($px / $base-font-size) * 1em;
}
@function mq-px2em($px, $base-font-size: $mq-base-font-size)
  @if unitless($px)
    @warn 'Assuming #{$px} to be in pixels, attempting to convert it into pixels.'
    @return mq-px2em($px + 0px)
  @else if unit($px) == em
    @return $px

  @return ($px / $base-font-size) * 1em

만약 값에 단위가 없으면 이 함수는 그 값이 픽셀로 표현되어야 하는 것으로 추정합니다. 이 시점에서, 추정은 위험할 수 있으며 따라서 사용자는 소프트웨어가 예기치 않은 것으로 간주될 수 있는 행동을 했다는 경고를 받아야 합니다.

오류

오류는 경고와 달리 컴파일러의 진행을 막습니다. 기본적으로 오류는 컴파일을 멈추고 스택 트레이스와 출력 스트림에 메시지를 표시하는데, 이는 디버깅에 유용합니다. 이 때문에, 오류는 프로그램이 계속 실행될 방법이 없을 때 던져져야 합니다. 가능하면 이 문제를 피해 가려 노력하고 대신 경고를 표시하세요.

가령 특정 맵의 값에 접근하는 getter 함수를 만든다고 합시다. 만약 요청된 키가 맵에 존재하지 않으면 오류를 던질 수 있습니다.

/// Z-index 맵. 어플리케이션의 Z 레이어들을 한데 모음.
/// @access private
/// @type Map
/// @prop {String} 키 - 레이어 이름
/// @prop {Number} 값 - 키에 연결된 Z 값
$z-indexes: (
  'modal': 5000,
  'dropdown': 4000,
  'default': 1,
  'below': -1,
);

/// 레이어 이름으로부터 z-index 값을 가져온다.
/// @access public
/// @param {String} $layer - 레이어 이름
/// @return {Number}
/// @require $z-indexes
@function z($layer) {
  @if not map-has-key($z-indexes, $layer) {
    @error 'There is no layer named `#{$layer}` in $z-indexes. '
         + 'Layer should be one of #{map-keys($z-indexes)}.';
  }

  @return map-get($z-indexes, $layer);
}
/// Z-index 맵. 어플리케이션의 Z 레이어들을 한데 모음.
/// @access private
/// @type Map
/// @prop {String} 키 - 레이어 이름
/// @prop {Number} 값 - 키에 연결된 Z 값
$z-indexes: ('modal': 5000, 'dropdown': 4000, 'default': 1, 'below': -1,)

/// 레이어 이름으로부터 z-index 값을 가져온다.
/// @access public
/// @param {String} $layer - 레이어 이름
/// @return {Number}
/// @require $z-indexes
@function z($layer)
  @if not map-has-key($z-indexes, $layer)
    @error 'There is no layer named `#{$layer}` in $z-indexes. '
         + 'Layer should be one of #{map-keys($z-indexes)}.'

  @return map-get($z-indexes, $layer)

도구

Sass처럼 인기 있는 CSS 전처리기의 좋은 점은 프레임워크, 플러그인, 라이브러리, 도구로 이루어진 완전한 생태계와 동반한다는 것입니다. 시작으로부터 8년이 지난 지금, 우리는 Sass로 쓰일 수 있는 모든 것은 Sass로 쓰인 지점에 점점 더 가까워지고 있습니다.

하지만 제가 드리는 조언은 의존성의 수를 최소한으로 줄이라는 것입니다. 의존성을 관리하는 것은 여러분이 피해야 할 일종의 지옥입니다. 게다가, Sass의 경우 외부 의존성이 거의 혹은 전혀 필요하지 않습니다.

Compass

Compass는 주요 Sass 프레임워크입니다. Sass 의 핵심 디자이너 둘 중 한 명인 Chris Eppstein에 의해 개발되었죠. 제 생각으로는 한동안은 그 인기가 크게 떨어질 것 같진 않습니다.

그렇지만, 전 더이상 Compass를 추천하지 않습니다. 가장 큰 이유로는 Sass를 매우 느리게 만들기 때문입니다. Ruby Sass는 그 자체로도 꽤 느리기 때문에 그 위에 Ruby와 Sass를 더해서 좋을 게 없습니다.

문제는 우리가 전체 프레임워크에서 아주 적은 일부만을 사용한다는 점입니다. Compass는 거대합니다. 크로스 브라우저 호환 믹스인은 빙산의 일각에 불과하죠. 수학 함수, 이미지 헬퍼, 스프라이트… 이 훌륭한 소프트웨어를 가지고 할 수 있는 일이 너무나도 많습니다.

불행하게도, 이들은 모두 구문상 편의성syntactic sugar이며 킬러 기능이 존재하지 않습니다. 정말로 훌륭한 스프라이트 빌더는 예외가 될 수 있겠지만, GrunticonGrumpicon도 같은 일을 잘 수행하며 빌드 프로세스에 삽입할 수 있다는 장점도 갖고 있습니다.

어쨌든, Compass의 사용을 금하진 않겠지만 추천하지도 않겠습니다. 특히 (그에 대한 노력이 있긴 하지만) LibSass와 호환이 되지 않기 때문입니다. 만약 사용하는 게 낫다 생각되면 사용하셔도 괜찮습니다. 하지만 결국에 거기서 많은 걸 얻지는 못할 것이라 생각합니다.

Ruby Sass는 현재 많은 함수와 믹스인을 가진 복잡한 로직의 스타일을 구체적으로 겨냥한 중요한 최적화 작업 중에 있습니다. 이는 Compass와 다른 프레임워크가 더이상 Sass의 속도를 늦추지 않을 수 있을 정도로 성능을 극적으로 향상시킬 것입니다.

참고

그리드 시스템

어디에든 반응형 웹 디자인이 있는 만큼 그리드 시스템을 사용하는 것은 이제 필수가 되었습니다. 일관성 있고 탄탄한 디자인을 만들기 위해 우리는 요소를 배치하는 일종의 그리드를 사용합니다. 이 그리드를 반복해서 코딩하는 것을 피하기 위해, 몇몇 영리한 사람들은 그들의 그리드를 재사용이 가능하도록 만들었습니다.

솔직하게 말씀드릴게요: 전 그리드 시스템의 열렬한 팬은 아닙니다. 물론 그 가능성은 알고 있습니다만, 대부분은 너무 지나치고 주로 너드 디자이너들의 연단에서 흰 배경 위에 빨간 칼럼을 그리는 데 사용됩니다. 마지막으로 정말-다행이야-2-5-3.1-π-그리드를-제작하는-이-툴이-있어서 라고 생각한 게 언제입니까? 맞습니다, 한 번도 생각한 적 없죠. 대부분의 경우, 복잡한 것이 아니라 평범하고 규칙적인 12 칼럼 그리드를 원할 뿐이니까요.

프로젝트에 Bootstrap이나 Foundation 같은 CSS 프레임워크를 사용한다면 이미 그리드 시스템을 포함하고 있을 확률이 높습니다. 이 경우에는 또다른 의존성을 피하기 위해 그것을 사용할 것을 추천합니다.

만약 여러분이 특정 그리드 시스템에 묶여 있지 않다면, 두 개의 아주 뛰어난 Sass 그리드 엔진이 있다는 사실에 기뻐하실 겁니다: Susy, Singularity. 둘 모두 여러분에게 필요한 것 이상을 제공하니 이 둘 중 맘에 드는 것을 고르시고 에지 케이스에도 잘 대응하는지 확인하세요. 제게 묻는다면, Susy가 좀 더 좋은 커뮤니티를 갖고 있습니다. 하지만 제 의견일 뿐입니다.

혹은 csswizardry-grids 같이 좀 더 가벼운 걸로 갈 수도 있습니다. 대부분의 경우, 뭘 선택하든 여러분의 코딩 스타일에 큰 영향을 미치진 않을 것이므로, 선택은 여러분에게 달렸습니다.

참고

SCSS-lint

코드 린트는 매우 중요합니다. 대개 스타일가이드의 가이드라인을 따르는 것이 코드 품질 실수를 줄이도록 도와주지만 누구도 완벽할 순 없고 개선의 여지는 항상 존재합니다. 코드 린트는 주석 만큼 중요하다고 할 수 있습니다.

SCSS-lint는 여러분의 SCSS 파일을 깔끔하고 읽기 좋게 유지하도록 돕는 도구입니다. 필요에 꼭 맞게 설정할 수 있고 여러분의 도구들과 쉽게 통합할 수 있습니다.

다행히도, SCSS-lint 권고는 이 문서에서 설명한 것과 유사합니다. Sass Guidelines에 따라 SCSS-lint를 구성하기 위해, 다음의 설정을 추천합니다:

linters:

  BangFormat:
    enabled: true
    space_before_bang: true
    space_after_bang: false

  BemDepth:
    enabled: true
    max_elements: 1

  BorderZero:
    enabled: true
    convention: zero

  ChainedClasses:
    enabled: false

  ColorKeyword:
    enabled: true

  ColorVariable:
    enabled: false

  Comment:
    enabled: false

  DebugStatement:
    enabled: true

  DeclarationOrder:
    enabled: true

  DisableLinterReason:
    enabled: true

  DuplicateProperty:
    enabled: false

  ElsePlacement:
    enabled: true
    style: same_line

  EmptyLineBetweenBlocks:
    enabled: true
    ignore_single_line_blocks: true

  EmptyRule:
    enabled: true

  ExtendDirective:
    enabled: false

  FinalNewline:
    enabled: true
    present: true

  HexLength:
    enabled: true
    style: short

  HexNotation:
    enabled: true
    style: lowercase

  HexValidation:
    enabled: true

  IdSelector:
    enabled: true

  ImportantRule:
    enabled: false

  ImportPath:
    enabled: true
    leading_underscore: false
    filename_extension: false

  Indentation:
    enabled: true
    allow_non_nested_indentation: true
    character: space
    width: 2

  LeadingZero:
    enabled: true
    style: include_zero

  MergeableSelector:
    enabled: false
    force_nesting: false

  NameFormat:
    enabled: true
    convention: hyphenated_lowercase
    allow_leading_underscore: true

  NestingDepth:
    enabled: true
    max_depth: 1

  PlaceholderInExtend:
    enabled: true

  PrivateNamingConvention:
    enabled: true
    prefix: _

  PropertyCount:
    enabled: false

  PropertySortOrder:
    enabled: false

  PropertySpelling:
    enabled: true
    extra_properties: []

  PropertyUnits:
    enabled: false

  PseudoElement:
    enabled: true

  QualifyingElement:
    enabled: true
    allow_element_with_attribute: false
    allow_element_with_class: false
    allow_element_with_id: false

  SelectorDepth:
    enabled: true
    max_depth: 3

  SelectorFormat:
    enabled: true
    convention: hyphenated_lowercase
    class_convention: '^(?:u|is|has)\-[a-z][a-zA-Z0-9]*$|^(?!u|is|has)[a-zA-Z][a-zA-Z0-9]*(?:\-[a-z][a-zA-Z0-9]*)?(?:\-\-[a-z][a-zA-Z0-9]*)?$'

  Shorthand:
    enabled: true

  SingleLinePerProperty:
    enabled: true
    allow_single_line_rule_sets: false

  SingleLinePerSelector:
    enabled: true

  SpaceAfterComma:
    enabled: true

  SpaceAfterPropertyColon:
    enabled: true
    style: one_space

  SpaceAfterPropertyName:
    enabled: true

  SpaceAfterVariableColon:
    enabled: true
    style: at_least_one_space

  SpaceAfterVariableName:
    enabled: true

  SpaceAroundOperator:
    enabled: true
    style: one_space

  SpaceBeforeBrace:
    enabled: true
    style: space
    allow_single_line_padding: true

  SpaceBetweenParens:
    enabled: true
    spaces: 0

  StringQuotes:
    enabled: true
    style: single_quotes

  TrailingSemicolon:
    enabled: true

  TrailingZero:
    enabled: true

  TransitionAll:
    enabled: false

  UnnecessaryMantissa:
    enabled: true

  UnnecessaryParentReference:
    enabled: true

  UrlFormat:
    enabled: false

  UrlQuotes:
    enabled: true

  VariableForProperty:
    enabled: false

  VendorPrefixes:
    enabled: true
    identifier_list: base
    include: []
    exclude: []

  ZeroUnit:
    enabled: true

SCSS lint를 Grunt 빌드 프로세스에 추가하고 싶으시다면, 기쁘게도 grunt-scss-lint라고 하는 Grunt 플러그인이 있습니다.

또한, SCSS-lint와 함께 작동하는 깔끔한 어플리케이션을 찾고 계신다면, Thoughtbot(Bourbon, Neat…)이 Hound에 공을 들이고 있습니다.

참고

너무 길어서 안 읽은 분들을 위해

요약하자면, 우리가 원하는 것은 다음과 같습니다:

  • 탭 대신 스페이스 두(2) 칸을 들여쓴다;
  • 행의 너비는 80 글자;
  • CSS를 여러 행으로 적절히 작성한다;
  • 공백을 의미 있게 사용한다;
  • 문자열과 URL에는 인용 부호(작은 따옴표)를 붙인다;
  • 뒤따르는 0은 표기하지 않는다. 앞장서는 0은 반드시 표기한다;
  • 연산은 괄호로 감싼다;
  • 매직 넘버를 피한다;
  • 색은 키워드 > HSL > RGB > 16진법 순으로 표기한다;
  • 리스트는 쉼표로 구분한다;
  • 리스트에는 뒤따르는 쉼표를 붙이지 않는다(한 줄로 표기하므로);
  • 맵에는 뒤따르는 쉼표를 붙인다;
  • 가상 클래스와 가상 요소 외에는 선택자를 내포하지 않는다;
  • 작명 시 하이픈으로 구분한다;
  • 많은 주석을 붙인다;
  • SassDoc을 이용하여 API 주석을 표기한다;
  • @extend는 제한적으로 사용한다;
  • 간단한 믹스인을 사용한다;
  • 반복문은 최소한으로 사용하고, @while은 사용하지 않는다;
  • 의존성의 수를 줄인다;
  • 경고와 오류를 의미 있게 사용한다.
맨 위로