콘텐츠로 이동

컴포넌트

컴포넌트는 모양만 담은 CSS입니다. 이름과 구조는 슬러 문법(블록 · i_ 내부 요소 · m_ 수정자 · data-state)을 따르고, 동작은 네이티브 → slur.js → 위임에서 옵니다. JS 라이브러리 의존은 없습니다.


컴포넌트 전체가 한 페이지에 있습니다. 오른쪽 위 버튼으로 다크 모드를, 「State」 섹션에서 data-state 전환을, 「Modal」·「Toast」에서 <dialog>와 토스트 큐를 직접 눌러 볼 수 있습니다.

컴포넌트 데모 — system/demo.html
새 탭에서 열기 ↗

색인 — 블록 · 변형 · 상태의 정본

섹션 제목: “색인 — 블록 · 변형 · 상태의 정본”
컴포넌트 블록과 변형 상태의 정본 파일
버튼 btn + m_primary m_ghost m_danger · 크기 m_small m_large · m_full · 아이콘 전용 m_icon(정사각) · 햄버거 btn_menu(48×48) data-state="loading", disabled button.css
입력 field(래퍼: i_label i_help) + input_text(+ m_textarea m_icon_left m_icon_right m_small m_large) · input_wrap(i_icon, 컨트롤 안 액션 i_action) field[data-state="error"] · success · disabled + aria-invalid input.css
셀렉트 select(네이티브 <select> + i_icon 화살표, 컨트롤은 input_text) 네이티브 select.css
선택 check radio(i_input) · switch(i_knob) 체크·라디오 :checked(네이티브) · 스위치 role="switch" + aria-checked selection.css
배지 badge(i_dot) + 색 m_brand m_success m_warning m_danger · 스타일 m_solid m_outline · 크기 m_small m_medium · 카운트 m_count · chip(i_close) badge.css
카드 card(i_head i_body i_foot i_title i_desc) + m_flat m_elevated m_link · 목록 행 m_list(i_row) · 통계 m_stat(i_label i_icon i_value i_delta) card.css
알림 alert(i_icon i_body i_title i_text i_close) + m_success m_warning m_danger · 형태 m_inline(한 줄) m_banner(액션 포함) role="alert"는 폼 전체 실패처럼 즉시 알릴 것에만 alert.css
모달 <dialog class="modal_dialog">(i_head i_title i_text i_close · i_body · i_foot) · <dialog>를 못 쓰는 대안은 div + i_wrap + data-state [open](네이티브, showModal()) — data-state 없음 modal.css
내비게이션 tab_menu(i_list[role=tablist] > i_tab + i_panel, 필 m_pill) · 사이드바 nav_side(i_group i_item i_label i_divider) · breadcrumb(i_link i_sep i_current) 탭·패널 data-state="active"(+ aria-selectedslur.js) · 현재 페이지 aria-current="page" navigation.css
table_wrap(i_body i_scroll i_foot i_count i_btn_wrap) + table_data(숫자 열 m_num, m_muted, 정렬 th.m_sort > i_sort, 셀 i_user i_avatar) 정렬 th[aria-sort]data-state 없음 table.css
페이지네이션 pagination(i_list i_prev i_next i_page i_ellipsis) 현재 페이지 aria-current="page" pagination.css
토스트 toast_message(i_icon i_text i_close) + m_success m_warning m_danger · 위치 m_top data-state="show" / close · 컨테이너는 role="status"(기본) / role="alert"(긴급) 각각 따로, 미리 DOM에 toast.css
상태 4상태 슬롯 i_status(> i_loading i_empty i_error) · empty_state(i_icon i_title i_text i_action, m_error m_compact) · skeleton(m_title m_circle m_rect m_inline) · spinner(m_large) 블록의 data-state="loading" / empty / error / success — 전환은 global.css state.css
메뉴 menu_action(i_group i_item i_divider i_shortcut, 위험 항목 m_danger) — popover + role="menu" :popover-open(네이티브) — data-state 없음 menu.css
툴팁 tooltip_helppopover="manual" + role="tooltip" + 트리거의 aria-describedby :popover-open tooltip.css

