Frontend UX: standardize loading states on Skeleton (retire "…" and empty role=status divs) #53
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium. From a frontend UX audit.
Problem
Loading is rendered (at least) three incompatible ways:
object-list.tsx:32,object-detail.tsx:36,search-panel.tsx:89,field-list.tsx:27(good).vocabulary-list.tsx:55,vocabulary-terms.tsx:57,authorities-page.tsx:66(looks like a half-rendered bug; no sense of how much is loading).<div role="status">with no visible content —require-auth.tsx:8(blank app on first load),object-edit-form.tsx:30(edit form looks broken/blank while the object loads).Plus the lazy-route
FormFallback(app.tsx:30) replaces the whole main pane with bare "Loading…" text → full-pane flash + layout shift on first navigation to/objects/newand/objects/:id/edit.Suggested fix
Standardize on the existing
Skeletoncomponent (or a sharedSpinner) for all list/detail/form/auth loading; make form fallbacks/skeletons mirror the real layout to avoid layout shift. Retire the "…" placeholders and emptyrole="status"divs.Source: frontend UX/design audit, 2026-06-06.
Done — merged to
main(53c9810).Added
web/src/components/ui/skeletons.tsxwith three shared recipes built on the existingSkeleton, each arole="status" aria-busy aria-label={t("common.loading")}live region (so screen readers now announce loading — the old emptyrole="status"divs announced nothing):ListSkeleton({ rows, rowClassName, className })FormSkeleton({ fields })— label+input pairs + a button, mirroring the object formAppShellSkeleton— a faux sidebar + header + content, mirroringAppShellApplied everywhere loading was inconsistent:
ListSkeletonat vocabulary-list, vocabulary-terms, authorities-page (rendered in place of the<ul>so it stays valid HTML).role="status"divs → object-edit-form loading usesFormSkeleton; the whole-app first-load gate (require-auth) usesAppShellSkeleton, so the app no longer flashes blank and doesn't shift when the real shell mounts.FormFallback("Loading…" text) with per-route skeleton fallbacks —FormSkeletonfor/objects/new+/objects/:id/edit,ListSkeletonfor/fields— removing the full-pane flash + layout shift.ListSkeleton; objects-table (tabletbodyrows) and object-detail (single block) keep their fitting inline skeletons.New i18n
common.loading(en/sv); en/sv parity; 207 tests green; typecheck/lint/build/check:size (214.7 KB gz)/check:colors clean; no codename; no new dependency.Follow-up (out of scope): a
Spinnerprimitive if ever wanted (this is Skeleton-only).