Design Audit · Produksi

Meridian CRM
Audit UX & UI

meridiancrm.pages.dev 6 Agustus 2026 7 surface diaudit 10 komentar ruttl divalidasi

1 · Ringkasan Eksekutif

Meridian punya fondasi visual di atas rata-rata: sistem token warna lengkap, hierarki heading benar, command palette yang matang, dan pola selection yang rapi. Craft-nya terlihat.

Masalahnya ada di lapisan lain. Dari tiga pekerjaan inti seorang sales, satu sudah jalan dengan baik dan dua belum ada:

Pekerjaan intiStatus
Memindahkan deal ke stage berikutnyaBISA drag & drop berfungsi, termasuk keyboard
Membuka detail sebuah dealTIDAK ADA tidak ada halaman/panel deal
Menutup deal sebagai Won / LostTIDAK ADA tidak ada stage Lost, Won tanpa kolom

Ditambah satu masalah kepercayaan yang serius: filter periode di Overview mengubah label tapi tidak mengubah satupun angka.

Task completion
5/10
Ubah stage sudah bisa; detail deal & Won/Lost belum ada
Information architecture
6/10
Nav jelas, tapi “Views” pindah halaman diam-diam
Feedback & status
5/10
Filter periode kosmetik; 4 KPI card dead affordance
Data integrity / trust
3/10
Angka tidak berubah saat filter diubah; count Board ≠ Table
Visual & consistency
7/10
Token system kuat, inkonsistensi antar tabel
Accessibility
5/10
ARIA banyak benar, 5 pasangan kontras gagal AA
Responsive
1/10
Hanya 1 media query di seluruh CSS

4 hal yang harus diperbaiki lebih dulu

  1. Deal tidak punya halaman/panel detail. Empat jalur berbeda menuju sebuah deal, tidak satupun membukanya. Dua di antaranya justru menampilkan toast yang menyatakan deal itu “lives on the Pipeline board”.
  2. Filter periode di Overview tidak mengubah data apapun. Hanya labelnya yang berubah — dan label yang berubah itu membuat angka lama terbaca sebagai angka baru.
  3. Tidak ada stage “Lost”, dan “Won” tidak punya kolom di Board. Deal tidak bisa ditutup.
  4. Aplikasi tidak responsif sama sekali. Layout 4-kolom dan 12-kolom fixed di semua lebar layar.

2 · Scope & Metodologi

Halaman yang diaudit: / (Overview), /pipeline (Board + Table), /contacts, Contact drawer, New deal modal, Workspace settings modal, Command palette (⌘K).

Yang dilakukan:

Yang belum dicakup: usability testing dengan user asli · screen reader run-through end-to-end · performance & bundle audit · uji perangkat fisik (audit responsif di sini berbasis analisis CSS).

Semua angka di dokumen ini hasil pengukuran langsung

Bukan estimasi visual. Script verifikasi ada di Lampiran B — bisa dijalankan ulang kapan saja di DevTools Console.

3 · Bagian A — Audit UX

A1 · Task Flow — pekerjaan inti tidak bisa diselesaikan

Ini bagian paling penting dalam dokumen ini. Semua temuan lain sifatnya sekunder dibanding ini.

A1.1 CRITICAL Tidak ada detail deal

Tab Deals · 2 di dalam contact drawer bukan deal detail. Itu daftar deal milik seorang kontak: nama deal, badge stage, probabilitas, dan nilai. Berguna, tapi read-only dan bersifat ringkasan. Chevron di ujung kanannya tidak membuka apapun.

Produk sendiri yang menyatakannya. Keempat jalur menuju sebuah deal diuji:

Jalur masukHasil
Klik kartu di BoardMembuka drawer kontak, bukan deal
Klik baris di Pipeline TableMembuka drawer kontak (Caldera — Investor deck system → drawer Yusuf Osei)
Klik deal di tab Deals contact drawerToast: “Halcyon — Launch campaign” lives on the Pipeline board — tidak navigasi, tidak membuka apapun
Klik deal di command palettePindah ke /pipeline, toast: Veldt — Data-viz sprint — Lead on the board — tanpa highlight/scroll ke kartunya

Dua dari empat jalur menampilkan pesan yang secara harfiah memberi tahu user bahwa deal itu hanya “ada di board”. Itu bukan bug — itu keputusan desain yang sudah disadari, dan produk memilih memberi tahu user daripada memberi mereka halamannya.

Masalah tambahan: aria-label kartu berbunyi "Arcadia — Ops dashboard, $24K" — screen reader menjanjikan sebuah deal, tapi aksinya membuka sebuah orang. Label/action mismatch, bukan sekadar navigasi yang salah.

