---
title: "App interface checklist: all 54 rules · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/checklist
updated: 2026-10-20
licence: "All rights reserved"
---

# App interface checklist

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

Every rule in the app interface playbook, by chapter, required rules first. Each links to its own page with how we built it and how to check it.

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/checklist`.

## Styles and components

- [ ] **Required** [Take every style value from design tokens](https://agentwranglers.se/playbook/app-ui/styles-and-components/design-tokens): Take every colour, space, font size, radius, shadow, duration and layer order from a named design token. Never write a raw value in a component.
- [ ] **Required** [Build each control once](https://agentwranglers.se/playbook/app-ui/styles-and-components/shared-components): Build each control once in a shared component library, and use it on every screen. Never style a raw button, input, select or table on a page.
- [ ] **Recommended** [Name tokens by purpose](https://agentwranglers.se/playbook/app-ui/styles-and-components/purpose-tokens): Give components tokens named by purpose, such as `color-danger` or `space-section`, which point to a base palette. Components never use the base palette directly.
- [ ] **Recommended** [Change components through variants](https://agentwranglers.se/playbook/app-ui/styles-and-components/component-variants): Change how a component looks only through its own options, such as `variant="danger"` or `size="small"`. Never override its classes from the outside.
- [ ] **Recommended** [Show every component state in a catalogue](https://agentwranglers.se/playbook/app-ui/styles-and-components/component-catalogue): Give every shared component a catalogue entry for each state it can show, such as default, loading, empty, error and disabled.

## Data views

- [ ] **Required** [Give every data view all its states](https://agentwranglers.se/playbook/app-ui/data-views/every-data-state): Give every view that loads data a distinct state for loading, empty, error and success. Add partial when it combines sources. Render a designed screen for each.
- [ ] **Required** [Tell an empty start from an empty search](https://agentwranglers.se/playbook/app-ui/data-views/empty-states): Show two different empty states. A new account with no records explains the feature and offers the first action. A search or filter with no matches names the filter and offers to clear it.
- [ ] **Required** [Show errors where the data should be](https://agentwranglers.se/playbook/app-ui/data-views/error-in-place): When data fails to load, show the error inside the view that failed, with a plain cause, a retry button and a reference. Keep any older data visible and marked as out of date.
- [ ] **Recommended** [Show loading without flicker](https://agentwranglers.se/playbook/app-ui/data-views/loading-without-flicker): Show a loading indicator only when loading takes longer than 300 ms. Once shown, keep it for at least 500 ms. Reserve the final size, so nothing jumps.
- [ ] **Recommended** [Show what loaded when one part fails](https://agentwranglers.se/playbook/app-ui/data-views/partial-results): When a view combines several sources and one fails, show the parts that loaded and name the part that is missing.
- [ ] **Recommended** [Say why a control is unavailable](https://agentwranglers.se/playbook/app-ui/data-views/disabled-with-reason): When a control can't be used yet, say why in visible text next to it, and what would make it work.

## Dates and numbers

- [ ] **Required** [Show times in the user's time zone](https://agentwranglers.se/playbook/app-ui/dates-and-numbers/user-time-zone): Show every moment in time in the user's chosen time zone, and name the zone wherever people in different zones see the same screen.
- [ ] **Required** [Keep calendar dates as dates](https://agentwranglers.se/playbook/app-ui/dates-and-numbers/calendar-dates): Keep a value with no time of day, such as a due date, as a plain `YYYY-MM-DD` date everywhere. Never turn it into a moment in time.
- [ ] **Recommended** [Read numbers the way people type them](https://agentwranglers.se/playbook/app-ui/dates-and-numbers/locale-number-input): Read numbers and amounts in the user's locale, so `1 234,50` in Sweden and `1,234.50` in the US both mean the same amount. Never parse money as a floating-point number.
- [ ] **Optional** [Show relative times with the exact time beside them](https://agentwranglers.se/playbook/app-ui/dates-and-numbers/relative-times): Put each relative time, such as "5 minutes ago", in a `<time>` element with the exact time in `datetime`. Show the exact local time in a tooltip on hover and on keyboard focus.

## Forms

- [ ] **Required** [Check every field on the server too](https://agentwranglers.se/playbook/app-ui/forms/server-side-validation): Validate every field on the server with the same rules the form uses, and treat the browser's checks as a convenience only.
- [ ] **Required** [Put server errors on their fields](https://agentwranglers.se/playbook/app-ui/forms/server-errors-on-fields): When the server rejects a form, show each error next to the field it names. Show errors that name no field in a summary at the top of the form.
- [ ] **Required** [Never lose what people typed](https://agentwranglers.se/playbook/app-ui/forms/keep-user-input): Keep every value people entered after a failed submit, a lost connection, an expired session or an accidental reload. Never clear a form unless it was saved.
- [ ] **Required** [Save each form only once](https://agentwranglers.se/playbook/app-ui/forms/single-submit): Make a double click, a second Enter or a retry save the record once. Block repeat submits in the form and reject duplicates on the server.
- [ ] **Recommended** [Check fields when people leave them](https://agentwranglers.se/playbook/app-ui/forms/validation-timing): Check a field first when focus leaves it, then on every change once it shows an error, and check all fields on submit. Never show an error on a field nobody has touched.
- [ ] **Recommended** [Save drafts on their own](https://agentwranglers.se/playbook/app-ui/forms/autosave): When a screen saves without a button, save within 2 seconds of the last change and when the page is hidden. Show the save state in words, and never overwrite someone else's change.
- [ ] **Recommended** [Warn before leaving unsaved changes](https://agentwranglers.se/playbook/app-ui/forms/unsaved-changes-warning): When a form has unsaved changes, warn before the tab closes or the app moves to another screen. Never warn when nothing changed.

## Client and server state

- [ ] **Required** [Keep server data in one cache](https://agentwranglers.se/playbook/app-ui/client-and-server-state/server-state-cache): Keep data that comes from the server in one data-fetching cache, and read it from there on every screen. Never copy it into a global store or component state.
- [ ] **Required** [Key cached data by every input](https://agentwranglers.se/playbook/app-ui/client-and-server-state/complete-cache-keys): Build every cache key from all the inputs that change the answer, including the account, the signed-in user, the filters, the sort and the page.
- [ ] **Required** [Show the answer to the latest request only](https://agentwranglers.se/playbook/app-ui/client-and-server-state/latest-request-wins): When a new request replaces an older one, such as a new search term, abort the older request and never render its answer.
- [ ] **Required** [Refresh every view a change touches](https://agentwranglers.se/playbook/app-ui/client-and-server-state/refresh-after-change): After a successful change, update or refetch every cached query that shows the changed record, including lists, counts and totals.
- [ ] **Required** [Keep screen state in the address](https://agentwranglers.se/playbook/app-ui/client-and-server-state/state-in-url): Put every choice that changes what a screen shows in the address, such as filters, search, sort, page, tab and the open record. Reloading or sharing the link shows the same screen.
- [ ] **Recommended** [Undo optimistic updates that fail](https://agentwranglers.se/playbook/app-ui/client-and-server-state/optimistic-with-rollback): When the screen changes before the server answers, keep a copy of the old data. If the request fails, put it back and say so in words. Refetch either way.
- [ ] **Recommended** [Compute values instead of storing them](https://agentwranglers.se/playbook/app-ui/client-and-server-state/derived-values): Compute totals, counts, filtered lists and labels from the source data when you render. Never keep them in their own state to update by hand.

## Lists and tables

- [ ] **Required** [Never render thousands of rows](https://agentwranglers.se/playbook/app-ui/lists-and-tables/bounded-lists): Keep at most 200 rows in the page at once. Page long lists on the server, or render only the visible rows with a virtual list.
- [ ] **Required** [Sort and filter on the server](https://agentwranglers.se/playbook/app-ui/lists-and-tables/server-sort-and-filter): When a list is paged, sort, filter and search on the server across all records. Never sort only the rows already loaded.
- [ ] **Required** [Make sortable columns buttons](https://agentwranglers.se/playbook/app-ui/lists-and-tables/sortable-headers): Put a button inside each sortable column header, and set `aria-sort` on the header of the sorted column only.
- [ ] **Required** [Key rows by their ID](https://agentwranglers.se/playbook/app-ui/lists-and-tables/stable-row-keys): Key every rendered list item by the record's stable ID, never by its position in the list.
- [ ] **Recommended** [Load more with a button](https://agentwranglers.se/playbook/app-ui/lists-and-tables/load-more-button): Load further results with page links or a "Load more" button, not endless scrolling. After loading, move focus to the first new item.
- [ ] **Recommended** [Use grid keys only for editable grids](https://agentwranglers.se/playbook/app-ui/lists-and-tables/grid-keyboard): Keep read-only tables as plain tables with normal Tab order. Add arrow-key movement, with one Tab stop, only to grids whose cells people edit or act on.
- [ ] **Recommended** [Say what select all selects](https://agentwranglers.se/playbook/app-ui/lists-and-tables/bulk-selection): When people select all rows, say whether that means this page or every matching record, and show the count before any bulk action.

## Feedback and dialogs

- [ ] **Required** [Use toasts only to confirm finished work](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/toasts-for-status): Use a toast only to confirm that something worked, such as "Invoice sent". Announce it through a live region, keep it for at least 6 seconds, and pause it while hovered or focused.
- [ ] **Required** [Keep errors that need action on screen](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/errors-stay-on-screen): Show an error that people must act on next to its cause, and keep it until it is fixed. Never put it only in a toast that disappears.
- [ ] **Required** [Put focus somewhere sensible after every change](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/managed-focus): After a client-side page change, move focus to the new page's heading and update the title. When the focused element disappears, move focus to the next item, the previous item or the control that opened it.
- [ ] **Required** [Let people undo or confirm destructive actions](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/confirm-or-undo): For deletes and other changes that lose data, offer undo when the change can be reversed, and ask for confirmation when it can't. Name the object and the count in both.
- [ ] **Recommended** [Keep dialogs for short tasks](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/dialogs-for-short-tasks): Use a modal dialog only for a short task that must finish before anything else, such as a confirmation. Give longer tasks their own page with its own address.
- [ ] **Recommended** [Show progress for long actions](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/long-action-progress): When an action takes longer than 10 seconds, run it in the background, show its progress, and let people leave the screen. Tell them when it is done.

## Permissions, flags and failures

- [ ] **Required** [Enforce permissions on the server](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/server-enforces-permissions): Treat hidden and disabled buttons as a convenience only. The server checks the user's permission on every request, whatever the screen shows.
- [ ] **Required** [Survive an expired session](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/session-expiry): When a session expires, keep the screen and the unsaved work. Let the person sign in again in place, then repeat the request that failed.
- [ ] **Required** [Contain each crash to its panel](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/error-boundaries): Wrap each route and each independent panel in an error boundary. One broken part then shows a fallback, and the rest keeps working. Report every caught error.
- [ ] **Required** [Give every request a time limit](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/request-timeouts): Give every request from the screen a time limit, say when a request is slow, and retry only reads and requests with an idempotency key.
- [ ] **Recommended** [Let the server say what each user may do](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/capabilities-from-server): Send the actions the user may take on each record with the record, and show controls from that list. Never rebuild the permission rules in the client.
- [ ] **Recommended** [Read feature flags before the first paint](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/feature-flags-in-ui): Decide feature flags on the server and send them with the first page or data load. A flagged feature then never flashes in or out. The API checks the same flag.
- [ ] **Recommended** [Say when the network is gone](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/offline-behaviour): When the connection drops, say so in a banner and keep everything already loaded on screen. Either queue changes with idempotency keys and send them on reconnect, or block them with a reason.
- [ ] **Recommended** [Recover when a new release replaces the old files](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/new-release-reload): When a tab still runs the old release and a code chunk fails to load after a deploy, reload once to get the new release. Keep older files online as [keep the previous build's assets](https://agentwranglers.se/playbook/website/site-architecture/keep-the-previous-build-s-assets) sets.

## Tests and budgets

- [ ] **Required** [Test every state of every data view](https://agentwranglers.se/playbook/app-ui/tests-and-budgets/component-state-tests): Write a component test for each state of each data view. Fake the network at the request level, and find elements by role and name.
- [ ] **Required** [Give each route a size budget](https://agentwranglers.se/playbook/app-ui/tests-and-budgets/route-bundle-budget): Give the first screen and each route its own budget for compressed JavaScript, and fail the build when a change goes over it.
- [ ] **Recommended** [Compare screenshots of every component state](https://agentwranglers.se/playbook/app-ui/tests-and-budgets/visual-regression): Take a screenshot of every catalogue story at a phone and a desktop width, in light and dark mode. Fail the build when one changes without a new baseline.
- [ ] **Recommended** [Load heavy parts when they are needed](https://agentwranglers.se/playbook/app-ui/tests-and-budgets/split-heavy-code): Load each route, and each widget over 20 kB compressed, such as a chart, an editor or a map, only when someone opens it.
