Back to scenario guides
Scenario guide 06

Validation, deletion, and permission boundaries

These actions carry consequences. Motion should put risk and state in view so people can make a considered decision at the critical moment.

Form validation, deletion confirmation, and permission changes all handle boundaries. Motion should place risk, scope, progress, and recovery where people make the decision, so consequences remain visible and work can continue.

Make this decision first

Separate reversible, confirm-required, and immediate actions, then decide whether feedback belongs to a field, object, or global layer.

Implementation path

  1. 01

    Validate beside the input

    Place the message, focus, and a brief cue around the field so people can correct it without searching.

  2. 02

    Explain deletion before commitment

    A confirmation layer states what will be lost; reversible actions expose undo where the change occurred.

  3. 03

    Show the scope of a permission change

    Update members, roles, and accessible content together so a changed label does not leave stale state behind.

Map risk and reversibility first

Making every high-risk interaction the same modal often blurs importance. An invalid email needs fast help beside the field; removing a team member needs the object, impact, and recovery option; changing a link from invited members to anyone needs to show where access expands. Group by consequence rather than control type: immediately correctable, reversible, explicitly committed, and settings that affect other people. Each group determines the location, pace, and language of feedback.

A risk map also prevents over-performance. A small input error does not need the whole page to shake, and a genuine deletion confirmation should not rely on red alone and one flash. Direct attention to the next step: field errors return focus and help text to the input; deletion confirmation makes the target name, impact count, and undo policy readable together; permission changes place old rule, new rule, and affected objects side by side. As risk rises, motion earns a clearer pause and information earns fuller evidence.

Four consequences range from immediate correction to affecting others, mapping to field help, undo, explicit confirmation, and scope summary.Risk risesImmediatelycorrectableField helpReversibleUndo in placeRequirescommitmentObject +consequenceAffectsothersScope summary
Diagram 1: assign feedback by consequenceFour consequences range from immediate correction to affecting others, mapping to field help, undo, explicit confirmation, and scope summary.

Make validation happen beside the input

Good inline validation begins with user behavior rather than the error state. Typing, leaving a field, and submitting require different strategies. While typing, offer anticipatory format and requirement guidance; on blur, confirm completeness; on submit, gather missing items and move focus to the first one that needs action. People are then neither interrupted on every character nor left with an unordered wall of red copy after submission. Each message should state the problem, the correction, and the expected result.

Motion only needs to help locate the issue. A field boundary can change over 120 to 180 milliseconds, an error icon and explanation can appear in place, and the input remains editable. One very short horizontal cue can suit a single post-submit error; repeated shaking makes the error more prominent than the correction. When several errors appear together, use clear document order, an error summary, and stepwise focus navigation instead of animating every field. The low-motion path keeps boundary, explanation, and focus while removing extra travel.

Deletion needs object, commitment, and recovery

The first responsibility of a deletion flow is confirming the object. The confirmation layer should show the item name, related count, irreversible content, and content that remains; similar names can be disambiguated with an avatar, thumbnail, or path. After Delete is pressed, the original list item can enter a processing state so the click never looks ignored. After the server confirms, let the item leave the list and retain an Undo action in or near the original position. The flow describes one event, so states across regions should stay synchronized rather than each playing an attractive animation.

Hold-to-confirm suits actions with high cost and a high chance of accidental activation because it turns commitment into visible time. Progress fill should stay attached to the finger or pointer origin and cancel immediately on release, leaving no ambiguous half-complete state. It does not replace textual explanation: people still need to know what is removed, whether recovery is possible, and when permanent removal happens. For reversible archive or remove actions, the undo window should remain stable long enough for people to continue other work; a fleeting overlay should not force an immediate decision.

A deletion shows consequence guidance, processing state, then result and undo.BeforecommitmentName, impact,choiceDuringcommitmentProcessing,preventrepeatsAftercommitmentResult, undo,continue
Diagram 2: feedback before, during, and after commitmentA deletion shows consequence guidance, processing state, then result and undo.

Show permission scope instead of updating one label

Permission settings are difficult because affected objects are often invisible. When a rule changes from invited members only to anyone with the link, the interface needs to explain visitors, visible content, link behavior, and the result after saving. Copy near the choice can update immediately; on confirmation, place the final rule in the page status area and update related markers in members or sharing lists. People see a traceable rule change rather than an isolated dropdown choice.

Permission hierarchy should match scope. A role adjustment that affects one document can confirm in place; a choice affecting a workspace, public link, or external invitations deserves a clearer summary and a route back. Color helps distinguish states while complete sentences explain who is affected. Collaborative products must also handle concurrency: if another administrator changes the rule, explain whether the local choice remains valid, what saving would overwrite, and how to reload the latest state. Motion makes conflict and update visible; it should never hide either one.

Review boundary interactions through failure paths

Real review begins with failure. Return delay and error from validation services and observe whether people know the field is still editable; fail a deletion request and confirm the object has not vanished early and the error lands near the original action; create a permission-save conflict and check that the page retains the person’s choice and offers a clear next step. Add offline networking, keyboard operation, narrow screens, and low-motion preference. Every exception tests one principle: as risk rises, the interface keeps cause, current state, and recovery action in the same context.