Akibatnya user tidak bisa: melihat riwayat deal, mengubah value, mengubah close date, mencatat note yang menempel di deal, melihat siapa saja yang terlibat, atau menutup deal.

Rekomendasi: Buat drawer/route Deal (/deals/:id) berisi header (nama, value, stage editable, close date, owner), tab Activity, Contacts terkait, Notes, dan tombol Mark as Won / Mark as Lost. Sambungkan keempat jalur di atas ke sana — termasuk mengganti kedua toast dengan navigasi sungguhan.

A1.2 HIGH Win probability tersembunyi di satu tempat, read-only

Tab Deals di contact drawer menampilkan 25% likely dan 70% likely. Ini satu-satunya tempat di seluruh aplikasi yang memperlihatkan probabilitas menang.

Padahal KPI Weighted forecast $434K di Overview hanya bisa dihitung dari angka-angka ini. Jadi ada field yang menggerakkan salah satu metrik utama, tapi tidak muncul di kartu Board, tidak muncul di Pipeline Table, tidak ada di modal New deal, dan tidak bisa diedit di manapun.

Rekomendasi: Tampilkan probability sebagai kolom di Pipeline Table dan field editable di deal detail. Kalau nilainya otomatis dari stage, jelaskan aturannya — user perlu tahu kenapa forecast-nya $434K dan bukan $958K.

A1.3 CRITICAL Deal tidak bisa ditutup

Tiga masalah saling mengunci:

TemuanBukti
Board hanya punya 4 kolom: Lead, Qualified, Proposal, NegotiationScreenshot Board
Modal New deal menyediakan opsi stage Won<select> Stage: Lead, Qualified, Proposal, Negotiation, Won
Tidak ada opsi “Lost” di manapunSama seperti di atas
Table menampilkan 2 deal ber-stage Won, Board tidakNorthbeam — Retail concept & Tidemark — Onboarding flow
Konsekuensi konkret

Buat deal dengan stage “Won” → deal itu langsung hilang dari Board dan tidak bisa diakses lagi lewat Board.

Dan: header Pipeline tertulis 16 open · $958K, tapi Table menampilkan 18 baris. Angka header tidak salah (16 memang open), tapi tabelnya tidak dilabeli dan tidak difilter — user melihat konflik.

Rekomendasi:

A1.4 CRITICAL Filter periode di Overview tidak berfungsi

Test: This quarterThis month.

ElemenSebelumSesudah
Pipeline value$958K, +12%$958K, +12%
Weighted forecast$434K, +8%$434K, +8%
Deals in motion1616
Avg. days to close34, −5 days34, −5 days
Sparkline (4 buah)identik
Needs attention / Snapshot / Activitytidak merespons
Label perbandingan“vs last quarter”“vs last month” ← satu-satunya yang berubah

Label yang berubah justru memperburuk keadaan: user membaca “$958K, +12% vs last month” dan menganggapnya fakta. Ini bukan bug kosmetik — ini menyajikan angka yang salah.

Rekomendasi: Sambungkan filter ke data. Kalau belum bisa — nonaktifkan kontrolnya dan beri tooltip “Coming soon”. Kontrol yang mati lebih baik daripada kontrol yang berbohong.

A1.5 HIGH Empat KPI card adalah dead affordance

Keempat card punya role="button" dan tabindex="0" (dapat fokus keyboard), tapi klik tidak menghasilkan apapun.

Rekomendasi: Hubungkan tiap card ke drill-down yang relevan (Pipeline value → /pipeline; Deals in motion → pipeline difilter closing bulan ini), atau hapus role="button" sepenuhnya.

A2 · Information Architecture

A2.1 HIGH “Views” di sidebar diam-diam berpindah halaman

ViewHalaman tujuanIndikasi di UI
Q3 Closing/pipelinetidak ada
Dormant > 30d/contactstidak ada
Referralsbelum diujitidak ada

Dot warna di sebelah kiri (hijau/oranye/biru) tidak memetakan ke halaman tujuan — jadi warnanya terlihat dekoratif dan malah menambah beban baca.

Rekomendasi: Kelompokkan dengan label (DEALS / PEOPLE), atau tempelkan ikon halaman tujuan pada tiap view. Ganti dot acak menjadi ikon yang bermakna.

A2.2 MEDIUM Isi “Q3 Closing” tidak sesuai namanya

View Q3 Closing menampilkan Juniper & Vale — Naming + identity dengan close date Oct 7 — itu Q4.

A2.3 HIGH “Needs attention” tidak diurutkan berdasarkan urgensi

Elena Brandt     6d
Yusuf Osei      12d
June Park        9d
Tomás Rivera    21d
Harriet Boyle   16d

