InfoGrab DocsInfoGrab Docs

SCSS 스타일 가이드

요약

사이트가 성장함에 따라 더 많은 CSS가 생성되는 것을 줄이기 위해, 새 CSS를 추가하는 것보다 유틸리티 클래스 사용을 권장합니다. 유틸리티 클래스는 Tailwind CSS에 의해 생성됩니다. GitLab Tailwind CSS 문서: GitLab Tailwind 구성에 특화된 문서 사이트입니다.

유틸리티 클래스#

사이트가 성장함에 따라 더 많은 CSS가 생성되는 것을 줄이기 위해, 새 CSS를 추가하는 것보다 유틸리티 클래스 사용을 권장합니다. 복잡한 경우에는 컴포넌트 클래스를 추가하여 CSS를 처리할 수 있습니다.

CSS 유틸리티 클래스는 어디에 정의되어 있나요?#

유틸리티 클래스는 Tailwind CSS에 의해 생성됩니다. Tailwind CSS 클래스를 확인하는 방법은 세 가지가 있습니다:

  • GitLab Tailwind CSS 문서: GitLab Tailwind 구성에 특화된 문서 사이트입니다. 사용 가능한 모든 Tailwind CSS 클래스를 검색할 수 있는 목록을 제공합니다.

  • Tailwind CSS 자동완성: VS Code 또는 RubyMine에서 사용할 수 있습니다.

  • Tailwind CSS config viewer: 디자인 시스템에 특화된 Tailwind CSS 클래스(간격, 색상, 크기 등)를 시각적으로 보여줍니다. 사용 가능한 모든 Tailwind CSS 클래스를 표시하지는 않습니다.

어떤 CSS 유틸리티 클래스가 deprecated되었나요?#

common.scss의 클래스는 deprecated 예정입니다. 디자인 시스템에 맞지 않는 값을 사용하는 common.scss의 클래스는 사용하지 않아야 합니다. 대신 규격에 맞는 값을 가진 클래스를 사용하세요.

Bootstrap의 유틸리티 클래스는 사용하지 마세요.

Bootstrap의 유틸리티 클래스GitLab UI 유틸리티 클래스로 마이그레이션할 때, margin과 padding 클래스가 서로 다름에 주의하세요. GitLab에서 사용하는 크기 척도는 Bootstrap 라이브러리의 척도와 다릅니다. Bootstrap의 padding 또는 margin 유틸리티를 사용하는 경우, 동일한 시각적 결과를 얻으려면 적용한 유틸리티 크기를 두 배로 늘려야 할 수 있습니다(예: ml-1gl-ml-2로 변경됨).

Tailwind CSS#

2024년 8월부터 CSS 유틸리티 제공자로 Tailwind CSS를 사용하고 있습니다. 이는 이전에 사용하던 커스텀 솔루션을 대체합니다. 도입 동기, 제안, 구현 세부 사항은 Tailwind CSS 설계 문서를 참고하세요.

Tailwind CSS 기본 사항#

아래에서 Tailwind CSS의 기본 사항과 Pajamas 디자인 시스템을 사용하도록 구성된 방식에 대한 정보를 확인할 수 있습니다. 더 자세한 가이드는 공식 Tailwind CSS 문서를 참고하세요.

접두사#

모든 유틸리티 클래스에 gl- 접두사가 붙도록 Tailwind CSS에 prefix를 구성했습니다. 반응형 유틸리티 또는 상태 수정자를 사용할 때는 접두사가 콜론 뒤에 위치합니다.

예시: gl-mt-5, lg:gl-mt-5.

반응형 CSS 유틸리티 클래스#

반응형 CSS 유틸리티 클래스는 중단점 이름 뒤에 : 문자가 붙는 형식으로 접두사가 붙습니다. 사용 가능한 중단점은 tailwind.defaults.js#L44에 구성되어 있습니다.

예시: lg:gl-mt-5

hover, focus 및 기타 상태 수정자#

상태 수정자를 사용하면 어떤 Tailwind CSS 클래스든 조건부로 적용할 수 있습니다. CSS 유틸리티 클래스 앞에 수정자 이름을 붙이고 : 문자로 구분합니다.

예시: hover:gl-underline

!important 수정자#

CSS 유틸리티 클래스 앞에 !를 추가하여 important 수정자를 사용할 수 있습니다. 반응형 유틸리티 클래스나 상태 수정자와 함께 사용할 때는 !: 문자 뒤에 위치합니다.

예시: !gl-mt-5, lg:!gl-mt-5, hover:!gl-underline

간격 및 크기 CSS 유틸리티 클래스#

간격 및 크기 CSS 유틸리티 클래스(예: margin, padding, width, height)는 src/tokens/build/tailwind/tokens.cjs에 정의된 간격 척도를 사용합니다. 사용 가능한 CSS 유틸리티 클래스는 https://design.gitlab.com/tailwind-documentation/margin에서 확인하세요.

예시: gl-mt-5margin-top: 1rem;입니다.

색상 CSS 유틸리티 클래스#

색상 CSS 유틸리티 클래스(예: color, background-color)는 src/tokens/build/tailwind/tokens.cjs에 정의된 색상을 사용합니다. 사용 가능한 CSS 유틸리티 클래스는 https://design.gitlab.com/tailwind-documentation/text-color에서 확인하세요.

