top of page

Acceptance Criteria Template for Agile Teams

Shawn West
Jul 16
5 min read

Updated: 33 minutes ago

The demo where "done" fell apart went like this — a composite drawn from patterns common in B2B SaaS permissions work. The engineer pulled up the workspace switcher, clicked through three workspaces, and sat back satisfied. The PM asked what happens when you switch to a workspace you were removed from yesterday.

Silence. Two more days of work, and a sprint commitment blown.

That case wasn't in the story — not because anyone decided it was out of scope, but because "let users switch workspaces" was the whole spec and everyone had filled in the blanks with a different picture. Acceptance criteria exist to have that argument on purpose, before code, while it costs ten minutes.

But criteria have a failure mode of their own, and it's the reason the six-criterion version of this story would still have missed it. Criteria are written by the person who already knows the answer, and gaps are invisible from inside the head that made them.

Two formats

Plain bullets — fast, and enough for most stories in our experience:

- Switcher shows every workspace the user has access to
- Switching preserves the current page where an equivalent exists
- Current workspace is clearly indicated
- Fully operable by keyboard

Given–When–Then — verbose, and worth it when the correct outcome depends on state:

Given a user viewing /projects/123 in workspace A
When they switch to workspace B
Then they land on the equivalent page in B, or the dashboard if none exists

Pick by risk, not by policy. Given–When–Then earns its words exactly where a wrong branch is expensive, because its structure forces you to name the starting state — and unstated starting state is where most ambiguity hides. Mandating GWT for a one-line CSS fix trains a team to produce ceremony instead of criteria.

The test: for each criterion, ask whether the outcome depends on what was true beforehand. If yes, it wants Given–When–Then. If no, a bullet is not a compromise.

To turn criteria like these into executable examples, work through Specification by Example: A Hands-On Tutorial.

The question that finds what you couldn't see

Here's the discipline that separates criteria which catch the demo failure from criteria that merely decorate the ticket. Hand the story to someone who didn't write it and ask one question:

"Can you describe a case where every one of these passes and the feature is still wrong?"

This works for a mechanical reason. The author's criteria are complete with respect to the model in their head — every case they can imagine is covered, which is precisely why re-reading their own criteria finds nothing. A second person brings a different model, and the gap between two models is exactly where the missing case lives.

Run it on the workspace story. The original criteria all pass if you switch between two workspaces you currently belong to. The reviewer's question: what if access changed since the page loaded? Nobody had considered it, because the author's mental model had a static list of workspaces — and the missing criterion writes itself:

Given a user whose access to workspace B was revoked after their page loaded
When they attempt to switch to B
Then they see a "no longer available" message and remain in the current workspace

That's the demo failure, found for the price of one question at refinement.

The test: every story that matters gets this question from someone who didn't write it. If the answer is "no, I can't think of one," you're either genuinely done or you asked someone who shares the author's assumptions — ask an engineer if a PM wrote it, and the reverse.

What separates a criterion from a wish

Property

Fails as

Passes as

Specific

"Easy to use"

"Primary action reachable in two clicks"

Observable

"User feels confident"

"Confirmation message shows the new workspace name"

Bounded

Three behaviours in one bullet

One behaviour per criterion

Necessary

Removing it changes nothing

Removing it makes the feature meaningfully worse

Sufficient

All pass, feature still broken

All pass, story genuinely done

The last row is the one the adversarial question tests, and it's the only one you cannot check alone. The other four you can verify by reading. Sufficiency is a claim about everything you didn't write, which is why it needs a second pair of eyes.

The test: hand your criteria to someone and ask what they'd click to verify each. Any criterion producing "well, it depends" is a review argument you've scheduled for three weeks' time.

Cover the unhappy path deliberately

Happy-path-only criteria are the default failure, and the cure is four questions asked of every story:

  • What if the user lacks permission?

  • What if state changed since the page loaded?

  • What if the input is invalid or empty?

  • What if the dependency fails or times out?

You don't need all four on every story. You need to have asked all four, and to have written down the ones that could plausibly happen. The workspace failure was question two, unasked.

The test: count your criteria that describe something going wrong. If it's zero, you've specified the demo, not the feature.

The worked version

## Story: Workspace switching

As an agency manager working across client workspaces, I want to switch
without losing where I am, so that I can check something in another
client's account and return to what I was doing.

## Acceptance criteria

Given a user with multiple workspaces
When they open any page
Then the switcher shows their current workspace

Given a user viewing /projects/123 in workspace A
When they switch to workspace B
Then they land on the equivalent page in B, or the dashboard if none exists

Given a user whose access to a workspace was revoked after page load
When they attempt to switch to it
Then they see a "no longer available" message and remain where they are

Given a user with exactly one workspace
When they view the app
Then the switcher is not shown

Given a user operating by keyboard
When they open the switcher
Then they can navigate and select without a mouse

Five criteria: three happy path, one state-change case, one edge. Three to seven is the range we work to — a rule of thumb rather than a measured finding — under three and you've probably under-specified; over seven and the story is likely two stories, which is a Small failure in INVEST terms.

Criterion three exists only because someone asked the adversarial question. It's also the one that would have saved the sprint.

What doesn't belong in criteria

Criteria define completion of this story. Four things get pushed in that belong elsewhere, and each one dilutes the list:

  • Team-wide baselines — tested, reviewed, deployed, accessible. That's your definition of done, and repeating it per story buries the story-specific criteria in boilerplate.

  • Implementation — "use Redis for the cache" fixes the mechanism and dates the criterion the moment a better approach appears.

  • Optional behaviour — "should also support X." Either it's in scope, or it's the next ticket. Acceptance criteria are all must.

  • Quality bars that span the system — latency budgets, retention rules. Those are non-functional requirements with their own bounds and verification.

The test: for each criterion, ask whether it would appear identically on the next five stories. If yes, it's your definition of done and doesn't belong here.

What to do at your next refinement

Add one step, costing about two minutes per story: after the criteria are written, the author hands the story to someone else and asks can you name a case where all of these pass and the feature is still wrong?

Write down whatever comes back. That's your missing criterion, and it will usually be a state-change or permission case — because those are the ones invisible from inside the model that produced the story.

The point of acceptance criteria was never to document what you already agree on; that part is easy and it's why criteria feel like busywork when they go wrong. The point is to surface the case nobody had pictured yet, while it's still a sentence on a ticket rather than a silence in front of everyone at the demo.

bottom of page