Bukan urut hari (6, 12, 9, 21, 16), bukan urut tanggal sumber (Jul 31, 25, 28, 16, 21), bukan urut nilai deal. Badge harinya sudah punya sistem warna (abu → amber → merah), tapi urutannya tidak mengikuti warnanya sendiri — user melihat merah di posisi 4 dan abu di posisi 1.

Modul ini satu-satunya bagian di Overview yang tugasnya memprioritaskan. Kalau urutannya acak, modulnya tidak melakukan pekerjaannya.

Rekomendasi: Default sort by severity (hari terlama × nilai deal), tampilkan kontrol sort, dan jelaskan aturannya (Sorted by risk).

A2.4 HIGH Pencarian mengembalikan hasil yang tidak relevan

Query north mengembalikan:

HasilRelevan?
Elena Brandt · Northbeam Goodsya
Owen Gallagher · Northbeam Goodsya
Dr. Amara Singh · Marrow Healthtidak
Northbeam — Brand systemya
Northbeam — Retail conceptya
Marrow — Patient portal UXtidak
Marrow — Brand refreshtidak

3 dari 7 hasil tidak mengandung kata kunci. Fuzzy matching-nya terlalu longgar — kemungkinan subsequence match lintas field.

Rekomendasi: Naikkan threshold, batasi fuzzy ke prefix/word-boundary, dan tampilkan highlight bagian yang match supaya user paham kenapa sebuah hasil muncul.

A3 · Feedback, State & Error Handling

#TemuanSeverityBukti
A3.1Aksi baris tersembunyi total saat rest state — 40 kontrol ber-opacity:0 di /contacts (20 checkbox + 20 tombol Copy email), plus 15 tombol Email/Task/Snooze di Needs attentionHIGHclass .reveal, muncul hanya pada :hover/:focus-within
A3.2Karena A3.1, modul Needs attention terlihat sebagai daftar bacaan pasif — padahal isinya tiga aksi paling berguna di seluruh aplikasiHIGH
A3.3Checkbox “select all” tidak masuk state indeterminate saat 1 dari 20 baris terpilihMEDthead input.checked === false
A3.4Bulk action bar menutupi baris summary footer (20 rows · Σ $958K · ⌀ 12d)MEDScreenshot
A3.5Submit form New deal kosong tidak memunculkan error inline — hanya native validation bubbleMEDfield required, tanpa UI error
A3.6Empty state pencarian (No results for "zzzz") polos — tanpa saran atau aksi lanjutanLOW
A3.7Dot aktivitas basi () di kartu Board hanya punya native title — tanpa legenda, tanpa aria-label. Tidak terlihat di touch, tidak terbaca screen readerMED<span class="rounded-full" title="No activity for 12 days"> tanpa accessible name

Rekomendasi utama: Untuk baris yang menjadi fokus utama (Needs attention), tampilkan aksi secara permanen — jangan sembunyikan di balik hover. Pola hover-reveal cocok untuk tabel padat, tidak cocok untuk modul triase. Tetap sediakan fallback non-hover di semua tempat — perangkat touch tidak punya hover sama sekali.

A4 · Modal “New deal”

Yang sudah benar

role="dialog" + aria-modal="true" · semua <label> membungkus input-nya (asosiasi valid tanpa for/id) · field Deal name required · default close date 30 hari ke depan (2026-09-05sudah diverifikasi benar; tampilan 05/09/2026 hanya format locale DD/MM, bukan tanggal lampau).

#TemuanSeverity
A4.1Tidak ada field Contact/People. Deal dibuat tanpa terhubung ke orang — padahal panel kontak adalah satu-satunya tempat deal bisa dilihat detailnyaHIGH
A4.2Company default ke item pertama (Northbeam Goods), bukan placeholder. User yang buru-buru akan membuat deal di company yang salahHIGH
A4.3Tidak ada penanda field mana yang wajibMED
A4.4Stage “Won” tersedia tapi tidak punya kolom di Board (lihat A1.3)CRIT

A5 · Modal “Workspace settings”

#TemuanSeverity
A5.1Konten terpotong di balik sticky footer — baris member terakhir (Sofía Reyes) terpotong separuh, tanpa indikasi batas scrollMED
A5.2Plan Pro · 12 seats vs MEMBERS · 5 members tanpa konteks — 7 seat kosong tidak dijelaskanLOW
A5.3Link “Manage” ×5 tanpa indikasi tujuanLOW

4 · Bagian B — Validasi 10 Komentar Ruttl

Semua 10 komentar berhasil diambil beserta selector elemen yang di-anchor, jadi validasi di bawah menunjuk ke elemen yang tepat — bukan tebakan.

