People aren’t perfect—they tend to make mistakes.
Empower people to undo the actions they take while using your software.
Undo is one of those nearly ubiquitous patterns—you should probably default to supporting it unless you have constraints that prevent you from doing providing it. It’s more a question of for what and how you support it.
People buy insurance for a reason—they like to know that they’re protected should something go wrong. Undo functions along a similar vein—when people know they have it, they feel more at ease and even confident while using software.
Consider the difference between authoring something in a word processing application and authoring that same item on a conventional typewriter. While the more advanced typewriters did have some basic undo capabilities, many required you to manually use strips of white or (or the brush-on kind) to correct mistakes. As a result, people were far more cautious and potentially slower when writing. While some consider that a good thing, most people greatly appreciate the ease with which they can simply hit backspace or even simply highlight a section of text and delete.
This rationale directly addresses helping people recover from accidents/mistakes. For actions that may have more severe consequences (such as deleting a record), it can be even more important for people to be able to undo their actions. And usually it is primarily for this reason that Undo is implemented.
However, a nice side effect of having Undo in place is that it actually gives people more confidence and more willingness to try new and different things. And as a result, people are able to more quickly learn (through trial and undo-able error) how to master software. In fact, the design process itself encourages this sort of approach—iterating over designs to find the right one, and Undo can help with this by letting people safely explore alternatives and enabling them to get back to a state they’re happy with if a particular path doesn’t pan out.
There are four main considerations in implementing this pattern. The first has to do with understanding what constitutes an undoable action, the second revolves around level of undo granularity, the third has to do with how to technically make it happen, and the fourth is how to present it to people.
For the first, it often will logically flow from the interaction model—the kinds of things that people are trying to achieve with the solution. If it something that people directly intend as a change to the underlying domain objects (e.g., the first name on a user, the dimensions of a shape in a drawing app, the text in a writing app, the removal of an object from a collection/repository, etc.), then it is a good candidate to be an undoable action.
On the other hand, if the actions do not modify the domain model (think things like scrolling, selecting, panning, browsing, expanding, collapsing, sorting, and perhaps even moving—assuming you can easily move back) then there is little or no sense in making these actions undoable. The reason is that people won’t perceive these as actions they would want to undo because they can easily use the same interactions to undo/redo them without the help of an explicit undo operation. Having these in the undo “stack” could actually be a bother and confusing.
You should also consider cases where you may not want to allow undo or where it is inherently not possible. Especially in workflow and asynchronous messaging situations (think email, purchasing, that sort of thing)—if there’s a possibility that an undo would not be successful, you should not support it as that would give people a false sense of security. Or perhaps there is a business reason to make undo difficult if not impossible.
Note: One good detector of when you might want to consider an undo is when you are tempted to create a confirmation/warning dialog. Instead of asking people to confirm they want to take an action, it is often better to just let them do it and then undo it. This is assuming the action passes all the other considerations, but it is a flag to help you find good candidates for undoable actions.
Level of Granularity
Once you figure out what can and should be undoable, you need think about at what level of granularity undo should be supported. Here again, if you are to finely grained in keeping track of actions, people will get frustrated by the number of times they have to request an undo to get the result they want.
Concretely, think about using words or even phrases, sentences, or paragraphs in a text editing situation. Many drawing apps use the mouse press and release as a kind of transaction—so that undoing would undo all modifications that occurred in between when the mouse was pressed and released, i.e., instead of undoing individual points in a line drawn, it would just undo the line. In terms of data-oriented apps, undo is usually sufficient on the last commit (e.g., a delete, save, or create action) instead of on the modification of individual attributes/fields.
With this, you should consider whether or not to support multiple levels of undo granularity. Sometimes this is built in—individual controls like a textbox might support undo for text editing within them and you would only need to implement the commit-level undo. Or maybe you can implement some sort of version control on files or objects that lets people create a history of changes at the file level that could be undone in addition to the more fine-grained undo supported while modifying individual files.
Most people innately recognize the value of this pattern; however, it can be a bear to make it happen due to technical constraints. At the core, you need to support a history in the form of a stack (last in first out). You have to figure out how to make this stack and where it should live (e.g., on a client, server, database, application, etc.). For rich client applications, usually on the client is best, though if there is a backing server (as is often the case in RIA situations), you may want to synchronize occasionally. For thin client/terminal situations, the server is the natural home.
Then there is the consideration of how well the undoable action in the domain maps to the technical model of the domain. Hopefully, with good domain-driven modeling, this should fit naturally; otherwise, you may have to do some hacking.
In addition, you have to figure out how to detect a transactional action—press and release of mouse button, based on text, based on explicit user commits, based on file changes, based on explicit user declaration of save/restore points, etc.—there are many options here. Picking the right one should flow from the interaction model, but it may be tricky to correctly detect intended transactional operations so as not to overload the stack unnecessarily.
Lastly, in terms of number of items, the more the merrier is a good rule, but it is usually a technical constraint (based on memory usage or stack management performance). You should shoot for no less than 10, but there are cases where just one is okay (such as in the case of a data-oriented application using commits—it is not usual to keep track of a long history of changes in these. Whether or not people can really use much more than 10 or so depends on your users and the context, but in general, if having more doesn’t negatively affect the experience, there’s probably no reason not to support more.
As you can see in the examples, there are many ways to surface undo to people. For rich client applications, putting Undo/Redo in the Edit menu is the accepted convention. Mapping CTRL/CMD-Z to undo (and CTRL/CMD-Y or CTRL/CMD-SHIFT-Z to redo) is going to be expected by most experienced computer users as it becomes second nature to them.
Each undo command should pop the last action off the stack so that repeated undo commands take people further back in their action history. It is also often good to support redo if it makes sense in your solution, which is just a parallel stack that you pop items onto while undoing—when a new action is taken, the redo stack is usually cleared.
You may want to enable people to see the undo stack, especially if your solution is one in which many incremental changes will be made and you can meaningfully represent them—there are some examples of this (e.g., Word and Fireworks). If you can’t provide meaningful labels or visualizations, this is probably not a good idea, and obviously if you only support one item in the undo history, it doesn’t make much sense, though in that case, you can be explicit about what will be undone (as in the Gmail and Google Calendar examples).
Finally, if you support versioning-style undo, you will need to provide dialogs that show histories, let people view past versions, compare, and of course rollback (undo to previous version). This can get hairy, but it is extremely useful, particularly if your domain involves the creative/design/iterative process. Letting people save a version, try something else, compare them, is extremely helpful and encourages that process.
Infragistics has some tools that can jumpstart your efforts to implement this pattern. Broken down by technology, they are as follows.
You can use the NetAdvantage for Windows Forms WinGrid control with the AllowMultiCellOperations property set to “All” to implement this pattern. If you download the NetAdvantage for Win Client bundle, you can find a sample in the WinForms sample browser called WinGrid Samples to see it in action.
The primary example is from Microsoft Office 2007. The undo uses a Drop Down Button—clicking the icon will undo the latest action, but people can use the drop down part of it to choose to rollback to an even earlier action in the history. Note how they try, when possible, to make the items intelligible (e.g., Typing “thing typed”), and it is usually word or phrase-based—not letter based. Not shown here is that it tracks a deep history and uses a scrollable area to let people explore the history—for instance, right now I have 267 items in the undo history.
This example from Adobe Fireworks CS4 shows their undo history window. They use icons along with the name of the tool that lets people know the type of action it was. They use the little slider on the left to let people slide back in their history—when you stop sliding the UI updates to show the state of the UI at that point in history. You can easily slide it back to the end—a nice take on the redo idea that further encourages exploration.
Google Calendar is a good example of a data-oriented, commit-based undo. Not only does it support undoing of delete operations, which is usually the most common undo scenario for these kinds of solutions, it also supports undo for creating events as shown here.
OmniGraffle 5 has the standard desktop/rich app approach of supporting CMD-Z as well as the Edit menu (another aspect of the convention is that Undo/Redo is at the top of the Edit menu). They further help people by describing what will be undone, in this case a Dropped Graphic (I had just dropped an item from the stencils pane on to the surface).
As with Calendar, Google supports this sort of single-item, commit-based undo by showing a message of what just happened and offering people the choice to undo it. In this case, there is also an informational link to learn more about Gmail’s use of archiving.
Here you see Visual Studio 2008’s Undo Pending Changes dialog. This is an example of file-based versioning undo—people can revert to the last state checked in on the server. As noted in the implementation, versioning is a great way to support an iterative design process.
This example shows the Snapshots capability in Scrivener, an authoring tool for Mac. It relies on people to declare save/restore points (snapshots), creating a version history as you go along. You can view the versions in the snapshot history and choose to rollback (undo) to it if you prefer.
The Trash, shown here from OS X, and Recycle Bin in Windows are a way that the OS supports Undo for files and folders in the file system. Windows still prompts people to confirm they want to send items to the Recycle Bin, but you can turn that off in its properties so that it functions more as is recommended for undo. OS X treats the trash like other folders—to restore, you just drag and drop the item out of the trash. Windows has a specific Restore action. But this is one way to implement undo—a temporary holding place that people can use to undo deletes, until of course the trash is taken out (emptied). SharePoint 2007 actually supports multiple levels of recycle bins—user level and when emptied from there, it still shows in an administrator level recycle bin—just in case!
In Click-Shirt you can design your own T-Shirt. Each time you change the design, you get a new entry in a scrollable bar with all the previous designs, from where you can choose. After you choosing a previous one, you still get the whole list, providing a way to redo your undone changes.
This example illustrates the idea that the Back and Forward buttons in browsers are a kind of Undo implementation—undoing navigation. We support deep linking and Journal Navigation in Quince, so you can see here that I could “undo” my last N navigation actions to get back to an earlier state. For RIAs, there is a pattern to support the Undo semantic, but it is still emerging and it likely depends a lot on your users as to whether or not they’ll expect to be able to undo, say, a commit action using the Back button. This is an updated example for Quince. We trimmed down the titles to focus on the key aspects and help people more easily see the things they've done and be able to pick what they might want to roll back to.
Jennifer Tidwell, Designing Interfaces
Aza Raskin, Never Use a Warning When you Mean Undo
Alan Cooper, About Face