컴포넌트
컴포넌트는 모양만 담은 CSS입니다.
이름과 구조는 슬러 문법(블록 · i_ 내부 요소 · m_ 수정자 · data-state)을 따르고,
동작은 네이티브 → slur.js → 위임에서 옵니다.
JS 라이브러리 의존은 없습니다.
라이브 데모
섹션 제목: “라이브 데모”컴포넌트 전체가 한 페이지에 있습니다. 오른쪽 위 버튼으로 다크 모드를, 「State」 섹션에서 data-state 전환을, 「Modal」·「Toast」에서 <dialog>와 토스트 큐를 직접 눌러 볼 수 있습니다.
색인 — 블록 · 변형 · 상태의 정본
섹션 제목: “색인 — 블록 · 변형 · 상태의 정본”| 컴포넌트 | 블록과 변형 | 상태의 정본 | 파일 |
|---|---|---|---|
| 버튼 | 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-selected는 slur.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_help — popover="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 값만 loading → empty / 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>가 1순위
섹션 제목: “모달 — <dialog>가 1순위”<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·모양은 슬러를 유지합니다. 같은 부품이 두 곳 이상에서 반복되면 그때 시스템으로 승격을 제안합니다.