HandStack 프로젝트 전용 DESIGN.md 템플릿
| 항목 | 내용 |
|---|---|
| 적용 범위 | wwwroot 화면 HTML |
| 적용 원칙 | 신규 화면은 필수 준수. 기존 화면은 수정하는 범위 내에서만 점진 적용 (일괄 리팩터링 금지) |
| 개정 절차 | 규칙 변경 시 이 문서를 먼저 수정하고, 근거가 되는 실제 화면 경로를 함께 기록 |
이 문서는 AI(코딩 어시스턴트)와 개발자가 이 프로젝트에서 화면(HTML)을 작성할 때 따라야 할 UI 규칙을 정의합니다. 일반 Tabler 문서와 다른 부분이 많으므로, Tabler 공식 문서보다 이 문서가 우선합니다.
1. 기술 스택 계층
이 프로젝트의 화면은 세 개의 계층이 조합되어 동작합니다. 각 계층의 역할을 혼동하지 마세요.
HandStack (syn.loader.js) ← 화면 로딩, 데이터 바인딩(syn-datafield), 컴포넌트(syn_*), 팝업
├─ Tabler CSS (Bootstrap 5 기반) ← 레이아웃·카드·버튼·폼·배지 등 시각 컴포넌트
└─ Master CSS (런타임 엔진) ← f:20, w:120, mr:4 같은 단축 유틸리티 클래스
- Tabler: CDN으로 로드됩니다 (
@tabler/core). 일부 화면은@latest를 사용 중이므로, 신규 참조 작성 시에는 버전을 고정(@tabler/core@1.3.2등)하는 것을 권장합니다.@latest는 예고 없는 스타일 변경 리스크가 있습니다. - Master CSS:
syn.loader.js가 런타임에/lib/master-css/index.min.js를 로드하여f:20같은 클래스를 실제 CSS로 변환합니다. 빌드 도구가 아니라 런타임 엔진이므로, 클래스만 정확히 쓰면 별도 빌드 없이 동작합니다. 끝의!(예:f:12!)는 Master CSS의!important문법입니다. - 아이콘: Tabler Icons 웹폰트(
ti ti-*)만 사용합니다.
2. 화면 유형 분류
새 화면을 만들기 전에 어떤 유형인지 먼저 결정하고, 해당 유형의 템플릿(6장)을 따르세요.
| 유형 | 특징 | 핵심 구성 |
|---|---|---|
| 목록(조회) 화면 | 검색 조건 + 데이터 그리드 | 필터 카드 + syn_auigrid 카드 (+ 하단 sticky-bottom 액션바) |
| 상세(입력) 화면 | 단건 조회/등록/수정 폼 | input-group 좌측 라벨 폼 카드 |
| 팝업 화면 | 다른 화면에서 호출되는 다이얼로그 | simplemodal-data 패턴 (Bootstrap modal 아님) |
| 보고서/인쇄 화면 | RPT 계열, 인쇄 전용 | 순수 <table> + 화면 전용 <style> 허용 (유일한 예외) |
3. 명명·파일 규칙
- 화면 파일은
{업무코드 3자}{일련번호 3자리}.html과 동일 이름의.js가 한 쌍입니다. 예:ATM030.html+ATM030.js- 파생 화면(팝업 등)은 일련번호 끝자리로 구분:
ATM010→ATM011,ATM012 - 보고서 화면은
RPT접두어를 사용
- 파생 화면(팝업 등)은 일련번호 끝자리로 구분:
- 경로 구조:
modules/{모듈}/wwwroot/{모듈}/view/{시스템}/{업무}/{화면ID}.html - 데이터 바인딩 식별자(
syn-datafield)는 PascalCase를 사용:MainForm,Grid1,Name - HTML을 만들면 반드시 짝이 되는
.js도 함께 만들거나, 팝업 조각처럼 JS가 불필요한 경우 그 이유를 명시합니다.
4. 색상 규칙
Tabler CSS 변수 및 bg-*/text-* 유틸리티 만 사용합니다. 임의의 HEX 색상 지정은 금지합니다.
Primary: --tblr-primary → btn-primary, text-primary
Success: --tblr-success → bg-success (완료/승인)
Danger: --tblr-danger → bg-danger (취소/반려/오류)
Warning: --tblr-warning → bg-warning (대기/주의)
Muted: bg-muted-lt → 비활성/보조 버튼 배경
배지(badge bg-*) 색상 규칙:
- 완료/승인 →
bg-green또는bg-success - 취소/반려/실패 →
bg-red또는bg-danger - 대기/진행중 →
bg-yellow또는bg-warning - 세부 상태가 많으면 Tabler 팔레트(
bg-blue,bg-cyan,bg-purple,bg-indigo,bg-orange)와-lt변형 사용 - 상태 값이 코드 테이블에서 오는 경우, 색상을 HTML에 하드코딩하지 말고 JS에서 상태 코드 → 색상 클래스 매핑 함수를 두고 그리드 렌더러에서 적용합니다. 동일 상태 코드는 모듈이 달라도 같은 색을 쓰는 것이 원칙입니다.
5. Master CSS 유틸리티 규칙
Bootstrap 표준 유틸리티(fs-*, w-*, me-*) 대신 Master CSS 단축 표기를 우선 사용합니다 (기존 화면과의 통일성).
| 목적 | 클래스 예시 | 비고 |
|---|---|---|
| 폰트 크기 | f:12, f:18, f:20, f:12! | 숫자는 px |
| 너비 | w:100, w:120, w:120! | 라벨·인풋 고정 너비 |
| 여백 | mr:4, mr:2, p:10! | |
| 테두리 | b:1 | |
| 줄 높이 | line-height:40 | |
| 컨테이너 폭 | max-width:1600!, min-width:1536 | 페이지 컨테이너 제어 |
- 끝의
!는 Master CSS의 important 문법입니다. 기존 스타일을 덮어써야 할 때만 사용합니다. - 인라인
style=""은display:none,visibility:hidden같은 JS 상태 토글 용도로만 허용합니다. 색상·크기·여백을 인라인 style로 넣지 마세요.
6. 화면 유형별 표준 템플릿
6.1 페이지 골격 (모든 화면 공통)
HandStack 화면은 완전한 HTML 문서가 아니라 syn.loader.js가 로드하는 프래그먼트입니다.
<body style="visibility:hidden">
<form id="form1" syn-datafield="MainForm">
<div class="page">
<div class="page-wrapper">
<div class="page-header">
<div class="container-fluid max-width:1600!">
<div class="row g-2 align-items-center">
<div class="col">
<div class="page-pretitle f:12!">모듈명 > 메뉴명</div>
<h2 class="page-title">화면 제목</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-fluid max-width:1600!">
<!-- 화면 유형별 본문 -->
</div>
</div>
</div>
</div>
</form>
</body>
- 컨테이너는
container-xl이 아니라container-fluid+ Master CSS 폭 제어(max-width:1600!또는min-width:1536 max-width:1920!)를 사용합니다. <body style="visibility:hidden">은 렌더링 완료 후 HandStack이 해제하므로 그대로 유지합니다.
6.2 목록(조회) 화면
검색 필터 카드와 그리드 카드를 분리하고, 목록은 반드시 syn_auigrid로 구현합니다.
<!-- 검색 필터 카드 -->
<div class="card">
<div class="card-status-top bg-dark-overlay"></div>
<div class="card-body">
<div class="row g-2">
<div class="col-3">
<div class="input-group">
<label class="w:100 col-form-label px-2">이름</label>
<input type="text" class="form-control" syn-datafield="Name" />
</div>
</div>
<div class="col-3">
<div class="input-group">
<label class="w:100 col-form-label px-2">상태</label>
<select class="form-select" syn-datafield="Status">
<option value="">전체</option>
</select>
</div>
</div>
<div class="col text-end">
<button type="button" class="btn btn-primary">
<i class="f:20 mr:4 ti ti-search"></i> 조회
</button>
</div>
</div>
</div>
</div>
<!-- 목록 카드 -->
<div class="card mt-2">
<div class="card-header">
<h3 class="card-title">목록</h3>
<div class="card-actions">
<button type="button" class="btn btn-primary btn-icon">
<i class="ti ti-plus"></i>
</button>
</div>
</div>
<div class="form-fieldset p-0">
<syn_auigrid syn-datafield="Grid1"
syn-options="{columns:[
{header:'번호', dataField:'Seq', width:60},
{header:'이름', dataField:'Name'},
{header:'상태', dataField:'Status', width:100}
]}">
</syn_auigrid>
</div>
</div>
<!-- 하단 고정 액션바 (필요 시) -->
<div class="footer p-2 sticky-bottom">
<div class="btn-list justify-content-end">
<button type="button" class="btn bg-muted-lt">취소</button>
<button type="button" class="btn btn-primary">
<i class="f:20 mr:4 ti ti-check"></i> 저장
</button>
</div>
</div>
6.3 상세(입력) 화면
라벨은 인풋 위가 아니라 input-group 내부 왼쪽에 배치합니다. 일반 Tabler 문서의 "라벨은 인풋 위" 규칙을 따르지 마세요.
<div class="card">
<div class="card-status-top bg-dark-overlay"></div>
<div class="card-header">
<h3 class="card-title">기본 정보</h3>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-6">
<div class="input-group">
<label class="w:100 col-form-label px-2">이름</label>
<input type="text" class="form-control" syn-datafield="Name" />
</div>
</div>
<div class="col-6">
<div class="input-group">
<label class="w:100 col-form-label px-2">사용 여부</label>
<label class="form-check form-switch col-form-label px-2">
<input class="form-check-input" type="checkbox" syn-datafield="UseYN" />
</label>
</div>
</div>
</div>
</div>
</div>
- 기간 선택은
syn_dateperiodpicker, 트리는syn_tree, 달력은syn_calendar컴포넌트를 사용합니다 (순수 HTML<input type="date">로 대체하지 않음). - 모든 데이터 바인딩 요소에
syn-datafield를 지정합니다.
6.4 팝업 화면
Bootstrap modal/modal-dialog를 사용하지 않습니다. HandStack의 simplemodal-data 패턴을 사용합니다.
<div class="simplemodal-data" style="display:none">
<div class="card">
<div class="card-header">
<h3 class="card-title">상세 정보</h3>
<div class="card-actions">
<button type="button" class="btn btn-icon"
syn-options="{triggerConfig:{triggerEvent:'click', method:'syn.$w.closeDialog'}}">
<i class="ti ti-x"></i>
</button>
</div>
</div>
<div class="card-body">
<!-- 상세 폼 -->
</div>
</div>
</div>
- 닫기/트리거는
syn-options="{triggerConfig:{...}}"로 바인딩합니다.data-bs-dismiss="modal"은 사용하지 않습니다.
6.5 인라인/전용 스타일이 허용되는 화면 (예외 유형)
RPT 계열 화면에 한해 모든 태그와 화면 전용 <style> 블록을 허용합니다. 이 유형이 인라인/전용 스타일이 허용되는 유일한 예외입니다.
7. 버튼 규칙
주 액션(저장/조회): btn btn-primary
보조 액션(토글/탭): btn btn-outline-secondary
중립/취소: btn bg-muted-lt ← 이 프로젝트 관례. btn-light 대신 사용
아이콘 전용: btn btn-icon
버튼 묶음: btn-group 또는 btn-list
- 아이콘+텍스트:
<i class="f:20 mr:4 ti ti-check"></i> 적용 - 크기 조절은
btn-sm/btn-lg보다 Master CSSf:클래스로 텍스트·아이콘 크기를 맞추는 방식을 우선합니다. - 새로운 버튼 스타일 클래스를 임의로 만들지 않습니다.
8. 카드 규칙
- 카드는 이 프로젝트의 기본 레이아웃 단위입니다. 검색 영역과 목록/상세 영역을 별도 카드로 분리합니다.
- 카드 상단 강조 바:
card-status-top bg-dark-overlay(의미가 있으면 해당bg-*로 교체 가능) - 그리드를 담는 카드는
card-body p-0또는form-fieldset p-0으로 내부 여백을 제거합니다. card-footer는 카드별 액션/요약 행이 꼭 필요할 때만 사용합니다.
9. 아이콘 규칙
<i class="ti ti-user"></i>
<i class="f:18 ti ti-x"></i>
- Tabler Icons(
ti ti-*)만 사용합니다. 크기는f:NN으로 조절합니다. - FontAwesome(
fa-*), Bootstrap Icons, 기타 아이콘 폰트 사용 금지. (일부 레거시 화면에 FontAwesome이 남아 있으나 신규 사용 금지)
10. 금지 사항 (Never)
- container-xl을 기본 컨테이너로 사용하지 말 것 (container-fluid 사용)
- 순수 <table>로 업무 목록을 만들지 말 것 (syn_auigrid 사용, RPT 보고서 화면만 예외)
- Bootstrap modal을 업무 팝업에 사용하지 말 것 (simplemodal-data 패턴 사용)
- FontAwesome 등 타 아이콘 폰트를 사용하지 말 것 (ti ti-* 만 사용)
- 임의의 HEX 색상, 인라인 색상 스타일을 사용하지 말 것 (Tabler 색상 유틸리티 사용)
- 시각적 스타일을 인라인 style로 넣지 말 것 (display/visibility 토글만 허용)
- 라벨을 인풋 위에 쌓는 일반 Tabler 폼 레이아웃을 쓰지 말 것 (input-group 좌측 라벨)
- 새로운 커스텀 CSS 클래스·버튼 스타일을 임의로 만들지 말 것 (Tabler + Master CSS 조합으로 해결)
- 상태 배지 색상을 화면마다 다르게 하드코딩하지 말 것 (상태 코드 → 색상 매핑 일관 유지)
11. 준수 검증 (QA 게이트)
새 화면 작성 후 아래를 확인합니다. 앞의 6개는 grep으로 기계 검증이 가능합니다.
| # | 검증 항목 | 확인 방법 |
|---|---|---|
| 1 | 타 아이콘 폰트 미사용 | 파일에서 fa-, bi- 검색 결과 0건 |
| 2 | Bootstrap modal 미사용 | modal-dialog 검색 결과 0건 |
| 3 | 기본 컨테이너 준수 | container-xl 검색 결과 0건 |
| 4 | 인라인 색상 스타일 없음 | style=" 내에 color, background, font-size 없음 |
| 5 | HEX 색상 하드코딩 없음 | # 뒤 3~6자리 HEX가 class/style에 없음 |
| 6 | 데이터 바인딩 지정 | 입력 컨트롤·그리드에 syn-datafield 존재 |
| 7 | 화면 유형 템플릿 준수 | 6장 템플릿과 골격 구조 일치 |
| 8 | 짝 파일 존재 | {화면ID}.html과 {화면ID}.js가 쌍으로 존재 |
| 9 | 라벨 배치 준수 | 폼 라벨이 input-group 내부 좌측에 위치 |
| 10 | 배지 색상 일관성 | 동일 상태 코드에 기존 화면과 같은 색상 사용 |