# UI Design System — KRDS

> 어떤 스택에서든 동일한 톤을 이식하기 위한 단일 소스. 대한민국 범정부 디자인 시스템 KRDS(krds.go.kr) 공개 가이드라인을 웹 이식용으로 재해석한 문서다.
>
> **한 줄 정의**: **접근성과 디지털 포용이 최우선**인 공공 서비스 시스템. 정부 블루(`#246beb`)와 딥 네이비(`#003675`), 경고 레드(`#e71825`)의 절제된 3색 위에 Pretendard GOV 서체, 그리고 "모든 사용자가 이용할 수 있는가"라는 단일 기준으로 모든 결정을 내린다.

---

## 목차

1. [디자인 원칙](#1-디자인-원칙)
2. [색상 토큰](#2-색상-토큰)
3. [타이포그래피](#3-타이포그래피)
4. [레이아웃 & 형태](#4-레이아웃--형태)
5. [핵심 컴포넌트](#5-핵심-컴포넌트)
6. [인터랙션](#6-인터랙션)
7. [고대비(선명한 화면) 모드](#7-고대비선명한-화면-모드)
8. [접근성](#8-접근성)
9. [안티패턴](#9-안티패턴)
10. [프레임워크 어댑터](#10-프레임워크-어댑터)
11. [이식 가이드](#11-이식-가이드)

---

## 1. 디자인 원칙

| 원칙 | 정의 |
|---|---|
| **디지털 포용** | 연령·장애·기기·통신 환경과 무관하게 누구나 이용 가능해야 한다. 접근성은 기능이 아니라 전제. |
| **일관된 정부 경험** | 어느 기관 사이트든 같은 패턴이면 같은 방식으로 동작한다. 기관별 변형 최소화. |
| **신뢰의 블루** | 주 액션·링크는 정부 블루(`#246beb`) 단일. 유채색 남용은 공공 신뢰를 해친다. |
| **텍스트가 우선** | 아이콘·색상은 텍스트를 보조할 뿐, 단독으로 의미를 전달하지 않는다. |
| **명시적 상태** | 필수 입력·오류·완료를 항상 텍스트로 명시. 추측을 요구하지 않는다. |
| **낮은 라운드** | 6px 내외의 절제된 라운드. 장식적 곡률 지양. |
| **사용자 제어** | 글자 크기 조절(작게~가장 크게), 화면 모드(기본/선명하게), TTS 등 표시 설정은 사용자의 권리. |

---

## 2. 색상 토큰

### 2-1. 브랜드 & 시맨틱

```css
:root {
  --krds-primary:        #246beb;  /* 정부 블루 — 주 액션·링크 */
  --krds-primary-hover:  #1d5cd0;
  --krds-primary-surface:#eff5ff;  /* 옅은 블루 면 — 선택·안내 */

  --krds-secondary:      #003675;  /* 딥 네이비 — 헤더·강조 면 */
  --krds-point:          #e71825;  /* 경고·필수 표시 */

  --krds-success:        #008a1e;
  --krds-warning:        #9e6a00;
  --krds-info:           #2768ff;
}
```

### 2-2. 그레이 스케일

```css
:root {
  --gray-0:   #ffffff;  /* 페이지 배경 */
  --gray-5:   #f4f5f6;  /* 섹션·비강조 배경 */
  --gray-10:  #e6e8ea;
  --gray-20:  #d7d9db;  /* 기본 보더 */
  --gray-30:  #c6c8ca;
  --gray-40:  #b1b8be;
  --gray-50:  #8a949e;  /* 플레이스홀더 */
  --gray-60:  #6d7882;  /* 캡션 */
  --gray-70:  #464c53;  /* 보조 텍스트 */
  --gray-80:  #33363d;
  --gray-90:  #1e2124;  /* 기본 텍스트 */
}
```

### 2-3. 시맨틱 별칭

```css
:root {
  --bg:          var(--gray-0);
  --surface:     var(--gray-5);
  --text:        var(--gray-90);
  --text-sub:    var(--gray-70);
  --text-caption:var(--gray-60);
  --border:      var(--gray-20);
  --primary:     var(--krds-primary);
  --danger:      var(--krds-point);
}
```

### 2-4. 사용 규칙

| 역할 | 토큰 |
|---|---|
| 본문·제목 | `var(--text)` |
| 보조 텍스트 | `var(--text-sub)` |
| 캡션·날짜 | `var(--text-caption)` |
| 페이지 배경 | `var(--bg)` |
| 섹션 배경 | `var(--surface)` |
| 기본 보더 | `var(--border)` 1px |
| 주 버튼·링크 | `var(--primary)` |
| 헤더·공식 배너 | `var(--krds-secondary)` |
| 필수 표시(*)·오류 | `var(--danger)` |

> **핵심 규칙**: 링크는 항상 `--primary` + 밑줄. 색약 사용자를 위해 색만으로 링크를 구분하지 않는다.

---

## 3. 타이포그래피

### 3-1. 폰트 — Pretendard GOV

정부 서비스 표준 서체. 일반 Pretendard보다 숫자·특수문자 판독성이 보강된 파생판이다.

```css
:root {
  --font-sans: 'Pretendard GOV', Pretendard, 'Noto Sans KR',
               -apple-system, 'Malgun Gothic', sans-serif;
}

body {
  font-family: var(--font-sans);
  font-size: 1.0625rem;   /* 기본 17px — 공공 가독성 기준 */
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  word-break: keep-all;
}
```

```html
<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-gov-dynamic-subset.min.css" />
```

### 3-2. 타입 스케일

| 역할 | `font-size` | `font-weight` | `line-height` |
|---|---|---|---|
| Display | 40px | 700 | 1.3 |
| Heading 1 | 32px | 700 | 1.35 |
| Heading 2 | 24px | 700 | 1.4 |
| Heading 3 | 19px | 700 | 1.5 |
| Body (기본) | 17px | 400 | 1.6 |
| Body Small | 15px | 400 | 1.6 |
| Caption | 13px | 400 | 1.5 |

- **본문 기본 17px** — 일반 상용 서비스(14~15px)보다 크게. 고령 사용자 기준.
- 글자 크기 조절 기능(작게 ~ 가장 크게 5단계)을 위해 모든 크기는 `rem`으로 작성한다.

---

## 4. 레이아웃 & 형태

```css
:root {
  --radius:       0.375rem;  /* 6px — 버튼·입력·카드 */
  --radius-small: 0.25rem;   /* 4px — 뱃지·태그 */
  --spacing:      0.25rem;   /* 4px 배수 */
}

.container { max-width: 75rem; margin-inline: auto; padding-inline: 1.25rem; }
```

- 페이지 상단에 공식 배너("이 누리집은 대한민국 공식 전자정부 누리집입니다") 영역을 확보한다.
- 본문 시작 전 스킵 링크(`본문 바로가기`)는 필수.

---

## 5. 핵심 컴포넌트

컴포넌트는 8개 그룹으로 분류된다: **아이덴티티**(공식 배너·헤더·푸터·운영기관 식별자), **탐색**(주 메뉴·브레드크럼·사이드/인페이지 내비·페이지네이션), **레이아웃 및 표현**(구조화 목록·달력·모달·뱃지·아코디언·캐러셀·탭·표), **액션**(링크·버튼·FAB), **선택**(라디오·체크박스·셀렉트·태그·토글), **피드백**(단계 표시기·스피너·토스트·스낵바), **도움**(도움 패널·컨텍스트 도움말·코치마크·툴팁·TTS), **입력**(텍스트 입력·날짜 입력·텍스트영역·파일 업로드).

### 5-1. 버튼

```css
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;          /* 48px — 터치 타깃 보장 */
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius);
  font-size: 1.0625rem;
  font-weight: 700;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.btn--primary {
  background: var(--primary);
  color: #ffffff;
  border: 1px solid transparent;
}
.btn--primary:hover { background: var(--krds-primary-hover); }

.btn--secondary {
  background: #ffffff;
  color: var(--primary);
  border: 1px solid var(--primary);
}

.btn--tertiary {
  background: #ffffff;
  color: var(--text);
  border: 1px solid var(--border);
}

.btn:disabled {
  background: var(--gray-10);
  color: var(--gray-50);
  border-color: transparent;
  cursor: default;
}
```

### 5-2. 입력 필드 — 명시적 라벨·오류

```html
<div class="form-group">
  <label class="form-label" for="name">
    이름 <span class="required" aria-hidden="true">*</span>
    <span class="sr-only">필수 입력</span>
  </label>
  <input class="form-input" id="name" aria-describedby="name-error" aria-invalid="true" />
  <p class="form-error" id="name-error">이름을 입력해 주세요.</p>
</div>
```

```css
.form-label { display: block; font-weight: 700; margin-bottom: 0.5rem; }
.required   { color: var(--danger); }

.form-input {
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--gray-40);
  border-radius: var(--radius);
  font-size: 1.0625rem;
  color: var(--text);
}
.form-input:focus {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
  border-color: var(--primary);
}
.form-input[aria-invalid="true"] { border-color: var(--danger); }

.form-error {
  margin-top: 0.375rem;
  font-size: 0.9375rem;
  color: var(--danger);
}
```

### 5-3. 단계 표시기 (Step Indicator)

```css
.steps { display: flex; gap: 0.5rem; }
.step {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.9375rem;
  color: var(--text-caption);
}
.step[aria-current="step"] { color: var(--primary); font-weight: 700; }
.step__num {
  width: 1.5rem; height: 1.5rem;
  border-radius: 50%;
  background: var(--gray-10);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.8125rem;
}
.step[aria-current="step"] .step__num { background: var(--primary); color: #fff; }
```

### 5-4. 공식 배너 & 헤더

```css
.gov-banner {
  background: var(--surface);
  font-size: 0.8125rem;
  color: var(--text-sub);
  padding: 0.5rem 1.25rem;
}
.header {
  background: #ffffff;
  border-bottom: 1px solid var(--border);
}
.header--emphasis { background: var(--krds-secondary); color: #ffffff; }
```

---

## 6. 인터랙션

- 전환은 `0.15s ease` 이내 — 기능적 피드백일 뿐 장식이 아니다.
- 포커스 표시는 어떤 경우에도 제거 금지: `outline: 2px solid var(--primary); outline-offset: 1px`.
- 키보드만으로 모든 기능에 도달 가능해야 한다 — 모달은 포커스 트랩, 닫기는 `Esc`.
- 자동 재생·자동 전환(캐러셀)은 일시정지 버튼 필수.

---

## 7. 고대비(선명한 화면) 모드

KRDS의 다크 모드는 장식이 아니라 **저시력 사용자를 위한 "선명하게(어두운 배경)" 표시 모드**다.

```css
.high-contrast {
  color-scheme: dark;

  --bg:          #101013;
  --surface:     #1b1d20;
  --text:        #f4f5f6;
  --text-sub:    #c6c8ca;
  --text-caption:#b1b8be;
  --border:      #6d7882;   /* 보더를 오히려 밝게 — 경계 인지 강화 */

  --primary:     #6299ff;   /* 블루 한 단계 밝게 */
  --danger:      #ff5a5a;
}
```

- 일반 다크 모드보다 **보더·텍스트 대비를 더 높게** 잡는다 (경계 인지가 목적).
- 사용자 선택은 `localStorage`에 저장하고 재방문 시 유지한다.
- 글자 크기 설정(5단계)과 독립적으로 동작해야 한다.

---

## 8. 접근성

KRDS에서 접근성은 별도 장이 아니라 시스템 전체의 존재 이유다.

- `--text`(#1e2124) 대비 16.9:1, `--text-sub`(#464c53) 9.2:1 — AAA 수준.
- 본문 기본 17px + 행간 1.6 — 고령·저시력 사용자 기준.
- 모든 컴포넌트는 스크린리더 검증을 거친다: 시맨틱 태그, `aria-*`, 스킵 링크, TTS 연동.
- 색상 단독 의미 전달 금지 — 필수(*)·오류·링크 모두 텍스트 병기.
- 터치 타깃 최소 48×48px (버튼 `min-height: 3rem`).
- 서식의 오류는 개별 필드 + 상단 요약(오류 목록 링크) 이중으로 안내한다.

---

## 9. 안티패턴

| 금지 | 이유 |
|---|---|
| 색상만으로 링크·오류·필수 표시 | 텍스트·밑줄·기호 병기 필수 |
| 포커스 아웃라인 제거 | 키보드 사용자 조작 불능 |
| 본문 15px 미만 | 공공 가독성 기준 미달 |
| px 고정 타이포 | 글자 크기 조절 기능이 깨진다 |
| 자동 전환 캐러셀 (정지 버튼 없이) | 인지·운동 장애 사용자 배제 |
| 기관별 임의 브랜드 컬러로 주 버튼 변경 | 범정부 일관성 붕괴 |
| 장식적 애니메이션 | 기능적 피드백만 허용 |
| 스킵 링크·공식 배너 생략 | 정부 누리집 필수 요소 |
| 이미지 안의 텍스트 | 확대·TTS·번역 불가 |

---

## 10. 프레임워크 어댑터

### 10-1. Tailwind CSS v4

```css
@import "tailwindcss";

@theme {
  --color-bg:        #ffffff;
  --color-surface:   #f4f5f6;
  --color-text:      #1e2124;
  --color-text-sub:  #464c53;
  --color-border:    #d7d9db;
  --color-primary:   #246beb;
  --color-secondary: #003675;
  --color-danger:    #e71825;
  --radius:          0.375rem;
}

@custom-variant hc (&:where(.high-contrast, .high-contrast *));
```

### 10-2. Tailwind CSS v3

```js
module.exports = {
  darkMode: ['class', '.high-contrast'],
  theme: {
    extend: {
      colors: {
        bg:        'var(--bg)',
        surface:   'var(--surface)',
        text:      'var(--text)',
        'text-sub':'var(--text-sub)',
        border:    'var(--border)',
        primary:   'var(--primary)',
        secondary: 'var(--krds-secondary)',
        danger:    'var(--danger)',
      },
      borderRadius: { DEFAULT: '0.375rem' },
    },
  },
};
```

### 10-3. React / Vue / Svelte

CSS 변수가 원천. 표시 모드·글자 크기 설정을 `localStorage`에 저장하는 설정 패널을 공통 컴포넌트로 만든다.

---

## 11. 이식 가이드

### Step 1 — 토큰 복사

§2의 브랜드/그레이/별칭 블록 + §7 고대비 블록을 전역 CSS에 붙인다.

### Step 2 — Pretendard GOV 연결

CDN 링크를 `<head>`에 추가하고 본문 기본 17px / 행간 1.6을 설정한다.

### Step 3 — 시그니처 3종 적용

| 패턴 | 핵심 CSS |
|---|---|
| **명시적 폼** | 라벨 Bold + 필수(*) + 오류 텍스트 — `aria-describedby` 연결 |
| **정부 블루 버튼** | `background: #246beb; min-height: 48px; font-weight: 700` |
| **포커스 링** | `outline: 2px solid #246beb; outline-offset: 1px` — 전 요소 공통 |

### Step 4 — 접근성 검증

키보드 단독 조작, 스크린리더(NVDA 등) 낭독, 글자 200% 확대, 고대비 모드 4가지를 통과해야 완료다.

### 신규 컴포넌트 결정 트리

```
새 컴포넌트가 필요하다
  → 기존 8개 그룹 37종으로 조합 가능한가?
    yes → 조합 사용
    no  → 접근성 4종 검증(키보드/스크린리더/확대/고대비)을 통과할 수 있는가?
      yes → 추가하되 상태를 텍스트로 명시
      no  → 설계 재검토 — 접근성이 안 되면 KRDS 컴포넌트가 아니다
```