예시: gl-text-subtlecolor: var(--gl-text-color-subtle, #626168);입니다.

Tailwind CSS 번들 빌드#

GitLab Development Kit에서 Vite 또는 Webpack을 사용할 때, Tailwind CSS는 파일 변경 사항을 감지하여 감지된 유틸리티를 즉시 빌드합니다.

새로운 Tailwind CSS 번들을 빌드하려면 yarn tailwindcss:build를 실행하세요. 이 스크립트는 bundle exec rake gitlab:assets:compile로 프로덕션 에셋을 빌드할 때 내부적으로 호출됩니다.

번들 빌드 방식에 관계없이 출력은 app/assets/builds/tailwind.css에 저장됩니다.

Tailwind CSS 자동완성#

Tailwind CSS 자동완성은 코드 편집기에서 사용 가능한 모든 클래스를 나열합니다.

VS Code#

자동완성이 느린 경우 TS 서버가 사용할 수 있는 메모리 양을 늘려야 할 수도 있습니다.

Tailwind CSS IntelliSense 확장을 설치하세요. HAML과 커스텀 *-class prop을 지원하려면 다음 설정을 권장합니다:

{
  "tailwindCSS.experimental.classRegex": [
    ["class: [\"|']+([^\"|']*)[\"|']+", "([a-zA-Z0-9\\-:!/]+)"],
    ["(\\.[\\w\\-.]+)[\\n\\=\\{\\s]", "([\\w\\-]+)"],
    ["[a-z]+-class(?:es)?=\"([^'\"]*)\""]
  ],
  "tailwindCSS.emmetCompletions": true
}
RubyMine#

Tailwind CSS 자동완성은 기본으로 활성화되어 있습니다. HAML과 커스텀 *-class prop을 완전히 지원하려면 기본 설정에서 다음과 같이 업데이트하는 것을 권장합니다:

{
  "includeLanguages": {
    "haml": "html"
  },
  "emmetCompletions": true,
  "experimental": {
    "classRegex": [
      ["class: [\"|']+([^\"|']*)[\"|']+", "([a-zA-Z0-9\\-:!/]+)"],
      ["(\\.[\\w\\-.]+)[\\n\\=\\{\\s]", "([\\w\\-]+)"],
      ["[a-z]+-class(?:es)?=\"([^'\"]*)\""]
    ]
  }
}

Tailwind CSS 임의 값#

임의 값은 다음과 같은 이유로 사용하지 않는 것이 좋습니다:

  • 임의 값은 하나의 페이지에서만 필요하지만 전역 CSS 번들에 추가되어 번들 크기가 증가합니다. CSS가 필요한 곳에서만 포함되도록 페이지별 CSS를 사용해야 합니다.

  • 미리 정의된 CSS 클래스가 이미 존재할 가능성이 높습니다. GitLab Tailwind CSS 문서에서 사용 가능한 Tailwind CSS 클래스를 확인하세요.

  • 임의 값은 디자인 시스템을 강제하지 않습니다.

새 유틸리티 클래스는 어디에 추가해야 하나요?#

유틸리티 클래스는 대부분의 CSS 기능을 지원하는 Tailwind CSS에 의해 생성됩니다. 사용할 수 없는 기능이 있다면 GitLab UI의 tailwind.defaults.js를 업데이트해야 합니다.

컴포넌트 클래스는 언제 생성해야 하나요?#

"유틸리티 우선(utility-first)" 접근 방식을 권장합니다.

  • 유틸리티 클래스로 시작하세요.

  • 유틸리티 클래스를 컴포넌트 클래스로 합성하면 코드 중복을 제거하고 명확한 책임을 캡슐화할 수 있다면, 그렇게 하세요.

이 방식은 컴포넌트 클래스가 유기적으로 성장하도록 장려하고 일회성의 재사용 불가능한 클래스가 생성되는 것을 방지합니다. 또한 "유틸리티 우선" 방식에서 나타나는 클래스는 도메인 중심(예: .security-report-widget, .commit-header-icon)보다 디자인 중심(예: .button, .alert, .card)이 되는 경향이 있습니다.

참고 자료:

HTML과 스타일시트에서 Tailwind CSS 활용#

컴포넌트 클래스를 작성할 때는 디자인 시스템과의 일관성을 유지하고 CSS 번들을 작게 유지하기 위해 Tailwind CSS의 유틸리티 클래스를 효과적으로 통합하는 것이 중요합니다.

HTML에서의 유틸리티 CSS 클래스 vs. 스타일시트에서의 유틸리티 CSS 클래스:

HTML에서 유틸리티 클래스를 직접 사용하면 CSS 파일 크기를 작게 유지하고 유틸리티 우선 철학을 따를 수 있습니다. 반드시 필요한 경우가 아니라면 유틸리티 클래스와 커스텀 스타일을 하나의 컴포넌트 클래스에 결합하는 것을 피함으로써 혼동과 잠재적인 충돌을 방지할 수 있습니다.

  • 선호하는 이유:

더 작은 CSS 파일 크기: 유틸리티 클래스를 직접 사용하면 더 간결한 CSS 파일을 만들 수 있고 더 일관된 디자인 시스템을 촉진합니다.

  • 명확성 및 유지보수성: HTML에서 유틸리티 클래스를 사용하면 스타일이 어떻게 적용되는지 더 명확하게 알 수 있어 충돌과 회귀가 발생할 위험이 줄어듭니다.

  • 스타일 결합 시 잠재적인 문제:

충돌: 유틸리티 클래스와 커스텀 스타일을 하나의 클래스에 결합하면 스타일 간 상호 의존성이 있을 때 충돌이 발생할 수 있습니다.

  • 회귀: 스타일이 어떻게 해결되어야 하는지가 덜 명확해져 회귀나 예상치 못한 동작이 발생할 수 있습니다.

이 가이드라인을 따르면 Tailwind CSS를 효과적으로 활용하는 깔끔하고 유지보수하기 쉬운 스타일시트를 만들 수 있습니다.

1. HTML에서 유틸리티 클래스 직접 사용 (권장 방식)#

유지보수성을 높이고 유틸리티 우선 원칙을 따르기 위해, HTML 요소에 유틸리티 클래스를 직접 추가하세요. 컴포넌트 클래스에는 유틸리티가 아닌 CSS 스타일만 포함되어야 합니다. 다음 예시에서는 SCSS 파일에 position: fixed; right: 0; left: 0;을 추가하는 대신 유틸리티 클래스 gl-fixedgl-inset-x-0을 추가합니다:

<!-- Bad -->
<div class="my-class"></div>

<style>
  .my-class {
    top: $header-height;
    min-height: $comparison-empty-state-height;
    position: fixed;
    left: 0px;
    right: 0px;
 }
</style>

<!-- Good -->
<div class="my-class gl-fixed gl-inset-x-0"></div>

<style>
  .my-class {
    top: $header-height;
    min-height: $comparison-empty-state-height;
  }
</style>

2. 컴포넌트 클래스에서 유틸리티 클래스 적용 (필요한 경우)#

HTML에서 유틸리티 클래스를 직접 사용하는 것이 불가능한 경우 커스텀 SCSS 파일에 포함시켜야 할 수 있습니다. 이때 관련 속성이나 값을 직접 파악하지 않고도 디자인 시스템의 스타일 정의를 상속받고 싶을 수 있습니다. 이 과정을 간소화하기 위해 Tailwind CSS의 @apply 지시어를 사용하여 커스텀 스타일에 유틸리티 스타일 정의를 포함할 수 있습니다.

@apply는 디자인 시스템에 의존하는 CSS 속성(예: margin, padding)을 적용하는 데 권장됩니다. 단위가 없는 CSS 속성(예: display: flex)은 CSS 속성을 직접 사용해도 괜찮습니다.

// Bad
.my-class {
  margin-top: 0.5rem;
}

// Okay
.my-class {
  display: flex;
}

// Good
.my-class {
  @apply gl-mt-5;
  @apply gl-flex;
}

반응형 디자인#

UI는 모바일과 데스크톱 모두에서 잘 동작해야 합니다. 이를 위해 CSS container queries를 사용합니다. 일반적으로 container queries에서는 모바일 우선 접근 방식을 취해야 합니다. 즉, 모바일용 CSS를 먼저 작성한 다음 min-width container queries를 사용하여 데스크톱에서 스타일을 재정의합니다. 이 규칙의 예외는 자식 컴포넌트의 display 모드를 설정하는 경우입니다. 예를 들어 모바일에서 GlButton을 숨길 때는 컴포넌트 CSS에서 설정한 display 모드를 재정의하지 않으려면 max-width container query를 사용해야 합니다. 현재 Tailwind 구성은 max-width container queries를 지원하지 않으므로, 이러한 대안이 필요한 경우 커스텀 스타일을 작성해야 합니다.

Tailwind CSS 클래스#

<!-- Bad (using desktop-first media queries) -->
<div class="gl-mt-5 max-lg:gl-mt-3"></div>

<div class="gl-mt-3 sm:max-lg:gl-mt-5"></div>

<!-- Good (using min-width container queries) -->
<div class="gl-mt-3 @md:gl-mt-5"></div>

<div class="gl-mt-3 @sm:gl-mt-5 @lg:gl-mt-3"></div>

<!-- Bad -->
<!--
`gl-hidden` applies `display: none` to all container sizes. This forces us to make assumptions on
what `display` value to reset the component to on larger viewports. In this case, we _assume_ `flex`
should be used. However, this might not match the component's internal styling and might end up
causing visual regressions.
-->
<gl-button class="gl-hidden @lg:gl-flex">Edit</gl-button>

<!-- Good -->
<!--
A `@max-*:gl-hidden` class only applies `display: none` in smaller containers,
ensuring that the component can gracefully fall back to its own `display` value in larger containers.
-->
<gl-button class="@max-lg:gl-hidden">Edit</gl-button>
<!-- One can also define a breakpoint range in which to apply the override -->
<gl-button class="@sm:@max-md:gl-hidden">Edit</gl-button>

컴포넌트 클래스#

// Bad (using desktop-first media queries)
.class-name {
  @apply gl-mt-5 max-lg:gl-mt-3;
}

// Good (using min-width container queries)
.class-name {
  @apply gl-mt-3 @lg:gl-mt-5;
}

// Bad (using max-width container queries)
.class-name {
  display: block;

  @include gl-container-width-up-down(lg) {
    display: flex;
  }
}

// Good (using min-width container queries)
.class-name {
  display: flex;

  @include gl-container-width-up(lg) {
    display: block;
  }
}

미디어 쿼리에서 마이그레이션#

2025년 8월부터 기존 미디어 쿼리를 container queries로 전환하고 있습니다. Container queries는 브라우저 창 너비에 의존하지 않고 자체 포함된 애플리케이션 내에서 반응형 레이아웃을 구축하는 더 나은 방법을 제공하며, 많은 경우 브라우저 창 너비가 최적의 기준이 되지 않을 수 있습니다.

이전 코드 대부분이 미디어 쿼리로 작성되어 있어 container queries로 마이그레이션해야 합니다. 이 과정을 돕기 위해 다음 작업을 수행하는 마이그레이션 스크립트를 만들었습니다:

  • Tailwind 미디어 쿼리 CSS 유틸리티를 container queries 유틸리티로 교체합니다.

  • Bootstrap 반응형 유틸리티를 Tailwind container 유틸리티로 교체합니다.

  • 기타 레거시 Bootstrap 비반응형 유틸리티를 Tailwind 유틸리티로 교체합니다. 이는 많은 페이지를 변경하는 김에 함께 처리할 수 있는 또 다른 진행 중인 마이그레이션에 도움이 됩니다.

  • SCSS 파일의 미디어 쿼리를 container queries 믹스인을 사용하도록 다시 작성합니다.

스크립트가 지원하는 파일 유형:

  • Vue

  • JavaScript

  • HAML*

  • Ruby

  • SCSS

*HAML 파일에서는 Tailwind container queries 문법이 HAML 인라인 클래스 이름이 지원하지 않는 특수 문자를 사용하기 때문에 스크립트가 문법을 깨뜨릴 수 있습니다. 스크립트 실행 후 해당 부분을 명시적인 class 속성으로 수동으로 이동시켜야 합니다:

# This breaks the HAML syntax:
%p.@md/panel:gl-mt-2

# To fix it, move the util to an explicit `class` attribute:
%p{ class: "@md/panel:gl-mt-2" }

파일을 마이그레이션하려면 마이그레이션할 파일을 인수로 스크립트를 실행하세요:

scripts/frontend/migrate_to_container_queries.mjs \
    app/assets/stylesheets/page_bundles/admin/geo_replicable.scss \
    ee/app/assets/javascripts/geo_replicable_item/components/app.vue

이 스크립트는 주어진 파일이 의존하는 모든 JavaScript/Vue 에셋을 찾는 find_frontend_files 스크립트와 함께 사용할 수 있습니다. 예를 들어, 이 조합을 사용하여 페이지의 JavaScript 진입점 의존성을 찾아 마이그레이션 스크립트에 한 번에 전달할 수 있습니다:

scripts/frontend/migrate_to_container_queries.mjs $(scripts/frontend/find_frontend_files.mjs app/assets/javascripts/todos/index.js)

일부 미디어 쿼리는 전역 동작에 필요하므로 모든 미디어 쿼리를 마이그레이션할 필요는 없습니다. 자동 마이그레이션에서 제외하려면 scripts/frontend/lib/container_queries_migration_exclusions.txt에 파일을 추가하면 됩니다.

특정 HAML 뷰가 의존하는 파셜을 찾으려면 GDK에서 해당 페이지로 이동한 다음 브라우저 콘솔에서 다음 스크립트를 실행하여 파일 목록을 클립보드에 복사하세요:

(() => {
  const partialRegex = /BEGIN\s+(?<partial>\S+.haml)\s*/;
  const extractPartial = node => {
    const match = partialRegex.exec(node.data);
    if (match?.groups?.partial) return match.groups.partial;
    return null;
  };

  const nodeIterator = document.createNodeIterator(document.documentElement, NodeFilter.SHOW_COMMENT);
  const partials = [];
  let currentNode;
  while ((currentNode = nodeIterator.nextNode())) {
    const partial = extractPartial(currentNode);
    if (partial) partials.push(partial);
  }

  copy(partials.join('\n'));
})();

스크립트가 마이그레이션을 빠르게 진행하는 데 도움이 될 수 있지만, 모든 것을 자동으로 마이그레이션할 수는 없다는 점을 명심하세요. 특히 다음 사항에 주의해야 합니다:

  • NPM 의존성의 UI 요소를 사용하는 경우, 해당 요소는 수동으로 마이그레이션하거나 재정의해야 합니다.

  • 마이그레이션하는 코드에 창 크기 조절에 반응하는 JavaScript가 포함되어 있을 수 있습니다. 이러한 스크립트는 "컨테이너 인식" 방식으로 수동으로 마이그레이션해야 합니다.

  • SCSS가 커스텀 중단점으로 미디어 쿼리를 구현하는 경우, 스크립트가 이를 다시 작성하지 못할 수 있으므로 수동으로 마이그레이션해야 합니다.

네이밍#

파일명은 snake_case를 사용해야 합니다.

CSS 클래스는 snake_casecamelCase 대신 lowercase-hyphenated 형식을 사용해야 합니다.

// Bad
.class_name {
  color: #fff;
}

// Bad
.className {
  color: #fff;
}

// Good
.class-name {
  color: #fff;
}

SCSS & 기능을 사용하여 복합 클래스 이름을 만드는 것은 피하세요. 사용 위치를 검색하기 어렵고 이점도 제한적입니다.

// Bad
.class {
  &-name {
    color: orange;
  }
}

// Good
.class-name {
  color: #fff;
}

태그 이름 선택자 대신 클래스 이름을 사용해야 합니다. 태그 이름 선택자는 계층 구조에서 의도하지 않은 요소에 영향을 줄 수 있으므로 사용하지 않는 것이 좋습니다.

// Bad
ul {
  color: #fff;
}

// Good
.class-name {
  color: #fff;
}

// Best
// prefer an existing utility class over adding existing styles

ID보다 클래스 이름이 더 적합합니다. ID를 사용하는 규칙은 페이지에 영향을 받는 요소가 하나밖에 없으므로 재사용할 수 없습니다.

// Bad
#my-element {
  padding: 0;
}

