Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 30 — Edge Cases & Error States

Product Design Essentials Bài 30/60

Mở đầu — vì sao bài này quan trọng

Khi bạn thiết kế một màn hình, điều dễ nhất là tưởng tượng ra "thế giới hoàn hảo": người dùng đã đăng nhập, dữ liệu đã đầy đủ, mạng chạy mượt, mọi thao tác đều thành công. Trong tiếng Anh người ta gọi đây là "happy path" — con đường hạnh phúc. Nhưng sự thật phũ phàng là phần lớn thời gian một sản phẩm thực tế không nằm trên con đường đó.

Một người dùng mới mở app lần đầu chưa có dữ liệu gì cả. Một người dùng ở vùng sóng 3G yếu đang chờ màn hình tải mãi không xong. Một đơn hàng bị từ chối thanh toán. Một ô tìm kiếm trả về 0 kết quả. Một phiên đăng nhập hết hạn ngay giữa lúc người dùng đang điền form. Tất cả những khoảnh khắc đó — những "góc khuất" mà ta hay quên — chính là edge cases (trường hợp biên) và error states (trạng thái lỗi).

Điều khiến bài này quan trọng đến vậy: edge cases và error states không phải là "phần phụ" làm cho có. Chúng chính là nơi quyết định người dùng tin tưởng sản phẩm của bạn hay bỏ đi. Một designer giỏi không được đo bằng cái màn hình đẹp lúc mọi thứ suôn sẻ, mà bằng cách họ xử lý những lúc mọi thứ trở nên rối ren. Khi người dùng gặp lỗi, họ đang ở trạng thái dễ tổn thương và bực bội nhất — và đó cũng là lúc thiết kế của bạn có cơ hội tỏa sáng hoặc thất bại thảm hại.

Trong bài này, chúng ta sẽ đi sâu vào bốn trạng thái cốt lõi mà gần như mọi màn hình đều phải xử lý — empty, loading, error, offline — cùng với cách tư duy để phát hiện và thiết kế cho chúng một cách có hệ thống, thay vì chỉ "chữa cháy" sau khi sản phẩm đã lên sóng.

Khái niệm cốt lõi

Trước hết, hãy phân biệt rõ hai khái niệm hay bị gộp chung.

Edge case là một tình huống xảy ra ở ranh giới của những gì hệ thống thường gặp — không sai, nhưng hiếm hoặc cực đoan. Ví dụ: một người dùng có tên dài 80 ký tự, một giỏ hàng có 999 sản phẩm, một danh sách rỗng, một số tiền âm. Edge case không nhất thiết là lỗi; nó là cái mà bạn dễ quên vì nó nằm ngoài "trường hợp trung bình".

Error state là trạng thái giao diện hiển thị khi có điều gì đó thực sự sai — yêu cầu mạng thất bại, máy chủ trả về lỗi, dữ liệu nhập không hợp lệ. Error state là một kết quả cần được trình bày cho người dùng một cách rõ ràng và có hướng xử lý.

Mỗi màn hình thực ra không phải có một trạng thái, mà có nhiều trạng thái song song. Cùng một màn hình "Danh sách đơn hàng" có thể tồn tại ở ít nhất bốn hình hài rất khác nhau, và mỗi hình hài cần được thiết kế riêng.

Empty state — chưa có dữ liệu

Đây là trạng thái khi chưa có gì để hiển thị. Có ba kiểu empty state khác nhau mà bạn nên phân biệt:

  • First use (lần đầu): người dùng mới, chưa tạo nội dung gì. Đây là cơ hội vàng để hướng dẫn (onboarding) — giải thích giá trị và mời họ thực hiện hành động đầu tiên.
  • User cleared (đã dọn sạch): người dùng từng có dữ liệu nhưng đã xóa hết, ví dụ inbox trống vì đã đọc hết thư. Đây thường là tin vui, nên giọng điệu có thể tích cực ("Bạn đã xử lý xong tất cả!").
  • No results (không có kết quả): tìm kiếm hoặc lọc trả về rỗng. Cần gợi ý người dùng điều chỉnh từ khóa hoặc bộ lọc.
Một empty state tốt luôn trả lời ba câu: Vì sao trống? Có sao không? Tôi nên làm gì tiếp theo?

Loading state — đang chờ

