Mở đầu — vì sao bài này quan trọng
Khi mới học UI, phần lớn chúng ta dành 90% thời gian để thiết kế cái gọi là "happy path" — trạng thái lý tưởng khi mọi thứ đầy đủ dữ liệu, đường truyền nhanh, người dùng nhập đúng và hệ thống trả về kết quả hoàn hảo. Nhưng trên thực tế, người dùng gặp trạng thái "không hoàn hảo" nhiều hơn bạn tưởng: màn hình trống rỗng lúc mới đăng ký, vòng xoay loading khi mạng 3G chập chờn ở quán cà phê, thông báo lỗi khi thanh toán VNPay bị timeout. Đây chính là ba trạng thái biên (edge states) mà bài này tập trung: Empty (rỗng), Loading (đang tải) và Error (lỗi).
Vì sao chúng quan trọng đến vậy? Vì đây là những khoảnh khắc người dùng dễ bỏ đi nhất. Một màn hình rỗng không có hướng dẫn khiến người dùng nghĩ "app này chẳng có gì". Một màn hình loading treo mãi khiến họ tưởng app bị hỏng và tắt đi. Một thông báo lỗi kiểu "Error 500" khiến họ hoang mang và mất niềm tin. Nghiên cứu về trải nghiệm cho thấy phần lớn tình huống người dùng rời bỏ (churn) trong tuần đầu tiên đều xảy ra ở các trạng thái biên này chứ không phải ở happy path.
Là một UI Designer chuyên nghiệp, bạn phải thiết kế cho cả ba trạng thái này ngay từ đầu, chứ không phải để dev "tự xử lý" khi ship. Bài học này sẽ giúp bạn nắm được giải phẫu (anatomy) của từng loại trạng thái, khi nào dùng loại nào, và cách biến những khoảnh khắc "xấu xí" thành cơ hội xây dựng lòng tin và dẫn dắt hành động.
Khái niệm cốt lõi
Ba loại trạng thái biên
Trước hết cần phân biệt rõ ba trạng thái, vì mỗi loại có mục tiêu thiết kế khác nhau:
- Empty state (trạng thái rỗng): Không có dữ liệu để hiển thị. Có thể vì người dùng mới (first-use), vì họ đã xóa hết, hoặc vì kết quả tìm kiếm không khớp. Mục tiêu: giải thích tại sao trống và dẫn người dùng đến hành động tiếp theo.
- Loading state (trạng thái đang tải): Hệ thống đang lấy dữ liệu, người dùng phải chờ. Mục tiêu: trấn an rằng hệ thống đang hoạt động, giảm cảm giác chờ đợi.
- Error state (trạng thái lỗi): Có gì đó sai — mất mạng, server lỗi, dữ liệu hỏng. Mục tiêu: giải thích rõ ràng, không đổ lỗi cho người dùng, và cho một lối thoát (retry, quay lại).
Giải phẫu một Empty State
Đây là cấu trúc chuẩn của một empty state tốt, đọc từ trên xuống:
[Illustration / Icon] ← hình minh họa hoặc icon, tạo cảm xúc, không quá to
[Headline] ← "Chưa có đơn hàng nào" — ngắn, rõ, thân thiện
[Subtitle] ← "Đơn đầu tiên của bạn sẽ hiển thị ở đây" — giải thích + trấn an
[Primary CTA] ← nút hành động chính: "Tạo đơn hàng đầu tiên"
[Secondary link (tùy chọn)] ← "Xem hướng dẫn" — lối phụ, ít nổi bật hơn
Điểm mấu chốt: empty state không phải là chỗ trống, mà là một cơ hội onboarding. Với người dùng mới, đây thường là màn hình đầu tiên họ thấy sau khi đăng ký. Một headline ấm áp cộng một CTA rõ ràng có thể biến "màn hình trống" thành "bước đầu tiên đầy hứng khởi".
Cần phân biệt ba kiểu empty state con, vì nội dung khác hẳn nhau:
- First-use empty (lần đầu): Người dùng chưa từng tạo dữ liệu. Giọng văn khích lệ, CTA mạnh. Ví dụ: "Chưa có ghi chú nào — tạo ghi chú đầu tiên để bắt đầu".
- User-cleared empty (đã dọn sạch): Người dùng đã hoàn thành/xóa hết. Giọng văn chúc mừng. Ví dụ: "Tuyệt vời! Bạn đã xử lý hết thông báo".
- No-results empty (không có kết quả): Sau khi tìm kiếm/lọc mà không khớp. Cần gợi ý điều chỉnh. Ví dụ: "Không tìm thấy sản phẩm nào cho 'áo len mùa hè' — thử từ khóa khác hoặc bỏ bớt bộ lọc".
Giải phẫu Loading State — và tại sao skeleton thắng spinner
Loading có nhiều mức độ, chọn đúng loại rất quan trọng:
- Spinner (vòng xoay): Đơn giản, dùng cho hành động ngắn hoặc khi không biết trước layout. Nhược điểm: không cho biết còn bao lâu, dễ gây cảm giác "treo".
- Skeleton screen (khung xương): Hiển thị hình dạng xám mờ của nội dung sắp tải (các khối chữ nhật, hình tròn avatar). Đây là chuẩn hiện đại vì tạo cảm giác nhanh hơn — người dùng thấy layout hình thành dần nên bớt sốt ruột. Facebook, LinkedIn, YouTube đều dùng skeleton.
- Progress bar (thanh tiến trình): Dùng khi có thể ước lượng phần trăm — upload file, tải video.
- Optimistic UI (giao diện lạc quan): Giả định hành động thành công và hiển thị kết quả ngay, xử lý ngầm ở nền. Ví dụ: khi bạn "thích" một bài trên Facebook, trái tim đỏ hiện tức thì dù server chưa xác nhận.
- Dưới ~1 giây: không cần chỉ báo gì.
- 1–3 giây: spinner hoặc skeleton.
- Trên ~3 giây: skeleton kèm dòng chữ trấn an, hoặc progress bar.
- Trên ~10 giây: cần thông báo cụ thể "Việc này có thể mất một chút thời gian…".
Giải phẫu Error State
Một error state tốt gồm: icon/màu cảnh báo nhẹ nhàng + headline dễ hiểu (không thuật ngữ kỹ thuật) + mô tả nguyên nhân + hành động khắc phục (Retry / Quay lại). Nguyên tắc vàng: không bao giờ đổ lỗi cho người dùng và không bao giờ để họ vào ngõ cụt. So sánh:
- Tệ: "Error 500 — Internal Server Error".
- Tốt: "Ối, có gì đó trục trặc bên phía chúng tôi. Vui lòng thử lại sau ít phút." + nút "Thử lại".
Tình huống thực tế
Ví dụ 1 — Tiki và empty state giỏ hàng biến thành kênh bán hàng
Hãy tưởng tượng một sàn thương mại điện tử Việt Nam như Tiki. Ban đầu, màn hình "Giỏ hàng trống" chỉ có một icon giỏ hàng xám và dòng chữ "Giỏ hàng của bạn đang trống". Đây là empty state kiểu "chết" — đúng về mặt kỹ thuật nhưng lãng phí hoàn toàn.
Sau khi phân tích, đội thiết kế nhận ra: mỗi ngày có hàng chục nghìn lượt mở giỏ hàng trống, phần lớn là người dùng vừa xóa sản phẩm hoặc vừa đăng nhập lại. Họ redesign empty state theo cấu trúc đầy đủ: illustration vui vẻ, headline "Giỏ hàng đang chờ được lấp đầy", subtitle gợi mở, và quan trọng nhất là một CTA chính "Khám phá sản phẩm hot" cùng một dải gợi ý "Sản phẩm bạn đã xem gần đây". Con số giả định nhưng hợp lý: tỉ lệ người dùng từ giỏ hàng trống quay lại duyệt sản phẩm tăng khoảng 18%.
Bài học: Empty state không phải điểm cuối mà là điểm rẽ. Mỗi màn hình trống là một cơ hội để dẫn người dùng đi tiếp thay vì để họ đóng app.
Ví dụ 2 — Ứng dụng gọi xe với skeleton screen trên mạng 3G
Xét một ứng dụng gọi xe như Grab hoặc Be, phục vụ hàng triệu người dùng Việt Nam, trong đó rất nhiều người dùng mạng 3G/4G chập chờn ở vùng ven đô. Bản đầu tiên của màn hình danh sách chuyến đi dùng một spinner giữa màn hình trắng. Kết quả: người dùng ở khu vực mạng yếu chờ 4–5 giây trước màn hình trắng toát, nhiều người tưởng app đơ và force-close, rồi mở lại — làm tăng tải server một cách vô ích.
Đội UI thay spinner bằng skeleton screen: ngay lập tức hiển thị khung xương của các thẻ chuyến đi (khối xám cho ảnh tài xế, thanh xám cho tên và giá). Dù dữ liệu vẫn mất 4–5 giây, cảm giác chờ đợi giảm hẳn vì người dùng thấy "app đang làm việc" và hình dung được thứ sắp hiện ra. Tỉ lệ force-close trong lúc loading giảm rõ rệt, giả định khoảng 30%.
Bài học: Tốc độ cảm nhận (perceived performance) quan trọng ngang tốc độ thực. Skeleton screen không làm mạng nhanh hơn nhưng làm người dùng kiên nhẫn hơn — đặc biệt ở bối cảnh hạ tầng mạng không đồng đều như Việt Nam.
Ví dụ 3 — Ví điện tử và error state khi thanh toán thất bại
Xét một ví điện tử như MoMo hoặc ZaloPay. Khi giao dịch nạp tiền thất bại do cổng ngân hàng timeout, bản cũ hiển thị: "Giao dịch thất bại. Mã lỗi: TXN_TIMEOUT_504". Người dùng hoảng loạn: "Tiền của tôi bị trừ chưa? Tôi có mất tiền không?" — và tổng đài chăm sóc khách hàng nhận hàng loạt cuộc gọi.
Đội thiết kế viết lại error state theo nguyên tắc "rõ ràng + trấn an + lối thoát": headline "Giao dịch chưa hoàn tất", mô tả "Ngân hàng phản hồi chậm nên chúng tôi đã tạm dừng giao dịch. Nếu tài khoản của bạn bị trừ tiền, số tiền sẽ được hoàn trong 24 giờ." kèm hai nút "Thử lại" và "Liên hệ hỗ trợ". Kết quả giả định: lượng cuộc gọi tổng đài liên quan giảm khoảng 40%.
Bài học: Trong các sản phẩm tài chính, error state không chỉ là UI mà là công cụ giữ niềm tin. Câu trả lời cho nỗi lo lớn nhất ("tôi có mất tiền không?") phải nằm ngay trong thông báo lỗi, không bắt người dùng tự đoán.
Hướng dẫn từng bước
Đây là quy trình thiết kế edge states cho một màn hình bất kỳ:
- Liệt kê mọi trạng thái của màn hình. Với mỗi component chứa dữ liệu, viết ra: happy (có dữ liệu), empty (không có), loading (đang tải), error (lỗi). Với empty, tách tiếp ba kiểu con: first-use, user-cleared, no-results nếu áp dụng.
- Thiết kế Loading trước. Vì đây là trạng thái người dùng luôn thấy đầu tiên. Chọn skeleton nếu bạn biết trước layout; chọn spinner cho hành động ngắn, không rõ layout. Vẽ skeleton bám đúng khung của happy path để lúc dữ liệu về, layout không "nhảy".
- Thiết kế Empty state theo giải phẫu chuẩn. Illustration/icon → headline → subtitle → primary CTA → secondary link. Viết headline dưới 6 từ, subtitle một câu. Đảm bảo CTA dẫn tới hành động tạo dữ liệu ngay.
- Thiết kế Error state với ba thành phần bắt buộc: giải thích không kỹ thuật, không đổ lỗi người dùng, và ít nhất một hành động khắc phục (Retry hoặc lối quay lại). Nếu là sản phẩm tài chính/y tế, trả lời trực tiếp nỗi lo lớn nhất của người dùng.
- Đồng bộ với hệ thống thiết kế. Dùng cùng token màu, spacing (nhớ hệ 8), typography đã có. Tạo một component chung
<EmptyState>,<Skeleton>,<ErrorState>để tái sử dụng, đảm bảo nhất quán toàn app.
- Viết microcopy cẩn thận. Nhờ đây là nơi văn phong thương hiệu tỏa sáng. Giọng nên ấm, ngắn gọn, tiếng Việt tự nhiên. Tránh dịch máy móc "Oops!" thành những câu cứng nhắc.
- Trao đổi rõ với dev khi handoff. Ghi chú (annotate) từng trạng thái: điều kiện kích hoạt, thời gian timeout cho loading, hành vi khi bấm Retry. Đây là phần dev hay bỏ sót nếu designer không ghi rõ.
Lỗi thường gặp & mẹo
- Chỉ thiết kế happy path. Đây là lỗi số một. Trong file Figma, hãy tạo hẳn một page hoặc section "States" liệt kê đủ empty/loading/error cho từng màn hình quan trọng.
- Dùng một empty state cho mọi ngữ cảnh. First-use, user-cleared và no-results cần copy khác nhau. Dùng chung khiến thông điệp sai lệch, ví dụ chúc mừng "Bạn đã xử lý hết!" trong khi thực ra người dùng chưa làm gì.
- Thông báo lỗi kỹ thuật. "Error 404", "Null pointer" là dành cho log của dev, không phải cho người dùng. Luôn dịch sang ngôn ngữ con người.
- Loading không có giới hạn thời gian. Spinner xoay mãi mãi là cơn ác mộng. Luôn đặt timeout và chuyển sang error state kèm nút Retry nếu quá lâu.
- Skeleton lệch layout thật. Nếu skeleton có kích thước khác nội dung thật, màn hình sẽ "giật" khi dữ liệu về (layout shift), gây khó chịu. Vẽ skeleton bám đúng khung.
- Illustration quá to, lấn át CTA. Hình minh họa nên tạo cảm xúc chứ không cướp sự chú ý khỏi nút hành động. Giữ nó vừa phải.
- Quên trạng thái không có mạng (offline). Đặc biệt quan trọng ở Việt Nam. Thiết kế riêng một offline error với thông điệp "Có vẻ bạn đang mất kết nối" và nút thử lại.
Bài tập thực hành
- Kiểm kê trạng thái. Chọn một app bạn hay dùng (Tiki, MoMo, Grab, Shopee…). Chọn một màn hình có danh sách dữ liệu (giỏ hàng, lịch sử giao dịch, danh sách đơn). Liệt kê tất cả trạng thái mà màn hình đó cần: happy, empty (ba kiểu), loading, error, offline.
- Thiết kế Empty state. Trong Figma, thiết kế empty state first-use cho màn hình "Lịch sử đơn hàng" của một app giao đồ ăn giả định. Áp dụng đủ giải phẫu: illustration, headline, subtitle, primary CTA, secondary link. Viết microcopy bằng tiếng Việt tự nhiên, headline dưới 6 từ.
- Thiết kế Skeleton. Tạo skeleton screen cho một danh sách gồm 3 thẻ sản phẩm (mỗi thẻ có ảnh, tên, giá, nút mua). Đảm bảo kích thước skeleton khớp đúng với happy path để không gây layout shift.
- Viết lại Error. Tìm một thông báo lỗi kỹ thuật thật ngoài đời (chụp màn hình), rồi viết lại nó theo nguyên tắc: rõ ràng, không đổ lỗi người dùng, có nút khắc phục. Giải thích ngắn tại sao bản mới tốt hơn.
- Nâng cao: Gom ba thiết kế trên thành ba component tái sử dụng trong Figma (dùng variants nếu đã học), để có thể áp dụng nhất quán cho toàn app.
Tóm tắt
Edge states — Empty, Loading, Error — là những khoảnh khắc người dùng dễ rời bỏ nhất, nên chúng phải được thiết kế chủ động ngay từ đầu chứ không phó mặc cho dev. Empty state là cơ hội onboarding và dẫn dắt hành động, với giải phẫu chuẩn illustration → headline → subtitle → primary CTA; nhớ tách ba kiểu first-use, user-cleared và no-results. Loading state ưu tiên skeleton screen hơn spinner để tăng tốc độ cảm nhận, và luôn đặt giới hạn thời gian. Error state phải rõ ràng, không đổ lỗi người dùng, luôn có lối thoát, và trong sản phẩm tài chính phải trả lời trực tiếp nỗi lo lớn nhất của họ.
Ba ví dụ từ bối cảnh Việt Nam — giỏ hàng biến thành kênh bán, skeleton cứu trải nghiệm trên mạng 3G, và error state giữ niềm tin cho ví điện tử — cho thấy edge states không phải chi tiết phụ, mà là nơi quyết định người dùng ở lại hay ra đi. Hãy biến những khoảnh khắc "xấu xí" này thành cơ hội xây dựng lòng tin. Đó là dấu hiệu của một UI Designer trưởng thành.