// Good
.my-element {
  padding: 0;
}

중첩#

불필요한 중첩을 피하세요. 래퍼 컴포넌트의 추가적인 명시도(specificity)는 재정의를 어렵게 만듭니다.

// Bad
.component-container {
  .component-header {
    /* ... */
  }

  .component-body {
    /* ... */
  }
}

// Good
.component-container {
  /* ... */
}

.component-header {
  /* ... */
}

.component-body {
  /* ... */
}

js- 접두사가 붙은 선택자#

js-로 시작하는 선택자는 스타일링 목적으로 사용하지 마세요. 이 선택자는 스타일을 깨뜨리지 않고 제거하거나 이름을 바꿀 수 있도록 JavaScript에서만 사용하기 위한 것입니다.

클래스 연결#

문자열을 연결하여 클래스를 만들지 마세요. 대신 가독성과 유지보수를 위해 전체 클래스 이름을 작성하세요.

// Bad
.foo {
  /* ... */
  &-bar {
    /* ... */
  }
}

// Good
.foo {
  /* ... */
}

.foo-bar {
  /* ... */
}

유틸리티 CSS 클래스를 사용한 선택자#

스타일시트에서 유틸리티 CSS 클래스를 선택자로 사용하지 마세요. 이러한 클래스는 변경될 가능성이 있어 선택자를 업데이트해야 하고 구현을 유지보수하기 어렵게 만듭니다. 대신 다른 기존 CSS 클래스를 사용하거나 스타일링 요소를 위한 새 커스텀 CSS 클래스를 추가하세요. 이 방식은 유지보수성을 향상시키고 버그 발생 위험을 줄입니다.