Người dùng ghét sự không chắc chắn hơn là ghét sự chờ đợi. Loading state có nhiệm vụ trấn an: "hệ thống đang làm việc, chưa bị treo". Có vài cấp độ:

  • Spinner đơn giản cho chờ ngắn (dưới 1 giây thì thậm chí không cần hiển thị gì).
  • Skeleton screen (khung xương) — hiển thị hình hài của nội dung sắp xuất hiện dưới dạng các khối xám. Cách này khiến cảm giác chờ ngắn hơn vì người dùng đã thấy "bố cục" sắp tới.
  • Progress bar / phần trăm cho tác vụ dài và xác định được tiến độ (upload file, tải báo cáo).
  • Optimistic UI — giả định thành công ngay lập tức (ví dụ nút "thích" đổi màu ngay), rồi âm thầm đồng bộ với máy chủ.

Error state — có gì đó sai

Một error state tốt cần ba thành phần: chuyện gì đã xảy ra (bằng ngôn ngữ người dùng hiểu, không phải mã lỗi 500), vì sao (nếu giúp ích), và quan trọng nhất — người dùng làm gì tiếp (nút "Thử lại", "Quay về", "Liên hệ hỗ trợ"). Đừng bao giờ để người dùng rơi vào ngõ cụt.

Offline state — không có mạng

Ở bối cảnh Việt Nam, đây là trạng thái cực kỳ thực tế: người dùng đi xe máy mất sóng, vào thang máy, hay dùng wifi quán cà phê chập chờn. Một thiết kế tử tế cần phát hiện được mất kết nối, thông báo nhẹ nhàng (thường là một banner ở trên), và nếu có thể, cho phép người dùng vẫn xem nội dung đã tải hoặc soạn nội dung để gửi khi có mạng trở lại.

Tình huống thực tế

Ví dụ 1: Empty state của ví điện tử MoMo cho người dùng mới

Hãy tưởng tượng một người vừa tải MoMo về và mở mục "Lịch sử giao dịch". Nếu màn hình chỉ hiện một danh sách trống trơn, người dùng sẽ bối rối: "App lỗi à? Tôi làm gì sai chưa?"

Cách làm tốt mà các ví điện tử lớn ở Việt Nam đang áp dụng: thay vì màn hình trắng, họ hiển thị một minh họa nhỏ kèm dòng chữ "Bạn chưa có giao dịch nào" và một nút CTA rõ ràng như "Nạp tiền điện thoại" hoặc "Chuyển tiền lần đầu". Một số ví còn tặng ưu đãi cho giao dịch đầu tiên ngay tại đây.

Giả sử đội sản phẩm đo được rằng trong 1.000 người dùng mới mỗi ngày, có khoảng 400 người mở mục lịch sử trước khi giao dịch. Nếu màn hình trống biến 400 người này thành bối rối, ta mất cơ hội. Nhưng nếu mỗi empty state là một lời mời hành động kèm ưu đãi, và chỉ cần 10% trong số đó thực hiện giao dịch đầu tiên, đó là 40 giao dịch mới mỗi ngày — chỉ từ việc thiết kế lại một màn hình "trống".

Bài học: Empty state của người dùng mới không phải là khoảng trống — nó là cơ hội onboarding và chuyển đổi đắt giá nhất của bạn.

Ví dụ 2: Error state khi đặt vé của một hãng hàng không

Một bạn của tôi từng đặt vé máy bay nội địa qua app. Đến bước thanh toán, mạng chập chờn, app hiện một thông báo: "Error code: TXN_FAILED_0x3F. Please try again." Bạn ấy hoảng loạn — tiền đã bị trừ chưa? Vé đã giữ chưa? Có nên bấm thử lại không, lỡ bị trừ tiền hai lần?

Đây là một error state tệ điển hình: nó dùng ngôn ngữ kỹ thuật, không nói rõ trạng thái tiền và vé, và đẩy người dùng vào sợ hãi.

Một thiết kế tốt sẽ nói: "Giao dịch chưa hoàn tất do mất kết nối. Tiền của bạn chưa bị trừ và ghế đang được giữ trong 10 phút. Vui lòng kiểm tra kết nối và thử lại." Kèm theo là hai nút: "Thử lại" và "Kiểm tra trạng thái đơn". Sự khác biệt giữa hai cách viết này không nằm ở công nghệ — backend giống hệt nhau — mà nằm ở việc designer đã nghĩ thay cho người dùng trong lúc họ hoảng loạn.

Bài học: Trong các luồng nhạy cảm như thanh toán, error state phải xóa tan nỗi sợ bằng cách nói rõ trạng thái của tiền và của giao dịch, rồi chỉ một đường ra an toàn.

Ví dụ 3: Offline state của ứng dụng học tập

Hãy lấy bối cảnh một nền tảng học trực tuyến (giống chính nền tảng bạn đang dùng). Học viên thường học trên đường — ngồi xe bus, vào vùng sóng yếu. Giả sử đội thiết kế phát hiện qua phân tích rằng 25% phiên học diễn ra trên mạng di động không ổn định.

