탐색

Master CSS 2.0

표현력 있는 클래스, CSS로 정의한 토큰, 디자인 시스템을 이해하는 도구로 인터페이스를 만드세요.

은은한 금빛 배경 위에 윤이 나는 흑요석 질감의 2.0 글자

나만의 디자인 언어를 CSS로. Master CSS 2.0은 표현력 있는 클래스, 프로젝트 토큰, 재사용 가능한 스타일을 같은 정의를 읽는 도구와 연결합니다. 클래스 하나로 반응형 상태를 표현하고, 스타일시트에서 테마를 만들고, 작업하면서 생성된 CSS를 살펴보세요.

영상 보기: Master CSS 2.0 — Your project, understood.
이 페이지의 내용

이 방식으로 만든 작은 저널, Fieldwork를 소개합니다.

웹에서 모습을 갖춘 아이디어

HTML과 CSS 보기

앱 스타일시트의 @import '@master/css' 뒤에 CSS를 추가하세요. 에셋을 소스에 표시된 경로에 두거나 HTML과 @font-face의 URL을 실제 경로로 바꾸세요.

다운로드 · 화이트 오크 인테리어 · 도자기 사진 · Geist 가변 글꼴

@font-face {  font-family: 'Geist';  font-style: normal;  font-weight: 100 900;  font-display: swap;  src: url('/fonts/GeistVariable.woff2') format('woff2');}@theme {  --color-brand: light-dark(#665442, #d7c2a6);  --spacing-story: 1.5rem;}@theme {  --font-sans: 'Geist';}@layer components {  .journal { font-family: var(--font-family-sans); font-size: 13px; line-height: 1.6; font-weight: 350; }  .journal :where(h2, h3, p) { margin: 0; }  .journal a { color: inherit; text-decoration: none; }  .journal a:hover { text-decoration: underline; text-underline-offset: .2em; }  .journal :focus-visible { outline: 2px solid var(--color-brand); outline-offset: 4px; }  .journal-head { padding: 20px 24px; border-bottom: 1px solid var(--color-line-subtle); }  .journal-brand { font-size: 25px; line-height: 1; font-weight: 500; letter-spacing: -.06em; }  .journal-edition { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; }  .journal-nav { display: flex; flex-wrap: wrap; gap: 12px 20px; font-size: 12px; }  .journal-main { min-width: 0; }  .journal-photo { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 4px; outline: 1px solid light-dark(rgb(0 0 0 / .08), rgb(255 255 255 / .08)); outline-offset: -1px; }  .journal-kicker { font-size: 11px; line-height: 1.5; letter-spacing: .09em; text-transform: uppercase; }  .journal-title { max-width: 12ch; font-size: clamp(28px, 4.8vw, 36px); line-height: 1.08; font-weight: 400; letter-spacing: -.04em; }  .journal-deck { max-width: 44ch; font-size: 14px; line-height: 1.7; }  .journal-meta { font-size: 11px; line-height: 1.5; }  .journal-read { border-top: 1px solid var(--color-line-subtle); padding-top: 4px; }  .journal-read > summary { width: fit-content; padding: 10px 0; cursor: pointer; font-size: 13px; }  .journal-copy { display: grid; gap: 12px; padding-top: 6px; font-size: 14px; line-height: 1.8; }  .journal-aside { min-width: 0; border-top: 1px solid var(--color-line-subtle); }  .journal-aside-title { font-size: 23px; line-height: 1.2; letter-spacing: -.03em; font-weight: 400; }  .journal-story { border-top: 1px solid var(--color-line-subtle); }  .journal-story > summary { display: flex; align-items: start; justify-content: space-between; gap: 12px; padding-block: 20px; cursor: pointer; list-style: none; }  .journal-story > summary::-webkit-details-marker { display: none; }  .journal-story-title { margin-top: 7px; font-size: 16px; line-height: 1.4; font-weight: 400; letter-spacing: -.015em; }  .journal-story-image { display: block; width: 64px; height: 76px; object-fit: cover; border-radius: 3px; }  .journal-story-arrow { font-size: 20px; line-height: 1; }  .journal-story[open] .journal-story-arrow { transform: rotate(45deg); }  .journal-story > p { padding-bottom: 20px; font-size: 13px; line-height: 1.8; }  .journal-about { display: grid; gap: 8px; padding: 20px 24px; border-top: 1px solid var(--color-line-subtle); font-size: 12px; line-height: 1.7; }  .journal-about p { max-width: 48ch; }  @media (width >= 38rem) {    .journal-grid { display: grid; grid-template-columns: 1.6fr 1fr; }    .journal-aside { border-top: 0; border-left: 1px solid var(--color-line-subtle); }    .journal-about { grid-template-columns: 1fr auto; align-items: center; gap: 24px; }  }  @media (width < 24rem) {    .journal-head { padding: 20px; }    .journal-edition-label { display: none; }    .journal-nav { gap: 16px; }  }}
생성된 CSS
@font-face {  font-family: Geist;  font-style: normal;  font-weight: 100 900;  font-display: swap;  src: url("/fonts/GeistVariable.woff2") format("woff2");}@layer components {  .journal {    font-family: var(--font-family-sans);    font-size: 13px;    font-weight: 350;    line-height: 1.6;  }  .journal :where(h2, h3, p) {    margin: 0;  }  .journal a {    color: inherit;    text-decoration: none;  }  .journal a:hover {    text-underline-offset: .2em;    text-decoration: underline;  }  .journal :focus-visible {    outline: 2px solid var(--color-brand);    outline-offset: 4px;  }  .journal-head {    border-bottom: 1px solid var(--color-line-subtle);    padding: 20px 24px;  }  .journal-brand {    letter-spacing: -.06em;    font-size: 25px;    font-weight: 500;    line-height: 1;  }  .journal-edition {    letter-spacing: .1em;    text-transform: uppercase;    font-size: 10px;  }  .journal-nav {    flex-wrap: wrap;    gap: 12px 20px;    font-size: 12px;    display: flex;  }  .journal-main {    min-width: 0;  }  .journal-photo {    aspect-ratio: 16 / 9;    object-fit: cover;    outline-offset: -1px;    border-radius: 4px;    outline: 1px solid light-dark(#00000014, #ffffff14);    width: 100%;    height: auto;    display: block;  }  .journal-kicker {    letter-spacing: .09em;    text-transform: uppercase;    font-size: 11px;    line-height: 1.5;  }  .journal-title {    letter-spacing: -.04em;    max-width: 12ch;    font-size: clamp(28px, 4.8vw, 36px);    font-weight: 400;    line-height: 1.08;  }  .journal-deck {    max-width: 44ch;    font-size: 14px;    line-height: 1.7;  }  .journal-meta {    font-size: 11px;    line-height: 1.5;  }  .journal-read {    border-top: 1px solid var(--color-line-subtle);    padding-top: 4px;  }  .journal-read > summary {    cursor: pointer;    width: fit-content;    padding: 10px 0;    font-size: 13px;  }  .journal-copy {    gap: 12px;    padding-top: 6px;    font-size: 14px;    line-height: 1.8;    display: grid;  }  .journal-aside {    border-top: 1px solid var(--color-line-subtle);    min-width: 0;  }  .journal-aside-title {    letter-spacing: -.03em;    font-size: 23px;    font-weight: 400;    line-height: 1.2;  }  .journal-story {    border-top: 1px solid var(--color-line-subtle);  }  .journal-story > summary {    cursor: pointer;    justify-content: space-between;    align-items: start;    gap: 12px;    padding-block: 20px;    list-style: none;    display: flex;  }  .journal-story > summary::-webkit-details-marker {    display: none;  }  .journal-story-title {    letter-spacing: -.015em;    margin-top: 7px;    font-size: 16px;    font-weight: 400;    line-height: 1.4;  }  .journal-story-image {    object-fit: cover;    border-radius: 3px;    width: 64px;    height: 76px;    display: block;  }  .journal-story-arrow {    font-size: 20px;    line-height: 1;  }  .journal-story[open] .journal-story-arrow {    transform: rotate(45deg);  }  .journal-story > p {    padding-bottom: 20px;    font-size: 13px;    line-height: 1.8;  }  .journal-about {    border-top: 1px solid var(--color-line-subtle);    gap: 8px;    padding: 20px 24px;    font-size: 12px;    line-height: 1.7;    display: grid;  }  .journal-about p {    max-width: 48ch;  }  @media (width >= 38rem) {    .journal-grid {      grid-template-columns: 1.6fr 1fr;      display: grid;    }    .journal-aside {      border-top: 0;      border-left: 1px solid var(--color-line-subtle);    }    .journal-about {      grid-template-columns: 1fr auto;      align-items: center;      gap: 24px;    }  }  @media (width < 24rem) {    .journal-head {      padding: 20px;    }    .journal-edition-label {      display: none;    }    .journal-nav {      gap: 16px;    }  }}@layer theme {  :root,  :host {    --color-zinc-30: oklch(78% .0025 265);    --color-zinc-40: oklch(68.6% .0028 265);    --color-zinc-60: oklch(48.6% .003 265);    --color-zinc-70: oklch(39.8% .0028 265);    --color-zinc-90: oklch(24.6% .0022 265);    --color-zinc-100: oklch(15% .0016 265)  }  :root,  :host {    --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90));    --color-line-subtle: light-dark(oklch(0% 0 none / .06), oklch(100% 0 none / .06));    --color-text-body: light-dark(var(--color-zinc-70), var(--color-zinc-30));    --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white));    --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40))  }  :root,  :host {    --color-white: oklch(100% 0 none)  }  :root,  :host {    --font-family-sans: var(--font-sans, ui-sans-serif), system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";    --radius-lg: .5rem;    --spacing-sm: .75rem;    --spacing-md: 1rem;    --spacing-lg: 1.5rem  }  :root,  :host {    --shadow-sm: 0 0 0 1px light-dark(oklch(0% 0 none / .04), oklch(100% 0 none / .05)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24))  }  :root,  :host {    --color-brand: light-dark(#665442, #d7c2a6);    --spacing-story: 1.5rem  }  :root,  :host {    --font-sans: "Geist"  }}@layer utilities {  .items-center {    align-items: center  }  .bg-surface-raised {    background-color: var(--color-surface-raised)  }  .r-lg {    border-radius: var(--radius-lg)  }  .shadow-sm {    box-shadow: var(--shadow-sm)  }  .fg-brand {    color: var(--color-brand)  }  .fg-text-body {    color: var(--color-text-body)  }  .fg-text-muted {    color: var(--color-text-muted)  }  .fg-text-strong {    color: var(--color-text-strong)  }  .block {    display: block  }  .flex {    display: flex  }  .grid {    display: grid  }  .flex-wrap {    flex-wrap: wrap  }  .gap-md {    gap: var(--spacing-md)  }  .gap-sm {    gap: var(--spacing-sm)  }  .justify-between {    justify-content: space-between  }  .overflow-hidden {    overflow: hidden  }  .p-story {    padding: var(--spacing-story)  }  .pb-lg {    padding-bottom: var(--spacing-lg)  }}
저널의 너비를 바꾸거나 테마를 전환하고 글을 열어 보세요. Master CSS와 네이티브 CSS로 만들었으며, 사진은 이 예제를 위해 생성했습니다.

미리보기 너비를 줄이면 사이드바가 본문의 읽기 흐름에 합류합니다. 글을 열면 페이지가 그에 맞춰 펼쳐집니다. 테마를 전환하면 강조색이 달라지고, 공통 간격은 레이아웃의 리듬을 유지합니다. 미리보기 안에서 HTML과 CSS도 확인할 수 있습니다.

#클래스 하나에 더 많은 의도를 담기

Master CSS에서는 스타일을 요소 가까이에서 결정할 수 있습니다. 레이아웃에는 flex와 grid를, 프로젝트 토큰에는 gap-md와 p-lg를 쓰세요. 선택자와 조건은 그 대상이 되는 클래스에 붙입니다.

<a class="inline-flex px-md py-xs r-md fg-blue-60 fg-blue-70:hover@sm"   href="/journal">  Read the journal</a>

fg-blue-70:hover@sm을 왼쪽에서 오른쪽으로 읽어 보세요. sm 중단점 이상에서 마우스를 올렸을 때 blue-70 색상 토큰을 사용합니다. 프리셋에서 이 기준값은 52.125rem입니다. Master CSS가 규칙을 생성하면 브라우저가 선택자와 미디어 쿼리를 평가합니다.

클래스 하나에 담긴 정확한 조건

클래스 하나에 값, 선택자, 조건을 함께 적습니다. 브라우저가 생성된 선택자와 미디어 쿼리를 평가합니다.

텍스트 설명
  1. fg-blue-70은 color: var(--color-blue-70)을 선택합니다. :hover는 클래스가 있는 요소에 호버 상태를 붙입니다.
  2. @sm은 프리셋의 미디어 조건 (width >= 52.125rem)을 사용합니다. 쿼리와 호버 조건이 모두 충족되어야 합니다.

의도에 맞는 표현을 고르세요.

클래스의미
p-lg--spacing-lg 토큰의 값을 패딩으로 적용합니다.
m(0)마진을 0으로 설정합니다.
p(16)패딩을 16px로 설정합니다.
grow(1)단위 없는 플렉스 증가 비율 1을 적용합니다.
grid-cols(3)너비가 같은 그리드 열 세 개를 만듭니다.
(aspect-ratio:4/3)네이티브 CSS 속성과 값을 직접 지정합니다.

숫자 함수의 값은 해당 속성에 따라 해석됩니다. p(16)은 길이이고 grow(1)에는 단위가 없습니다. 네이티브 선언에도 CSS 함수와 사용자 지정 속성을 사용할 수 있습니다. (width:var(--progress))를 쓰면 클래스는 그대로 두고 JavaScript로 값을 갱신할 수 있습니다.

클래스를 조합하는 다른 방법
클래스의미
fg-blue-60/.5 · -mt-sm색상의 불투명도를 50%로 설정하거나 간격 토큰을 음수로 적용합니다.
fg-blue-60:hover>a · fg-blue-60_a부모에 마우스를 올렸을 때 바로 아래 자식 링크에 적용하거나, 모든 하위 링크에 적용합니다.
grid@supports(display:grid)규칙을 네이티브 기능 쿼리로 감쌉니다.
(opacity:0)@starting-styleCSS 전환이 시작될 때의 불투명도를 설정합니다.
p-md!패딩 선언에 우선 표시를 붙입니다.

:focus-visible, :has(), ::before 같은 선택자는 @media(...), @supports(...), @container(...)와 함께 쓸 수 있습니다. 조건을 여러 개 쓰면 중첩되므로 모두 충족되어야 합니다. 결과는 직접 검사할 수 있는 일반 CSS입니다.

구문 튜토리얼에서 직접 시작해 보세요.

#나만의 디자인 시스템

프리셋의 색상, 간격, 타이포그래피, 모서리 반경, 그림자, 모션 토큰에서 시작하세요. 인터페이스의 개성이 뚜렷해지면 @theme으로 그 결정에 이름을 붙이세요.

#넓은 색 영역을 담은 팔레트

17개 색상 계열에 각각 11단계의 색이 있습니다. 프리셋은 OKLCH를 사용하며 sRGB를 넘어 Display P3 색 영역에 이르는 색도 포함합니다. 각 열은 밝은색에서 어두운색으로 이어집니다. 실제로 보이는 색의 범위는 디스플레이에 따라 달라집니다.

빨간색에서 진홍색까지 17개 색상 계열이 열을 이루고, 각 열은 밝은색에서 어두운색까지 11단계로 이어집니다. 호환되는 디스플레이에서는 OKLCH 값의 넓은 색 영역을 볼 수 있습니다. 실제 표시 범위는 화면에 따라 달라집니다.

fg-blue-50이나 bg-green-90처럼 특정 색을 바로 쓰거나 그 역할을 나타내는 이름을 정의할 수 있습니다. Fieldwork는 강조색과 글 안쪽 여백을 공통으로 사용합니다. 앱 스타일시트의 @import '@master/css' 뒤에 다음 정의를 추가하세요.

나만의 색을 적용한 현장 노트

@theme {  --color-brand: light-dark(#665442, #d7c2a6);  --spacing-story: 1.5rem;}
생성된 CSS
@layer theme {  :root,  :host {    --color-zinc-30: oklch(78% .0025 265);    --color-zinc-40: oklch(68.6% .0028 265);    --color-zinc-60: oklch(48.6% .003 265);    --color-zinc-70: oklch(39.8% .0028 265);    --color-zinc-90: oklch(24.6% .0022 265);    --color-zinc-100: oklch(15% .0016 265)  }  :root,  :host {    --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90));    --color-text-body: light-dark(var(--color-zinc-70), var(--color-zinc-30));    --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white));    --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40))  }  :root,  :host {    --color-white: oklch(100% 0 none)  }  :root,  :host {    --font-weight-regular: 400;    --font-size-xs: .75rem;    --font-size-sm: .875rem;    --font-size-2xl: 1.5rem;    --text-xs: var(--font-size-xs);    --text-xs--line-height: 1.5;    --text-xs--letter-spacing: 0;    --text-sm: var(--font-size-sm);    --text-sm--line-height: 1.5;    --text-sm--letter-spacing: 0;    --text-2xl: var(--font-size-2xl);    --text-2xl--line-height: 1.35;    --text-2xl--letter-spacing: -.015em;    --radius-lg: .5rem;    --spacing-xs: .5rem;    --spacing-sm: .75rem;    --container-sm: 24rem  }  :root,  :host {    --shadow-sm: 0 0 0 1px light-dark(oklch(0% 0 none / .04), oklch(100% 0 none / .05)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24))  }  :root,  :host {    --color-brand: light-dark(#665442, #d7c2a6);    --spacing-story: 1.5rem  }}@layer utilities {  .text-2xl {    font-size: var(--text-2xl);    line-height: var(--text-2xl--line-height, normal);    letter-spacing: var(--text-2xl--letter-spacing, normal)  }  .text-sm {    font-size: var(--text-sm);    line-height: var(--text-sm--line-height, normal);    letter-spacing: var(--text-sm--letter-spacing, normal)  }  .text-xs {    font-size: var(--text-xs);    line-height: var(--text-xs--line-height, normal);    letter-spacing: var(--text-xs--letter-spacing, normal)  }  .bg-surface-raised {    background-color: var(--color-surface-raised)  }  .r-lg {    border-radius: var(--radius-lg)  }  .shadow-sm {    box-shadow: var(--shadow-sm)  }  .fg-brand {    color: var(--color-brand)  }  .fg-text-body {    color: var(--color-text-body)  }  .fg-text-muted {    color: var(--color-text-muted)  }  .fg-text-strong {    color: var(--color-text-strong)  }  .grid {    display: grid  }  .font-regular {    font-weight: var(--font-weight-regular)  }  .gap-sm {    gap: var(--spacing-sm)  }  .m\(0\) {    margin: calc(0 * 1px)  }  .mx-auto {    margin-inline: auto  }  .max-w-sm {    max-width: var(--container-sm)  }  .overflow-hidden {    overflow: hidden  }  .p-story {    padding: var(--spacing-story)  }  .py-xs {    padding-block: var(--spacing-xs)  }  .pt-sm {    padding-top: var(--spacing-sm)  }  .\(cursor\:pointer\) {    cursor: pointer  }}
색 구성표를 바꾸면 강조색과 표면색이 함께 바뀌고 글의 간격은 유지됩니다. 현장 노트를 열어 이어서 읽어 보세요.

fg-brand는 --color-brand의 값을, p-story는 --spacing-story의 값을 사용합니다. 토큰 하나를 바꾸면 그 토큰을 사용하는 모든 요소가 갱신됩니다. 아래 두 카드는 색 구성표에 따라 강조색이 달라져도 안쪽 여백은 모두 1.5rem입니다.

공통 토큰과 두 가지 색 구성표

두 카드는 같은 마크업을 사용합니다. 네이티브 light-dark()가 강조색을 선택하며, 측정 표시에서 p-story로 적용된 안쪽 여백이 모두 1.5rem임을 볼 수 있습니다.

텍스트 설명
  1. 스타일시트는 --color-brand를 light-dark(#665442, #d7c2a6)으로, --spacing-story를 1.5rem으로 정의합니다.
  2. 텍스트 패널 두 개에 fg-brand와 p-story를 적용합니다. 밝은 패널은 첫 번째 강조색을, 어두운 패널은 두 번째 강조색을 사용합니다. 글과 레이아웃, 1.5rem 패딩은 동일합니다.

네이티브 light-dark()는 현재 color-scheme에 맞는 강조색을 선택합니다. 프리셋에서 역할이 정해진 표면색과 텍스트 색도 같은 색 구성표를 따릅니다. 지금 값이 우연히 같다는 이유가 아니라, 함께 바뀌어야 하는 디자인 결정을 기준으로 토큰 이름을 붙이세요.

#반응형 조건에 이름 붙이기

뷰포트 조건에는 @custom-media로, 컨테이너 조건에는 @custom-container로 이름을 붙이세요.

@custom-media --reading (width >= 60rem);@custom-container --story-wide (width >=28rem);
<section class="container-inline (container-name:story)">  <article class="grid gap-md p-md p-lg@reading grid-cols(2)@container(story_--story-wide)">    <h2 class="m(0)">A layout that knows its space</h2>    <p class="m(0)">Two columns when this container has room.</p>  </article></section>
생성된 CSS
@layer theme {  :root,  :host {    --spacing-md: 1rem;    --spacing-lg: 1.5rem  }}@layer utilities {  .container-inline {    container-type: inline-size  }  .grid {    display: grid  }  .gap-md {    gap: var(--spacing-md)  }  .m\(0\) {    margin: calc(0 * 1px)  }  .p-md {    padding: var(--spacing-md)  }  .\(container-name\:story\) {    container-name: story  }  @container story (width >= 28rem) {    .grid-cols\(2\)\@container\(story_--story-wide\) {      display: grid;      grid-template-columns: repeat(2, minmax(0, 1fr))    }  }  @media (width >= 60rem) {    .p-lg\@reading {      padding: var(--spacing-lg)    }  }}

p-lg@reading은 뷰포트 너비에 반응하고, grid-cols(2)@container(story_--story-wide)는 부모인 story 컨테이너의 너비에 반응합니다. 하나는 페이지 너비를 기준으로 하고, 다른 하나는 컴포넌트에 주어진 공간을 기준으로 합니다. 클래스에서 _는 값이나 쿼리 안의 공백을 나타내므로 @container(story_--story-wide)는 story 컨테이너를 --story-wide 조건으로 판단합니다. 밑줄 문자 자체는 \_로 씁니다. 앱 스타일시트에서 같은 조건을 @media (--reading)과 @container story (--story-wide)로 쓸 수 있습니다. Master CSS는 두 방식 모두 일반 CSS 쿼리로 펼칩니다.

테마 토큰과 색상에서 프로젝트의 기반을 만드는 방법을 살펴보세요.

#네이티브 CSS로 패턴 재사용하기

글 링크, 탐색 항목, 대화상자에는 각각의 CSS를 작성할 수 있습니다. 프리셋은 theme, base, defaults, components, utilities라는 다섯 개의 캐스케이드 레이어를 정의합니다. 공통 패턴은 components에 두고 특정 요소만 조정할 때는 유틸리티를 쓰세요.

@layer components {  .story-link {    display: inline-flex;    padding-inline: 1rem;    padding-block: 0.5rem;    border-radius: 0.375rem;    background-color: var(--color-surface-raised);    color: var(--color-text-body);    text-decoration: none;  }}
<a class="story-link" href="/journal">Read the journal</a><a class="story-link px-lg" href="/journal">Read the journal</a>
생성된 CSS
@layer components {  .story-link {    background-color: var(--color-surface-raised);    color: var(--color-text-body);    border-radius: .375rem;    padding-block: .5rem;    padding-inline: 1rem;    text-decoration: none;    display: inline-flex;  }}@layer theme {  :root,  :host {    --color-zinc-30: oklch(78% .0025 265);    --color-zinc-70: oklch(39.8% .0028 265);    --color-zinc-90: oklch(24.6% .0022 265)  }  :root,  :host {    --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90));    --color-text-body: light-dark(var(--color-zinc-70), var(--color-zinc-30))  }  :root,  :host {    --color-white: oklch(100% 0 none)  }  :root,  :host {    --spacing-lg: 1.5rem  }}@layer utilities {  .px-lg {    padding-inline: var(--spacing-lg)  }}

두 링크는 .story-link를 공유합니다. 두 번째 링크에 px-lg를 추가하면 가로 패딩이 1rem에서 1.5rem으로 늘어납니다. 일반 선언의 경우 뒤에 오는 utilities 레이어가 components보다 우선합니다.

공통 컴포넌트에 개별 요소 조정하기

두 레이어의 일반 선언이 경쟁할 때는 utilities가 components보다 우선합니다. 파란 띠는 패딩을 확인하기 위한 표시입니다.

텍스트 설명
  1. .story-link 컴포넌트는 components 레이어에 가로 패딩 1rem을 정의합니다. 두 링크에 같은 모양과 타이포그래피를 적용합니다.
  2. 두 번째 링크는 utilities 레이어의 px-lg를 추가합니다. 양쪽 패딩이 각각 1.5rem으로 늘어나고 링크 텍스트는 그대로입니다.

#무엇을 재사용할지 정하기

공유할 대상에 맞는 방법을 고르세요.

공유할 대상방법
디자인 값@theme의 토큰
컴포넌트 스타일@layer components의 네이티브 클래스
필요할 때 생성하는 클래스@utility 정의
선언과 조건@apply로 펼치는 @mixin 정의

믹스인을 정의해도 클래스는 생기지 않습니다. 이 예제에서 --action-row는 네이티브 컴포넌트와 필요할 때 생성하는 유틸리티에 같은 선언을 제공합니다.

@mixin --action-row {  display: inline-flex;  align-items: center;  gap: var(--spacing-xs);}@utility action-row {  @apply --action-row;}@mixin --hocus {  &:is(:hover, :focus-visible) {    @contents;  }}@layer components {  .story-action {    @apply --action-row;  }}
<a class="story-action underline@apply(--hocus)" href="/journal">Read a story</a><a class="action-row underline@apply(--hocus)" href="/archive">Browse the archive</a>
생성된 CSS
@layer components {  .story-action {    display: inline-flex;    align-items: center;    gap: var(--spacing-xs)  }}@layer theme {  :root,  :host {    --spacing-xs: .5rem  }}@layer utilities {  .action-row {    display: inline-flex;    align-items: center;    gap: var(--spacing-xs)  }  .underline\@apply\(--hocus\):is(:hover, :focus-visible) {    text-decoration-line: underline  }}

--hocus는 마우스를 올리거나 키보드로 포커스를 받았을 때 적용하는 선택자를 공유합니다. underline@apply(--hocus)의 밑줄 선언이 믹스인의 @contents 자리에 들어갑니다. @apply는 유틸리티 클래스 목록이 아니라 믹스인 호출을 받습니다. Master CSS가 지원하는 정적 믹스인 구문은 컴파일되므로 브라우저의 네이티브 CSS 믹스인 지원은 필요하지 않습니다.

사용자 지정 유틸리티에는 정적 매개변수를 받거나 토큰 네임스페이스를 클래스 계열에 연결하는 정의도 쓸 수 있습니다. 이렇게 정의한 프로젝트 고유의 표현을 컴파일러와 도구에서 프리셋과 함께 사용할 수 있습니다.

패턴은 스타일 재사용에서, 우선 선언과 레이어 밖 스타일의 적용 순서는 캐스케이드 레이어에서 확인할 수 있습니다.

#CSS 전달 방식 고르기

앱에서 클래스 이름을 알 수 있는 시점에 맞춰 렌더링 모드를 선택하세요.

기본값은 정적 렌더링입니다. 빌드할 때 완전한 클래스 이름을 스캔해 스타일시트를 전달합니다. 브라우저에 Master CSS 런타임이 필요하지 않습니다.

사전 렌더링 모드(prerender)는 생성한 CSS를 HTML과 함께 전달합니다. 통합에서 렌더링을 마친 HTML을 읽을 수 있고 그 HTML에 페이지에 필요한 클래스가 모두 포함되어 있을 때 사용하세요. 브라우저에 Master CSS 런타임이 필요하지 않습니다.

런타임 렌더링은 브라우저에서 CSS를 생성합니다. 편집기, 플레이그라운드 또는 배포 후 제공되는 콘텐츠에서 빌드할 때 알 수 없었던 클래스 이름이 생길 수 있다면 이 방식을 쓰세요. 런타임과 점진적 렌더링 모두 브라우저에서 JavaScript를 사용합니다.

점진적 렌더링은 렌더링된 HTML에서 시작합니다. 초기 CSS를 먼저 전달하고, 이후 기존 규칙으로 처리할 수 없는 클래스가 나타나면 브라우저에서 규칙을 생성합니다. 유효한 사전 렌더링 CSS와 하이드레이션 매니페스트가 있으면 기본 런타임은 그때까지 엔진 로드를 미룰 수 있습니다.

같은 작성 방식과 네 가지 전달 경로

각 경로는 독립적으로 선택할 수 있습니다. 점진적 모드에서는 하이드레이션 데이터가 유효하면 엔진 로드를 미룰 수 있습니다.

텍스트 설명
  1. 정적 렌더링에서는 빌드 시 소스에서 완전한 클래스 이름을 스캔합니다. 브라우저는 CSS 스타일시트를 받고 Master CSS 런타임을 실행하지 않습니다.
  2. 사전 렌더링 모드는 렌더링된 HTML에서 초기 CSS를 생성해 해당 HTML과 함께 전달합니다. Master CSS 런타임은 필요하지 않습니다.
  3. 런타임 렌더링에서는 DOM에 연결된 요소의 클래스로 브라우저에서 CSS를 생성합니다. 새로 추가된 클래스 이름도 처리할 수 있으며 브라우저에서 JavaScript를 사용합니다.
  4. 점진적 렌더링에서는 렌더링된 HTML과 함께 초기 CSS를 전달합니다. 유효한 스타일시트와 페이지 매니페스트가 있으면 기본 런타임은 기존 규칙으로 처리할 수 없는 클래스가 나타날 때 엔진을 로드합니다.

Fieldwork는 정적 렌더링으로도 글을 펼치고 미디어 쿼리에 반응하며 테마를 바꿀 수 있습니다. 이런 상호작용에는 런타임 CSS 생성이 필요하지 않습니다.

렌더링 모드별로 알아둘 점

CLI와 Vite의 프로덕션 빌드는 조건에 맞는 로컬 가져오기를 생성 CSS에 합칩니다. 남아 있는 스타일시트와 글꼴, 이미지도 함께 제공되도록 전체 에셋을 배포하세요.

Next.js의 점진적 렌더링 어댑터는 빌드 시 생성된 HTML을 처리합니다. 이 모드에서 요청 시 동적으로 서버 렌더링하는 응답은 브라우저 런타임에 의존합니다.

소스 스캔은 임의의 문자열 보간을 평가하지 않습니다. 어댑터별 처리 범위와 응답 확인 방법은 렌더링 모드를 참고하세요.

언어의 핵심 동작은 Rust가 구현합니다. 브라우저에서는 JavaScript 엔진이 컴파일된 프로그램을 실행합니다. 두 구현의 출력이 일치하는지 검사합니다.

스캔, 공유 정의, 불필요한 CSS 제거 설정
지시문용도
@source / @source not스캔할 소스 파일을 추가하거나 제외합니다.
@safelist / @blocklist클래스 후보를 유지하거나 제외합니다.
@reference참조하는 파일의 네이티브 CSS는 가져오지 않고 정의만 공유합니다.
@prune native빌드가 클래스 사용 정보를 제공할 때 파일의 네이티브 스타일과 관리 대상 키프레임을 제거할 수 있도록 설정합니다.

기본적으로 네이티브 스타일은 유지됩니다. 스캔과 가지치기에서 스캔 및 제거 범위를 명시하는 방법을 확인할 수 있습니다.

#실제로 제공하는 CSS 크기 측정하기

v2.0.0 프로덕션 빌드의 Master CSS 가이드에는 인라인 스타일과 연결된 스타일시트를 합쳐 Brotli 압축 시 24.5 kB · 압축 전 110.4 kB의 CSS가 들어 있습니다. CSS의 대부분은 사이트 자체 컴포넌트의 스타일을 포함한 외부 스타일시트에 있습니다. 이 페이지에서 생성된 style#master-css 블록은 압축 전 833바이트입니다.

문서 페이지가 전달하는 CSS

문서 페이지 7개 · CSS 크기(kB)

압축 전
Master CSS
110.4 kB
Angular
151.8 kB
Vue.js
186.3 kB
Material UI
277.4 kB
Bootstrap
293.9 kB
Next.js
479.9 kB
Tailwind CSS
657.7 kB
2026. 11. 4.(UTC)에 공개 페이지를 기록했고 Master CSS는 v2.0.0 프로덕션 빌드에서 측정했습니다. 합계에는 인라인 스타일과 연결된 스타일시트가 포함됩니다. Brotli는 각 CSS 본문을 개별적으로 압축합니다. 각 보기는 별도의 눈금을 사용합니다. 1 kB = 1,000바이트. 페이지 측정 기록 · Master CSS v2.0.0 빌드 매니페스트 · 측정 방법과 페이지별 내역

페이지마다 디자인, 콘텐츠, 빌드 방식이 다릅니다. 차트는 최초 HTML의 인라인 스타일과 연결된 스타일시트의 크기를 집계합니다. 네트워크 전송량과 프레임워크 성능은 별도의 측정이 필요합니다. 동일한 조건에서 비교하려면 Master CSS와 Tailwind CSS로 구현한 동일한 UI를 참고하세요.

#프로젝트를 이해하는 도구

언어 서비스는 프로젝트 정의를 읽습니다. 편집기에서 토큰 이름을 자동 완성하고, 클래스에 마우스를 올려 생성된 CSS를 살펴보고, 색상도 다룰 수 있습니다. 사용자 지정 유틸리티와 토큰에도 같은 기능이 적용됩니다.

#클래스 구성을 한눈에 파악하기

VS Code 확장의 기본값인 active 모드에서는 커서가 놓인 클래스의 맥락에 맞춰 시맨틱 강조 표시가 바뀝니다. 주변 마크업은 그대로 두고 클래스의 값, 선택자, 쿼리, 단위, 우선 표시를 구별할 수 있습니다.

클래스를 구성하는 요소 읽기

<h2 class="m(0) text-lg">Field notes</h2><a class="fg-blue-70:hover@sm" href="/journal">  Read the journal</a>

클래스 값 위에 마우스를 올리거나 Tab 키로 포커스를 옮기면 구문을 구분하는 색이 나타납니다. 터치 기기에서는 색이 바로 보입니다. 이 웹 데모는 Master CSS의 시맨틱 분석을 사용합니다. VS Code에서는 편집기 커서 위치를 따릅니다.

색으로 구별하는 요소

첫 번째 클래스 목록에는 숫자 함수 m(0)과 토큰 유틸리티 text-lg가 들어 있습니다. 두 번째는 fg-blue-70, :hover 선택자, @sm 미디어 조건을 조합합니다. 강조 표시는 이 요소를 구별해 보여 주며 생성 CSS에는 영향을 주지 않습니다.

편집기의 강조 표시 모드 선택

masterCSS.embeddedSyntaxHighlighting을 active로 설정하면 커서가 놓인 클래스에 맞춰 강조 표시합니다. always는 발견된 모든 인라인 유틸리티를 강조하고, off는 인라인 유틸리티의 강조 표시를 끕니다. CSS 지시문 구문은 계속 강조 표시됩니다. 현재 VS Code 테마에서 시맨틱 강조 표시를 활성화해야 합니다.

#코드 린팅으로 실수 찾기

ESLint 통합은 프로젝트 정의를 기준으로 클래스를 검사합니다. 권장 설정은 다음 네 가지 규칙을 활성화합니다. 규칙 이름에는 @master/css/ 접두사가 붙습니다.

규칙용도
sort-classes클래스 순서를 통일하고 중복을 제거합니다.
no-invalid-classes잘못된 구문과 유효하지 않은 생성 CSS를 찾습니다.
no-conflicting-classes같은 변형 범위 안에서 서로 겹치는 선언을 보고합니다.
prefer-canonical-classes의미와 적용 순서를 유지하는 표준 표현을 제안합니다.

다음 두 규칙은 직접 활성화해야 합니다.

규칙팀 정책
no-unapproved-raw-values토큰을 지원하는 클래스에 원시 값을 쓰려면 토큰으로 바꾸거나 예외를 명시하게 합니다.
prefer-component-classes결합자를 사용하는 클래스가 여러 개 모이면 컴포넌트 사용을 제안합니다.

자동 수정은 순서 정리, 표준 표현으로의 교체, 완전히 덮어쓴 클래스 제거를 처리합니다. 알 수 없는 클래스 이름은 기본적으로 무시합니다. 원시 값과 컴포넌트 사용 정책은 작성자가 판단해야 합니다.

충돌을 찾아 안전하게 수정하기

생성된 CSS에서 p-md가 우선합니다. 안전한 수정으로 p-lg를 제거해도 결과는 같습니다.

텍스트 설명
  1. 충돌 진단은 <article class="p-md p-lg">Field notes</article>에서 생성 CSS의 "p-md"가 "p-lg"보다 우선하므로 "p-lg"를 제거하라고 안내합니다.
  2. 안전한 수정 결과는 <article class="p-md">Field notes</article>입니다. 편집기, 린팅 도구, MCP는 프로젝트의 해석된 정의를 참조합니다. 미리 본 수정을 적용할 때는 소스 파일의 해시도 검사합니다.

p-md p-lg에서는 도구가 p-lg가 덮어써진다고 진단하고 p-md를 남깁니다. HTML에 적은 클래스 순서는 CSS의 우선순위를 결정하지 않습니다. 선언이 일부만 겹칠 때는 직접 검토해야 합니다. 린팅을 통과한 뒤에도 브라우저 지원 여부와 시각적 결과를 별도로 확인하세요. 설정과 규칙 옵션은 코드 린팅에서 볼 수 있습니다.

#AI에 프로젝트 맥락 제공하기

로컬 Master CSS MCP 서버는 코딩 에이전트에도 같은 프로젝트 맥락을 제공합니다. 에이전트는 토큰과 유틸리티를 조회하고, 생성 CSS를 살펴보고, 린팅을 실행하고, 수정 내용을 미리 볼 수 있습니다. 미리 본 수정을 적용할 때 서버는 대상 파일이 미리보기를 만들 당시의 소스와 일치하는지 확인합니다.

여러 AI 도구에서 프로젝트 고유의 표현 사용하기

로컬 stdio로 AI 클라이언트를 연결하세요. 서버는 설정한 프로젝트 루트를 읽습니다.

텍스트 설명
  1. Codex, Claude Code, Cursor, Gemini CLI 중에서 MCP 클라이언트를 선택할 수 있습니다. 각 클라이언트가 검사할 프로젝트의 루트를 Master CSS 서버에 설정하세요.
  2. Master CSS MCP는 불러온 프로젝트 맥락을 바탕으로 매니페스트 조회, 생성 CSS 검사, 린트 진단, 수정 미리보기를 제공합니다.
  3. 테마 토큰, 유틸리티, 네이티브 CSS는 프로젝트에 있습니다. 미리 본 수정을 적용할 때는 소스 해시를 검사하며 도구 권한은 클라이언트가 관리합니다.

Codex, Claude Code, Cursor, Gemini CLI처럼 로컬 stdio MCP를 지원하는 클라이언트를 연결하세요. 프로젝트 루트를 설정하고 올바른 작업 공간을 불러왔는지 확인하세요. 도구 권한은 클라이언트가 관리합니다.

언어 서비스와 AI 코딩 워크플로에서 편집기와 에이전트에 프로젝트 맥락을 연결하는 방법을 살펴보세요.

#나만의 방식으로 만들어 보기

컴포넌트 하나로 시작해 보세요. 상태를 하나 추가하고, 공통으로 쓸 값에 이름을 붙이고, 그 결과를 살펴보세요. 실제로 반복해서 필요한 것이 생기면 그때 재사용할 대상을 정하세요.

Master CSS를 테스트하고 문제를 알려 주고 직접 만든 결과물을 공유해 주신 모든 분께 감사드립니다. 여러분이 2.0을 함께 만들어 주셨습니다.

Master CSS를 설치하고 구문 튜토리얼을 따라 첫 인터페이스를 만들어 보세요.

Master CSS가 도움이 되었다면 GitHub에서 스타를 눌러 더 많은 개발자가 찾을 수 있도록 도와주세요.