// ❌ Bad
.gl-mb-5 {
  /* ... */
}

// ✅ Good
.component-header {
  /* ... */
}

ARIA 속성을 사용한 선택자#

스타일링 목적으로 ARIA 속성 선택자를 사용하지 마세요. 이 속성과 권한은 보조 기술을 지원하기 위한 것입니다. ARIA로 주석이 달린 컴포넌트의 구조는 변경될 수 있으며 그에 따라 스타일도 변경됩니다. 스타일을 깨뜨리지 않고 이러한 권한과 속성을 다른 요소로 이동할 수 있어야 합니다.

// Bad
&[aria-expanded=false] &-header {
  border-bottom: 0;
}

// Good
&.is-collapsed &-header {
  border-bottom: 0;
}

extend at-rule 사용#

extend at-rule은 메모리 누수규칙이 의도한 대로 작동하지 않는 문제로 인해 사용이 금지되어 있습니다.

린팅#

스타일 가이드 준수 여부를 확인하기 위해 stylelint를 사용합니다. .stylelintrc의 규칙셋과 SCSS 구성의 규칙을 사용합니다. .stylelintrc는 프로젝트의 홈 디렉터리에 위치합니다.

변경 사항에서 경고가 발생하는지 확인하려면 GitLab 디렉터리에서 yarn lint:stylelint를 실행하세요. Stylelint는 GitLab CI/CD에서도 실행되어 경고를 감지합니다.

Rake 태스크에서 이해하기 어려운 경고가 발생하는 경우, SCSS Lint의 문서에서 전체 규칙 목록을 확인하세요.

