모든 글

앱 세 개를 하나의 결로: 플레인 CSS로 디자인 시스템을 만들었다

날씨앱·블로그·(예정)쇼핑몰. 스택이 다 다른데도 한 덩어리로 보이게 하려고, 빌드 도구 없이 순수 CSS와 custom property로 디자인 시스템 "Nacre"를 만든 이야기. 이름은 자개(螺鈿), 결은 수묵 — 한국의 정서를 화면에 심어봤습니다.

제 손에는 스택이 제각각인 웹 프로젝트가 몇 개 있어요.

날씨앱 today.nacre.kr은 React + Tailwind, 이 블로그는 Astro, 언젠가 붙일 Shop은 또 뭐가 될지 모르죠.

문제는 이것들이 전부 제 이름(nacre)을 달고 있다는 거예요.

그런데 하나씩 열어보면 버튼 모양도, 여백도, 글자 크기도 미묘하게 다 달라요.

같은 사람이 만든 티가 안 나요.

그래서 만든 게 Nacre라는 디자인 시스템이에요.

앱마다 다시 칠하지 말고, 한 번 정한 결을 공유하자는 거죠.

제일 먼저 정한 건 무엇으로 만들지였어요.

후보는 뻔했어요.

Tailwind 프리셋, MUI 같은 컴포넌트 라이브러리, Tokens Studio 류.

전부 접었어요.

이유는 하나예요.

소비하는 앱들의 스택이 통일돼 있지 않다.

Tailwind 설정을 공유하려면 모든 앱이 Tailwind여야 하고, React 컴포넌트 라이브러리는 Astro나 워드프레스에선 그대로 못 써요.

결을 공유하려고 만드는 건데 그 결을 쓰려면 특정 빌드 체인을 강요받는 건 앞뒤가 안 맞죠.

그래서 가장 바닥의 공통분모로 내려갔어요.

브라우저라면 무조건 이해하는 것 — 플레인 CSS + custom property.

<link rel="stylesheet" href="nacre-fonts.css" />  <!-- 글꼴 (선택·교체 가능) -->
<link rel="stylesheet" href="nacre.css" />         <!-- 시스템 본체 -->

<button class="nacre-btn nacre-btn--primary">확인</button>
<div class="nacre-card">…</div>

이 두 줄이면 순수 HTML이든 React든 Astro든 워드프레스든 똑같은 결이 입혀져요.

빌드 파이프라인도, npm install도 필요 없어요.

토큰은 :root의 custom property라 런타임에 바뀌고, 클래스 어휘(nacre-btn, nacre-card)는 어느 프레임워크에서나 문자열 그대로 동일하고요.

나중에 React 래퍼를 얹더라도 그건 편의 래퍼일 뿐, 결의 본체는 CSS 파일 하나예요.

10년쯤 퍼블리싱하며 프레임워크는 몇 번이고 갈아탔지만, class 하나로 스타일을 입히는 방식만큼은 한 번도 저를 배신한 적이 없거든요.

도구를 정했으면 다음은 정체성인데, 그 전에 이름부터 이야기해야겠어요.

Nacre(나크르)는 자개예요.

나전칠기(螺鈿漆器) — 옻칠한 검은 바탕에 전복·소라 껍데기를 얇게 오려 박아, 빛의 각도에 따라 영롱하게 반짝이는 그 공예.

어릴 적 장롱이나 문갑에서 본, 지금은 좀 잊고 지내는 그런 것들이요.

제가 이 브랜드에 담고 싶은 게 거기 있어요.

한국 특유의 정서, 그리고 잊고 지내던 아름다움.

도메인을 nacre로 잡은 것도 그래서고요.

그러니 디자인의 결도 서양식 플랫·네온이 아니라 자연스럽게 동양화 쪽, 수묵담채로 갔어요.

재밌는 건 자개와 수묵이 한 화면에서 만나는 방식이에요.

바탕과 글자는 옻칠한 먹 — 수묵의 절제죠.

