8. 아이콘 · 폰트 리소스
아이콘과 폰트는 파일이라서 토큰과 다르게 느껴지지만, 관리 대상인 건 똑같다. 통일되지 않으면 화면이 어색해지고, 잘못 불러오면 화면이 늦게 뜬다.
이 장에서 처음 나오는 말5개
lucide- shadcn/ui의 기본 아이콘 세트. 계속 추가되는 선(line) 스타일 오픈소스 아이콘 모음.
SVGScalable Vector Graphics- 수학적으로 그려지는 이미지 형식. 아무리 확대해도 안 깨지고, CSS로 색을 바꿀 수 있다.
`currentColor`- CSS 키워드. "지금 이 요소의 글자색"을 뜻한다. SVG 아이콘이 주변 글자색을 자동으로 따라오게 만드는 장치.
FOIT / FOUTFlash of Invisible / Unstyled Text- 웹폰트를 내려받는 동안 글자가 안 보이거나(FOIT) 다른 폰트로 보였다가 바뀌는(FOUT) 현상.
서브셋Subset- 폰트 파일에서 필요한 글자만 잘라낸 것. 한글 폰트를 실용적으로 쓰려면 필수.
아이콘 리소스
섹션 제목: “아이콘 리소스”왜 세트를 통일해야 하는가
섹션 제목: “왜 세트를 통일해야 하는가”아이콘은 저마다 선 굵기, 모서리 처리, 여백 비율이 다르다. 두 세트를 섞으면 개별로는 멀쩡한데 나란히 놓으면 어색하다.
{/* ❌ 섞으면 굵기와 크기가 안 맞는다 */}import { Search } from 'lucide-react'import { FaUser } from 'react-icons/fa'import { MdSettings } from 'react-icons/md'{/* ✅ 한 세트에서만 */}import { Search, User, Settings } from 'lucide-react'세트 선택도 테마 결정이다. 프로젝트당 하나로 정하고 리뷰에서 지킨다.
lucide 쓰기
섹션 제목: “lucide 쓰기”init이 기본으로 깔아 주는 세트다.
import { Search, Loader2 } from 'lucide-react'
<Search className="size-4" /><Loader2 className="size-4 animate-spin" />| 규칙 | 이유 |
|---|---|
크기는 size-4(16px) 기본 | 14px 글자 옆에 놓았을 때 균형이 맞는다. 버튼 안 아이콘은 대부분 이것 |
| 색은 지정하지 않는다 | lucide는 stroke="currentColor"라서 글자색을 자동으로 따라온다 |
aria-hidden | 장식용 아이콘은 화면낭독기가 읽지 않게 한다 |
| 의미 있는 아이콘엔 라벨 | 아이콘만 있는 버튼은 aria-label="검색"이 필요하다 |
크기 규칙
섹션 제목: “크기 규칙”| 클래스 | 크기 | 어디에 |
|---|---|---|
size-3 | 12px | 배지 안, 아주 작은 표시 |
size-4 | 16px | 기본. 버튼·메뉴·입력창 |
size-5 | 20px | 카드 헤더, 조금 강조 |
size-6+ | 24px+ | 빈 화면 안내, 히어로 |
다른 세트를 쓰고 싶으면
섹션 제목: “다른 세트를 쓰고 싶으면”components.json에 등록하면 CLI가 그 세트로 컴포넌트를 받아 온다.
{ "iconLibrary": "tabler" }새 프로젝트라면 shadcn/create나 preset에서 고르는 편이 안전하다.
이미 받은 컴포넌트는 import와 아이콘 이름이 남아 있으므로, 설정 필드만 바꾸지 말고
CLI의 현재 migration/preset 흐름으로 diff를 확인한다.
폰트 리소스
섹션 제목: “폰트 리소스”폰트는 언제 어떻게 내려받게 할 것인가가 전부다. 잘못하면 화면이 늦게 뜨거나 덜컹거린다.
무슨 문제가 생기나
섹션 제목: “무슨 문제가 생기나”첫 방문처럼 폰트가 캐시에 없을 때 브라우저는 대기 중 글자를 숨기거나 폴백으로 보여 준다. 대부분의 본문에서는 폴백을 먼저 보여 주는 쪽이 낫다 — 글자가 안 보이는 것보다 낫기 때문이다.
@font-face { font-family: 'Pretendard Variable'; src: url('/fonts/pretendard.woff2') format('woff2'); font-display: swap; /* ← 기본 폰트로 먼저 보여준다 = FOUT 선택 */}세 가지 방법
섹션 제목: “세 가지 방법”@theme inline { --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;}- 내려받을 게 없다. FOIT도 FOUT도 없다
- 기기마다 글꼴이 다르다 — 브랜드 통일이 안 된다
- 관리도구·내부 시스템이라면 최선의 선택
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })
export default function RootLayout({ children }) { return ( <html className={inter.variable}> <body className="font-sans">{children}</body> </html> )}- 빌드 시점에 폰트 파일을 내 서버로 가져온다 — 구글 서버 요청이 사라진다
font-display: swap과 폴백 크기 보정을 자동으로 해 준다- 한글 폰트는 구글 폰트에 있는 것만 가능하다 (Noto Sans KR 등)
/* Pretendard가 공식 제공하는 CSS를 불러온다 */@import url('https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
@theme inline { --font-sans: 'Pretendard Variable', system-ui, sans-serif;}- 폰트를 수백 개 조각으로 쪼개 두고, 페이지에 실제 쓰인 글자의 조각만 내려받는다
- 수 MB짜리 한글 폰트가 페이지당 수십 KB로 줄어든다
- 가변 폰트라 굵기 전부가 파일 하나에 들어 있다 (6장)
- 위 예시는 외부 CDN 방식이다. 보안 정책·개인정보·가용성이 중요하면 같은 파일을 직접 호스팅한다
토큰에 연결한다
섹션 제목: “토큰에 연결한다”어느 방법을 쓰든 마지막은 똑같다 — 폰트를 토큰에 매달아 컴포넌트가 이름만 알게 한다.
-
폰트를 불러온다 (위 셋 중 하나)
-
토큰에 연결한다
@theme inline {--font-sans: 'Pretendard Variable', system-ui, sans-serif;--font-mono: ui-monospace, Menlo, monospace;} -
바탕에 한 번만 적용한다
<body className="font-sans antialiased"> -
컴포넌트는 아무것도 안 한다
font-sans는 상속되므로 개별 컴포넌트에 폰트 클래스를 붙일 일이 없다. 코드에 폰트가 필요한 자리에만font-mono를 준다.
8장 요약
섹션 제목: “8장 요약”- 아이콘 세트는 하나로 통일한다. 섞으면 선 굵기와 여백이 안 맞아 어색해진다
- lucide 아이콘은
currentColor라 글자색을 자동으로 따라온다 — 색 클래스를 붙이지 않는다 - 크기는
size-4(16px)가 기본, 버튼 안에서는shrink-0을 같이 준다 - 웹폰트가 아직 없을 때는 FOIT(안 보임) 또는 폴백 표시 정책이 필요하다.
font-display: swap은 폴백을 먼저 보인다 - 방법 셋 — 시스템 폰트(가장 빠름) ·
next/font(Next.js 기본) · 동적 서브셋(한글의 강력한 선택지) - 어느 방법이든 마지막은
--font-sans토큰에 연결 +<body>에 한 번 적용
참고 자료
섹션 제목: “참고 자료”- Pretendard 웹폰트 문서 — 공식 CDN·동적 서브셋·가변 폰트 경로
- Next.js Font 최적화 —
next/font의 셀프호스팅과 폴백 처리
9. 테마 두 벌 — 다크 모드같은 이름, 다른 값 — 그리고 첫 화면이 하얗게 번쩍이는 문제.