SCSS 스타일 가이드

GitLab v19.1
원문 보기
요약

사이트가 성장함에 따라 더 많은 CSS가 생성되는 것을 줄이기 위해, 새 CSS를 추가하는 것보다 유틸리티 클래스 사용을 권장합니다. 유틸리티 클래스는 Tailwind CSS에 의해 생성됩니다. GitLab Tailwind CSS 문서: GitLab Tailwind 구성에 특화된 문서 사이트입니다.

유틸리티 클래스#

사이트가 성장함에 따라 더 많은 CSS가 생성되는 것을 줄이기 위해, 새 CSS를 추가하는 것보다 유틸리티 클래스 사용을 권장합니다. 복잡한 경우에는 컴포넌트 클래스를 추가하여 CSS를 처리할 수 있습니다.

CSS 유틸리티 클래스는 어디에 정의되어 있나요?#

유틸리티 클래스는 Tailwind CSS에 의해 생성됩니다. Tailwind CSS 클래스를 확인하는 방법은 세 가지가 있습니다:

  • GitLab Tailwind CSS 문서: GitLab Tailwind 구성에 특화된 문서 사이트입니다. 사용 가능한 모든 Tailwind CSS 클래스를 검색할 수 있는 목록을 제공합니다.

  • Tailwind CSS 자동완성: VS Code 또는 RubyMine에서 사용할 수 있습니다.

  • Tailwind CSS config viewer: 디자인 시스템에 특화된 Tailwind CSS 클래스(간격, 색상, 크기 등)를 시각적으로 보여줍니다. 사용 가능한 모든 Tailwind CSS 클래스를 표시하지는 않습니다.

어떤 CSS 유틸리티 클래스가 deprecated되었나요?#

common.scss의 클래스는 deprecated 예정입니다. 디자인 시스템에 맞지 않는 값을 사용하는 common.scss의 클래스는 사용하지 않아야 합니다. 대신 규격에 맞는 값을 가진 클래스를 사용하세요.

Bootstrap의 유틸리티 클래스는 사용하지 마세요.

Bootstrap의 유틸리티 클래스GitLab UI 유틸리티 클래스로 마이그레이션할 때, margin과 padding 클래스가 서로 다름에 주의하세요. GitLab에서 사용하는 크기 척도는 Bootstrap 라이브러리의 척도와 다릅니다. Bootstrap의 padding 또는 margin 유틸리티를 사용하는 경우, 동일한 시각적 결과를 얻으려면 적용한 유틸리티 크기를 두 배로 늘려야 할 수 있습니다(예: ml-1gl-ml-2로 변경됨).

Tailwind CSS#

2024년 8월부터 CSS 유틸리티 제공자로 Tailwind CSS를 사용하고 있습니다. 이는 이전에 사용하던 커스텀 솔루션을 대체합니다. 도입 동기, 제안, 구현 세부 사항은 Tailwind CSS 설계 문서를 참고하세요.

Tailwind CSS 기본 사항#

아래에서 Tailwind CSS의 기본 사항과 Pajamas 디자인 시스템을 사용하도록 구성된 방식에 대한 정보를 확인할 수 있습니다. 더 자세한 가이드는 공식 Tailwind CSS 문서를 참고하세요.

접두사#

모든 유틸리티 클래스에 gl- 접두사가 붙도록 Tailwind CSS에 prefix를 구성했습니다. 반응형 유틸리티 또는 상태 수정자를 사용할 때는 접두사가 콜론 뒤에 위치합니다.

예시: gl-mt-5, lg:gl-mt-5.

반응형 CSS 유틸리티 클래스#

반응형 CSS 유틸리티 클래스는 중단점 이름 뒤에 : 문자가 붙는 형식으로 접두사가 붙습니다. 사용 가능한 중단점은 tailwind.defaults.js#L44에 구성되어 있습니다.

예시: lg:gl-mt-5

hover, focus 및 기타 상태 수정자#

상태 수정자를 사용하면 어떤 Tailwind CSS 클래스든 조건부로 적용할 수 있습니다. CSS 유틸리티 클래스 앞에 수정자 이름을 붙이고 : 문자로 구분합니다.

예시: hover:gl-underline

!important 수정자#

CSS 유틸리티 클래스 앞에 !를 추가하여 important 수정자를 사용할 수 있습니다. 반응형 유틸리티 클래스나 상태 수정자와 함께 사용할 때는 !: 문자 뒤에 위치합니다.

예시: !gl-mt-5, lg:!gl-mt-5, hover:!gl-underline

간격 및 크기 CSS 유틸리티 클래스#

간격 및 크기 CSS 유틸리티 클래스(예: margin, padding, width, height)는 src/tokens/build/tailwind/tokens.cjs에 정의된 간격 척도를 사용합니다. 사용 가능한 CSS 유틸리티 클래스는 https://design.gitlab.com/tailwind-documentation/margin에서 확인하세요.

예시: gl-mt-5margin-top: 1rem;입니다.

색상 CSS 유틸리티 클래스#

색상 CSS 유틸리티 클래스(예: color, background-color)는 src/tokens/build/tailwind/tokens.cjs에 정의된 색상을 사용합니다. 사용 가능한 CSS 유틸리티 클래스는 https://design.gitlab.com/tailwind-documentation/text-color에서 확인하세요.

