12 KiB
UX/UI Designer Audit — GoodGo POS System
Auditor: UX/UI Designer Agent
Date: 2026-03-20
Scope: apps/web-client-tpos-net — Blazor WASM + MudBlazor frontend
Focus: UI consistency, component reuse, theme & styling, accessibility, UX flows
Executive Summary
The GoodGo POS frontend has a solid design foundation — centralized theming via AppTheme.cs, a reusable auth component library, and responsive multi-layout architecture. However, the codebase suffers from critical accessibility failures (no keyboard navigation, missing ARIA attributes, no focus-visible styles), 2,316+ inline style attributes that undermine design system consistency, and pervasive hardcoded Vietnamese strings that break English localization throughout the POS UI. Overall UI/UX maturity: 6/10.
Critical Issues
Issues that block production readiness or violate WCAG 2.1 AA standards.
1. Missing :focus-visible Styles — WCAG 2.1 §2.4.7
Files: wwwroot/css/admin.css, wwwroot/css/pos.css, wwwroot/css/auth.css, wwwroot/css/marketing.css
Impact: Keyboard users cannot see which element has focus — entire app is effectively unusable without a mouse.
No CSS file defines :focus-visible styles. Browsers' default focus ring is often suppressed by MudBlazor reset styles.
Fix:
/* Add to app.css global scope */
:focus-visible {
outline: 2px solid #FF5C00;
outline-offset: 2px;
border-radius: 4px;
}
2. Clickable <div> Elements Instead of <button> — WCAG 2.1 §4.1.2
Files: Pages/Pos/Cafe/CafeDesktop.razor, Pages/Pos/Restaurant/RestaurantDesktop.razor, and all other POS vertical desktop pages
Impact: Product cards, order items, and action elements are <div> with @onclick handlers. Screen readers cannot identify them as interactive. Keyboard users cannot Tab to them or activate with Enter/Space.
Example (CafeDesktop.razor):
<!-- BAD: div with onclick -->
<div class="pos-product-card" @onclick="() => AddToCart(product)">
...
</div>
<!-- GOOD: semantic button -->
<button class="pos-product-card" @onclick="() => AddToCart(product)"
aria-label="Thêm @product.Name vào giỏ hàng">
...
</button>
3. No ARIA Labels on Interactive Icons — WCAG 2.1 §4.1.2
Files: Components/Auth/AuthButton.razor, Components/Auth/AuthInput.razor (line 37), all layout files
Impact: Icon-only buttons (close, toggle, back) have no accessible names — screen readers announce nothing or raw Unicode.
Example (AuthInput.razor ~line 37):
<!-- BAD: no aria-label -->
<button @onclick="TogglePassword">
<i data-lucide="eye"></i>
</button>
<!-- GOOD -->
<button @onclick="TogglePassword"
aria-label="@(showPassword ? "Ẩn mật khẩu" : "Hiện mật khẩu")"
aria-pressed="@showPassword">
<i data-lucide="@(showPassword ? "eye-off" : "eye")"></i>
</button>
4. Error/Success Messages Missing role="alert" — WCAG 2.1 §4.1.3
Files: Pages/Auth/LoginAdmin.razor (lines 28–40), Pages/Pos/Cafe/CafeDesktop.razor
Impact: Dynamic status changes (login error, payment success, cart update) are not announced to screen readers.
Example (LoginAdmin.razor ~line 29):
<!-- BAD: static div, not announced -->
<div style="background:rgba(239,68,68,0.12);color:#EF4444;">
@_errorMessage
</div>
<!-- GOOD -->
<div role="alert" aria-live="assertive" class="auth-error-message">
@_errorMessage
</div>
5. Hardcoded Vietnamese Strings in POS UI — Localization Failure
File: Pages/Pos/Cafe/CafeDesktop.razor and all POS vertical pages
Impact: English-language users see Vietnamese text. App cannot support international merchants.
Examples found (CafeDesktop.razor):
| Line | Hardcoded String | Should Be |
|---|---|---|
| 24 | "Đang tải..." |
@L["Common_Loading"] |
| 25 | "Không thể tải dữ liệu" |
@L["Common_LoadError"] |
| 66 | "Đơn hàng" |
@L["Pos_OrderPanel_Title"] |
| 67 | "món" |
@L["Pos_CartItem_Unit"] |
| 89 | "Nhập mã voucher..." |
@L["Pos_Voucher_Placeholder"] |
| 97 | "Giảm giá" |
@L["Pos_Discount_Label"] |
| 107 | "Tổng cộng" |
@L["Pos_Total_Label"] |
| 117 | "Đang tạo đơn..." |
@L["Pos_CreatingOrder"] |
| 122 | "Thanh toán" |
@L["Pos_Checkout_Button"] |
Also found in layouts:
AdminLayout.razor(line ~160):"Có lỗi xảy ra","Vui lòng thử lại","Tải lại"StaffLayout.razor(line 148): Same error boundary textPosLayout.razor(lines 29, 65, 115):"GoodGo POS","Menu","Đơn hàng"
6. Hardcoded Color Values Instead of CSS Variables
Files: Pages/Pos/Cafe/CafeDesktop.razor, Pages/Auth/LoginAdmin.razor, multiple admin pages
Impact: Theme overrides are impossible. Colors are not consistent with the design token system.
Examples:
<!-- BAD: hardcoded rgba -->
style="background:rgba(139,92,246,0.1);color:#8B5CF6;"
style="background:rgba(239,68,68,0.12);color:#EF4444;"
style="background-color:rgba(139,92,246,0.125);"
<!-- GOOD: CSS variables -->
class="status-purple"
class="status-danger"
Hardcoded colors to replace:
| Value | Should Be |
|---|---|
#16A34A |
var(--color-success) |
#EF4444 |
var(--color-danger) |
#8B5CF6 |
var(--color-purple) |
rgba(139,92,246,0.1) |
var(--color-purple-subtle) |
rgba(239,68,68,0.12) |
var(--color-danger-subtle) |
Warnings
Issues that create significant technical debt or UX inconsistency.
7. 2,316 Inline Style Attributes
Inline styles are scattered across all pages — especially POS desktop views, admin shop pages, and dashboard. This makes CSS maintenance impossible and prevents design system enforcement.
Top offending patterns:
style="display:flex;flex-direction:column;gap:24px;"
style="padding:6px 12px;border-radius:8px;border:none;"
style="position:absolute;left:@{X}px;top:@{Y}px;"
Strategy: Create CSS utility classes and component classes. Move inline styles to scoped .razor.css files.
8. Inconsistent Spacing Scale
The design uses 10+ different spacing values without a defined scale:
- Gaps: 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 48px
- Padding: 6, 8, 10, 12, 16, 20, 24px
- Border radius: 6, 8, 10, 12, 14, 20, 24px
Recommended scale:
/* Spacing: 4-point scale */
--space-1: 4px; --space-2: 8px; --space-3: 12px;
--space-4: 16px; --space-5: 20px; --space-6: 24px;
--space-8: 32px; --space-12: 48px;
/* Border radius: 3 values */
--radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px;
9. Missing Contrast Ratio for Secondary Text — WCAG 2.1 §1.4.3
File: wwwroot/css/admin.css
/* Current — MAY fail WCAG AAA */
--admin-text-secondary: #ADADB0; /* ratio ~5.08:1 on #1A1A1D — FAILS AAA (7:1) */
--admin-text-tertiary: #8B8B90; /* ratio ~4.13:1 on #1A1A1D — FAILS AA (4.5:1) */
--admin-text-tertiary likely fails WCAG AA for normal text. Needs contrast validation.
10. Cart Items Use <div> Instead of Semantic List
File: Pages/Pos/Cafe/CafeDesktop.razor
Cart items rendered as generic <div> elements. Should be <ul>/<li> structure for screen readers to announce list count and items.
11. No .razor.css Scoped Stylesheets Per Component
All 8 reusable components (AuthButton, AuthInput, OtpInput, etc.) have zero scoped CSS files. Styles are defined globally in auth.css. This creates unintended style leakage and makes component refactoring dangerous.
12. OTP Input Missing ARIA Group Label
File: Components/Auth/OtpInput.razor (lines 12–19)
The 6-digit OTP input is a group of individual <input> elements but lacks role="group" and aria-label="One-time password". Screen readers will announce 6 separate unlabeled inputs.
13. No Focus Trap in Modal Overlays
Files: Layout/PosLayout.razor, admin dialog pages
When mobile overlays or modals open, focus is not trapped within them. Users pressing Tab will cycle through background content, violating WCAG 2.1 §2.1.2.
Improvements
Recommendations that would improve UX quality and developer experience.
A. Create Component-Scoped CSS Files
For each component in Components/, create a matching .razor.css file:
Components/Auth/AuthButton.razor.css
Components/Auth/AuthInput.razor.css
Components/Pos/ResponsiveOrderPanel.razor.css
B. Add Password Strength Indicator
File: Pages/Auth/Register.razor
Registration form has no visual password strength feedback. Add a 4-step strength bar (weak → fair → good → strong) using the existing PasswordStrengthCalculator pattern from Swift app.
C. Standardize Icon Sizes to 3 Tiers
Currently uses: 12, 14, 16, 18, 20, 24, 28, 32px. Reduce to:
--icon-sm: 16px; /* Inline, label */
--icon-md: 20px; /* Button, nav */
--icon-lg: 24px; /* Header, feature */
D. Add aria-expanded to Expandable Sections
File: Pages/Admin/Shop/ShopRecipes.razor
Accordion-style expand/collapse sections lack aria-expanded and aria-controls. Screen readers cannot determine collapsed state.
E. Add aria-busy to Async Loading States
Async data-fetch loading spinners don't set aria-busy="true" on the container, so screen readers don't know content is loading.
F. Introduce Design Token Documentation
Create a living style guide page at /admin/design-system (dev only) that shows all color tokens, spacing, typography, and component variants. Reference: MudBlazor Theme Manager pattern.
G. Validate Table Semantics
Admin tables (staff management, inventory, orders) should verify <th scope="col"> on all headers. MudBlazor's MudTable typically handles this, but custom HTML tables may not.
Action Items
Prioritized next steps.
| # | Priority | Effort | Item | Files |
|---|---|---|---|---|
| 1 | 🔴 Critical | Small | Add :focus-visible styles globally |
wwwroot/css/app.css |
| 2 | 🔴 Critical | Large | Replace clickable <div> with <button> in POS pages |
All *Desktop.razor |
| 3 | 🔴 Critical | Medium | Add aria-label to all icon-only buttons |
Auth components, layouts |
| 4 | 🔴 Critical | Medium | Add role="alert" to all error/success messages |
Login pages, POS |
| 5 | 🔴 Critical | Large | Move hardcoded Vietnamese strings to L["key"] in POS | All POS vertical pages |
| 6 | 🔴 Critical | Medium | Replace hardcoded colors with CSS variables | CafeDesktop, Dashboard |
| 7 | 🟠 High | XL | Extract 2,316 inline styles to CSS classes | All pages |
| 8 | 🟠 High | Medium | Fix secondary text contrast (--admin-text-tertiary) |
admin.css |
| 9 | 🟠 High | Small | Add role="group" + aria-label to OTP input |
OtpInput.razor |
| 10 | 🟠 High | Medium | Implement focus trap in modal overlays | Layouts, dialogs |
| 11 | 🟠 High | Small | Localize error boundary strings in layouts | AdminLayout, StaffLayout |
| 12 | 🟡 Medium | Medium | Standardize spacing scale (4-point system) | All CSS files |
| 13 | 🟡 Medium | Small | Standardize border-radius (3 values) | All CSS files |
| 14 | 🟡 Medium | Medium | Create .razor.css per component |
Components/Auth/ |
| 15 | 🟡 Medium | Small | Add aria-expanded to expandable sections |
ShopRecipes.razor |
| 16 | 🟡 Medium | Small | Add password strength indicator | Register.razor |
| 17 | 🟡 Medium | Small | Standardize icon size scale (3 tiers) | Icon usage site-wide |
| 18 | 🟡 Medium | Medium | Add cart item <ul>/<li> semantics |
POS order panels |
| 19 | 🟢 Low | Large | Create design system documentation page | New page |
| 20 | 🟢 Low | Medium | Screen reader testing (NVDA/JAWS) | Full app regression |
Audit performed by UX/UI Designer Agent — GoodGo Platform — 2026-03-20