22 KiB
Portable UI Style Guide - AccManager Inspired
Tài liệu này dùng để mang phong cách UI hiện tại của AccManager sang một dự án khác. Nội dung tập trung vào ngôn ngữ thiết kế, token, layout, component pattern và checklist triển khai, không phụ thuộc chặt vào nghiệp vụ tài khoản/tài sản của AccManager.
1. Design DNA
Phong cách UI gốc là một admin console nội bộ: gọn, sáng, nhiều dữ liệu, dễ quét bảng, thao tác nhanh và ít trang trí thừa.
Tinh thần chính:
- Ưu tiên hiệu quả vận hành hơn cảm giác marketing.
- Nền sáng, bề mặt trắng/xám nhạt, màu nhấn xanh primary.
- Layout chắc, dày thông tin nhưng vẫn thoáng.
- Component nhỏ gọn: table, filter, modal, form, badge, icon action.
- Button và icon rõ chức năng, phản hồi hover/active nhẹ.
- Typography dùng heading đậm, label nhỏ uppercase, nội dung bảng vừa đủ đọc.
- Phù hợp cho dashboard, CRM, ERP mini, tool nội bộ, quản lý dữ liệu, asset/account/user management.
Không nên biến phong cách này thành landing page, hero lớn, nhiều gradient trang trí, card marketing hoặc layout quá thoáng kiểu portfolio.
2. Stack Khuyến Nghị
Để tái tạo đúng style, dự án mới nên dùng:
- Tailwind CSS.
- Font heading:
Manrope. - Font body:
Inter. - Icon: Google Material Symbols Outlined.
- Toast/notification: Notiflix hoặc thư viện tương đương.
- Table data với sticky header, horizontal scroll trên mobile.
Google Fonts:
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet">
Material Symbols base CSS:
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
font-size: 1.25rem;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-flex;
white-space: nowrap;
direction: ltr;
}
3. Tailwind Design Tokens
Có thể copy phần theme.extend này sang tailwind.config.js của dự án mới.
theme: {
extend: {
colors: {
"primary": "#3755c3",
"primary-dim": "#2848b7",
"primary-container": "#dde1ff",
"on-primary": "#f8f7ff",
"background": "#f7f9fb",
"surface": "#f7f9fb",
"surface-container-lowest": "#ffffff",
"surface-container-low": "#f0f4f7",
"surface-container": "#e8eff3",
"surface-container-high": "#e1e9ee",
"surface-container-highest": "#d9e4ea",
"on-surface": "#2a3439",
"on-surface-variant": "#566166",
"outline": "#717c82",
"outline-variant": "#a9b4b9",
"error": "#9f403d",
"error-container": "#fe8983"
},
fontFamily: {
headline: ["Manrope", "sans-serif"],
body: ["Inter", "sans-serif"],
label: ["Inter", "sans-serif"]
},
borderRadius: {
DEFAULT: "0.125rem",
lg: "0.25rem",
xl: "0.5rem",
full: "0.75rem"
}
}
}
Base body:
<body class="bg-background text-on-surface antialiased">
Font rule:
body { font-family: 'Inter', sans-serif; }
h1, h2, h3, .brand-logo { font-family: 'Manrope', sans-serif; }
4. Color Usage
Màu nền:
- App background:
bg-background. - Main card/table:
bg-whitehoặcbg-surface-container-lowest. - Filter bar/input subtle:
bg-surface-container-low. - Header/table head:
bg-slate-50.
Màu chữ:
- Text chính:
text-on-surface,text-slate-900. - Text phụ:
text-on-surface-variant,text-slate-500,text-slate-600. - Link/action chính:
text-primary.
Màu hành động:
- Primary:
bg-primary hover:bg-primary-dim text-on-primary. - Secondary: border slate, text slate.
- Danger:
bg-red-600 hover:bg-red-700 text-whitehoặctext-error. - Success: emerald/green.
- Pending/warning: amber/yellow.
- Info: blue.
Tránh:
- Dùng quá nhiều gradient.
- Dùng một palette đơn sắc toàn xanh.
- Nền tối toàn trang nếu chưa thiết kế dark mode đầy đủ.
- Màu quá rực trên bảng dữ liệu.
5. Typography Scale
Page title:
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Page Title</h1>
Subtitle:
<p class="text-sm text-on-surface-variant">Short page description.</p>
Table header / form label:
<span class="text-[10px] font-bold uppercase tracking-wider text-on-surface-variant">Label</span>
Table body:
<td class="px-4 py-3 text-sm text-slate-600">Value</td>
Badge text:
<span class="px-2 py-1 rounded text-xs font-semibold">Status</span>
Nguyên tắc:
- Heading đậm, ngắn, dễ scan.
- Label nhỏ, uppercase, tracking rộng.
- Nội dung bảng không quá lớn để giữ mật độ dữ liệu.
- Không dùng hero-scale type trong dashboard/tool view.
6. App Shell Pattern
Dùng shell này cho các app quản trị/dữ liệu.
<body class="app-shell bg-background text-on-surface antialiased flex h-screen w-screen overflow-hidden">
<aside id="appSidebar" class="h-screen w-56 flex flex-col bg-slate-100 border-r border-outline-variant/10 shrink-0">
<!-- Sidebar content -->
</aside>
<button id="sidebarBackdrop" type="button" aria-label="Close navigation"></button>
<main id="appMain" class="flex-1 flex flex-col h-screen min-w-0">
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<!-- Topbar actions -->
</header>
<div id="mainContent" class="flex-1 overflow-hidden">
<!-- Current page -->
</div>
</main>
</body>
Sidebar style:
- Width desktop:
w-56. - Background:
bg-slate-100. - Border right:
border-outline-variant/10. - Group label: uppercase,
text-[10px], tracking rộng. - Active nav: left border xanh, nền slate nhạt, text đậm.
- Nav item: icon + label, height vừa phải, hover nền slate.
Active nav example:
<a class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 text-slate-900 font-bold rounded-r-lg">
<span class="material-symbols-outlined">dashboard</span>
<span>Tổng quan</span>
</a>
Inactive nav:
<a class="flex items-center gap-3 px-3 py-2 text-slate-600 hover:text-slate-900 hover:bg-slate-200/50 transition-all rounded-r-lg">
<span class="material-symbols-outlined">apps</span>
<span>Ứng dụng</span>
</a>
7. Page Layout Pattern
Mỗi page nên theo cấu trúc:
<div class="page-name flex flex-col p-4 md:p-6 overflow-hidden h-full">
<div class="page-header flex items-center justify-between gap-4 mb-5 shrink-0">
<div>
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Page Title</h1>
<p class="text-sm text-on-surface-variant">Short explanation.</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<!-- Action buttons -->
</div>
</div>
<div class="page-filters flex items-center gap-3 mb-4 shrink-0">
<!-- Search/filter controls -->
</div>
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
<!-- Table/list/content -->
</div>
</div>
Nguyên tắc:
- Page padding luôn
p-4 md:p-6. - Header không quá cao.
- Action chính nằm bên phải.
- Filter nằm ngay trên bảng.
- Content panel chiếm hết phần còn lại.
- Không bọc section bằng quá nhiều card lồng nhau.
8. Dashboard Pattern
Dashboard nên có:
- Header ngắn.
- CTA chính.
- Metric grid.
- Recent activity hoặc bảng tóm tắt.
Metric card:
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Applications</span>
<div class="flex items-baseline justify-between">
<span class="text-2xl font-black text-on-surface">24</span>
<span class="text-[10px] font-bold text-on-surface-variant">18 Active</span>
</div>
</div>
Grid:
<div class="dashboard-stats grid grid-cols-4 gap-4 shrink-0">
Responsive:
- Desktop: 4 cột.
- Tablet/mobile: 2 cột.
- Mobile nhỏ: 1 cột.
9. Table Pattern
Đây là component quan trọng nhất của style này.
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
<div class="table-wrap overflow-y-auto overflow-x-auto flex-1">
<table class="w-full text-left border-collapse" style="min-width: 900px;">
<thead class="sticky top-0 z-10 bg-slate-50 border-b border-slate-200">
<tr>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Name</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<tr class="hover:bg-slate-50/80 transition-colors">
<td class="px-4 py-3 text-sm text-slate-700">Example</td>
<td class="px-4 py-3 text-right">...</td>
</tr>
</tbody>
</table>
</div>
<div class="page-pager flex items-center justify-between px-4 py-2 border-t border-slate-200 bg-slate-50 text-xs text-slate-600">
<span>Showing 1-10 of 50</span>
<div class="flex items-center gap-2">
<button class="px-2 py-1 border border-slate-200 rounded text-xs">Prev</button>
<span class="text-[11px]">Page 1 / 5</span>
<button class="px-2 py-1 border border-slate-200 rounded text-xs">Next</button>
</div>
</div>
</div>
Quy ước table:
- Header sticky.
- Row hover nhẹ.
- Cell padding đều.
- Text trong bảng ưu tiên
text-sm. - Header dùng
text-[10px] uppercase. - Nếu nhiều cột, đặt
min-widthvà cho horizontal scroll. - Nếu có action column quan trọng, có thể sticky bên phải.
Sticky action column:
<td class="sticky right-0 z-10 bg-white group-hover:bg-slate-50/80 px-4 py-3 text-right shadow-[-10px_0_12px_-12px_rgba(15,23,42,0.45)]">
10. Filter/Search Pattern
Filter bar:
<div class="page-filters flex items-center gap-3 mb-4">
<div class="flex items-center gap-1.5">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Status</span>
<select class="bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 pr-6 focus:ring-1 focus:ring-primary shadow-sm">
<option>All</option>
</select>
</div>
<div class="flex items-center gap-1.5 flex-1">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Search</span>
<input class="flex-1 bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm" placeholder="Search...">
</div>
</div>
Nguyên tắc:
- Filter ngắn, đặt sát bảng.
- Search chiếm phần còn lại.
- Label filter nhỏ và uppercase.
- Trên mobile, filter chuyển thành column/full width.
11. Button Pattern
Primary:
<button class="bg-primary hover:bg-primary-dim text-on-primary px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-all active:scale-95">
<span class="material-symbols-outlined text-base">add</span>
Add New
</button>
Secondary:
<button class="border border-slate-300 hover:bg-slate-100 text-slate-700 px-3 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-all active:scale-95">
<span class="material-symbols-outlined text-base">download</span>
Export
</button>
Danger:
<button class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-xs font-bold">
Delete
</button>
Icon action:
<button class="p-1.5 text-slate-400 transition-colors hover:text-primary" title="Edit">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
Disabled:
<button class="opacity-50 cursor-not-allowed" disabled>Disabled</button>
12. Form Pattern
Form field:
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Name</label>
<input class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Enter name">
</div>
Readonly:
<input readonly class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">
Textarea:
<textarea class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 h-20 resize-none"></textarea>
Field with icon:
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant/60">
<span class="material-symbols-outlined text-base">person</span>
</span>
<input class="w-full pl-10 pr-4 py-2.5 bg-surface-container-low border border-outline-variant/30 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-sm font-medium">
</div>
13. Badge/Status Pattern
Online/offline:
<div class="flex items-center gap-1.5">
<div class="w-1.5 h-1.5 rounded-full bg-primary ring-2 ring-primary/20"></div>
<span class="text-xs font-medium text-on-primary-fixed-variant">Online</span>
</div>
Status badges:
<span class="inline-block px-2 py-1 rounded text-xs font-semibold bg-emerald-100 text-emerald-700">Trong kho</span>
<span class="inline-block px-2 py-1 rounded text-xs font-semibold bg-blue-100 text-blue-700">Đang sử dụng</span>
<span class="inline-block px-2 py-1 rounded text-xs font-semibold bg-yellow-100 text-yellow-700">Đang chờ</span>
<span class="inline-block px-2 py-1 rounded text-xs font-semibold bg-red-100 text-red-700">Từ chối</span>
Role chip:
<span class="px-3 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-700">Admin</span>
14. Modal Pattern
Backdrop + content:
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="exampleModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Modal Title</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<form class="p-6 space-y-4">
<!-- Fields -->
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg">Cancel</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Save</button>
</div>
</form>
</div>
</div>
Modal CSS:
.modal-backdrop {
opacity: 0;
transition: opacity 0.2s ease-in-out;
pointer-events: none;
}
.modal-backdrop.open {
opacity: 1;
pointer-events: auto;
}
.modal-content {
transform: scale(0.95);
transition: transform 0.2s ease-in-out;
}
.modal-backdrop.open .modal-content {
transform: scale(1);
}
Danger confirm modal:
<div class="px-6 py-4 border-b border-slate-100 bg-red-50 flex items-center gap-3">
<span class="material-symbols-outlined text-red-600 text-2xl">warning</span>
<h3 class="text-base font-extrabold text-red-700">Xác nhận xóa</h3>
</div>
15. Login/Auth Screen Pattern
Auth screen dùng centered card:
<body class="bg-gradient-to-br from-blue-50 via-background to-purple-50 antialiased">
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md">
<div class="bg-white rounded-2xl shadow-lg border border-outline-variant/10 p-8">
<div class="text-center mb-8">
<div class="flex justify-center mb-4">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-4xl">security</span>
</div>
</div>
<h1 class="text-2xl font-black text-slate-900 tracking-tight">Product Name</h1>
<p class="text-xs uppercase tracking-widest text-on-surface-variant font-bold mt-2">Admin Console</p>
</div>
<!-- Form -->
</div>
</div>
</div>
</body>
Auth tabs:
<div class="flex gap-2 mb-6" role="tablist">
<button class="flex-1 py-2.5 rounded-lg text-xs font-bold uppercase tracking-wider border border-outline-variant/30 bg-primary text-on-primary shadow-sm">Đăng nhập</button>
<button class="flex-1 py-2.5 rounded-lg text-xs font-bold uppercase tracking-wider border border-outline-variant/30 bg-surface-container-low text-on-surface-variant">Đăng ký</button>
</div>
Alert states:
<div class="bg-error-container/20 text-error/80 border border-error/30 rounded-lg px-4 py-3 text-xs font-medium">Error message</div>
<div class="bg-green-50 text-green-800 border border-green-200 rounded-lg px-4 py-3 text-xs font-medium">Success message</div>
<div class="bg-amber-50 text-amber-800 border border-amber-200 rounded-lg px-4 py-3 text-xs font-medium">Warning message</div>
16. Responsive Rules
Recommended CSS:
#mobileMenuBtn,
#sidebarBackdrop {
display: none;
}
@media (max-width: 900px) {
body.app-shell {
width: 100%;
min-height: 100dvh;
height: 100dvh;
overflow: hidden;
position: relative;
}
#mobileMenuBtn {
display: inline-flex;
align-items: center;
justify-content: center;
}
#sidebarBackdrop {
display: block;
position: fixed;
inset: 0;
z-index: 70;
background: rgba(15, 23, 42, 0.45);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease-in-out;
}
#appSidebar {
position: fixed;
inset: 0 auto 0 0;
height: 100dvh;
width: min(82vw, 16rem);
z-index: 80;
transform: translateX(-100%);
transition: transform 0.2s ease-in-out;
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.35);
}
body.mobile-nav-open #sidebarBackdrop {
opacity: 1;
pointer-events: auto;
}
body.mobile-nav-open #appSidebar {
transform: translateX(0);
}
.page-header,
.page-filters,
.users-controls {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.table-wrap {
overflow-x: auto;
}
.table-wrap table {
min-width: 700px;
}
.page-pager {
gap: 0.5rem;
flex-direction: column;
align-items: flex-start;
}
.modal-backdrop {
align-items: flex-end;
}
.modal-backdrop .modal-content {
width: calc(100% - 1rem);
max-height: min(88dvh, 700px);
margin: 0.5rem;
overflow-y: auto;
border-radius: 0.9rem;
}
}
@media (max-width: 560px) {
.dashboard-stats {
grid-template-columns: 1fr;
}
}
Mobile behavior:
- Sidebar thành off-canvas.
- Topbar gọn lại.
- Profile metadata nên ẩn.
- Header/filter chuyển column.
- Table luôn scroll ngang nếu nhiều cột.
- Modal gần bottom và giới hạn chiều cao.
17. UX Rules
Giữ những nguyên tắc này khi áp dụng sang dự án khác:
- Mỗi màn dữ liệu cần có search hoặc filter nếu danh sách có thể dài.
- Action chính luôn rõ ràng, nằm ở page header.
- Action nguy hiểm luôn cần confirm.
- Action theo quyền nên disabled trực quan, không chỉ báo lỗi sau khi bấm.
- Table phải có empty state.
- Table dài cần pagination.
- Form dài nên chia grid 2 cột trên desktop, 1 cột mobile.
- Modal dài phải scroll trong content, không làm tràn viewport.
- Badge trạng thái phải thống nhất màu theo ý nghĩa.
- Icon-only button phải có
titlehoặc accessible name. - Không dùng nhiều card trang trí trong dashboard vận hành.
- Text không được overlap hoặc tràn button/card.
18. Accessibility Checklist
Khi port sang dự án mới, nên bổ sung:
aria-labelcho icon-only buttons.role="dialog"vàaria-modal="true"cho modal.- Focus trap trong modal.
- Đóng modal bằng phím
Escape. aria-expandedcho mobile menu.aria-selectedcho tabs.- Label đầy đủ cho form input.
- Không chỉ dùng màu để truyền đạt trạng thái; nên có text status.
- Contrast đủ tốt cho text nhỏ.
19. Implementation Checklist
Khi bắt đầu dự án mới:
- Cài Tailwind và plugin forms nếu cần.
- Copy design tokens vào
tailwind.config.js. - Thêm Google Fonts và Material Symbols.
- Tạo app shell: sidebar, topbar, content area.
- Tạo component base cho button, table, filter, modal, badge.
- Tạo responsive sidebar theo breakpoint
900px. - Áp dụng page layout pattern cho từng màn hình.
- Kiểm tra table trên mobile với horizontal scroll.
- Chuẩn hóa status color map.
- Thêm empty/loading/error states.
20. Mapping Sang Dự Án Khác
Nếu dự án mới không phải quản lý tài khoản/tài sản, vẫn có thể dùng style này bằng cách đổi tên module:
| AccManager concept | Dự án mới có thể thay bằng |
|---|---|
| Applications | Products, Services, Modules, Integrations |
| Accounts | Records, Credentials, Customers, Contracts |
| Assets | Inventory, Devices, Documents, Items |
| Users | Members, Staff, Operators |
| Departments | Teams, Groups, Categories |
| Projects | Campaigns, Workspaces, Sites |
| Borrow/Return | Requests, Tickets, Approvals, Workflows |
Điều quan trọng là giữ cùng cấu trúc:
- Sidebar nhóm chức năng.
- Page header + actions.
- Filter/search.
- Table/list.
- Modal CRUD.
- Badge trạng thái.
- Toast feedback.
21. Những Gì Nên Tránh Khi Port
- Copy nguyên text nghiệp vụ AccManager nếu dự án mới khác domain.
- Copy table quá nhiều cột khi domain không cần.
- Lạm dụng modal cho mọi thứ nếu workflow mới cần full page editor.
- Dùng gradient nền cho app shell chính.
- Tăng border radius quá lớn làm mất chất admin console.
- Dùng font khác quá mềm hoặc decorative.
- Thêm quá nhiều shadow, glassmorphism, decorative blobs.
- Trộn nhiều style icon khác nhau.