birchholt 직접 해보고 남기는 기록

Hugo 코드 블록에 색이 하나도 없었습니다: noClasses=false로 두고 Chroma CSS를 안 만들었습니다

제 글에는 코드 블록이 많습니다. 그런데 어느 날 보니 코드 블록이 전부 본문 글씨와 같은 한 가지 색이었습니다. 대부분의 블록에 yaml, bash처럼 언어 이름을 붙여 썼으니 색이 칠해져 있을 줄 알았는데, 본문 문단과 똑같은 색으로 나오고 있었습니다.

같은 먹물 한 가지로만 채색된 식물 도감 페이지를 펼쳐 든 사람과 옆에 놓인 손대지 않은 물감 상자

원인은 설정 한 줄과 만들지 않은 파일 하나였습니다. 고치는 것 자체는 CSS(화면의 색과 모양을 정하는 파일)를 붙이면 끝납니다. 다만 공식 명령이 기본값으로 만들어 주는 CSS를 그대로 붙이면 다크 모드에서 글자가 배경에 묻힙니다. 아래 숫자는 2026-10-06에 Hugo v0.165.0으로 이 사이트 사본을 빌드해서 잰 것입니다.

하이라이트는 켜져 있었고, 색을 줄 CSS만 없었습니다

칸마다 색 이름표만 붙어 있고 물감은 비어 있는 나무 팔레트

Hugo는 Chroma라는 문법 강조기(코드의 키워드·문자열·주석을 구분해 색을 입히는 도구)를 품고 있습니다. 색을 입히는 방법은 두 가지입니다.

제 설정 파일은 두 번째였습니다. 사이트를 처음 만들 때(2026-08-26) 넣은 줄입니다.

[markup.highlight]
  noClasses = false
  lineNos = false

라이브 글 하나를 받아서 HTML을 보면 이름표는 제대로 붙어 있습니다.

$ curl -s https://birchholt.com/posts/deploy-automation/ | grep -o '<pre[^>]*>' | sort | uniq -c
   7 <pre tabindex=0 class=chroma>
   1 <pre tabindex=0>

class=chroma가 붙은 블록 안에는 <span class=k>(키워드), <span class=s2>(큰따옴표 문자열) 같은 조각이 줄줄이 들어 있습니다. 그런데 그 이름표에 색을 대응시켜 줄 CSS에는 chroma라는 글자가 0건이었습니다.

브라우저에서 실제로 칠해진 색(computed color, 브라우저가 최종 계산한 값)을 뽑아 봤습니다. 이 글의 코드 조각 208개가 라이트 모드에서는 전부 rgb(35, 32, 27), 다크 모드에서는 전부 rgb(236, 231, 221)였습니다. 둘 다 본문 문단 글자색과 똑같습니다. 코드 블록이 있는 16편으로 넓혀도 조각 1,218개에 쓰인 색이 모드마다 한 가지뿐이었습니다.

공식 문서도 같은 관계를 적어 둡니다 — noClasses는 「Whether to use inline CSS styles instead of an external CSS file. Default is true.」, 그리고 외부 CSS를 쓰려면 「set this value to false and generate the CSS file using the hugo gen chromastyles command」. Hugo · Syntax highlighting, 2026-10-06에 확인했습니다.

빌드는 성공하고 경고도 없습니다. 단색이어도 코드는 읽히니 눈에 띄지 않습니다. 그래서 한 달 넘게 몰랐습니다.

기본값 그대로 붙이면 다크 모드에서 글자가 사라집니다

밤이 되자 흰 벽에 붙인 흰 쪽지의 글씨가 어둠에 묻혀 보이지 않는 복도

문서대로 CSS를 만들어 사이트 CSS 끝에 붙였습니다.

hugo gen chromastyles > chroma.css

라이트 모드에서는 색이 생깁니다. 그런데 이 사이트는 운영체제 설정을 따라 다크 모드로 바뀝니다 (@media (prefers-color-scheme: dark)로 색 변수를 갈아끼우는 구조입니다). 다크 모드로 바꿔 보니 이렇게 됐습니다.

밝은 바탕에 밝은 글씨입니다. 글자와 배경의 대비비(밝기 차이 비율. WCAG는 본문 글자에 「at least 4.5:1」을 요구합니다)를 16편 전체에서 쟀습니다.

기본 CSS를 붙인 뒤4.5:1 미만 조각가장 낮은 대비
라이트 모드1,218개 중 93개2.42
다크 모드1,218개 중 470개1.08