Phiên bản đầu, khi mất mạng giữa bài, video dừng và hiện màn hình lỗi đỏ "Không thể tải nội dung", đẩy học viên về trang đầu — mất sạch tiến độ. Tỷ lệ bỏ giữa chừng cao.

Đội thiết kế xử lý lại theo hướng "offline-aware": khi mất mạng, một banner vàng nhẹ trượt xuống "Mất kết nối — đang chờ mạng để tiếp tục", video tạm dừng đúng vị trí, và phần text bài học đã tải vẫn đọc được. Khi mạng trở lại, banner đổi xanh "Đã kết nối lại" và tự động tiếp tục. Sau thay đổi này, giả định tỷ lệ hoàn thành bài trên mạng di động tăng từ 58% lên 74%.

Bài học: Offline không phải lỗi của người dùng. Thiết kế nên bảo vệ công sức của họ (giữ tiến độ, giữ nội dung đã tải) thay vì trừng phạt họ vì mất sóng.

Hướng dẫn từng bước

Đây là quy trình thực tế để bạn không bỏ sót edge cases khi thiết kế bất kỳ màn hình nào.

Bước 1 — Liệt kê các trạng thái cho từng màn hình. Với mỗi màn hình, viết ra danh sách: trạng thái lý tưởng (đầy đủ dữ liệu), empty, loading, error, offline. Hỏi thêm: "Còn trạng thái nào nữa không?" — ví dụ partial data (dữ liệu một phần), permission denied (không có quyền), expired session (hết phiên).

Bước 2 — Truy nguồn dữ liệu. Với mỗi phần của màn hình lấy dữ liệu từ đâu? Mỗi nguồn dữ liệu là một điểm có thể trống, có thể chậm, có thể lỗi. Một màn hình dashboard lấy dữ liệu từ 4 API khác nhau thì về lý thuyết có thể có hàng chục tổ hợp trạng thái — bạn không cần thiết kế hết, nhưng cần biết chúng tồn tại.

Bước 3 — Ưu tiên theo tần suất và mức độ nghiêm trọng. Không phải edge case nào cũng đáng đầu tư. Dùng ma trận đơn giản: tần suất xảy ra (cao/thấp) × mức độ ảnh hưởng (nghiêm trọng/nhẹ). Ưu tiên những ô "hay xảy ra + ảnh hưởng nặng" trước (ví dụ: lỗi thanh toán), rồi mới đến những ô hiếm.

Bước 4 — Thiết kế nội dung trước, hình ảnh sau. Với mỗi trạng thái, viết phần chữ trước: tiêu đề, dòng mô tả, nhãn nút. Microcopy chính là phần quan trọng nhất của một error state. Sau khi câu chữ rõ ràng rồi mới thêm minh họa.

Bước 5 — Định nghĩa hành động phục hồi. Mỗi error/empty state phải có ít nhất một lối ra: nút "Thử lại", "Tạo mới", "Quay về", hoặc "Liên hệ hỗ trợ". Đảm bảo người dùng không bao giờ kẹt trong ngõ cụt.

Bước 6 — Bàn giao đầy đủ cho engineer. Khi handoff, đừng chỉ đưa màn hình lý tưởng. Đưa cả bộ trạng thái dưới dạng một bảng rõ ràng để lập trình viên biết phải code cho từng tình huống. Đây là điểm phân biệt designer có kinh nghiệm với người mới.

Lỗi thường gặp & mẹo

Lỗi 1 — Chỉ thiết kế happy path. Đây là lỗi phổ biến nhất. Bạn vẽ màn hình đẹp với dữ liệu mẫu hoàn hảo rồi giao đi, để rồi engineer tự chế ra empty/error state theo cảm tính. Kết quả là sản phẩm thật trông rất khác bản thiết kế. Mẹo: coi việc liệt kê trạng thái là một phần bắt buộc của mọi file thiết kế, không phải tùy chọn.

Lỗi 2 — Dùng dữ liệu giả "đẹp như mơ". Tên người dùng "Nguyễn Văn A", avatar đầy đủ, mọi dòng văn bản vừa khít. Thực tế có người tên dài, không có avatar, văn bản tràn dòng. Mẹo: luôn test thiết kế với dữ liệu "xấu nhất" — tên dài nhất, số lớn nhất, danh sách rỗng, văn bản tiếng Việt có dấu dài hơn tiếng Anh khoảng 30%.

