uatr

Audit

Inconsistencies, state gaps, contrast failures and accessibility findings. Generated from audit.md.

Source: https://www.vv-it.com/webordering/static/ (UAT build v0.50.3, blue theme), 13 pages crawled at 1440px and 390px on 2026-10-07 with an authenticated UAT account (read-only). All findings come from computed styles, rendered text and the site's public CSS/JS. Severity: High = blocks users or fails WCAG AA; Med = inconsistency that costs rework; Low = tidy-up.

Summary

Area Finding Sev
Contrast 16 distinct text/background pairs fall below WCAG AA (worst 1.26:1) High
Focus No author-defined :focus / :focus-visible styles on any page; the only rule (usermenu.js) removes the outline. Controls rely on the browser default ring High
Forms 720 text inputs/selects have no programmatic label (placeholder only); 600 of them are the Rx grid on rx-lookup High
Keyboard 55 onclick handlers on non-interactive elements (53 table cells on user-management, 2 on tracking) are unreachable by keyboard High
Responsive Horizontal overflow at 390px on 6 of 13 pages (worst: branch-admin +720px); only two media queries exist High
Color 75 distinct opaque colors collapse to 42 tokens; two parallel neutral palettes (theme.css vs hard-coded Tailwind-style grays) Med
Semantics --green is bound to the theme accent, so "ok"/success text renders blue in the UAT theme; --teal variables hold blue Med
Components 33 button variants, 6 button class families, 8 input variants, 4 modal implementations + 1 drawer, 4 error-message treatments Med
Type / space 14 font sizes (half-pixel steps), 6 font stacks, 33% of spacing values off a 4px-based scale Med
Structure No <main>/<header>/<footer> landmarks, almost no <h1>, th without scope, modals without role="dialog" (except login) Med
Errors 404 returns bare JSON; API 500 on order-drafts/{id}/delivery-preview and 403 on admin/tog/connection seen during crawl Low

1. Near-duplicate values

Colors (merged into tokens)

Token Canonical Near-duplicates observed (uses)
color.text.default (color) #22292f #1f2937 (239)
color.text.muted (color) #6b7280 #6b7680 (398), #64748b (8)
color.primary.tint-1 (color) #d6e4f7 #cfe3f5 (5)
color.success.text (color) #0f6b34 #166534 (4)
color.bg.control (bg) #e3e8ef #e5e7eb (5)
color.bg.subtle (bg) #f7fafd #f9fafb (60), #f8fafc (24), #f7f9fc (12), #fafbfd (3), #f6fbf8 (2)
color.bg.sunken (bg) #eef1f6 #eef3f8 (11), #efefef (4)
color.bg.page (bg) #f3f6fa #f1f5f9 (8), #f3f6f9 (3), #f3fbf6 (2), #f0fdf4 (2), #f3f5f7 (1)
color.success.bg (bg) #e2f4e8 #dcfce7 (4)
color.primary.tint-4 (bg) #e1ebf8 #e0e7ff (6), #dfe9f6 (2)
color.border.subtle (borderColor) #e0e7f0 #e5e7eb (2445)

