Review queue
Expenses
Twenty-six card expenses in three groups, Ready to approve, Missing receipt and Over policy, with a Suggested action column that says why on hover. It works the way a spend inbox does: you approve by the item or by the whole group, flagged items get their own tab, and each expense comes with a suggested action and its reason. The merchants, cardholders and reasons are made up, but the amounts and the policy limits are the kind a real inbox has.
Everything the grid can do is one event into a reducer, and everything it shows is a selector over that state. Statuses live in one map and the view, cursor, selection, sort and column widths next to it, so a row never has to keep three booleans in sync. Approving and flagging push an entry onto an undo log with the statuses they replaced, and Z pops the newest and puts the cursor back on its first row, as many times as there are entries. A script asserts the reducer's outputs, so the range selection, the tri-state groups and the undo are checked without a browser.
Approve is optimistic. The rows leave the moment you press A and the fake request settles six hundred milliseconds later. In the Partial error state it rejects about one in four, fixed by id, and those come back in the list with the reason and can be approved again, when they go through. A reply that lands after an undo finds its rows no longer in flight and leaves them alone. The toast sits inside the card, over the grid, so it never pushes a row, and the undo on it is the same undo as the key.
The grid is a single tab stop with a cursor row as its active descendant. J and K move it, Space selects, Shift extends, A and F act on the selection or the cursor row, Enter opens a detail panel that follows the cursor without taking focus, and Escape closes it, then clears the selection. After an approval the cursor lands on the next row that stayed, so J, A, A, A walks down the list, and a polite live region reads each row as you reach it. The column handles come after the grid in the tab order and move by the arrows, since a drag handle you cannot reach is decoration.
The shortcuts are borrowed from mail clients, where J, K, A, F and Z have meant next, previous, archive and undo for twenty years, along with G then I and G then F to go to the inbox or the flagged tab. Selecting is Space rather than the X those clients use, since Space is what a grid row or a checkbox already answers to. There is sorting and column resizing but no pagination, since an inbox you review by the group should not have pages.
Keyboard and access
- Keyboard
- The view tabs are ordinary tabs, and the grid is one tab stop. Inside it J and K or the arrows move a cursor, Shift with them extends a selection, Space selects or deselects the cursor row, Home and End jump to the ends, G then I or G then F goes to Inbox or Flagged, and Cmd or Ctrl+A selects every visible row. A approves and F flags the selection, or the cursor row when nothing is selected; in Flagged, F puts a row back in the inbox. Z or Cmd+Z, anywhere in the card, undoes the last action, and again for the one before. Enter or O opens the detail panel, which follows the cursor without taking focus, and Escape closes it, then clears the selection. The question mark opens the shortcuts. After the grid come the column headers, buttons that sort by their column, where a second Enter or click flips the order, and Shift with the left or right arrow narrows or widens that column by sixteen pixels. The drag handles between headers are for the mouse and are not tab stops. The Approve, Flag and Undo buttons, the button beside the selection count that clears it, the keyboard button for the shortcuts and the panel's Close button are ordinary tab stops, and after Approve or Flag focus goes back to the grid.
- Screen readers
- The grid is a real table with the cursor row as its active descendant, each row marked selected or not, and group rows with a tri-state checkbox named after the group. Sorted columns carry aria-sort, and each header that can be resized announces its Shift and arrow shortcut. A polite live region reads the cursor row as it moves, with its merchant, amount, cardholder, group and suggested action, and selected when it is; a second one reads each action, such as "Approved 3. Press Z to undo", which rows came back with their reason, and each undo. The suggested action's reason sits behind a tooltip for the mouse, and in the detail panel and the live region for everyone else. The toast is a status region with a real Undo button.
- Reduced motion
- With reduced motion rows that leave disappear at once instead of collapsing, a group checkbox selects its rows together instead of one after another, the detail panel opens and closes at once instead of growing from the edge, and the toast appears and leaves without a transition.