1. 클래스 이름과 문법
일반 *.css를 사용하고 전역에서 고유한 클래스 이름을 붙입니다. 클래스 이름은 소유자와 역할을 드러내고, 요소와 수정자는 정해진 문법으로 구분합니다.
1.1 프로젝트 표준이 없으면 CSS Modules 대신 일반 CSS를 씁니다
클래스 이름이 전역에서 고유해야 범위_식별자로 소유자를 되짚을 수 있습니다.
별도 합의가 없으면 일반 *.css와 전역에서 고유한 클래스 이름을 씁니다.
.module.css를 새로 만들거나 클래스를 styles.foo처럼 객체 속성으로 참조하지 않습니다.
CSS Modules가 프로젝트의 공식 표준이고 별도의 이름 규칙이 있으면 그 규칙을 따릅니다.
프로젝트 표준이 없는데도 CSS Modules를 기본처럼 씁니다.
import styles from "./products.module.css";
<section className={styles.hero}>
<span className={styles.eyebrow}>Products</span>
</section>
.hero {
display: grid;
}
.eyebrow {
letter-spacing: 0.08em;
}
기본으로 일반 CSS와 전역 고유 클래스 이름을 씁니다.
import {clsx} from "clsx";
import "./pg-products.css";
<section className={clsx("pg_products__hero")}>
<span className={clsx("pg_products__eyebrow")}>Products</span>
</section>
.pg_products__hero {
display: grid;
}
.pg_products__eyebrow {
letter-spacing: 0.08em;
}
1.2 클래스 이름은 범위_식별자__요소--수정자 형식으로 씁니다
클래스 이름에서 소유자와 역할을 확인할 수 있습니다.
클래스 이름은 <scope>_<slug>__<element>[--<modifier>] 문법을 씁니다.
다른 규칙에서도 아래 한국어 이름을 씁니다.
| 자리 | 읽는 이름 | 담는 것 |
|---|---|---|
scope | 범위 | pg, wg, ui 중 하나. 소문자로 씁니다 |
slug | 식별자 | CSS 파일 소유자의 이름. camelCase로 씁니다 |
element | 요소 | 소유자 안의 UI 역할. listButton, emptyState처럼 camelCase로 씁니다 |
modifier | 수정자 | 클래스 뒤에 --로 붙는 이름. camelCase로 씁니다 |
수정자의 허용 범위는 규칙 3.2가 정합니다.
식별자에는 접두사가 이미 드러낸 낱말을 반복하지 않으므로 UiButton은 ui_button입니다.
기계 검사는 이 문법을 정규식으로 등록한 selector-class-pattern이 담당합니다.
식별자, 요소, 수정자에 snake_case와 kebab-case가 섞입니다.
ui_uiButton__root
ui_tag_list__root
ui_tagList__list-item
wg_site_header__root
wg_siteHeader__brand-link
pg_product_detail__root
pg_productDetail__main-content
pg_productDetail__main--route_active
범위는 소문자로 쓰고 식별자, 요소, 수정자는 camelCase로 씁니다.
ui_tagList__root
ui_tagList__listItem
wg_siteHeader__root
wg_siteHeader__brandLink
pg_productDetail__root
pg_productDetail__mainContent
pg_productDetail__main--routeActive
1.3 요소와 수정자는 역할로 이름을 짓습니다
구조나 치수 대신 역할을 이름에 담아 UI의 어느 부분인지 구분합니다.
요소와 수정자는 구조나 치수 대신 UI 역할로 이름을 짓습니다.
container, wrapper, box는 합성어로도 쓰지 않고 gap12처럼 숫자에 뜻을 담지 않습니다.
수정자를 붙일 수 있는지는 규칙 3.2가 정합니다.
역할 대신 구조나 치수로 이름을 짓습니다.
ui_card__wrapper
ui_card__box
ui_card__body--gap12
역할과 상태를 기준으로 이름을 붙입니다.
ui_card__toolbar
ui_card__body
ui_card__body--dense
1.4 pg_* 식별자에 화면 이름을 드러냅니다
클래스 이름에서 해당 화면을 추적할 수 있습니다.
pg_* 식별자에는 어느 화면인지 추적할 수 있는 이름을 씁니다.
화면 소유 여부는 활성화된 프레임워크 규약이 정하고 CSS는 그 이름을 따릅니다.
flowchart LR
q1{"화면 안의<br>컴포넌트인가?"} -- 예 --> q2{"다른 화면의 식별자와<br>충돌하는가?"} -- 예 --> r2("충돌한 화면의 세그먼트 하나를<br>pg_ 뒤에 덧붙임")
q1 -- 아니요 --> r1("라우트 세그먼트와 같은 낱말")
q2 -- 아니요 --> r3("자기 이름만")| 대상 | 식별자 |
|---|---|
| 라우트 진입 파일 | 라우트 세그먼트나 폴더 이름과 같은 낱말. 어느 화면에나 붙는 shell, page, content는 쓰지 않습니다 |
Outlet 프레임 | 활성화된 프레임워크 규약이 정한 진입 파일 이름을 씁니다. pg-outlet.tsx는 pg_outlet, pg-main-outlet.tsx는 pg_mainOutlet입니다 |
:id처럼 값이 런타임에 정해지는 동적 세그먼트 | 화면의 역할로 바꿉니다. orders/:id라면 라우트 폴더는 order-detail, 식별자는 pg_orderDetail입니다 |
| 화면 안의 컴포넌트 | 자기 이름만 씁니다 |
라우트 경로나 폴더 이름에 없는 줄임말은 쓰지 않고, 덧붙일 때 중간 컴포넌트 이름은 넣지 않습니다.
화면 이름이 아닌 식별자를 씁니다.
pg_shell__body <- 역할 낱말이라 어느 화면인지 안 나옴
pg_doc__content <- 라우트에 없는 줄임말
pg_x__root <- 되짚을 이름이 없음
라우트 진입 파일에는 라우트 세그먼트를 그대로 씁니다.
pg_orders__root <- orders 화면
pg_orderDetail__body <- order-detail 화면
pg_settings__body <- settings 화면
충돌이 없는데도 부모 식별자를 미리 붙입니다.
pg_productDetailProductTableOverviewSection__root
pg_productDetailProductTableSummaryBand__root
화면 안의 컴포넌트는 자기 식별자만 씁니다.
pg_overviewSection__root
pg_summaryBand__root
충돌을 피하려고 상위 경로 전체를 식별자에 붙입니다.
pg_productDetailProductTableOverviewSection__root
pg_productsProductTableOverviewSection__root
충돌한 화면 이름만 최소로 덧붙입니다.
pg_productDetailOverviewSection__root
pg_productsOverviewSection__root