Conflict state
Two-version chrome from resolveConflictStatus in @sometic/dom/status. Everything resolveStatus({ kind: "conflict" }) returns, plus resolved localLabel and remoteLabel for the side-by-side comparison. Assertive live region, role="alert", data-status="conflict", and the default title Conflicting changes. Same resolver family as Empty state, Error state, and Offline state.
PreviewStatus
Nothing here yet
Demo empty surface
Something went wrong
Demo error surface
You are offline
Demo offline surface
Conflicting changes
Your version vs Server version
Usage
js
import { resolveConflictStatus, resolveStatusAction } from "@sometic/dom/status";
const panel = document.querySelector("#conflict");
const view = resolveConflictStatus({
title: "This record changed while you were editing",
description: "Choose which version to keep.",
hasAction: true,
versions: { localLabel: "Your draft", remoteLabel: "Saved on server" },
});
panel.className = view.className;
for (const [key, value] of Object.entries(view.attributes)) {
panel.setAttribute(key, value);
}
for (const label of [view.localLabel, view.remoteLabel]) {
const choice = document.createElement("button");
for (const [key, value] of Object.entries(resolveStatusAction().attributes)) {
choice.setAttribute(key, value);
}
choice.textContent = `Keep ${label}`;
panel.append(choice);
}ts
import { resolveConflictStatus, type ConflictStatusViewModel } from "@sometic/dom/status";
export function recordConflict(): ConflictStatusViewModel {
return resolveConflictStatus({
kind: "conflict",
title: "This record changed while you were editing",
hasAction: true,
versions: { localLabel: "Your draft", remoteLabel: "Saved on server" },
});
}html
<section id="conflict"></section>
<script type="module">
import { resolveConflictStatus, resolveStatusAction } from "@sometic/dom/status";
const panel = document.querySelector("#conflict");
const view = resolveConflictStatus({ hasAction: true });
panel.className = view.className;
for (const [key, value] of Object.entries(view.attributes)) {
panel.setAttribute(key, value);
}
const title = document.createElement("h3");
title.dataset.slot = "title";
title.textContent = view.title;
const actions = document.createElement("div");
actions.dataset.slot = "actions";
for (const label of [view.localLabel, view.remoteLabel]) {
const choice = document.createElement("button");
for (const [key, value] of Object.entries(resolveStatusAction().attributes)) {
choice.setAttribute(key, value);
}
choice.textContent = `Keep ${label}`;
actions.append(choice);
}
panel.replaceChildren(title, actions);
</script>Notes
resolveConflictStatusalways resolveskindtoconflict, so passing anotherkindhas no effect. TypeScript still requireskindbecause the options extendResolveStatusOptions.- Labels default to
Your versionandServer version. Override them withversionsso the copy names the real thing (draft, revision, invoice) instead of a generic version. - Conflict is assertive like error, because unsaved work is at stake. Announce it, then keep both versions reachable.
- Never auto-resolve silently. Render both choices, and make the destructive one explicit ("Discard your draft").
- Keep an unmodified copy of the remote payload while the conflict is on screen. If the user picks the server version you need it, and refetching may have moved on again.
Full API, accessibility, styling, edge cases, and FAQ: Status surfaces.