Tìm hiểu hệ thống đơn giản để đồng nhất các trạng thái tải, lỗi và trống trên web và di động, giúp giao diện do AI tạo giữ tính liên tục và giảm công việc hoàn thiện muộn.

Trạng thái tải, lỗi và trống là những màn hình (hoặc khối UI nhỏ) người dùng thấy khi app đang chờ, có việc thất bại, hoặc đơn giản là không có gì để hiển thị. Chúng là bình thường: mạng chậm, quyền bị từ chối, và tài khoản mới bắt đầu với không dữ liệu.
Chúng trở nên lộn xộn vì thường được thêm vào muộn và vội. Nhóm xây dựng hành trình chính trước, rồi vá vào một spinner, một thông báo đỏ, và một placeholder “không có mục” ở khắp nơi khi UI bị vỡ. Làm vậy trên hàng chục màn hình và cuối cùng bạn có một đống các giải pháp rời rạc.
Phát triển nhanh còn làm tình hình tồi tệ hơn. Khi UI được tạo nhanh (kể cả UI do AI sinh), bố cục chính có thể xuất hiện trong vài phút, nhưng các trạng thái này dễ bị bỏ qua. Mỗi màn hình mới có thể có spinner khác nhau, cách diễn đạt khác nhau (“Thử lại” vs “Retry”), và vị trí nút khác nhau. Tốc độ ban đầu đổi thành công việc hoàn thiện ngay trước khi ra mắt.
Các trạng thái không khớp gây nhầm lẫn cho người dùng và tốn thời gian cho nhóm. Người dùng không biết liệu danh sách trống có nghĩa là “không có kết quả”, “chưa tải xong”, hay “bạn không có quyền truy cập”. QA phải kiểm thử nhiều biến thể nhỏ, và lỗi lọt qua vì hành vi khác nhau giữa web và mobile.
“Lộn xộn” thường trông như sau:
Mục tiêu đơn giản: một phương pháp chia sẻ giữa web và mobile. Nếu nhóm của bạn tạo tính năng nhanh (ví dụ dùng một nền tảng như Koder.ai), có một pattern trạng thái chung càng quan trọng hơn vì mỗi màn hình mới bắt đầu đã có tính nhất quán theo mặc định.
Hầu hết app lặp lại cùng các điểm áp lực: danh sách, trang chi tiết, form, dashboard. Đây là nơi spinner, banner và thông điệp “không có gì” nhân lên.
Bắt đầu bằng cách đặt tên và chuẩn hóa năm loại trạng thái:
Hai trường hợp đặc biệt đáng có quy tắc riêng vì hành vi khác:
Trên mọi màn hình và nền tảng, giữ cấu trúc nhất quán: nơi trạng thái xuất hiện, phong cách biểu tượng, tông văn bản, và các hành động mặc định (Retry, Refresh, Clear filters, Create). Những gì có thể khác là ngữ cảnh: tên màn hình và một câu dùng từ của người dùng.
Ví dụ: nếu bạn tạo cả danh sách web và danh sách mobile cho “Dự án”, chúng nên chia sẻ cùng pattern zero-results. Nhãn hành động vẫn có thể phù hợp với nền tảng (“Xóa bộ lọc” vs “Đặt lại”).
Nếu mỗi màn hình tự phát minh spinner, thẻ lỗi và thông điệp trống riêng, bạn sẽ có một tá phiên bản hơi khác nhau. Cách khắc phục nhanh nhất là một “state kit” nhỏ mà mọi tính năng có thể dùng.
Bắt đầu với ba component tái sử dụng hoạt động ở mọi nơi: Loading, Error và Empty. Làm chúng đơn giản có chủ đích. Chúng nên dễ nhận diện và không cạnh tranh với UI chính.
Làm cho các component có tính dự đoán bằng cách định nghĩa một tập input nhỏ:
Rồi khoá giao diện lại. Quyết định một lần về spacing, kiểu chữ, kích thước icon và kiểu nút, và coi đó là quy tắc. Khi kích thước icon và kiểu nút giữ ổn định, người dùng ngừng chú ý đến UI trạng thái và bắt đầu tin tưởng nó.
Giữ số lượng biến thể hạn chế để kit không trở thành hệ thống thiết kế thứ hai. Ba kích thước thường đủ: nhỏ (inline), mặc định (section), và toàn trang (blocking).
Nếu bạn sinh màn hình trong Koder.ai, một chỉ dẫn đơn giản như “use the app StateKit for loading/error/empty with default variant” ngăn chặn drift. Nó cũng giảm công việc dọn dẹp cuối chu kỳ trên React web và Flutter mobile.
Copy là một phần của hệ thống, không phải trang trí. Ngay cả khi bố cục nhất quán, cách diễn đạt tự phát làm màn hình cảm thấy khác nhau.
Chọn một giọng điệu dùng chung: ngắn, cụ thể, bình tĩnh. Nói chuyện đã xảy ra bằng ngôn ngữ đơn giản, rồi bảo người dùng làm gì tiếp theo. Hầu hết màn hình chỉ cần một tiêu đề rõ, một lời giải thích ngắn, và một hành động ngay ra mắt.
Một vài mẫu thông điệp che phủ hầu hết tình huống. Giữ ngắn để vừa màn hình nhỏ:
Tránh văn bản mơ hồ như “Có lỗi xảy ra” một mình. Nếu bạn thực sự không biết nguyên nhân, hãy nói những gì bạn biết và người dùng có thể làm gì bây giờ. “Chúng tôi không thể tải dự án của bạn” tốt hơn “Lỗi.”
Đặt một quy tắc: mọi lỗi và trạng thái trống đều cung cấp bước tiếp theo.
Điều này càng quan trọng với UI do AI sinh, nơi màn hình xuất hiện nhanh. Mẫu giữ copy nhất quán để bạn không phải viết lại hàng chục thông điệp riêng lẻ trong giai đoạn hoàn thiện.
Khi màn hình trạng thái đề xuất các hành động khác nhau từ trang này sang trang khác, người dùng do dự. Nhóm rồi sửa đổi nút và copy ngay trước khi ra mắt.
Quyết định hành động phù hợp cho mỗi trạng thái, và giữ vị trí cùng nhãn nhất quán. Hầu hết màn hình chỉ nên có một hành động chính. Nếu thêm hành động thứ hai, nó nên hỗ trợ con đường chính, không cạnh tranh.
Giữ danh sách hành động chặt:
Các nút nhàm là tính năng. Chúng khiến UI quen thuộc và giúp màn hình được sinh ra giữ tính nhất quán.
Hiển thị “Thử lại” chỉ khi việc thử lại có khả năng thành công (timeout, mạng không ổn định, 5xx). Thêm debounce ngắn để lượt chạm liên tiếp không spam request, và chuyển nút sang trạng thái loading trong khi thử lại.
Sau thất bại lặp lại, giữ cùng nút chính và cải thiện trợ giúp phụ (ví dụ, mẹo “Kiểm tra kết nối” hoặc “Thử lại sau”) thay vì giới thiệu layout mới chỉ vì thất bại hai lần.
Về chi tiết lỗi, hiển thị lý do rõ ràng người dùng có thể hành động (“Phiên làm việc của bạn đã hết. Đăng nhập lại.”). Ẩn chi tiết kỹ thuật theo mặc định. Nếu cần, giấu chúng sau một affordance “Chi tiết” nhất quán trên mọi nền tảng.
Ví dụ: danh sách “Dự án” tải thất bại trên mobile. Cả hai nền tảng đều hiện cùng hành động chính “Thử lại”, vô hiệu hoá nó khi đang thử lại, và sau hai lần thất bại thêm một mẹo nhỏ về kết nối thay vì thay đổi toàn bộ bố cục nút.
Xem sự nhất quán trạng thái như một thay đổi sản phẩm nhỏ, không phải một redesign. Đi từng bước, và làm cho việc áp dụng dễ dàng.
Bắt đầu với một snapshot nhanh những gì bạn đã có. Đừng hướng tới hoàn hảo. Ghi lại các biến thể phổ biến: spinner vs skeleton, lỗi toàn trang vs banner, màn hình “không có kết quả” với tông khác nhau.
Một kế hoạch rollout thực tế:
Khi component tồn tại, tiết kiệm thời gian thực sự là một bộ quy tắc ngắn loại bỏ tranh luận: khi nào trạng thái chặn toàn trang vs chỉ một card, và hành động nào bắt buộc.
Giữ quy tắc ngắn:
Nếu bạn dùng trình tạo UI AI như Koder.ai, những quy tắc này mang lại hiệu quả nhanh. Bạn có thể yêu cầu “use the state kit components” và nhận màn hình khớp hệ thống trên cả React web và Flutter mobile với ít dọn dẹp hơn.
Công việc hoàn thiện muộn thường xảy ra vì xử lý trạng thái được xây như các one-off. Màn hình “hoạt động”, nhưng trải nghiệm mỗi lần lại khác khi có chậm trễ, lỗi hoặc không có dữ liệu.
Skeleton hữu ích, nhưng để chúng quá lâu khiến người ta nghĩ app bị treo. Nguyên nhân phổ biến là hiện skeleton toàn trang cho một cuộc gọi chậm mà không có dấu hiệu cho thấy tiến trình.
Giới hạn thời gian: sau một khoảng ngắn, chuyển sang thông điệp nhẹ hơn “Vẫn đang tải…” hoặc hiện tiến trình khi có thể.
Nhóm thường viết thông điệp mới mỗi lần, ngay cả khi vấn đề giống nhau. “Something went wrong”, “Unable to fetch”, và “Network error” mô tả một trường hợp nhưng khiến trải nghiệm không nhất quán và gây khó cho hỗ trợ.
Chọn một nhãn cho mỗi loại lỗi và tái sử dụng trên web và mobile, với cùng tông và mức chi tiết.
Một lỗi kinh điển nữa là hiện trạng thái trống trước khi dữ liệu tải xong, hoặc hiện “No items” khi vấn đề thực sự là request thất bại. Người dùng làm hành động sai (ví dụ thêm nội dung trong khi họ nên thử lại).
Rõ ràng thứ tự quyết định: loading trước, nếu thất bại thì error, và chỉ empty khi bạn biết request thành công.
Lỗi không có hành động phục hồi tạo dead end. Ngược lại phổ biến là ba nút cạnh tranh thu hút. Giữ chặt:
Khác biệt nhỏ tích tụ: kiểu icon, padding, hình dạng nút. Đây cũng là nơi UI do AI sinh có thể drift nếu prompt thay đổi theo màn hình.
Khoá spacing, bộ icon và layout cho component trạng thái để mỗi màn hình mới kế thừa cấu trúc giống nhau.
Nếu muốn xử lý trạng thái nhất quán giữa web và mobile, hãy làm rõ các quy tắc “nhàm”. Phần lớn công việc hoàn thiện muộn xảy ra vì mỗi màn hình tự nghĩ ra hành vi loading, timeout và nhãn.
Với tải toàn trang, chọn một mặc định: skeleton cho các màn nặng nội dung (danh sách, thẻ, dashboard) và spinner cho chờ ngắn khi bố cục chưa rõ.
Thêm ngưỡng timeout để UI không treo im lặng. Nếu tải lâu hơn khoảng 8–10 giây, chuyển sang thông điệp rõ ràng và hành động nhìn thấy được như “Thử lại.”
Với tải một phần, đừng làm trống màn hình. Giữ nội dung hiện tại và hiển thị chỉ báo tiến trình nhỏ gần phần đang cập nhật (ví dụ thanh mảnh ở header hoặc spinner inline).
Với dữ liệu cache, ưu tiên “cũ nhưng dùng được”. Hiện nội dung cache ngay và thêm chỉ báo “Đang làm mới…” tinh tế để người dùng hiểu dữ liệu có thể thay đổi.
Offline là một trạng thái riêng. Nói rõ và nói phần nào vẫn hoạt động. Ví dụ: “Bạn đang offline. Bạn có thể xem dự án đã lưu, nhưng đồng bộ sẽ tạm dừng.” Đưa một bước tiếp theo như “Thử lại” hoặc “Mở mục đã lưu”.
Giữ nhất quán:
Nếu bạn sinh UI bằng công cụ như Koder.ai, nhúng các quy tắc này vào StateKit chung giúp mọi màn hình mới mặc định đồng nhất.
Hãy tưởng tượng CRM đơn giản với màn danh sách Contacts và màn chi tiết Contact. Nếu coi loading, error và empty là one-off, web và mobile sẽ nhanh chóng drift. Một hệ thống nhỏ giữ mọi thứ đồng bộ ngay cả khi UI được tạo nhanh.
Empty lần đầu (Danh sách Contacts): người dùng mở Contacts và chưa có gì. Trên cả web và mobile, tiêu đề giữ nguyên (“Contacts”), thông điệp trống giải thích lý do (“Chưa có liên hệ nào”), và một bước tiếp theo rõ ràng được đề xuất (“Thêm liên hệ đầu tiên của bạn”). Nếu cần thiết lập (như kết nối hộp thư hoặc nhập CSV), trạng thái trống chỉ ra bước đó chính xác.
Mạng chậm khi tải: người dùng mở trang chi tiết Contact. Cả hai nền tảng hiện skeleton có cấu trúc tương ứng với trang cuối cùng (header, trường chính, ghi chú). Nút quay lại vẫn hoạt động, tiêu đề trang hiển thị, và tránh spinner ngẫu nhiên ở nhiều chỗ.
Lỗi server: request chi tiết lỗi. Pattern giống nhau xuất hiện trên web và mobile: tiêu đề ngắn, một câu, và hành động chính (“Thử lại”). Nếu thử lại vẫn lỗi, cung cấp thêm tùy chọn như “Quay lại Contacts” để người dùng không bị mắc kẹt.
Những gì giữ nhất quán rất đơn giản:
Một release có thể trông “xong” cho đến khi ai đó gặp kết nối chậm, tài khoản mới, hoặc API không ổn. Checklist này giúp bạn phát hiện lỗ hổng cuối cùng mà không biến QA thành săn lỗi.
Bắt đầu với các màn danh sách, vì chúng nhân bản. Chọn ba danh sách phổ biến (kết quả tìm kiếm, mục đã lưu, hoạt động gần đây) và đảm bảo tất cả dùng cùng cấu trúc empty-state: tiêu đề rõ, một câu hữu ích, và một hành động chính.
Đảm bảo empty state không bao giờ xuất hiện khi dữ liệu vẫn đang tải. Nếu bạn chớp “Không có gì” trong một khoảnh khắc rồi thay bằng nội dung, niềm tin sụt nhanh.
Kiểm tra chỉ báo loading về kích thước, vị trí và thời lượng tối thiểu hợp lý để không nháy. Nếu web hiển thị spinner trên thanh trên cùng nhưng mobile hiển skeleton toàn màn cho cùng màn hình, cảm giác như hai sản phẩm khác nhau.
Lỗi nên luôn trả lời “bây giờ làm gì?”. Mỗi lỗi cần một bước tiếp theo: thử lại, làm mới, đổi bộ lọc, đăng nhập lại, hoặc liên hệ hỗ trợ.
Một kiểm tra nhanh trước khi đánh dấu build sẵn sàng:
Nếu bạn dùng bộ dựng AI như Koder.ai, những kiểm tra này càng quan trọng vì màn hình có thể sinh nhanh, nhưng tính nhất quán vẫn phụ thuộc vào kit chung và quy tắc copy.
Sự nhất quán dễ nhất khi nó là công việc hàng ngày, không phải dọn dẹp một lần. Mỗi màn hình mới nên dùng cùng pattern mà không ai phải nhớ “làm cho giống” vào cuối.
Đặt hành vi trạng thái vào định nghĩa hoàn thành. Một màn hình chưa hoàn thành nếu nó chưa có trạng thái loading, trạng thái empty (nếu cần), và trạng thái lỗi với hành động rõ ràng.
Giữ quy tắc nhẹ nhưng ghi ra. Một tài liệu ngắn với vài ảnh chụp màn hình và mẫu copy chính xác thường đủ. Xem các biến thể mới như ngoại lệ. Khi ai đó đề xuất thiết kế trạng thái mới, hãy hỏi liệu đó có thực sự là trường hợp mới hay không.
Nếu bạn đang refactor nhiều màn hình, giảm rủi ro bằng cách làm theo bước có kiểm soát: cập nhật một flow một lần, kiểm tra trên web và mobile, sau đó tiếp tục. Trong Koder.ai, snapshot và rollback có thể làm thay đổi lớn an toàn hơn, và chế độ lập kế hoạch giúp xác định StateKit chung để màn hình được sinh mới theo mặc định từ ngày đầu.
Chọn một khu vực trong tuần này nơi vấn đề trạng thái gây sửa muộn (thường là kết quả tìm kiếm, onboarding, hoặc feed hoạt động). Rồi:
Một dấu hiệu cụ thể cho thấy hiệu quả: ít ticket nhỏ như “thêm retry”, “empty state xấu”, hoặc “spinner chặn trang”.
Chỉ định một người chịu trách nhiệm cho tiêu chuẩn trạng thái (một designer, tech lead, hoặc cả hai). Họ không cần duyệt mọi thứ, nhưng nên bảo vệ kit khỏi việc dần dần tách ra thành các biến thể mới trông giống nhau nhưng hành vi khác nhau, gây tốn thời gian sau này.
Bắt đầu bằng cách đặt tên cho một tập hợp trạng thái mà bạn sẽ dùng khắp nơi: initial loading (tải ban đầu), refreshing (làm mới), empty baseline (trạng thái trống cơ bản), zero results (không có kết quả) và error (lỗi). Thêm quy tắc rõ ràng cho offline và mạng chậm để chúng không bị coi là lỗi ngẫu nhiên. Khi nhóm thống nhất tên và trigger, UI sẽ trở nên dễ đoán trên các màn hình và nền tảng.
Xây một StateKit nhỏ gồm ba phần tái sử dụng: Loading, Error và Empty. Mỗi phần nhận cùng tập input (tiêu đề, thông điệp ngắn, một hành động chính và chi tiết tùy chọn) để mọi màn hình có thể dùng mà không phải chế ra giao diện mới. Làm cho biến thể mặc định dễ dùng nhất để nhóm ngừng tạo các one-off.
Dùng thứ tự quyết định đơn giản: hiện loading cho đến khi yêu cầu kết thúc, nếu thất bại thì hiện error, và chỉ hiển thị empty khi đã nhận được phản hồi thành công mà không có dữ liệu. Cách này tránh lỗi phổ biến khi “No items” hiện trước khi nội dung tải xong.
Chọn một hành động mặc định cho mỗi trạng thái và dùng cùng nhãn cùng vị trí trên mọi màn hình. Errors thường dùng “Thử lại”, empty baseline dùng “Tạo” (hoặc bước thiết lập tiếp theo), zero results dùng “Xóa bộ lọc”. Khi hành động chính dễ đoán, người dùng thao tác nhanh hơn và nhóm cũng ít tranh luận về cách đặt tên nút.
Viết copy theo mẫu dùng chung: một tiêu đề ngắn nêu tình huống, một câu giải thích rõ ràng, và một bước tiếp theo dễ hiểu. Ưu tiên câu cụ thể như “Không thể tải dự án của bạn” thay vì mơ hồ như “Có lỗi xảy ra”. Giữ tông điệu bình tĩnh và thống nhất để web và mobile cảm giác như một sản phẩm.
Đối xử offline như một trạng thái riêng, không phải lỗi chung. Hiện nội dung cache nếu có, nói rõ “Bạn đang offline”, và giải thích phần nào vẫn hoạt động. Đưa một bước tiếp theo duy nhất như “Thử lại” để người dùng không phải đoán.
Tránh nháy nhanh lỗi khi mạng chậm bằng cách đợi một lúc trước khi thay đổi UI. Nếu thời gian tải vượt ngưỡng, chuyển sang thông điệp “Vẫn đang tải…” rõ ràng và cung cấp hành động hiển nhiên như “Thử lại”. Cách này giúp ứng dụng có cảm giác phản hồi dù mạng không ổn định.
Dùng ba kích thước biến thể: inline nhỏ (trong thẻ hoặc đoạn), mặc định theo phần, và toàn trang khi cần chặn. Định rõ khi nào dùng mỗi kích thước để nhóm không tùy ý sáng tạo. Khóa cùng một khoảng cách, bộ icon và kiểu nút cho các biến thể để trải nghiệm đồng nhất.
Áp dụng vài quy tắc: khi trạng thái xuất hiện, chuyển focus đến thông điệp và hành động chính; thông báo tải/lỗi rõ ràng cho trình đọc màn hình; đảm bảo nút đủ lớn để chạm trên di động; không chỉ dựa vào màu sắc hay animation để truyền thông tin. Nếu những điều này là một phần của StateKit, mọi màn hình mới tự thừa hưởng.
Triển khai theo từng khu vực sản phẩm, bắt đầu từ các danh sách và màn hình chi tiết có lưu lượng cao. Lập inventory, chọn vài vị trí chuẩn, rồi thay các trạng thái one-off bằng component chung khi sửa từng màn hình. Nếu bạn tạo UI trong Koder.ai, thêm hướng dẫn mặc định dùng StateKit để màn hình mới không bị lệch.