#HalamanKomentarVerdict
1/contactsukuran beda dengan profile userVALID — diagnosis perlu dikoreksi
2/contactscheckbox tidak munculVALID discoverability, bukan bug
3/contactshug contentVALID
4/contactspadding atas bawah kolom terlalu sempitVALID
5/pipelinebetter diberi paginationVALID BERSYARAT
6/contactsarrow tidak munculPERLU KONFIRMASI
7/tinggi content tdk balance, better disamakanVALID solusinya beda
8/contactsarrow tidak munculDUPLIKAT #6
9/contactsmicro interactionVALID lebih luas dari dugaan
10/contactsbetter diberi paginationDUPLIKAT #5, VALID
01“ukuran beda dengan profile user”/contacts
tbody > tr:1 > td:4 > span > span:1 — badge company di kolom COMPANY
ElemenUkuranFontBentuk
Avatar orang (kolom NAME)24 × 2410pxlingkaran
Avatar owner (kolom OWNER)24 × 2410pxlingkaran
Badge company (kolom COMPANY)18 × 188pxrounded-square (r 4px)

Verdict: valid — tapi jangan disamakan

Perbedaan bentuk (kotak vs lingkaran) justru pola yang bagus — itu konvensi standar untuk membedakan organisasi dari orang. Pertahankan.

Masalah sebenarnya adalah font 8px. Itu di bawah batas keterbacaan praktis untuk teks apapun, bahkan inisial.

Rekomendasi: Naikkan company badge ke 20 × 20 dengan font 10px, pertahankan radius 4px. Perbedaan 20 vs 24 tetap membentuk hierarki tanpa mengorbankan keterbacaan.

02“checkbox tidak muncul”/contacts
tbody > tr:1 > td:1 > input

Verdict: valid sebagai masalah discoverability — bukan bug fungsional

Checkbox ada di DOM dengan opacity: 0 dan class .reveal. Aturan CSS-nya:

.reveal                              → opacity: 0
.reveal-parent:hover .reveal         → opacity: 1
.reveal-parent:focus-within .reveal  → opacity: 1
Yang perlu dikoreksi dari komentarmu