Lỗi 3 — Thông báo lỗi đổ lỗi cho người dùng. "Bạn đã nhập sai", "Thao tác không hợp lệ" nghe như trách móc. Mẹo: viết error theo hướng giúp đỡ, không phán xét. Thay vì "Sai mật khẩu" hãy cân nhắc "Mật khẩu chưa đúng, bạn thử lại nhé" và đặt nút "Quên mật khẩu" ngay cạnh.

Lỗi 4 — Loading vô tận không có giới hạn. Spinner quay mãi khi mạng chết là một trải nghiệm tra tấn. Mẹo: luôn đặt timeout — sau N giây không phản hồi thì chuyển sang error state với nút "Thử lại".

Lỗi 5 — Quên trạng thái khi danh sách có ít item. Bạn thiết kế cho danh sách đầy và danh sách rỗng, nhưng quên mất danh sách chỉ có 1-2 item nhìn lạc lõng. Mẹo: luôn kiểm tra cả ba mốc: rỗng, ít, nhiều/tràn.

Mẹo chung: Hãy tạo cho mình một "checklist trạng thái" cá nhân và dán nó cạnh màn hình làm việc. Mỗi khi hoàn thành một màn hình, chạy qua checklist một lượt. Việc này biến edge case từ thứ "tình cờ nhớ ra" thành một thói quen có hệ thống.

Bài tập thực hành

Chọn một màn hình quen thuộc trong một app bạn hay dùng — ví dụ màn hình "Đơn hàng của tôi" trên Shopee, hoặc màn hình "Lịch sử chuyến đi" trên Grab. Sau đó:

  • Vẽ bảng trạng thái. Liệt kê tối thiểu năm trạng thái cho màn hình đó: lý tưởng, empty (chia rõ first-use và no-results), loading, error, offline. Với mỗi trạng thái, ghi một câu mô tả ngắn về điều người dùng nhìn thấy.
  • Viết microcopy. Cho ba trạng thái khó nhất (empty first-use, error mạng, offline), viết phần chữ hoàn chỉnh: tiêu đề, dòng mô tả, và nhãn của (các) nút hành động. Cố gắng viết bằng giọng giúp đỡ, không đổ lỗi.
  • Thiết kế nhanh. Phác (sketch hoặc wireframe) hai trong số các trạng thái trên. Không cần đẹp, chỉ cần thể hiện bố cục và vị trí nút hành động phục hồi.
  • Stress-test dữ liệu. Tìm một edge case dữ liệu cho màn hình này (ví dụ: đơn hàng có 50 sản phẩm, hoặc tên sản phẩm dài 100 ký tự) và mô tả thiết kế sẽ xử lý ra sao để không vỡ bố cục.
  • Tự phản biện. Viết hai câu trả lời cho câu hỏi: "Nếu người dùng mất mạng đúng lúc đang thao tác quan trọng trên màn hình này, công sức của họ có bị mất không, và tôi đã bảo vệ họ thế nào?"
Hãy lưu lại bài tập này — bạn sẽ dùng chính cách tư duy bảng trạng thái này trong suốt sự nghiệp, ở mọi dự án.

Tóm tắt

Edge cases và error states là phần thiết kế phân biệt người làm nghề nghiêm túc với người chỉ vẽ màn hình đẹp. Hãy nhớ những điểm cốt lõi:

  • Mỗi màn hình không có một mà có nhiều trạng thái song song: lý tưởng, empty, loading, error, offline — và mỗi cái cần được thiết kế riêng.
  • Empty state không phải khoảng trống — đặc biệt với người dùng mới, đó là cơ hội onboarding và chuyển đổi quý giá. Phân biệt rõ first-use, đã-dọn-sạch và không-có-kết-quả.
  • Loading state có nhiệm vụ trấn an. Dùng skeleton screen, progress bar hoặc optimistic UI tùy ngữ cảnh, và luôn có timeout.
  • Error state phải nói bằng ngôn ngữ người dùng, xóa tan nỗi sợ (nhất là trong luồng tiền bạc), và luôn cho một lối ra an toàn.
  • Offline state rất thực tế ở Việt Nam — hãy bảo vệ tiến độ và nội dung đã tải của người dùng thay vì trừng phạt họ vì mất sóng.
  • Quy trình có hệ thống: liệt kê trạng thái → truy nguồn dữ liệu → ưu tiên theo tần suất × mức độ → viết microcopy trước → định nghĩa hành động phục hồi → bàn giao đầy đủ cho engineer.
Lần tới khi bạn mở một file thiết kế mới, đừng vội vẽ màn hình hoàn hảo. Hãy dừng lại một phút và hỏi: "Màn hình này sẽ trông thế nào khi mọi thứ không hoàn hảo?" Câu trả lời cho câu hỏi đó chính là dấu ấn của một product designer trưởng thành.