예시: gl-text-subtlecolor: var(--gl-text-color-subtle, #626168);입니다.

Tailwind CSS 번들 빌드#

GitLab Development Kit에서 Vite 또는 Webpack을 사용할 때, Tailwind CSS는 파일 변경 사항을 감지하여 감지된 유틸리티를 즉시 빌드합니다.

새로운 Tailwind CSS 번들을 빌드하려면 yarn tailwindcss:build를 실행하세요. 이 스크립트는 bundle exec rake gitlab:assets:compile로 프로덕션 에셋을 빌드할 때 내부적으로 호출됩니다.

번들 빌드 방식에 관계없이 출력은 app/assets/builds/tailwind.css에 저장됩니다.

Tailwind CSS 자동완성#

Tailwind CSS 자동완성은 코드 편집기에서 사용 가능한 모든 클래스를 나열합니다.

VS Code#

자동완성이 느린 경우 TS 서버가 사용할 수 있는 메모리 양을 늘려야 할 수도 있습니다.

Tailwind CSS IntelliSense 확장을 설치하세요. HAML과 커스텀 *-class prop을 지원하려면 다음 설정을 권장합니다:

{
  "tailwindCSS.experimental.classRegex": [
    ["class: [\"|']+([^\"|']*)[\"|']+", "([a-zA-Z0-9\\-:!/]+)"],
    ["(\\.[\\w\\-.]+)[\\n\\=\\{\\s]", "([\\w\\-]+)"],
    ["[a-z]+-class(?:es)?=\"([^'\"]*)\""]
  ],
  "tailwindCSS.emmetCompletions": true
}
RubyMine#

Tailwind CSS 자동완성은 기본으로 활성화되어 있습니다. HAML과 커스텀 *-class prop을 완전히 지원하려면 기본 설정에서 다음과 같이 업데이트하는 것을 권장합니다:

{
  "includeLanguages": {
    "haml": "html"
  },
  "emmetCompletions": true,
  "experimental": {
    "classRegex": [
      ["class: [\"|']+([^\"|']*)[\"|']+", "([a-zA-Z0-9\\-:!/]+)"],
      ["(\\.[\\w\\-.]+)[\\n\\=\\{\\s]", "([\\w\\-]+)"],
      ["[a-z]+-class(?:es)?=\"([^'\"]*)\""]
    ]
  }
}

Tailwind CSS 임의 값#

임의 값은 다음과 같은 이유로 사용하지 않는 것이 좋습니다:

  • 임의 값은 하나의 페이지에서만 필요하지만 전역 CSS 번들에 추가되어 번들 크기가 증가합니다. CSS가 필요한 곳에서만 포함되도록 페이지별 CSS를 사용해야 합니다.

  • 미리 정의된 CSS 클래스가 이미 존재할 가능성이 높습니다. GitLab Tailwind CSS 문서에서 사용 가능한 Tailwind CSS 클래스를 확인하세요.

  • 임의 값은 디자인 시스템을 강제하지 않습니다.

새 유틸리티 클래스는 어디에 추가해야 하나요?#

유틸리티 클래스는 대부분의 CSS 기능을 지원하는 Tailwind CSS에 의해 생성됩니다. 사용할 수 없는 기능이 있다면 GitLab UI의 tailwind.defaults.js를 업데이트해야 합니다.

컴포넌트 클래스는 언제 생성해야 하나요?#

"유틸리티 우선(utility-first)" 접근 방식을 권장합니다.

  • 유틸리티 클래스로 시작하세요.

  • 유틸리티 클래스를 컴포넌트 클래스로 합성하면 코드 중복을 제거하고 명확한 책임을 캡슐화할 수 있다면, 그렇게 하세요.

이 방식은 컴포넌트 클래스가 유기적으로 성장하도록 장려하고 일회성의 재사용 불가능한 클래스가 생성되는 것을 방지합니다. 또한 "유틸리티 우선" 방식에서 나타나는 클래스는 도메인 중심(예: .security-report-widget, .commit-header-icon)보다 디자인 중심(예: .button, .alert, .card)이 되는 경향이 있습니다.

참고 자료:

HTML과 스타일시트에서 Tailwind CSS 활용#

컴포넌트 클래스를 작성할 때는 디자인 시스템과의 일관성을 유지하고 CSS 번들을 작게 유지하기 위해 Tailwind CSS의 유틸리티 클래스를 효과적으로 통합하는 것이 중요합니다.

HTML에서의 유틸리티 CSS 클래스 vs. 스타일시트에서의 유틸리티 CSS 클래스:

HTML에서 유틸리티 클래스를 직접 사용하면 CSS 파일 크기를 작게 유지하고 유틸리티 우선 철학을 따를 수 있습니다. 반드시 필요한 경우가 아니라면 유틸리티 클래스와 커스텀 스타일을 하나의 컴포넌트 클래스에 결합하는 것을 피함으로써 혼동과 잠재적인 충돌을 방지할 수 있습니다.

  • 선호하는 이유:

더 작은 CSS 파일 크기: 유틸리티 클래스를 직접 사용하면 더 간결한 CSS 파일을 만들 수 있고 더 일관된 디자인 시스템을 촉진합니다.

  • 명확성 및 유지보수성: HTML에서 유틸리티 클래스를 사용하면 스타일이 어떻게 적용되는지 더 명확하게 알 수 있어 충돌과 회귀가 발생할 위험이 줄어듭니다.

  • 스타일 결합 시 잠재적인 문제:

충돌: 유틸리티 클래스와 커스텀 스타일을 하나의 클래스에 결합하면 스타일 간 상호 의존성이 있을 때 충돌이 발생할 수 있습니다.

  • 회귀: 스타일이 어떻게 해결되어야 하는지가 덜 명확해져 회귀나 예상치 못한 동작이 발생할 수 있습니다.

이 가이드라인을 따르면 Tailwind CSS를 효과적으로 활용하는 깔끔하고 유지보수하기 쉬운 스타일시트를 만들 수 있습니다.

1. HTML에서 유틸리티 클래스 직접 사용 (권장 방식)#

유지보수성을 높이고 유틸리티 우선 원칙을 따르기 위해, HTML 요소에 유틸리티 클래스를 직접 추가하세요. 컴포넌트 클래스에는 유틸리티가 아닌 CSS 스타일만 포함되어야 합니다. 다음 예시에서는 SCSS 파일에 position: fixed; right: 0; left: 0;을 추가하는 대신 유틸리티 클래스 gl-fixedgl-inset-x-0을 추가합니다:

<!-- Bad -->
<div class="my-class"></div>

<style>
  .my-class {
    top: $header-height;
    min-height: $comparison-empty-state-height;
    position: fixed;
    left: 0px;
    right: 0px;
 }
</style>

<!-- Good -->
<div class="my-class gl-fixed gl-inset-x-0"></div>

<style>
  .my-class {
    top: $header-height;
    min-height: $comparison-empty-state-height;
  }
</style>

2. 컴포넌트 클래스에서 유틸리티 클래스 적용 (필요한 경우)#

HTML에서 유틸리티 클래스를 직접 사용하는 것이 불가능한 경우 커스텀 SCSS 파일에 포함시켜야 할 수 있습니다. 이때 관련 속성이나 값을 직접 파악하지 않고도 디자인 시스템의 스타일 정의를 상속받고 싶을 수 있습니다. 이 과정을 간소화하기 위해 Tailwind CSS의 @apply 지시어를 사용하여 커스텀 스타일에 유틸리티 스타일 정의를 포함할 수 있습니다.

@apply는 디자인 시스템에 의존하는 CSS 속성(예: margin, padding)을 적용하는 데 권장됩니다. 단위가 없는 CSS 속성(예: display: flex)은 CSS 속성을 직접 사용해도 괜찮습니다.

// Bad
.my-class {
  margin-top: 0.5rem;
}

// Okay
.my-class {
  display: flex;
}

// Good
.my-class {
  @apply gl-mt-5;
  @apply gl-flex;
}

반응형 디자인#

UI는 모바일과 데스크톱 모두에서 잘 동작해야 합니다. 이를 위해 CSS container queries를 사용합니다. 일반적으로 container queries에서는 모바일 우선 접근 방식을 취해야 합니다. 즉, 모바일용 CSS를 먼저 작성한 다음 min-width container queries를 사용하여 데스크톱에서 스타일을 재정의합니다. 이 규칙의 예외는 자식 컴포넌트의 display 모드를 설정하는 경우입니다. 예를 들어 모바일에서 GlButton을 숨길 때는 컴포넌트 CSS에서 설정한 display 모드를 재정의하지 않으려면 max-width container query를 사용해야 합니다. 현재 Tailwind 구성은 max-width container queries를 지원하지 않으므로, 이러한 대안이 필요한 경우 커스텀 스타일을 작성해야 합니다.

Tailwind CSS 클래스#

<!-- Bad (using desktop-first media queries) -->
<div class="gl-mt-5 max-lg:gl-mt-3"></div>

<div class="gl-mt-3 sm:max-lg:gl-mt-5"></div>

<!-- Good (using min-width container queries) -->
<div class="gl-mt-3 @md:gl-mt-5"></div>

<div class="gl-mt-3 @sm:gl-mt-5 @lg:gl-mt-3"></div>

<!-- Bad -->
<!--
`gl-hidden` applies `display: none` to all container sizes. This forces us to make assumptions on
what `display` value to reset the component to on larger viewports. In this case, we _assume_ `flex`
should be used. However, this might not match the component's internal styling and might end up
causing visual regressions.
-->
<gl-button class="gl-hidden @lg:gl-flex">Edit</gl-button>

<!-- Good -->
<!--
A `@max-*:gl-hidden` class only applies `display: none` in smaller containers,
ensuring that the component can gracefully fall back to its own `display` value in larger containers.
-->
<gl-button class="@max-lg:gl-hidden">Edit</gl-button>
<!-- One can also define a breakpoint range in which to apply the override -->
<gl-button class="@sm:@max-md:gl-hidden">Edit</gl-button>

컴포넌트 클래스#

// Bad (using desktop-first media queries)
.class-name {
  @apply gl-mt-5 max-lg:gl-mt-3;
}

// Good (using min-width container queries)
.class-name {
  @apply gl-mt-3 @lg:gl-mt-5;
}

// Bad (using max-width container queries)
.class-name {
  display: block;

  @include gl-container-width-up-down(lg) {
    display: flex;
  }
}

// Good (using min-width container queries)
.class-name {
  display: flex;

  @include gl-container-width-up(lg) {
    display: block;
  }
}

미디어 쿼리에서 마이그레이션#

2025년 8월부터 기존 미디어 쿼리를 container queries로 전환하고 있습니다. Container queries는 브라우저 창 너비에 의존하지 않고 자체 포함된 애플리케이션 내에서 반응형 레이아웃을 구축하는 더 나은 방법을 제공하며, 많은 경우 브라우저 창 너비가 최적의 기준이 되지 않을 수 있습니다.

이전 코드 대부분이 미디어 쿼리로 작성되어 있어 container queries로 마이그레이션해야 합니다. 이 과정을 돕기 위해 다음 작업을 수행하는 마이그레이션 스크립트를 만들었습니다:

  • Tailwind 미디어 쿼리 CSS 유틸리티를 container queries 유틸리티로 교체합니다.

  • Bootstrap 반응형 유틸리티를 Tailwind container 유틸리티로 교체합니다.

  • 기타 레거시 Bootstrap 비반응형 유틸리티를 Tailwind 유틸리티로 교체합니다. 이는 많은 페이지를 변경하는 김에 함께 처리할 수 있는 또 다른 진행 중인 마이그레이션에 도움이 됩니다.

  • SCSS 파일의 미디어 쿼리를 container queries 믹스인을 사용하도록 다시 작성합니다.

스크립트가 지원하는 파일 유형:

  • Vue

  • JavaScript

  • HAML*

  • Ruby

  • SCSS

*HAML 파일에서는 Tailwind container queries 문법이 HAML 인라인 클래스 이름이 지원하지 않는 특수 문자를 사용하기 때문에 스크립트가 문법을 깨뜨릴 수 있습니다. 스크립트 실행 후 해당 부분을 명시적인 class 속성으로 수동으로 이동시켜야 합니다:

# This breaks the HAML syntax:
%p.@md/panel:gl-mt-2

# To fix it, move the util to an explicit `class` attribute:
%p{ class: "@md/panel:gl-mt-2" }

파일을 마이그레이션하려면 마이그레이션할 파일을 인수로 스크립트를 실행하세요:

scripts/frontend/migrate_to_container_queries.mjs \
    app/assets/stylesheets/page_bundles/admin/geo_replicable.scss \
    ee/app/assets/javascripts/geo_replicable_item/components/app.vue

이 스크립트는 주어진 파일이 의존하는 모든 JavaScript/Vue 에셋을 찾는 find_frontend_files 스크립트와 함께 사용할 수 있습니다. 예를 들어, 이 조합을 사용하여 페이지의 JavaScript 진입점 의존성을 찾아 마이그레이션 스크립트에 한 번에 전달할 수 있습니다:

scripts/frontend/migrate_to_container_queries.mjs $(scripts/frontend/find_frontend_files.mjs app/assets/javascripts/todos/index.js)

일부 미디어 쿼리는 전역 동작에 필요하므로 모든 미디어 쿼리를 마이그레이션할 필요는 없습니다. 자동 마이그레이션에서 제외하려면 scripts/frontend/lib/container_queries_migration_exclusions.txt에 파일을 추가하면 됩니다.

특정 HAML 뷰가 의존하는 파셜을 찾으려면 GDK에서 해당 페이지로 이동한 다음 브라우저 콘솔에서 다음 스크립트를 실행하여 파일 목록을 클립보드에 복사하세요:

(() => {
  const partialRegex = /BEGIN\s+(?<partial>\S+.haml)\s*/;
  const extractPartial = node => {
    const match = partialRegex.exec(node.data);
    if (match?.groups?.partial) return match.groups.partial;
    return null;
  };

  const nodeIterator = document.createNodeIterator(document.documentElement, NodeFilter.SHOW_COMMENT);
  const partials = [];
  let currentNode;
  while ((currentNode = nodeIterator.nextNode())) {
    const partial = extractPartial(currentNode);
    if (partial) partials.push(partial);
  }

  copy(partials.join('\n'));
})();

스크립트가 마이그레이션을 빠르게 진행하는 데 도움이 될 수 있지만, 모든 것을 자동으로 마이그레이션할 수는 없다는 점을 명심하세요. 특히 다음 사항에 주의해야 합니다:

  • NPM 의존성의 UI 요소를 사용하는 경우, 해당 요소는 수동으로 마이그레이션하거나 재정의해야 합니다.

  • 마이그레이션하는 코드에 창 크기 조절에 반응하는 JavaScript가 포함되어 있을 수 있습니다. 이러한 스크립트는 "컨테이너 인식" 방식으로 수동으로 마이그레이션해야 합니다.

  • SCSS가 커스텀 중단점으로 미디어 쿼리를 구현하는 경우, 스크립트가 이를 다시 작성하지 못할 수 있으므로 수동으로 마이그레이션해야 합니다.

네이밍#

파일명은 snake_case를 사용해야 합니다.

CSS 클래스는 snake_casecamelCase 대신 lowercase-hyphenated 형식을 사용해야 합니다.

// Bad
.class_name {
  color: #fff;
}

// Bad
.className {
  color: #fff;
}

// Good
.class-name {
  color: #fff;
}

SCSS & 기능을 사용하여 복합 클래스 이름을 만드는 것은 피하세요. 사용 위치를 검색하기 어렵고 이점도 제한적입니다.

// Bad
.class {
  &-name {
    color: orange;
  }
}

// Good
.class-name {
  color: #fff;
}

태그 이름 선택자 대신 클래스 이름을 사용해야 합니다. 태그 이름 선택자는 계층 구조에서 의도하지 않은 요소에 영향을 줄 수 있으므로 사용하지 않는 것이 좋습니다.

// Bad
ul {
  color: #fff;
}

// Good
.class-name {
  color: #fff;
}

// Best
// prefer an existing utility class over adding existing styles

ID보다 클래스 이름이 더 적합합니다. ID를 사용하는 규칙은 페이지에 영향을 받는 요소가 하나밖에 없으므로 재사용할 수 없습니다.

// Bad
#my-element {
  padding: 0;
}