1.08:1은 사실상 안 보이는 수준입니다. hugo version, | sed -E 같은 부분이 배경에 묻혔습니다.

라이트 모드에서도 낮은 이유는 기본 스타일의 주석 색(#60a0b0)이 회색 배경 위에서 연하기 때문입니다. 한 블록 기준으로는 2.58:1이었습니다.

여기서 하나 더 알게 됐습니다. 기본값이 두 개이고 서로 다릅니다. hugo gen chromastyles --help에는 --style 기본이 friendly로 나오고 설정 파일의 markup.highlight.style 기본은 문서상 monokai입니다. 그래서 스타일을 따로 정하지 않은 채 noClasses만 true↔false로 바꾸면 색 구성이 달라질 수 있습니다.

–mode로 라이트·다크를 따로 뽑아도 구두점이 남습니다

밝은 가지와 어두운 가지를 한 줄에 엮었는데 작은 매듭 몇 개만 엉뚱한 색으로 남은 화환

Hugo v0.164.0부터는 한 스타일에서 라이트용과 다크용을 따로 뽑는 --mode가 있습니다.

$ hugo gen chromastyles --style=friendly --mode=dark
ERROR style "friendly" does not have a "dark" mode

기본 스타일인 friendly에는 다크 짝이 없습니다. 짝이 있는 스타일을 골라야 합니다. github으로 바꾸니 양쪽이 다 나왔습니다(--mode=dark 결과는 --style=github-dark와 첫 줄 주석만 다르고 같았습니다).

「Some styles provide separate light and dark color palettes. Use the --mode flag to generate a stylesheet for a specific mode」 — Hugo · Syntax highlighting. vs도 다크가 없었고 vim은 반대로 라이트가 없었습니다.

그래서 라이트 CSS를 먼저 두고 다크 CSS를 미디어 쿼리 안에 통째로 넣었습니다.

/* github light 출력 */
@media (prefers-color-scheme: dark) {
  /* github dark 출력 */
}

다크 모드 결과는 1,218개 중 378개가 4.5:1 미만, 최저 1.20이었습니다. [, ], =가 안 보였습니다.

원인은 두 출력의 규칙 목록이 같지 않다는 데 있었습니다. 라이트 출력에는 .chroma .p { color:#1f2328 }(구두점)가 있는데 다크 출력에는 .p 규칙이 아예 없습니다. 그러면 다크 모드에서도 라이트의 짙은 회색(#1f2328)이 그대로 살아남습니다. 미디어 쿼리는 덮어쓸 뿐 지워 주지 않습니다.

두 출력을 스크립트로 비교해 라이트에만 있는 규칙을 정리하면 이렇습니다.

셀렉터 통째로 없음: .chroma .na  .chroma .nb  .chroma .bp  .chroma .p
속성만 없음:       .chroma .err 의 background-color, .chroma .ge 의 color

다크 블록 안에 이 값들을 되돌리는 줄을 넣었습니다.

@media (prefers-color-scheme: dark) {
  /* … github dark 규칙 … */
  .chroma .err { color:#f85149;background-color:transparent }
  .chroma .ge  { font-style:italic;color:inherit }
  .chroma .na  { color:inherit }
  .chroma .nb  { color:inherit }
  .chroma .bp  { color:inherit }
  .chroma .p   { color:inherit }
}

--modeSelector를 쓰면 셀렉터가 .dark .chroma …처럼 클래스 아래로 들어갑니다. 루트에 dark 클래스를 붙였다 뗐다 하는 사이트용입니다. 이 사이트처럼 운영체제 설정(미디어 쿼리)으로 바뀌는 사이트에는 맞지 않습니다. 반대 경우가 Hugo 포럼의 이 질문입니다 — 테마가 .dark 클래스로 다크를 켜서 미디어 쿼리가 안 먹었습니다(2026-10-06 기준 조회 4,271회). 사이트가 다크 모드를 무엇으로 켜는지부터 확인해야 합니다.

배경은 사이트에 맡기고, 스타일은 대비로 골랐습니다

같은 크기의 상자들이 모두 같은 자작나무 선반 위에 나란히 놓인 정돈된 작업실

두 출력을 이어붙인 버전에는 문제가 하나 더 있었습니다. 언어를 안 적은 코드 블록은 Chroma를 거치지 않아서 사이트 배경(#232019)이 되고 언어를 적은 블록은 github-dark 배경(#0d1117)이 됩니다. 한 글 안에서 코드 상자 색이 두 가지가 됩니다.

그래서 생성된 CSS에서 .bg 규칙과 .chroma의 배경색·기본 글자색을 걷어냈습니다. 상자는 원래 사이트 CSS가 칠하고 Chroma는 조각의 글자색만 칠합니다.

그렇게 하면 대비를 재는 기준도 사이트 배경이 됩니다. 짝이 있는 스타일 여덟 개를 --mode=light·--mode=dark로 뽑아 이 사이트 배경(라이트 #ffffff, 다크 #232019) 위에 놓고 실제로 쓰이는 조각 종류 26가지만 대상으로 최저 대비를 쟀습니다.

스타일라이트 최저다크 최저
github5.365.28
xcode5.073.65
gruvbox-light3.674.43
solarized-light2.673.02
catppuccin-latte2.603.33

양쪽 모두 4.5:1을 넘은 건 github 하나였습니다. xcode(다크)와 gruvbox-light(양쪽)는 주석 색이 걸렸습니다.

나머지 셋(tokyonight-day, monokailight, rose-pine-dawn)은 라이트 최저가 2.24~2.66이었습니다. 공백 조각(w)은 글자가 없으니 뺐습니다.

최종안을 사본에 넣고 16편을 브라우저로 다시 쟀습니다.

최종안4.5:1 미만최저쓰인 색
라이트0개5.369가지
다크0개5.288가지

코드 상자 102개의 높이 합은 붙이기 전과 같은 12,987px였습니다. 생성된 CSS에 들어 있는 .chroma .line { display:flex }가 줄 간격을 바꾸지 않았다는 뜻입니다. CSS 파일은 11,082바이트에서 18,050바이트로 늘었고 gzip으로 압축하면 늘어난 양은 1,450바이트입니다.

이 CSS를 운영 사이트에 반영하고(2026-10-07) 라이브에서 다시 쟀습니다. 라이브 CSS는 사본에서 만든 파일과 바이트 단위로 같았고 chroma 0건이던 파일에 .chroma 규칙이 148개(라이트 72개, 다크 76개) 들어갔습니다.

$ curl -s https://birchholt.com/css/main.89e4….css | grep -c '\.chroma \.k '
2

키워드 규칙이 라이트에 하나, 다크 블록 안에 하나입니다. 처음에 한 가지 색뿐이던 그 글의 조각 208개는 공백 조각을 빼고 세면 라이트 8가지, 다크 7가지 색이 됐습니다. 16편 전체로는 라이트·다크 모두 4.5:1 미만이 0개였고 최저 대비는 5.36과 5.28로 사본에서 잰 값과 같았습니다. 코드 상자 102개의 높이 합도 12,987px 그대로였습니다.

noClasses = true로 돌아가지 않은 이유

어디에 놓든 늘 같은 짙은 색 덮개를 쓴 상자 하나가 밝은 방과 어두운 방에 각각 놓인 모습

CSS를 관리하기 싫으면 noClasses를 기본값 true로 돌리면 됩니다. 사본에서 그렇게도 빌드해 봤습니다.

<pre tabindex=0 style=color:#f8f8f2;background-color:#272822;…>

기본 스타일 monokai의 짙은 배경이 HTML에 직접 박힙니다. 라이트 모드든 다크 모드든 같은 상자입니다. 인라인 스타일(태그에 직접 적힌 색)은 미디어 쿼리가 바꿀 수 없으니 다크 모드를 따라갈 방법이 없습니다. 게다가 언어 없는 블록은 사이트 배경을 쓰니, 라이트 모드에서 검은 상자와 흰 상자가 한 글에 섞입니다.

크기는 오히려 거의 같았습니다. 16편 HTML 합계가 353,581바이트에서 354,014바이트로 433바이트 늘었을 뿐입니다. 결정을 가른 건 크기가 아니라 다크 모드를 따라가느냐였습니다.

Hugo v0.165.0에는 템플릿 안에서 같은 CSS를 만들어 주는 css.ChromaStyles 함수도 새로 생겼습니다. 파일을 손으로 갱신하지 않아도 되는 길인데, 이번에 쓴 배경 걷어내기와 다크 되돌리기를 거기서도 똑같이 할 수 있는지는 시험하지 않았습니다.

코드 블록 색을 다시 볼 때 확인할 네 가지

작업대 위에 돋보기와 색 견본 카드, 밝은 등과 어두운 등을 나란히 놓은 두 손

이 글의 대비비는 WCAG 상대 휘도 공식으로 브라우저가 계산한 색을 넣어 구했습니다. Chromium 한 가지로만 쟀습니다.