1 · Loading primitives
4 atoms — skeleton (cấu trúc trống), spinner (action ngắn), progress (xác định, >10s), dots (subtle inline).
Skeleton
atoms/loading-skeleton.htmlrole="status" + aria-label.<div role="status" aria-label="Đang tải…"> <span class="skeleton skeleton-line skeleton-line--title"></span> <span class="skeleton skeleton-line"></span> <span class="skeleton skeleton-line skeleton-line--short"></span> </div>
Spinner
atoms/loading-spinner.htmlaria-busy="true" + spinner aria-hidden.<span class="spinner" role="status" aria-label="Đang tải"></span>
Progress · linear + circular
atoms/loading-progress.htmlaria-valuenow.<div class="progress" role="progressbar"
aria-valuenow="32" aria-valuemin="0" aria-valuemax="100"
aria-label="Tải file lên">
<div class="progress__bar" style="width: 32%"></div>
</div>
Dots · subtle inline
atoms/loading-dots.html<span class="dots" role="status" aria-label="Đang tải"> <span class="dots__dot"></span> <span class="dots__dot"></span> <span class="dots__dot"></span> </span>
2 · Empty state
4 biến thể tuỳ ngữ cảnh "tại sao trống". Icon + title + body + CTA phù hợp.
No data · No results · First use · Offline
molecules/empty-state.htmlChưa có dữ liệu nào
Bắt đầu bằng mục đầu tiên.
Không tìm thấy kết quả
Thử thay đổi từ khoá hoặc bỏ bộ lọc.
Chào mừng đến Cowork
Thiết lập 3 bước để bắt đầu.
Mất kết nối mạng
Kiểm tra Internet rồi thử lại.
navigator.onLine === false → offline.3 · Error state
3 scope: inline form field · block (1 widget fail) · full-page (route fatal).
Inline form error
molecules/error-state.htmlEmail chưa đúng định dạng. Thử name@brand.com.
aria-invalid="true" + aria-describedby trỏ tới <p role="alert">. SR sẽ đọc lỗi khi focus vào input.Block error (1 widget fail)
molecules/error-state.htmlKhông tải được biểu đồ doanh thu
Mạng đang chậm hoặc server analytics đang bận. Phần còn lại của trang vẫn dùng được.
role="alert" để SR ngắt và đọc.4 · Toast
Phản hồi tạm thời sau action. Auto-dismiss 5s, hover pause, max 3 visible, slide-in motion.
4 severity
organisms/toast.htmlrole="status" polite. warn/error → role="alert" assertive. Mỗi toast có aria-atomic="true" để SR đọc đủ title + body.Toast.show({
severity: 'success', // info | success | warn | error
title: 'Đã lưu',
body: 'Báo cáo đã cập nhật.',
timeout: 5000 // ms; 0 = giữ vĩnh viễn
});
5 · Focus management
JS utilities lo focus trap (modal/drawer), Escape broadcast, skip-link auto-inject, keyboard shortcut hint badges.
Focus trap demo (modal)
_assets/focus-trap.jsBấm Tab/Shift+Tab — focus vòng quanh trong modal. Nhấn Esc để đóng.
<script src="../_assets/focus-trap.js"></script>
<script>
const trap = UXKit.createFocusTrap(document.getElementById('my-modal'));
trap.activate(); // khi mở
trap.deactivate(); // khi đóng — focus trả về previous element
</script>
Keyboard shortcuts
_assets/keyboard.jsThử: nhấn Esc để dispatch event ux:escape (mọi toast đang mở sẽ đóng). Bấm Ctrl+K để demo shortcut registry.
Skip-link auto-inject: nhấn Tab lần đầu trên trang → thấy "Bỏ qua tới nội dung" góc trên-trái.
<script src="../_assets/keyboard.js"></script>
<script>
// Auto behavior: Escape → dispatch CustomEvent 'ux:escape'
document.addEventListener('ux:escape', () => closeOverlay());
// Shortcut registry (optional)
UXKit.kbd.shortcut('Ctrl+K', () => openCommandPalette());
</script>