// Good
.my-element {
  padding: 0;
}

중첩#

불필요한 중첩을 피하세요. 래퍼 컴포넌트의 추가적인 명시도(specificity)는 재정의를 어렵게 만듭니다.

// Bad
.component-container {
  .component-header {
    /* ... */
  }

  .component-body {
    /* ... */
  }
}

// Good
.component-container {
  /* ... */
}

.component-header {
  /* ... */
}

.component-body {
  /* ... */
}

js- 접두사가 붙은 선택자#

js-로 시작하는 선택자는 스타일링 목적으로 사용하지 마세요. 이 선택자는 스타일을 깨뜨리지 않고 제거하거나 이름을 바꿀 수 있도록 JavaScript에서만 사용하기 위한 것입니다.

클래스 연결#

문자열을 연결하여 클래스를 만들지 마세요. 대신 가독성과 유지보수를 위해 전체 클래스 이름을 작성하세요.

// Bad
.foo {
  /* ... */
  &-bar {
    /* ... */
  }
}

// Good
.foo {
  /* ... */
}

.foo-bar {
  /* ... */
}

유틸리티 CSS 클래스를 사용한 선택자#

스타일시트에서 유틸리티 CSS 클래스를 선택자로 사용하지 마세요. 이러한 클래스는 변경될 가능성이 있어 선택자를 업데이트해야 하고 구현을 유지보수하기 어렵게 만듭니다. 대신 다른 기존 CSS 클래스를 사용하거나 스타일링 요소를 위한 새 커스텀 CSS 클래스를 추가하세요. 이 방식은 유지보수성을 향상시키고 버그 발생 위험을 줄입니다.

