| name | ui-design-skill |
| description | Use when: creating or updating Flutter UI while enforcing Beacon typography, color theme, icon system, layout, and shared widget conventions. |
Skill: UI Design Consistency cho Beacon
Mục tiêu
Đảm bảo màn hình mới/được sửa tuân thủ typography, color theme, icon system, layout guide, component conventions và giữ trải nghiệm nhất quán trong toàn app.
Khi dùng
- Tạo màn hình UI mới.
- Redesign màn hình cũ.
- Chỉnh sửa component liên quan đến theme, text, icon, button, input, layout.
Đầu vào tối thiểu
- User story/màn hình cần làm
- Figma hoặc mô tả visual
- Danh sách state UI quan trọng (loading, empty, error, success)
- Icon cần dùng là icon phổ biến hay SVG custom export từ Figma
- Có cần dark mode hay không
Source of truth
docs/theme.md
docs/text_theme.md
docs/shared_widgets.md
docs/assets.md
lib/core/theme/color/app_colors.dart
lib/core/theme/text/app_text_theme.dart
lib/core/theme/app/app_theme.dart
lib/core/theme/icons/app_icons.dart
lib/core/theme/icons/app_icon.dart
lib/core/widgets/
Quy trình thực thi
1) Map design với token hiện có
- Xác định token màu semantic (
colorScheme.primary, surface, onSurface, ...).
- Xác định token typo từ
docs/text_theme.md: title dùng AppTextPreset.title1/title2/title3; body/label dùng đủ AppTextSize + AppTextSpacing + AppTextWeight.
- Xác định icon có sẵn trong
AppIcons hoặc cần thêm icon mới.
- Xác định component có sẵn có thể tái sử dụng.
2) Dùng text system thống nhất
- Page/screen ưu tiên render chữ bằng
AppText từ lib/core/widgets/text/text.dart.
- Không hardcode
fontSize, fontWeight, height trong page nếu style map được về token.
- Heading theo Figma dùng
AppText(preset: AppTextPreset.title1/title2/title3).
- Body/label theo Figma dùng
AppText(size: ..., spacing: ..., weight: ...).
- Reusable widget cần default
TextStyle thì dùng Theme.of(context).textTheme.ui(...) và cho phép caller truyền TextStyle? khi cần.
- Chỉ dùng
Text(...) trực tiếp khi viết reusable widget cần kiểm soát TextStyle, widget Material/platform, hoặc có lý do kỹ thuật rõ ràng.
- Khi text có thể dài, thêm
maxLines, overflow, và kiểm tra không vỡ layout trên mobile nhỏ.
- Nếu cần override màu, dùng
color của AppText hoặc copyWith(color: ...); tránh override fontWeight/style nếu không có chỉ định thiết kế.
Text color roles
- Heading/page title: dùng
AppColors.ink600 hoặc colorScheme.onSurface với weight mạnh.
- Section title/item title/body chính: dùng
AppColors.ink500 hoặc colorScheme.onSurface.
- Subtitle/description/helper/timestamp: dùng
AppColors.ink100 hoặc colorScheme.onSurface.withValues(alpha: 0.55-0.72).
- Disabled/placeholder-like text: dùng
AppColors.sky600 hoặc token disabled từ component theme.
- Text trên nền tối/primary/accent: dùng
AppColors.sky100, colorScheme.onPrimary, hoặc colorScheme.onSecondary.
- Link/action text: dùng
AppColors.teal500 hoặc colorScheme.primary.
- Accent/fun highlight text: dùng
AppColors.coral500 hoặc colorScheme.secondary, dùng tiết chế.
- Error text: dùng
AppColors.red500 hoặc colorScheme.error.
- Success text: dùng
AppColors.success, chỉ cho trạng thái thành công.
- Warning text nhỏ nên tránh vàng quá sáng; ưu tiên
AppColors.coral500 nếu cần tương phản tốt hơn AppColors.amber400.
- Nếu text đang nằm trên surface/card thông thường, ưu tiên
colorScheme.onSurface và alpha thay vì gọi sắc độ ngẫu nhiên.
- Không dùng
AppColors.ink300/ink400 cho text mới nếu không có lý do thiết kế rõ; chúng thường khó đọc hoặc tạo hierarchy lửng giữa body và muted.
3) Dùng icon system thống nhất
- UI mới hoặc phần icon đang chạm tới dùng
AppIcon(AppIcons.xxx) thay vì import trực tiếp PhosphorIcons, thêm mới Icons.*, hoặc hardcode path SVG.
- Icon phổ biến dùng Phosphor thông qua
AppIconData.phosphor(...) trong AppIcons.
- Icon custom/branding export từ Figma dùng SVG thông qua
AppIconData.svg(...) trong AppIcons.
- Không render SVG custom trong UI nếu asset chưa tồn tại trong
assets/icons/; chỉ khai báo constant path trước nếu chưa dùng ngay.
- Khi cần size/color, truyền qua
AppIcon(size: ..., color: ...); với SVG, AppIcon tự áp ColorFilter.
- Nếu thêm icon mới, cập nhật
lib/core/theme/icons/app_icons.dart; không thêm path SVG trực tiếp trong page/widget.
4) Ưu tiên shared widgets
- Kiểm tra
lib/core/widgets/ trước khi viết widget mới.
- Nếu phát sinh pattern lặp lại, đưa về core widget thay vì để trong feature.
- Với widget dùng icon, API nên nhận
AppIconData nếu widget cần chọn icon linh hoạt; chỉ nhận Widget khi caller cần custom render hoàn toàn.
- Với widget dùng text, API nên nhận
String + TextStyle?/Color? khi component có typography mặc định; chỉ nhận Widget khi caller cần custom render hoàn toàn.
5) Triển khai UI trong presentation layer
- Giữ logic nghiệp vụ ở notifier/usecase; page/widget chỉ render state.
- Side effects thông báo người dùng thông qua
appMessageProvider.
- Bọc nội dung page bằng
AppScreenLayout để bám layout guide, trừ khi page được yêu cầu full-bleed, camera/media preview, hoặc là demo/dev surface.
- Không hardcode route, endpoint, storage key, asset path hoặc icon library trực tiếp trong UI mới/phần đang sửa.
6) Tự review visual và consistency
- Soát hardcode style.
- Soát usage
Text(...) trực tiếp trong page; đổi sang AppText nếu không có lý do rõ ràng.
- Soát hardcode icon/path.
- Soát contrast và trạng thái disabled/error.
- Soát spacing, radius, typography theo token.
- Soát responsive trên thiết bị Android thật nếu layout phụ thuộc width.
Checklist verify UI trước merge
- Không hardcode
Colors.* trong page trừ khi có lý do rõ ràng.
- Không hardcode
fontSize/fontWeight/height nếu đã có token.
- Màu chủ yếu dùng qua
Theme.of(context).colorScheme.
- Text trong page/screen chủ yếu dùng
AppText; reusable widget dùng Theme.of(context).textTheme.ui(...) khi cần TextStyle.
- Icon mới hoặc icon đang sửa dùng qua
AppIcon(AppIcons.xxx); không import trực tiếp PhosphorIcons trong screen/widget.
- Không hardcode path SVG trong UI; path nằm trong
AppIcons hoặc AppImages tùy loại asset.
- Không render SVG chưa tồn tại trong
assets/icons/.
- Button/input/card/icon action ưu tiên dùng core widgets và theme chung.
- Trạng thái loading/empty/error/success được render đầy đủ.
- Kiểm tra màn hình trên mobile nhỏ, Android device thật nếu có thể, và màn hình rộng.
- Kiểm tra light/dark mode nếu có ảnh hưởng đến contrast.
- Không hardcode route/endpoint/storage key trong UI.
- Chạy
flutter analyze và verify tay flow liên quan khi môi trường cho phép.
- Page body được bọc bởi
AppScreenLayout nếu không thuộc ngoại lệ full-bleed/media/dev/demo.
- Text color có role rõ: heading/body/muted/action/error/success, không chọn sắc độ chỉ vì giống Figma từng điểm.
Done khi
- UI mới nhất quán với design system hiện tại.
- Không phát sinh style ad-hoc khó bảo trì.
- Không phát sinh icon import/path ad-hoc trong feature UI.
- Có bằng chứng verify theo checklist trước merge.