Two neutral palettes. Theme variables (--wo-*, e.g. text #22292f, muted #6b7680, border #e0e7f0) sit beside hard-coded Tailwind-style grays (#111827 ×1401, #6b7280 ×936, #374151, #e5e7eb ×2445). Pick one. Five reds for errors: #c0392b, #842029, #a03026, #b91c1c, #b3261e. Four greens for success text: #0f6b34, #166534, #15803d, #0a7d32.

Typography

Radius, motion, breakpoints

2. One-off and off-scale spacing

33.0% of spacing declarations (≤40px) do not land on the proposed scale (2/4/6/8/12/16/20/24/36). Most common off-scale values:

Value Uses Snaps to
9px 3092 8px
5px 2926 6px
7px 2068 8px
11px 478 12px
10px 225 12px
18px 160 20px
3px 159 4px
14px 103 16px

Table cells use padding: 6px 9px pervasively; cards use 18px padding where the rest of the UI uses 16/20. Other one-offs: margin: -2px 0 10px on #groupTabs, modal padding: 90px 16px for vertical offset, min-width: 34px on .qty.

3. Component inconsistencies

Component Observation
Button 33 computed variants. Class families btn-teal, btn-primary, btn-gray, btn-secondary, btn-ghost, btn-danger overlap (btn-gray ≈ btn-secondary ≈ btn-ghost; btn-teal ≈ btn-primary). btn-teal is blue in the UAT theme. Same variant renders at 30px (12.5px text, 5/11 padding) on some pages and 33px (13px, 6/12) on others.
Input Radius 6/7/8px, padding 4/6 → 11/12, heights 26–41px; some inputs are unstyled UA controls (0 radius, 13.33px, Arial).
Modal Four implementations + a drawer (section 8): .modal-backdrop/.modal-card (POS gate, settings confirm), .ov (login ×2), .um-overlay (change password), .modal-bg/.modal (CS ticket), .drawer (tracking). z-index 2000 on four, 50 on the rest; widths 420/480/520px; scrim rgba(0,0,0,.45) ×3 and .5 ×2; shadow .25 vs .3.
Banner/alert .banner is defined at least 3 ways (radius 8 vs 10, padding 10/12 vs 12/14, font 13.5 vs 14); .err appears with 4 different reds/backgrounds; .status.ok uses #15803d on #f0fdf4 while .banner-done uses #0f6b34 on #e2f4e8.
Badge/pill 22 computed variants across .pill, .badge, .chip, .togtag, .cnt, .status; radius 99/999px, font 11/11.5/12/13px.
Tabs .btn-tab, .btn-sub, .tab are three tab idioms; active state differs (fill vs inset shadow vs pill).
Links 47 text elements use the browser-default link blue #0000ee; others use --wo-primary-dark.
Table 7 variants; th fill, border and padding differ per page; none use scope, <caption>, or a responsive pattern.
Page chrome Top bar differs: .topbar vs .top; phase chip restyled per bar. No footer anywhere (version badge is fixed bottom-right).
Page titles VV Web Ordering — X on 9 pages, X — VV Web Ordering on 4 (orders, tracking, audit, review).

4. State coverage (hover · focus · disabled · error)

5. Contrast failures (WCAG 2.2 AA: 4.5:1 text, 3:1 large)

Ratio Foreground on background Size Example (page)
1.26 #6b7680 on #3b82f6 12.5px “(คลังมองเด้ · ตัดประกอบที่ VVD)” (settings)
2.01 #b0b8bd on #ffffff 14.5px “— ไม่ได้ระบุ —” (review)
3.68 #ffffff on #3b82f6 11px “ใช้งานอยู่” (settings)
3.68 #ffffff on #3b82f6 13px “1.” (settings)
4.05 #6b7680 on #e2f4e8 12.5px “⚠️ เอกสารยังไม่ถูก Release — คลังย” (review)
4.28 #ffffff on #467cc2 11.5px “Phase 2.1” (rx-lookup, stock)
4.28 #ffffff on #467cc2 13px “Phase 2.1” (branch-admin, settings, tog-admin, user-management)
4.34 #64748b on #f1f5f9 11.5px “ปิด” (branch-admin, tog-admin)
4.4 #6b7680 on #f3fbf6 13px “Server” (settings)
4.4 #6b7680 on #f3fbf6 12.5px “บิลที่มีกรอบ FS → · หน้าเปิดออเดอร” (settings)
4.4 #6b7680 on #f7f9fc 12px “สาขา pilot 2.1” (branch-admin, tog-admin)
4.4 #6b7680 on #f7f9fc 12.5px “รหัสนี้เป็น ใช้เข้าคอมพิวเตอร์บริษ” (login)
4.42 #6b7280 on #f3f5f7 12.5px “(footer credit line)” (signoff)
4.43 #6b7680 on #f6fbf8 12.5px “กำลังตรวจสต็อก BC…” (review)
4.48 #6b7680 on #fafbfd 13px “Server” (settings)
4.48 #6b7680 on #fafbfd 12.5px “บิลที่มีกรอบ FS → · หน้าเปิดออเดอร” (settings)

The systemic ones: #6b7680 muted text on tinted surfaces (4.05–4.48:1; passes only on pure white), white on #3b82f6 (3.68:1), and the translucent phase chip (4.28:1). Fix by darkening text.muted to #5b6670 (5.1–5.9:1 on white and the tinted surfaces) and using primary.default (#1d5fb4, 6.3:1 with white) for filled badges instead of accent.bright.

6. Accessibility & structure

Check Result
Targets under 24×24px 109 elements, mostly inline .muted links and .back links (exempt as inline text); real offenders: .btn-ghost (17px tall ×8), Rx grid inputs (26px)
Unlabeled controls index: 1, orders: 2, tracking: 3, stock: 3, rx-lookup: 602, audit: 5, settings: 1, user-management: 53, branch-admin: 29, tog-admin: 19, login: 2
Landmarks <main>: 0/13 · <header>: 0/13 · <nav>: 1/13 · <footer>: 0/13 · skip link: none
Headings <h1> present on 2 pages (signoff ×2, login ×1); other pages start at <h2>
Tables 167 th cells without scope
Mouse-only handlers onclick on td/tr/div/span: user-management 53, tracking 2
Dialogs role="dialog"/aria-modal: login only (2). Focus trapping / return-focus in the others was not verified (modals were not opened)
Language lang="th" on all pages (good); English UI strings (Username, Password, Settings, Audit log) are not marked lang="en"
Mobile overflow @390px orders +304px, stock +143px, audit +284px, review +113px, branch-admin +720px, tog-admin +345px
Zoom / viewport <meta name="viewport"> present on all pages (good)

8. Modals and error states (Stage 6 — triggered in UAT)

Modals were opened (never confirmed) and error states were provoked with bogus input. Writes were limited to two rejected sign-in attempts (POST /api/auth/login, POST /api/pos/refresh with an invented username); the password-change, CS-ticket and settings-save actions were not submitted — their validation errors were captured client-side or the dialog was cancelled.

Modal comparison

Modal (page) z-index Scrim Width Radius Shadow Title Primary button role=dialog / aria-modal
First-change password (login) 2000 0, 0, 0, 0.5 420px 12px α0.3) 16px / 700 #1d5fb4 @ 13.5px dialog / true
POS gate (bills) 2000 0, 0, 0, 0.45 420px 12px α0.25) 16px / 700 #1d5fb4 @ 13px dialog / true
Change password (user menu) 2000 0, 0, 0, 0.5 420px 12px α0.3) 16px / 700 #2563eb @ 13.5px dialog / true
CS ticket (review) 50 0, 0, 0, 0.45 520px 12px α0.25) 17px / 700 #2f6fd6 @ 14.5px — / —
Settings confirm (settings) 2000 0, 0, 0, 0.45 480px 12px α0.25) 16px / 700 #1d5fb4 @ 14px dialog / true
Side drawer (tracking) 50 none (no scrim) 520px 0 -4px 0 18px / 0.15 16px / 700 n/a — / —