네이티브·ARIA 상태가 있는 곳에 data-state를 겹쳐 붙이지 않는 이유는 동작 층 문서에 있습니다 — 브라우저가 관리하는 상태를 우리가 다시 관리하지 않습니다.


  • 공유 컨트롤 높이 2 / 2.5 / 3rem — 버튼·입력·셀렉트가 같은 줄에서 키가 맞습니다. 모바일 터치 타깃은 48.
  • 상태색 면 위 텍스트는 on-토큰, 포커스 링은 global.css 한 곳, 아이콘은 em + currentColor.
  • 컴포넌트 내부를 덮을 때는 페이지 스코프에서.page_list .table_data { min-width: 760px; }. 컴포넌트 CSS 파일 자체를 고치지 않습니다.
  • 토스트 컨테이너는 미리 DOM에 — 라이브 영역이 먼저 있어야 다음 알림이 보조 기술에 읽힙니다. 모달이 열린 동안은 토스트를 띄우지 않습니다(slur.toast()가 닫힐 때까지 기다립니다).

4상태 — 블록에 data-state, 안내는 i_status 상자 안

섹션 제목: “4상태 — 블록에 data-state, 안내는 i_status 상자 안”
<section class="p_programs" data-state="loading" aria-label="프로그램 목록">
<div class="i_status" role="status"> <!-- 항상 렌더 — 상태 전환이 보조 기술에 읽힌다 -->
<div class="i_loading"><span class="spinner" aria-hidden="true"></span> 불러오는 중…</div>
<div class="i_empty"><div class="empty_state"></div></div>
<div class="i_error"><div class="empty_state m_error"></div></div>
</div>
<ul class="i_body"></ul> <!-- success일 때만 보인다 -->
</section>

data-state 값만 loadingempty / error / success로 바꾸면 global.css의 스위치가 슬롯을 고릅니다. 룩(스피너·빈 상태 패널·스켈레톤)은 state.css가 줍니다 — 로딩 기본은 스피너 + 읽힐 문장, 스켈레톤은 넓은 표·목록의 선택지입니다(화면 상태 설계).

토스트 — 컨테이너 선존재 + slur.toast()

섹션 제목: “토스트 — 컨테이너 선존재 + slur.toast()”
<div class="toast_message" role="status" data-state="close">
<span class="i_icon"></span><p class="i_text"></p>
<button type="button" class="i_close" aria-label="닫기"></button>
</div>
<div class="toast_message" role="alert" data-state="close"></div>
slur.toast('저장됐습니다'); // status — 자동 소멸
slur.toast('저장에 실패했습니다', { level: 'alert' }); // alert — 직접 닫기
<dialog class="modal_dialog" id="renameDialog" aria-labelledby="renameTitle">
<div class="i_head">
<h2 class="i_title" id="renameTitle">프로젝트 이름 변경</h2>
<button type="button" class="i_close" aria-label="닫기"></button>
</div>
<div class="i_body"></div>
<div class="i_foot">
<button type="button" class="btn">취소</button>
<button type="button" class="btn m_primary">저장</button>
</div>
</dialog>

dialog.showModal() 하나로 배경 차단(inert)·Esc·포커스 복귀·최상위 레이어를 브라우저가 처리합니다. 상태 정본은 [open]이고 배경 딤은 ::backdrop(--color-surface-overlay)입니다 — 팝업 / 모달 패턴.


토큰만으로 새로 조립합니다. 이름은 슬러 네이밍(블록 + i_ + m_), 위치는 프로젝트의 컴포넌트 CSS, 새 토큰·새 색상값은 만들지 않습니다. 콤보박스처럼 동작이 큰 위젯은 3순위 위임 — 동작만 빌리고 구조·네이밍·data-state·모양은 슬러를 유지합니다. 같은 부품이 두 곳 이상에서 반복되면 그때 시스템으로 승격을 제안합니다.