Teams should record which feedback happens before commitment, during commitment, and after commitment. Before-commitment guidance explains consequences; during-commitment feedback prevents duplicate work; after-commitment result restores task rhythm. This framing gives design, product, and engineering a shared language and reduces the urge to pile warning, loading, and success into one component. When every boundary interaction on a page can be described this way, the motion spec is close to a reusable product rule.

Old rule, new rule, affected objects, and saved result create a comparable permission-change explanation.Old ruleInvitedmembers onlyNew ruleAnyone withthe linkAffectedobjectsMembers,links,visiblecontentSaved resultFinal rule instatus area
Diagram 3: evidence for a permission scope changeOld rule, new rule, affected objects, and saved result create a comparable permission-change explanation.

High-risk interaction shipping checklist

  • Confirmation names the object and impactName, count, scope, and recoverability are readable together.
  • Errors stay beside the field and retain editing focusThe message explains the issue, correction, and expected result.
  • Processing state prevents duplicate commitmentButton, original object, and status region communicate progress together.
  • Failure and undo stay in the original contextPeople do not have to relocate the object or remember what they just did.
  • Permission changes show affected scopeOld rule, new rule, and affected people or content can be compared.

Case: preserve task rhythm after deleting a project

A project-list deletion must confirm the object, await the server, update the list, and offer an undo opportunity.

<section class="delete-demo">
  <ul data-project-list>
    <li class="project-row" data-project-row data-project-id="alpha" data-state="ready">
      <span>Project Alpha</span>
      <button type="button" data-delete>Delete</button>
    </li>
  </ul>
  <div data-project-undo hidden>
    <span>Project deleted.</span>
    <button type="button" data-undo>Undo</button>
  </div>
  <p role="status" aria-live="polite" data-delete-status>Project Alpha is available.</p>
</section>

<style>
.delete-demo { display: grid; gap: 12px; max-width: 28rem; }
.project-row { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
@media (prefers-reduced-motion: no-preference) {
  .project-row { transition: opacity 180ms ease, transform 180ms ease; }
  .project-row[data-state="leaving"] { opacity: 0; transform: translateX(8px); }
}
</style>

<script>
const row = document.querySelector("[data-project-row]");
const list = document.querySelector("[data-project-list]");
const deleteButton = row.querySelector("[data-delete]");
const undoRegion = document.querySelector("[data-project-undo]");
const undoButton = undoRegion.querySelector("[data-undo]");
const status = document.querySelector("[data-delete-status]");
const projectId = row.dataset.projectId;
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
let leaveTimer = 0;
let onLeaveEnd = null;

function waitForServer() {
  return new Promise((resolve) => window.setTimeout(resolve, 160));
}

async function deleteProject(id) {
  await waitForServer();
}

async function restoreProject(id) {
  await waitForServer();
}

function clearLeaving() {
  window.clearTimeout(leaveTimer);
  leaveTimer = 0;
  if (onLeaveEnd) row.removeEventListener("transitionend", onLeaveEnd);
  onLeaveEnd = null;
}

function removeAfterExit() {
  const finishRemoval = () => {
    clearLeaving();
    if (row.isConnected) row.remove();
  };

  if (reducedMotion.matches) {
    finishRemoval();
    return;
  }

  onLeaveEnd = (event) => {
    if (event.target !== row || event.propertyName !== "opacity") return;
    finishRemoval();
  };
  row.addEventListener("transitionend", onLeaveEnd);
  leaveTimer = window.setTimeout(finishRemoval, 220);
}

async function removeProject() {
  if (deleteButton.disabled) return;

  row.dataset.state = "deleting";
  deleteButton.disabled = true;
  status.textContent = "Deleting Project Alpha.";

  try {
    await deleteProject(projectId);
    row.dataset.state = "leaving";
    undoRegion.hidden = false;
    undoButton.disabled = false;
    status.textContent = "Project deleted. Undo is available.";
    undoButton.focus();
    removeAfterExit();
  } catch {
    row.dataset.state = "ready";
    deleteButton.disabled = false;
    status.textContent = "Project deletion failed. Try again.";
    deleteButton.focus();
  }
}

async function restoreProjectRow() {
  if (undoButton.disabled) return;

  clearLeaving();
  undoButton.disabled = true;
  status.textContent = "Restoring Project Alpha.";

  try {
    await restoreProject(projectId);
    if (!row.isConnected) list.append(row);
    row.dataset.state = "ready";
    deleteButton.disabled = false;
    undoButton.disabled = false;
    undoRegion.hidden = true;
    status.textContent = "Project restored.";
    deleteButton.focus();
  } catch {
    undoButton.disabled = false;
    status.textContent = "Project restoration failed. Try again.";
    undoButton.focus();
  }
}

deleteButton.addEventListener("click", removeProject);
undoButton.addEventListener("click", restoreProjectRow);
</script>

The list row leaves only after server confirmation. Both paths retain status copy and Undo; the default path adds a very short exit cue while the low-motion path updates the result directly.