순백·순흑이 아니라 살짝 바랜 한지(#f0ebe1)와 초록기 도는 먹빛(#1e211c), 다크 테마는 초록빛 깊은 먹색(#12140f)으로 뒤집고요.

강조는 청자(celadon) 초록, 자개의 반짝임이에요.

화면에서 유일하게 “색”이라 부를 만한 지점인데, 평소엔 가라앉아 있다가 상호작용할 때 은은하게 올라오게 했어요.

딱 빛 각도 따라 반짝이는 자개를 떠올린 거죠.

금(金)은 장식 전용이에요.

낙관 같은 가는 선과 테두리, 작은 마크에만.

본문 글자로는 안 써요 — 나전의 금속 상감을 떠올리면 돼요.

컨셉은 감성이지만, 토큰을 실제로 짜는 건 전부 공학이었어요.

당연할 줄 알았다가 틀린 게 많았고요.

강조색은 하나가 아니라 배경용과 글자용으로 쪼개야 했어요.

처음엔 강조색 하나로 버튼 배경도 칠하고 링크 글자도 칠하면 될 줄 알았는데, 청자 초록처럼 진한 색은 둘의 요구가 정반대예요.

버튼 배경으로 쓰려면 위에 얹는 크림색 글자가 읽혀야 하니 꽤 깊어야 하고, 반대로 한지 바탕 위 링크 글자로 쓰려면 그 깊은 값은 너무 진해서 눈이 아파요.

하나로 묶으면 둘 중 하나가 반드시 깨져요.

그래서 토큰을 나눴어요.

--nc-primary: var(--nc-brand);       /* 버튼·배지 배경 (중간 깊이) */
--nc-primary-text: var(--nc-accent); /* 링크·강조 글자 (바탕에서 읽히는 값) */

파생 색은 손으로 고르지 않고 계산시켰어요.

hover, active, 연한 선택 배경, 경계선, 포커스 링… 강조색 하나에 딸린 변주가 이렇게나 많은데, 예전엔 포토샵에서 하나하나 찍어 hex로 박았거든요.

그러면 테마 바꿀 때마다 전부 다시 찍어야 해요.

이번엔 씨앗 색 하나에서 color-mix로 나머지를 파생시켰어요.

--nc-brand: #3f6b58;  /* 씨앗 */
--nc-primary-hover:  color-mix(in oklab, var(--nc-brand), #000 14%);
--nc-primary-active: color-mix(in oklab, var(--nc-brand), #000 24%);
--nc-primary-subtle: color-mix(in oklab, var(--nc-brand) 16%, var(--nc-card));

oklab 공간에서 섞으니 밝기가 눈에 고르게 움직여요.

씨앗만 바꾸면 hover·active·subtle이 한꺼번에 따라오고요.

라이트/다크의 모든 색 대비는 WCAG AA(4.5:1)를 계산으로 검증해서 통과한 값만 썼어요.

”예뻐 보인다”가 아니라 “숫자로 읽힌다”를 기준으로.

@layer로 남의 CSS에 항상 지는 구조도 만들었어요.

디자인 시스템을 배포할 때 진짜 무서운 건 가져다 쓰는 앱의 기존 CSS와 특정도 싸움이 나는 거예요.

소비하는 쪽이 !important 지옥에 빠지죠.

그래서 시스템 규칙을 전부 @layer nacre 안에 넣었어요.

캐스케이드 레이어에 든 규칙은 레이어 없는(= 소비자의) CSS에 특정도와 무관하게 항상 져요.

그래서 가져다 쓰는 쪽이 .nacre-btn { ... } 한 줄만 써도 아무 수싸움 없이 덮어써지고요.

반대로 시스템 내부에선 절제 모드 규칙을 뒤 레이어(nacre-accent)에 둬서 다크 테마 토큰까지 확실히 이기게 했어요.

레이어는 순서가 곧 힘이라, 알고 나면 설계가 훨씬 편해져요.

그리고 저처럼 거의 무채색으로 가고 싶은 취향을 위해 ink(절제) 모드를 따로 뒀어요.

이 블로그가 딱 그렇고요.

<html data-nc-accent="ink"> 한 줄이면 강조 글자가 평소엔 먹색으로 가라앉아 있다가 :hover/:focus 할 때만 청자빛이 속삭이듯 올라와요.

정지는 무채, 상호작용은 청자 한 방울.

자개장롱이 평소엔 까맣다가 빛 드는 각도에서만 영롱해지는 것과 같은 원리예요.

이 블로그가 ink 모드의 첫 소비자고요.

정리된 결과만 보면 깔끔하지만, 여기까지 오는 길엔 버린 게 훨씬 많았어요.

키컬러는 원래 라벤더였어요.

처음엔 파랑–보라 계열(#8aa0ff쯤)로 갔는데, 예쁘긴 한데 “너무 중간 같다”는 느낌이 가시질 않았어요.

한국적 정서를 담자는데 라벤더는 국적이 없었거든요.

그래서 물 먹은 듯 깊은 초록, 도자기의 그 색 — 청자로 갈아탔어요.

이 한 번의 교체가 사실 시스템 전체의 톤을 결정했고요.

명조와 붓글씨체도 대봤지만 버렸어요.

”수묵이니까 붓글씨 느낌?”

싶어 serif를 본문에 얹어봤는데 오히려 촌스러웠어요.

수묵의 절제는 글자를 꾸며서가 아니라 여백으로 내는 거더라고요.

본문은 결국 가독성 좋은 Pretendard로 확정했어요.

제일 많이 버린 건 “자개의 반짝임”이었어요.

손이 닿으면 자개처럼 빛나게 하고 싶었는데 이게 쉽지 않았어요.

대여섯 가지를 만들어 나란히 비교했어요 — 빛이 한 번 스치기, 머물며 어른거리기, 무지갯빛 유막, 커서를 따라오는 빛, 숨 쉬듯 밝아지기.

커서 추적이 제일 감동적이었는데, 그게 빛나는 원을 옮기는 방식이라 버튼 모서리에서 반원이 삐져나왔어요.

그래서 테두리 선을 도는 빛(회전 그라데이션을 마스크로 링만 오려냄)으로.

이번엔 빛이 흰색으로 튀어서 또 버리고, 결국 자개색 자체가 밝아지는 쪽으로.

규칙 하나로 수렴했어요.

빛은 그 요소 자기 색에서 나오고, 표면 밝기의 반대로만 민다. (밝은 바탕 → 어둡게, 어두운 바탕 → 밝게.)

그래서 색값을 케이스마다 손으로 박지 않아요.

청자 버튼은 청자로, 무채 요소는 무채로 저절로 빛나요.

지금 이 글의 본문 링크에 마우스를 올려보면, 밑줄을 청자 빛이 흐르는 게 그 결과예요.

정직하게 남겨둔 한계도 있어요.

거의 흰 버튼 위에선 빛이 물리적으로 더 밝아질 수 없어서, 닦은 금속에 빛 스치듯 어두운 사선으로 갈 수밖에 없었고요.

커서 추적 빛은 감동적이었지만 모바일엔 hover가 없어 결국 접었어요.

대신 테두리를 도는 빛은 터치에서도 뜨고요.

지금 Nacre엔 버튼·카드·입력·배지·탭·표 같은 12종 컴포넌트, 라이트/다크 두 테마, 상태색까지 한 벌로 들어 있어요.

다음 목표는 날씨앱에서 Tailwind를 걷어내고 이 시스템으로 갈아끼우는 것.

돌아보면 이번 작업의 핵심은 화려한 기능이 아니라 무엇을 공통분모로 둘 것인가였어요.

빌드 도구도 프레임워크도 유행 따라 바뀌지만, 브라우저가 CSS와 custom property를 읽는 방식은 잘 안 바뀌어요.

가장 안 변하는 바닥에 결을 심어두면 그 위에 무슨 앱을 올리든 같은 얼굴을 하게 되더라고요.

그리고 그 바닥에 심고 싶었던 건 결국 기술이 아니라 정서였어요.

자개의 반짝임, 수묵의 여백, 잊고 지내던 그 결.

기술은 유행을 타지만 그런 정서는 오래 남거든요.

N
NACRE
Writing on less · Seoul
다른 글 보기

댓글

  1. 불러오는 중…