Back to blog
FILE 0xFB·MULTI-SELECT WITH CONTEXT-AWARE BULK ACTIONS

Multi-select with context-aware bulk actions

April 20, 2026 · ux, email, frontend

I built multi-select for the mail reader UI and then almost shipped a bad version of the bulk-archive button. The fix was making the button's behavior depend on what was selected, instead of always doing the same thing.

The multi-select itself

Standard pattern, three nice touches:

The bulk action bar appears at the top when there's a selection, with buttons for Read, Unread, Star, Archive, Spam, Trash. Bulk operations are batched 100 at a time on the backend.

The bad version of the Archive button

First pass: the Archive button always archived. Click it, every selected message gets the archived flag set.

Then I tried using it from inside the Archive folder. I'd selected a bunch of messages in Archive that I wanted to pull back into the inbox. Clicked Archive. Nothing visibly changed, because they were already archived.

Hit Spam. They went to spam. Hit Unread. They became unread. The Archive button was the only one that had no inverse, because the underlying operation only had one direction.

Context-aware behavior

The fix: the button looks at what's selected and chooses the action.

Same trick works for Star/Unstar, Read/Unread, Spam/Not-spam. The button is now a verb that adapts to the noun.

Feedback that doesn't lie

After clicking, the button shows explicit feedback:

Selection auto-clears on success after about a second so the next selection feels fresh. On failure it stays visible for a few seconds so I can either retry or scroll to see what went wrong.

The reason for the partial-failure case: bulk operations batch on the server side, and a batch can have individual failures inside it (Dynamo conditional checks, optimistic concurrency, etc.). Hiding those silently is worse than showing the count.

What I'd do differently

I'd have written the "what does this button do given the selection state" logic as a pure function from selection → action upfront, instead of bolting it on. The current code has it inline in the React component, which is fine but means the logic isn't tested independently of the UI. A pure function returning {label, action, severity} would be trivial to unit-test and would make the "what should this say?" question a one-liner.

Also: the Trash button still has the "always trashes" problem in some corners — selecting items already in Trash, the button still says Trash and clicking it does nothing useful. The right action would be "Restore" when selection is in Trash. I'll fix that the next time I'm in there.