// ❌ Bad
.gl-mb-5 {
  /* ... */
}

// ✅ Good
.component-header {
  /* ... */
}

ARIA 속성을 사용한 선택자#

스타일링 목적으로 ARIA 속성 선택자를 사용하지 마세요. 이 속성과 권한은 보조 기술을 지원하기 위한 것입니다. ARIA로 주석이 달린 컴포넌트의 구조는 변경될 수 있으며 그에 따라 스타일도 변경됩니다. 스타일을 깨뜨리지 않고 이러한 권한과 속성을 다른 요소로 이동할 수 있어야 합니다.

// Bad
&[aria-expanded=false] &-header {
  border-bottom: 0;
}

// Good
&.is-collapsed &-header {
  border-bottom: 0;
}

extend at-rule 사용#

extend at-rule은 메모리 누수규칙이 의도한 대로 작동하지 않는 문제로 인해 사용이 금지되어 있습니다.

린팅#

스타일 가이드 준수 여부를 확인하기 위해 stylelint를 사용합니다. .stylelintrc의 규칙셋과 SCSS 구성의 규칙을 사용합니다. .stylelintrc는 프로젝트의 홈 디렉터리에 위치합니다.

변경 사항에서 경고가 발생하는지 확인하려면 GitLab 디렉터리에서 yarn lint:stylelint를 실행하세요. Stylelint는 GitLab CI/CD에서도 실행되어 경고를 감지합니다.

Rake 태스크에서 이해하기 어려운 경고가 발생하는 경우, SCSS Lint의 문서에서 전체 규칙 목록을 확인하세요.