Error and validation treatments

Where Trigger Treatment Colors Announced to AT?
Login wrong credentials (server) boxed message, 13px, radius 8 #c0392b on #fdecea, border #f3c1bc no (role/aria-live absent)
First-change password empty / <8 chars (client) same boxed message as login same no
Change password (menu) empty / <8 / mismatch (client) boxed message, 13.5px, padding 9×12 #b91c1c on #fef2f2 no
POS gate rejected credentials (server) plain red text, no box #c0392b no
CS ticket send failure plain red text (.err), no box #b3261e no
Stock check unknown SKU inline bold red line with ❌, raw JSON shown (({})) #c0392b no

All error text passes AA on its own background (4.76–6.54:1), but there are four visual treatments and five reds (#c0392b, #b91c1c, #b3261e, #842029, #a03026). None is tied to its field (aria-describedby / aria-invalid) or announced (role="alert"). The login message exposes the lockout rule (“3 more attempts before 30-minute lock”), which is useful but is colored the same as an error. The stock error prints a raw ({}) payload.

Empty and no-result states

Page Behavior
Bills (index) Table renders headers only; the only hint is small grey text above (“0 บิล”). No empty-row message.
Orders One muted row “— ไม่พบออเดอร์ที่ตรงกับ "…" —” (dash-wrapped). The filter form uses unstyled browser controls (no radius, default buttons) unlike the bills page.
Audit log One muted row “— ไม่มีรายการ —”; filter inputs are styled (radius 8) but the submit button is #1f4e79, a fifth blue.
Rx lookup Plain muted line outside the table: “ไม่พบลูกค้าที่ตรงกับคำค้น” (no dashes, no row).
User management Table row “ไม่พบ User ที่ตรงกับคำค้น” (no dashes).
Tracking drawer Loads on row click (read-only GET); shows a muted “กำลังโหลด…” then the history.

Five pages, four different empty-state treatments (none on bills, dash-wrapped row, plain row, outside-the-table line).

7. Recommended order of work

  1. Add one global :focus-visible ring (2px solid primary.strong, offset 2px) and stop removing outlines — fixes the biggest a11y gap in one rule.
  2. Label every field (<label for> or aria-label), starting with the Rx grid and user management; turn clickable cells into buttons/links.
  3. Adopt tokens.css as the single source: replace the 5 reds / 4 greens / two grays, and rename --teal/--green to semantic names that do not change with theme.
  4. Collapse button families to primary | secondary | danger | ghost × md | sm, and one modal + one banner implementation.
  5. Introduce 700/480px (or a single 720px) breakpoints and a responsive table pattern; fix the 6 overflowing pages.
  6. Darken text.muted, fix white-on-#3b82f6 badges and the phase chip.
  7. Add landmarks, <h1>, scope on th, role="dialog", and a styled 404.
  8. Standardise one modal (see Components), one error message (role="alert", aria-describedby) and one empty state.

Method & limits

Read-only: pages were loaded and screenshotted; hover/focus/active were forced via DevTools (no clicks), and non-GET API calls were blocked. State "disabled" was toggled in-page without submitting. Stage 6 provoked error states with bogus input (two rejected sign-ins) and opened modals without confirming them; password-change, CS-ticket send, settings save, order-open/submit/cancel and admin create/edit actions were not exercised, so their server-side success/failure paths are unverified. Findings reflect the UAT build and the blue theme; the green production theme was not crawled.