# 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:**
```css
/* 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):**
```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):**
```razor
<!-- 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):**
```razor
<!-- 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 text
- `PosLayout.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:**
```razor
<!-- 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:**
```razor
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:**
```css
/* 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`

```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:
```css
--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*