State terpilih sebenarnya sudah ditangani dengan baik. Setelah dicentang, baris mendapat background --ember-50 (#fdf3ec), checkbox tetap terlihat, dan muncul bulk action bar 1 selected · Add tag · Assign · Export · Delete. Bagian itu solid — jangan diubah.

Masalah yang sesungguhnya

  1. Checkbox “select all” di header selalu terlihat (opacity 1), sedangkan checkbox baris tidak. Inkonsistensi ini yang bikin user berpikir fiturnya rusak — header menjanjikan bulk selection, baris tidak menunjukkan caranya.
  2. Di perangkat touch, hover tidak ada. Bulk selection mustahil ditemukan di tablet/mobile.
  3. Total ada 40 kontrol tersembunyi di halaman ini (20 checkbox + 20 tombol Copy email).

Rekomendasi

  • Tampilkan checkbox baris pada opacity: 0.35 saat rest, 1 saat hover/focus/checked. Tetap tenang secara visual, tapi terlihat ada.
  • Atau: tampilkan permanen saat ada ≥1 baris terpilih (mode seleksi aktif).
  • Wajib: @media (hover: none) { .reveal { opacity: 1 } }
03“hug content”/contacts
main > div > div:2 — wrapper tabel

Verdict: valid

Wrapper punya class flex-1 overflow-auto → selalu meregang setinggi viewport terlepas dari jumlah baris.

KondisiEfek
20 baris (default)Tabel 790px di dalam container 676px → scroll internal
2 baris (view Dormant > 30d)Container tetap penuh → ~500px area kosong di bawah data

Rekomendasi: Ganti flex-1 dengan max-h-[…] h-auto. Container shrink-wrap ke konten sampai batas maksimum, lalu baru scroll. Ini juga otomatis memperbaiki “kotak kosong besar” di semua saved view yang hasilnya sedikit.

04“padding atas bawah kolom terlalu sempit”/contacts
tbody > tr:1 > td:4

Verdict: valid — dan lebih ekstrem dari dugaan

Pengukuran: padding-top: 0px, padding-bottom: 0px pada semua <td>. Tinggi baris 36px murni berasal dari line-height.

36px untuk sel berisi dua baris teks (nama + email) itu sangat padat. Jarak vertikal antar baris data praktis nol.

Rekomendasi:

  • Density Comfortable (default): padding: 10px 12px, tinggi baris ~48px
  • Density Compact (power user): padding: 6px 12px, tinggi baris ~40px
  • Sediakan toggle density di toolbar tabel — dua kebutuhan ini nyata dan berbeda
05 + 10“better diberi pagination”/pipeline · /contacts
#5 → main > div · #10 → tfoot > tr > td:1

Verdict: valid sebagai antisipasi skala — belum jadi masalah pada data saat ini

Kondisi sekarang: Contacts 20 baris, Pipeline Table 18 baris. Keduanya masih nyaman dengan scroll internal. Tapi tidak ada pagination, virtualisasi, maupun “load more” — pada 500 kontak halaman ini akan berat.

Rekomendasi: Untuk data-tabel CRM, virtualisasi + infinite scroll lebih tepat daripada pagination bernomor — sales bekerja dengan scanning, bukan browsing halaman. Pertahankan sticky footer summary (Σ $958K · ⌀ 12d) karena itu justru nilai tambahnya. Kalau tetap ingin pagination, taruh di footer bersama summary — jangan menggantikannya.

06 + 08“arrow tidak muncul”/contacts
html — ruttl gagal me-resolve elemen aslinya

Ruttl sendiri menandai salah satunya dengan “This comment may point to a missing element.” Jadi lokasi persis yang kamu maksud tidak bisa dikonfirmasi.

Yang bisa diverifikasi

LokasiKondisi arrow
Header tabel ContactsChevron sort adaopacity: 0.5 untuk kolom non-aktif, 1 untuk kolom aktif (Last touch ↑)
Header tabel PipelineTidak ada chevron sort sama sekali — tabel ini tidak sortable
<th> di kedua tabelTidak ada atribut aria-sort

Dugaan terkuat: yang kamu maksud adalah chevron sort di Contacts yang terlalu samar (opacity 0.5 di atas warna yang sudah lemah) sehingga terbaca “tidak ada”.

Rekomendasi (berlaku untuk semua skenario):

  1. Naikkan chevron non-aktif ke opacity: 0.7 dan gelapkan warnanya
  2. Tambahkan aria-sort="ascending|descending|none" pada tiap <th>
  3. Buat Pipeline Table sortable — saat ini ada dua tabel dengan kemampuan berbeda tanpa alasan jelas
Perlu konfirmasi darimu

Arrow yang mana persisnya? Kalau bukan sort chevron, kemungkinan lain adalah chevron pada kartu deal di dalam contact drawer.

07“tinggi content tdk balance, better disamakan”/
main > div > div:2 — grid dua kolom Overview

Verdict: valid — tapi solusinya bukan “disamakan”

KolomIsiTinggi
Kiri (col-span-7)Needs attention495px
Kanan (col-span-5)Pipeline snapshot (207px) + Recent activity (319px) + gap541px
selisih46px

Grid memakai items-start, jadi kolom tidak stretch dan tepi bawahnya bergerigi.

Kenapa “disamakan” bukan solusi terbaik: kalau dipaksa items-stretch, card yang lebih pendek akan punya ruang kosong di dalamnya — itu memindahkan masalah, bukan menyelesaikannya.

Rekomendasi, urut dari yang paling disarankan:

  1. Seimbangkan lewat konten — tambah 1 item di Needs attention (6 flagged, bukan 5) atau kurangi 1 item Recent activity. Selisih 46px ≈ tinggi satu baris activity.
  2. Beri min-height yang sama pada kedua kolom, biarkan yang lebih pendek punya padding bawah yang disengaja.
  3. Kalau tetap ingin rata: items-stretch dan buat Recent activity scrollable di dalam card-nya — jadi ruang ekstra terpakai, bukan kosong.
09“micro interaction”/contacts
aside > header > div:2 — tombol ikon (Email / Schedule) di header contact drawer

Verdict: valid — dan masalahnya jauh lebih luas dari titik yang kamu tandai

Ironisnya, tombol yang kamu tandai justru termasuk yang sudah benar: transition 0.12s, dan drawer-nya punya animasi meridian-slide-in.

Yang sebenarnya bermasalah adalah keseluruhan aplikasi:

MetrikAngka
Total elemen interaktif (button / role=button / a / input / select)103
Punya transition-duration: 0s79 (77%)
Contohseluruh nav sidebar, seluruh saved views, status pill di drawer — semua all 0s

Jadi: 77% interaksi di aplikasi ini berpindah state secara instan — hover, active, dan selected muncul seperti kedipan, bukan respons.

Rekomendasi:

  • Terapkan transisi dasar konsisten: transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease
  • Jangan transisi all — mahal dan sering menimbulkan efek tak terduga
  • Prioritaskan: nav sidebar (paling sering disentuh), baris tabel, kartu board
  • Sudah ada @media (prefers-reduced-motion: reduce)bagus, pertahankan dan pastikan aturan baru ikut di-cover
Catatan tentang komentarmu secara keseluruhan

Tidak satupun dari 10 komentar menyentuh A1.1–A1.4 — deal tanpa detail, filter periode kosmetik, deal tidak bisa ditutup. Sepuluh komentar ini semuanya di lapisan visual/polish. Kalau prioritas engineering diambil dari daftar ini saja, produk akan terlihat lebih rapi tapi tetap tidak bisa menutup satupun deal.

5 · Bagian C — Audit UI

C1 · Sistem Warna & Token KEKUATAN UTAMA

Token terdefinisi rapi dan konsisten dipakai:

ink       : 900 #1a1915 · 700 #4d4a41 · 500 #767266 · 300 #a8a498
surface   : 0 #fdfcfa · 1 #f7f5f1 · 2 #f0ede7 · 3 #e8e4dc
border    : subtle #e9e5dd · strong #d6d1c6
ember     : 700 #a3360e · 600 #c2410c · 500 #e05a1f · 100 #fce9dd · 50 #fdf3ec
semantic  : positive #2e7d4f · warning #a86a0f · danger #b3322e · info #3d5fa8
chip      : clay · ochre · sage · pine · iris · slate (pasangan text+bg)

Palet warm-neutral ini membedakan Meridian dari CRM lain yang cenderung biru-korporat. Ini aset — pertahankan.

C2 · Kontras 5 PASANGAN GAGAL WCAG AA

Diukur terhadap seluruh text node yang terlihat, dihitung terhadap background efektif:

TeksForegroundBackgroundSizeRasioButuhInstance
from email thread · Jul 31#a8a498#fdfcfa11.5px2.43:14.5:15
Badge 12d#a86a0f#f8edd811.5px3.82:14.5:12
+12% +8% −5 days#2e7d4f#e1f0e611.5px4.28:14.5:13
Sidebar: Studio North, Views, Command palette, Workspace settings#767266#f7f5f111–12.5px4.41:14.5:14
Pill tanggal di kartu Board (Sep 23, dll)#767266#f0ede711px4.11:14.5:111
Yang paling mendesak: --ink-300 pada 2.43:1

Dipakai untuk baris provenance di Needs attention (from email thread · Jul 31) — informasi yang justru membuat modul itu bisa dipercaya. Saat ini hampir tidak terbaca.

TokenSekarangGanti keRasio baru
--ink-300#a8a498#8a8578~4.6:1
--ink-500#767266#6b6759~5.1:1
--positive-600#2e7d4f#256b43~5.2:1
--warning-600#a86a0f#8a5709~5.0:1
Quick win terbesar di seluruh dokumen

Empat perubahan token ini memperbaiki 25 instance sekaligus tanpa menyentuh satupun komponen. Rasio effort:impact terbaik yang ada di sini.

C3 · Tipografi

TemuanDetailSeverity
Font 8px pada company badgeDi bawah batas keterbacaan apapunHIGH
Body text 11–11.5px sangat dominanMetadata, badge, pill, provenance semua 11.5pxMED
Skala heading rapih1 → h2 konsisten, hierarki jelasOK
Monospace untuk angka/tanggalKonsisten & tepat gunaOK

Rekomendasi: Naikkan lantai ukuran font ke 11px absolut, dan pindahkan teks yang membawa makna (provenance, badge hari) ke 12px. 11.5px untuk semua metadata menghilangkan hierarki di dalam metadata itu sendiri.

C4 · Konsistensi Komponen

#InkonsistensiDetail
C4.1Status pill vs tag chip identikCallum Doyle menampilkan Champion · Champion · Referral — satu status, satu tag, bentuknya sama persis. User tidak bisa membedakan mana status (tunggal, terkontrol) dan mana tag (jamak, bebas)
C4.2Dua tabel, dua kemampuanContacts: sortable + footer summary + row checkbox. Pipeline: tidak satupun
C4.3Company vs owner avatar di kartu BoardDitumpuk vertikal dengan treatment mirip — sulit membedakan mana perusahaan, mana pemilik deal
C4.4Dot warna saved viewTidak memetakan ke apapun yang bisa dipahami

Rekomendasi C4.1: Bedakan secara struktural — status sebagai pill solid (chip-*-bg penuh), tag sebagai outline chip dengan border dan background transparan. Atau pisahkan barisnya dengan label mikro (Status / Tags).

C5 · Spacing & Layout

#TemuanBuktiSeverity
C5.1<td> padding vertikal = 0measured 0pxHIGH
C5.2Kolom Board tetap tinggi penuh walau hanya 1 kartufilter “My deals”MED
C5.3Kolom Overview 495px vs 541pxmeasuredLOW
C5.4Kolom Board ke-4 (Negotiation) terpotong di 1440px — $15… bukan $150KScreenshotMED
C5.5Tidak ada affordance “tambah deal ke stage ini” di area kolom kosongLOW

C7 · Responsive PRAKTIS TIDAK ADA

Analisis stylesheet produksi (29 KB):

Media queryJumlah pemakaianUntuk apa
(prefers-reduced-motion: reduce)1aksesibilitas motion OK
(hover: hover)2hover states
(min-width: 40rem)1margin atas modal (sm:mt-0)

Itu satu-satunya breakpoint layout di seluruh aplikasi.

KPI row     : grid-cols-4  → 272px 272px 272px 272px   (fixed, semua lebar layar)
Main layout : grid-cols-12 → 80px × 12                (fixed, semua lebar layar)
Sidebar     : w-60 shrink-0                           (fixed 240px, tidak bisa collapse)
Drawer      : w-[480px]                               (fixed)

Ada <meta name="viewport" content="width=device-width, initial-scale=1.0"> — jadi browser mobile tidak akan zoom-out otomatis. Di layar 390px: 4 KPI card masing-masing ~60px, angka $958K akan terpotong atau overflow, sidebar 240px memakan 62% layar.

Rekomendasi bertahap:

BreakpointPerubahan
< 640pxSidebar → drawer off-canvas · KPI grid-cols-2 · main grid-cols-1 · Board scroll horizontal 1 kolom penuh · drawer w-full
640–1024pxSidebar collapse ke icon-only (64px) · KPI grid-cols-2 · main grid-cols-1
1024–1440pxSidebar penuh · KPI grid-cols-4 · main grid-cols-12 (kondisi sekarang)
TabelPada mobile: ubah baris jadi card stack atau sembunyikan kolom sekunder

6 · Bagian D — Aksesibilitas

Yang sudah benar DI ATAS RATA-RATA

AspekBukti
Kartu deal punya semantik & namarole="button" + tabindex="0" + aria-label="Arcadia — Ops dashboard, $24K"
Modal & drawerrole="dialog" + aria-modal="true" + aria-label="Callum Doyle record"
Label formSemua <label> membungkus input-nya — asosiasi valid tanpa for/id
Tombol ikon punya nama deskriptifaria-label="Snooze Elena Brandt for 3 days" — bukan generik
Checkbox barisaria-label="Select Callum Doyle"
Kontrol tersembunyi tetap bisa keyboard.reveal merespons :focus-within, bukan hanya :hover
Focus styling4 aturan :focus-visible di stylesheet
Hierarki heading Overviewh1: Overviewh2: Needs attention / Pipeline snapshot / Recent activity
Motion@media (prefers-reduced-motion: reduce) ada

Ini sudah di atas rata-rata untuk produk sekelas ini. Kredit di tempat yang layak.

Yang perlu diperbaiki

#TemuanWCAGSeverity
D15 pasangan kontras gagal AA (25 instance) — lihat C21.4.3HIGH
D2Touch target di bawah 44×44: tombol Needs attention 35×28, tombol drawer header 36×28, checkbox baris 13×132.5.5MED
D3Tidak ada aria-sort pada <th> yang sortable4.1.2MED
D4<th> sortable tidak punya role="button" / tidak jelas dapat diaktifkan keyboard2.1.1MED
D5Drawer aria-modal="true" tanpa scrim — konten di belakang masih pointer-events: auto dan bisa diklik. Screen reader menyembunyikan latar, pengguna awas tidak4.1.2MED
D6Dot aktivitas basi hanya title — tanpa accessible name1.1.1MED
D7Header checkbox tidak punya state indeterminate4.1.2LOW
D8Kolom Board (Lead, Qualified, …) bukan heading — hanya <h1>Pipeline</h1> di seluruh halaman1.3.1LOW
D9Tidak ada dark mode (0 aturan prefers-color-scheme) meski token system sudah siapLOW
Catatan D5

Token --overlay-scrim: rgba(26,25,21,.2) sudah ada di stylesheet tapi tidak dipakai untuk drawer. Perbaikan satu baris.

7 · Prioritas & Rekomendasi

P0 Blokir rilis

#ItemRefEst.
1Deal detail (drawer atau route) + sambungkan keempat jalur masukA1.1L
2Stage Lost + kolom Won/Lost di BoardA1.3M
3Sambungkan filter periode ke data — atau nonaktifkanA1.4S / XS
4Perbaiki 4 token warna (memperbaiki 25 instance kontras)C2XS

Item #4 adalah quick win terbesar di dokumen ini: 4 baris CSS, 25 pelanggaran WCAG selesai.

P1 Sebelum user testing

#ItemRef
5Tampilkan win probability di Pipeline Table + jadikan editableA1.2
6Sort default berbasis urgensi di Needs attentionA2.3
7Tampilkan aksi Needs attention secara permanen (jangan hover-only)A3.1
8Fallback non-hover untuk semua .reveal (@media (hover: none))A3.1
9Perbaiki relevance pencarianA2.4
10Field Contact di modal New deal + placeholder untuk CompanyA4.1, A4.2
11Padding sel tabel + toggle densityRuttl #4
12Company badge 20×20 @10pxRuttl #1
13Table wrapper hug contentRuttl #3
14Pipeline Table sortable + footer summaryC4.2
15Beri label halaman tujuan pada saved viewsA2.1
16KPI card: sambungkan ke drill-down atau hapus role="button"A1.5
17Scrim untuk drawer (token sudah ada)D5

P2 Polish

#ItemRef
18Transisi 120ms pada seluruh elemen interaktifRuttl #9
19Breakpoint responsif (mobile / tablet / desktop)C7
20Bedakan status pill vs tag chip secara visualC4.1
21aria-sort + role="button" pada <th>D3, D4
22Touch target minimum 44×44D2
23Legenda untuk dot aktivitas basiA3.7, D6
24State indeterminate pada select-allA3.3, D7
25Empty state pencarian dengan saranA3.6
26Perbaiki logika filter view “Q3 Closing”A2.2
27Dark mode (token sudah siap)D9

8 · Yang Sudah Bagus — Jangan Diubah

Audit ini berat di sisi kritik, jadi bagian ini perlu ditegaskan.

Lampiran A · Ringkasan Angka

MetrikNilai
Kartu deal dengan aria-roledescription="Draggable deal card"18
Live region untuk pengumuman drag3
Elemen HTML5 draggable di Board0 — tidak relevan, dnd-kit pakai Pointer Events
Jalur masuk ke deal yang membuka deal detail0 dari 4
Media query layout di seluruh CSS1 (min-width: 40rem, dipakai 1×)
Elemen interaktif total103
Tanpa transisi (0s)79 (77%)
Kontrol tersembunyi (opacity: 0) di /contacts40
Pasangan kontras gagal WCAG AA5 pasangan / 25 instance
Kontras terburuk2.43:1 (--ink-300 @11.5px, butuh 4.5:1)
Padding vertikal <td>0px
Font terkecil8px (company badge)
Aturan prefers-color-scheme0
Ukuran stylesheet produksi29 KB

Lampiran B · Cara Verifikasi Ulang

Jalankan di DevTools Console pada halaman terkait:

// ⚠️ JANGAN pakai ini untuk mengecek drag-and-drop — hasilnya false negative.
//    dnd-kit / Pragmatic DnD berbasis Pointer Events, tidak memakai atribut draggable.
document.querySelectorAll('[draggable]').length              // → 0 (menyesatkan)

// Cara yang benar: cek kontrak aksesibilitas DnD
document.querySelectorAll('[aria-roledescription]').length   // → 18 kartu draggable
document.getElementById('DndDescribedBy-0').textContent      // → instruksi keyboard drag

// Uji keyboard drag beneran (jalankan di /pipeline)
const c = [...document.querySelectorAll('[role=button]')]
  .find(e => e.getAttribute('aria-roledescription') === 'Draggable deal card');
c.focus();
const key = k => c.dispatchEvent(new KeyboardEvent('keydown',
  { key: k, code: k === ' ' ? 'Space' : k, keyCode: k === ' ' ? 32 : 39,
    bubbles: true, cancelable: true }));
key(' '); key('ArrowRight');
[...document.querySelectorAll('[aria-live]')].map(e => e.textContent).filter(Boolean)
// → "Draggable item d-arcadia-ops was moved over droppable area d-pomelo-refresh."

// Breakpoint responsif
fetch(document.querySelector('link[rel=stylesheet]').href)
  .then(r => r.text())
  .then(c => console.log([...c.matchAll(/@media[^{]+/g)].map(m => m[0])))
  // → prefers-reduced-motion, hover:hover ×2, min-width:40rem

// Grid fixed
[...document.querySelectorAll('*')]
  .filter(e => getComputedStyle(e).display === 'grid')
  .map(e => getComputedStyle(e).gridTemplateColumns)
  // → "272px 272px 272px 272px", "80px × 12"

// Elemen tanpa transisi
[...document.querySelectorAll('button,[role=button],a,input,select')]
  .filter(e => getComputedStyle(e).transitionDuration === '0s').length   // → 79

// Kontrol tersembunyi (di /contacts)
[...document.querySelectorAll('.reveal')]
  .filter(e => getComputedStyle(e).opacity === '0').length               // → 40

// Padding sel tabel
getComputedStyle(document.querySelector('tbody td')).paddingTop          // → "0px"