A button can look unavailable, expose aria-disabled="true" in the DOM and still run the JavaScript action attached to its click handler. That is not a browser contradiction. It is the result of treating several different contracts as though they were one: communicated semantics, focus reachability, event delivery and admission of the application command.
For a native button, the practical release question is not merely, “Does the element say disabled?” It is, “When the product says this preview is unavailable, can any supported activation route still append the apply-local-preview command?” A reliable answer must also preserve the product’s chosen focus policy. Some interfaces should remove an unavailable control from sequential keyboard navigation by using native disabled. Others deliberately keep the control reachable so a user can discover it and its explanation; that variant needs aria-disabled plus an explicit guard immediately before the effect.
This article provides a self-contained local laboratory for that decision. It predicts pointer, Enter, Space and HTMLElement.click() results across nine cases, records focus and event evidence, and separates a matched platform demonstration from an acceptable product outcome. No browser, assistive-technology or Playwright run is claimed here. The only completed execution supplied with the fixture is JavaScript syntax validation under Node v24.19.0 on October 12, 2026. Every browser result below remains an expected outcome until a named environment produces and preserves the evidence.
Define what an unavailable action must prevent
Begin with an application contract that can be measured: while the preview is unavailable, an attempted activation must admit no apply-local-preview command. While it is available, one ordinary activation must admit exactly one such command. That contract is narrower and more useful than saying the button “looks disabled.”
The review therefore needs separate evidence columns:
Question | Evidence to record |
What state is communicated? | aria-disabled, native disabled, accessible role/name and visible reason |
Can sequential focus reach the target? | Before → target → After focus trace, or a recorded native-disabled skip |
Did an activation reach the handler? | Captured focus, key, pointer and click events; receivedClicks |
Was the attempt rejected? | blocked increment before the effect |
Was the command admitted? | Exact identity and count in the commands ledger |
A dashed border or muted color can support comprehension, but styling is not effect evidence. Likewise, aria-disabled="true" communicates an intended state; it does not, by itself, rewrite the handler’s JavaScript. The proposed product policy in this article is explicit: semantics and the Boolean used for command admission must describe the same current availability.
The fixture keeps the scope deliberately small: one native button type="button", ordinary light DOM, one stable click handler and one synchronous local command. It excludes forms, transport, server authorization, asynchronous work, cloned listeners and framework-specific behavior. Native disabled has other documented consequences; the neighboring article on disabled controls and FormData eligibility covers a different effect and a different search intent. Here, the only business effect under review is whether the preview command enters the ledger.
Choose a focus policy before choosing an attribute
There are two defensible product choices, provided each is implemented and tested consistently.
With native disabled, the browser supplies the disabled behavior of a form control and normally removes that button from sequential keyboard navigation. This is appropriate when skipping the unavailable action is the intended navigation policy and its absence from the tab sequence does not hide essential explanation.
With guarded ARIA, the button remains focusable so the unavailable action and its reason can be discovered in sequence. The application renders aria-disabled="true", presents a visible reason and rejects the command in JavaScript. The APG keyboard-interface guidance on disabled controls treats disabled focusability as contextual: keeping a disabled item reachable can improve discoverability, while every additional focus stop also has a navigation cost. That guidance informs the choice; it does not make one policy universally superior.
Write the focus contract independently from the command contract. For the focus-preserving variant, the expected sequence is Before → target → After whether the command is available or unavailable. For the native-disabled variant, the expected unavailable sequence is Before → After. Neither sequence says whether a command executed; the ledger must answer that separately.
Keep native keyboard activation in the native button
The target remains a genuine button type="button" outside a form. According to the informative APG button pattern, Enter and Space activate a focused button. The native element already supplies that keyboard contract, so the fixture does not add a second keydown handler that could execute the business action twice or create a route that differs from click handling.
This implementation must not be generalized to a div role="button". A custom element has different keyboard obligations. The laboratory intentionally avoids that problem so one stable native activation path reaches one stable click handler.
Run the self-contained button laboratory
Save the following file as aria-disabled-lab.html. From its containing directory, serve it over localhost:
python -m http.server 8765 --bind 127.0.0.1Then open the local laboratory page in the browser being qualified. The server only delivers the file; the preview command never leaves the page. The 127.0.0.1 loopback address is treated as a potentially trustworthy origin by the Secure Contexts algorithm, which matters because crypto.randomUUID() is available only in secure contexts in supporting browsers.
Before testing, record the exact browser build, operating system, viewport and any platform keyboard-navigation setting that changes whether controls receive sequential focus. Keep those facts in the review record in addition to the captured navigator.userAgent; a user-agent string is not a complete environment description.
The file is revision aria-disabled-command-admission-v1. With LF line endings and its final newline, its SHA-256 is d07fcf5b02caae63cad48d2d0a19dcd733c52ea36a3c30058e2a43c487ccbc35. The supplied research record reports that the extracted JavaScript passed node --check with Node v24.19.0 on October 12, 2026. That syntax check is the entire observed execution claim supplied with this article. It did not execute HTML parsing, DOM behavior, pointer input, keyboard input, focus movement, assistive technology or browser click() behavior. Any edited fixture requires a new revision, a new hash and separately labelled evidence.
Read the report as provisional evidence
Each page load creates a new run ID. Each armed trial creates a new trial ID, resets trial-local counters and starts as HOLD. The report keeps received clicks, blocked attempts and admitted commands separate. A completed row may become OBSERVATION_MATCHES_PENDING_REVIEW, but the overall run deliberately remains HOLD because an operator must reconcile the requested route, the event trace, focus behavior and independent notes.
Use one fresh trial for one route. Download the current JSON before reloading. If an exception occurs, preserve the error and hold the result; an empty ledger after an exception is not proof that an unavailable action was correctly rejected.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Unavailable button command-admission laboratory</title>
<style>
body { font: 1rem/1.5 system-ui; max-width: 56rem; margin: 2rem auto; padding: 1rem; }
button, select { font: inherit; margin: .4rem; padding: .6rem; }
button:focus-visible, select:focus-visible { outline: 3px solid #124; outline-offset: 3px; }
#target[aria-disabled="true"] { border-style: dashed; }
#stage { border: 1px solid; padding: 1rem; }
pre { white-space: pre-wrap; overflow-wrap: anywhere; }
</style>
<h1>Unavailable button command-admission laboratory</h1>
<p>This local demonstration records observations; it does not certify accessibility.
No browser run is supplied with this fixture. Use a fresh page for a new run.</p>
<label>Case <select id="case"></select></label>
<label>Input route <select id="mode">
<option>pointer</option><option>Enter</option><option>Space</option><option>script</option>
</select></label>
<button id="arm" type="button">Arm one trial</button>
<p id="instruction"></p>
<div id="stage">
<button id="before" type="button">Before target</button>
<button id="target" type="button" aria-describedby="reason">Apply local preview</button>
<button id="after" type="button">After target</button>
<p id="reason"></p>
</div>
<p>Attempt exactly one activation. For keyboard routes, Tab from Before target.
If native disabled skips the target, record that skip without claiming an activation.
Script mode calls target.click() once and is a separate route.</p>
<label><input id="attest" type="checkbox">I followed the displayed route once and recorded any skip.</label>
<button id="finish" type="button">Finish and record trial</button>
<button id="download" type="button">Download current JSON</button>
<pre id="report"></pre>
<script>
"use strict";
const $ = id => document.getElementById(id);
const target = $("target");
const configurations = {
A1: {label:"Enabled baseline", policy:"plain", available:true},
A2: {label:"Unavailable: ARIA only", policy:"aria", available:false},
A3: {label:"Unavailable: ARIA plus pointer CSS", policy:"css", available:false},
A4: {label:"Unavailable: guarded ARIA", policy:"guard", available:false},
A5: {label:"Available: guarded ARIA", policy:"guard", available:true},
A6: {label:"Unavailable: native disabled", policy:"native", available:false},
A7: {label:"Available: native disabled removed", policy:"native", available:false, final:true},
A8: {label:"Guarded: unavailable then available", policy:"guard", available:false, final:true},
A9: {label:"Guarded: available then unavailable", policy:"guard", available:true, final:false}
};
// Literal expectations are independent of the handler/configuration derivation.
const oracle = {
A1: {pointer:1, Enter:1, Space:1, script:1, tab:true, product:"ACCEPT_BASELINE"},
A2: {pointer:1, Enter:1, Space:1, script:1, tab:true, product:"REJECT_UNAVAILABLE_ACTION"},
A3: {pointer:0, Enter:1, Space:1, script:1, tab:true, product:"REJECT_KEYBOARD_AND_SCRIPT_ACTION"},
A4: {pointer:0, Enter:0, Space:0, script:0, tab:true, product:"ACCEPT_GUARDED"},
A5: {pointer:1, Enter:1, Space:1, script:1, tab:true, product:"ACCEPT_GUARDED"},
A6: {pointer:0, Enter:null, Space:null, script:0, tab:false, product:"NATIVE_ALTERNATIVE"},
A7: {pointer:1, Enter:1, Space:1, script:1, tab:true, product:"NATIVE_ALTERNATIVE"},
A8: {pointer:1, Enter:1, Space:1, script:1, tab:true, product:"ACCEPT_REENABLED"},
A9: {pointer:0, Enter:0, Space:0, script:0, tab:true, product:"ACCEPT_DISABLED_TRANSITION"}
};
const evidence = {
revision:"aria-disabled-command-admission-v1", runId:crypto.randomUUID(),
startedUtc:new Date().toISOString(), userAgent:navigator.userAgent,
status:"HOLD", scope:"Local synchronous command; no browser/AT certification",
rows:[], pending:null, errors:[]
};
let state = null;
let active = null;
function renderReport() { $("report").textContent = JSON.stringify(evidence, null, 2); }
function renderControl() {
target.disabled = state.policy === "native" && !state.available;
if (["aria","css","guard"].includes(state.policy))
target.setAttribute("aria-disabled", String(!state.available));
else target.removeAttribute("aria-disabled");
target.style.pointerEvents = state.policy === "css" ? "none" : "auto";
$("reason").textContent = state.available ? "Preview is available." : "Preview is unavailable in this test state.";
active.states.push({available:state.available, disabled:target.disabled,
ariaDisabled:target.getAttribute("aria-disabled"), pointerEvents:getComputedStyle(target).pointerEvents});
}
function recordEvent(event) {
if (!active) return;
if (["target","before","after","stage"].includes(event.target.id)) {
active.events.push({type:event.type, target:event.target.id, key:event.key ?? null,
trusted:event.isTrusted, repeat:event.repeat ?? false, at:performance.now()});
renderReport();
}
}
for (const type of ["focusin","keydown","keyup","pointerdown","pointerup","click"])
$("stage").addEventListener(type, recordEvent, true);
target.addEventListener("click", event => {
if (!active) return;
active.receivedClicks += 1;
if (state.policy === "guard" && !state.available) {
active.blocked += 1;
renderReport();
return;
}
active.commands.push({command:"apply-local-preview", sequence:active.commands.length + 1});
renderReport();
});
for (const [id, config] of Object.entries(configurations))
$("case").add(new Option(id + " — " + config.label, id));
$("arm").addEventListener("click", () => {
if (active) { $("instruction").textContent = "Finish the pending trial first."; return; }
const id = $("case").value, mode = $("mode").value;
state = {...configurations[id]};
active = {trialId:crypto.randomUUID(), caseId:id, mode, status:"HOLD",
startedUtc:new Date().toISOString(), states:[], events:[], receivedClicks:0, blocked:0, commands:[]};
evidence.pending = active;
$("attest").checked = false;
renderControl();
if (Object.hasOwn(state, "final")) { state.available = state.final; renderControl(); }
$("instruction").textContent = id + ": " + mode + ". " +
(mode === "script" ? "One programmatic click was requested; this is not user-input evidence." :
mode === "pointer" ? "Click the centre of the target once, then attest and finish." :
"Press Tab from Before target, then press and release " + mode + " once. Native-disabled A6 skips the target; do not activate a different control as a substitute.");
$("before").focus();
if (mode === "script") { active.scriptRequested = true; target.click(); }
renderReport();
});
$("finish").addEventListener("click", () => {
if (!active || !$("attest").checked) return;
const expected = oracle[active.caseId], wanted = expected[active.mode];
const reached = active.events.some(e => e.type === "focusin" && e.target === "target");
const keyboard = ["Enter","Space"].includes(active.mode);
active.expectedCommands = wanted;
active.expectedTabReachable = expected.tab;
active.targetReceivedFocus = reached;
active.operatorAttested = true;
active.finishedUtc = new Date().toISOString();
active.productDecision = expected.product;
const countMatches = active.commands.length === (wanted === null ? 0 : wanted);
const focusMatches = !keyboard || reached === expected.tab;
active.status = countMatches && focusMatches ? "OBSERVATION_MATCHES_PENDING_REVIEW" : "MISMATCH";
evidence.rows.push(active);
evidence.pending = null;
active = null;
// HOLD is intentional: an operator must reconcile routes, event traces and independent notes.
evidence.status = "HOLD";
renderReport();
});
$("download").addEventListener("click", () => {
const url = URL.createObjectURL(new Blob([JSON.stringify(evidence,null,2)], {type:"application/json"}));
const a = document.createElement("a"); a.href = url;
a.download = "aria-disabled-" + evidence.runId + ".json"; a.click();
setTimeout(() => URL.revokeObjectURL(url), 0);
});
window.addEventListener("error", event => {
evidence.errors.push(String(event.message)); evidence.status = "HOLD"; renderReport();
});
renderReport();
</script>
</html>The literal oracle is intentionally independent of the configuration and handler derivation. A defect in the implementation should not silently rewrite the expected result. The harness also records event.isTrusted, but treat it as one useful field rather than a universal route detector. A trusted event does not prove every assistive-technology path, and an untrusted script click does not stand in for real pointer or keyboard input.
Establish an enabled baseline for every input route
A1 proves that the local effect is capable of occurring before negative cases are interpreted. Run four separate A1 trials: pointer, Enter, Space and script. Do not combine them merely because all four expect one command.
For each route:
Select A1 and the route, then choose Arm one trial.
For pointer, click the center of the target once.
For Enter or Space, begin at Before target, press Tab until the target visibly has focus, then press and release the selected key once. Avoid key autorepeat.
For script, allow the harness to call target.click() once. Do not describe this as user input.
Attest only after following the displayed route, finish the trial and preserve the row.
The expected command ledger for every A1 row is exactly:
[{"command":"apply-local-preview","sequence":1}]This positive control protects the negative cases from a common false conclusion. If an unavailable trial records zero commands because the handler never worked, the page was not focused as assumed or the operator used the wrong route, that zero is not evidence of correct disabling. A1 must establish the effect in the same environment and with the same handler before route-specific rejection is credited.
The event trace should be interpreted by route, not by a demand that all browsers emit an identical incidental sequence. Pointer evidence should establish a real attempt at the target. Keyboard evidence should establish target focus and the selected key. Script evidence should show the separately labelled programmatic request and a not-trusted click where the platform behaves as documented.
The target handler records commands only while a trial is armed. That is a laboratory boundary, not a recommended production availability mechanism. The Arm button resets counters by creating a new row; it does not make an unavailable product action safe. The Before, After and harness controls have no route to apply-local-preview.
A completed attestation means the operator states that the displayed procedure was followed once. It is not an automatic accessibility conclusion. If A1 cannot produce one command through a route that the environment is supposed to support, stop interpreting later zeroes as successful blocking. The baseline defect, focus setting or procedure must be resolved first. Preserve the failed baseline row rather than rerunning until a convenient result appears; it may identify an environment assumption that affects the entire matrix.
Demonstrate the action that ARIA alone leaves active
A2 renders the application Boolean as unavailable and sets aria-disabled="true", but it leaves the native disabled property false and keeps the same click handler. The expected command count is one for pointer, Enter, Space and script. That is a successful demonstration of the predicted platform distinction and an unacceptable product result.
The normative WAI-ARIA 1.2 definition of aria-disabled describes the disabled semantics an author communicates for supported roles, including button. It says authors should also change the appearance and notes that proper scripting is part of disabling behavior. The MDN aria-disabled reference makes the operational consequence explicit: ARIA alone does not remove the element from focus order or suppress its functionality; the author must implement the JavaScript behavior.
Those sources are compatible. The semantic state is real, but it is not a command interceptor. The WAI-ARIA text also lists button among the roles that support the state; the separate deprecation of indiscriminate global use does not mean aria-disabled is deprecated on a button. In A2, the target remains a native button, retains native keyboard activation and reaches the unchanged click handler. That handler has no unavailable branch, so it appends the preview command.
The state can also affect focusable descendants when authors place it on a container. That broader inheritance concern is intentionally outside this one-button fixture, which keeps the attribute on the target itself and uses plain text content.
Do not replace the effect evidence with DOM inspection. Seeing the attribute proves only that the attribute is present. Do not claim a screen reader announced “unavailable” unless a named browser and assistive-technology combination was actually run and recorded. The expected A2 result is bounded to this fixture: four admitted commands across four separate trials, one per route.
The product decision is REJECT_UNAVAILABLE_ACTION even when every observation matches the oracle. “The browser behaved as predicted” and “the implementation satisfies the product contract” are separate verdicts. A release review must preserve both.
Expose a pointer-only disabling fix
A3 adds pointer-events: none to the same ARIA-disabled button. In this fixture, the expected pointer command count becomes zero, while Enter, Space and script still each admit one command. The change addresses hit testing for one route; it does not implement the application’s unavailable-command contract.
The MDN pointer-events reference explains that none removes the element itself as a pointer-event target and can allow the pointer to reach content underneath. It does not remove the element from sequential keyboard focus. Here the target contains only text, has no descendant that restores pointer behavior and has no actionable control beneath it. A center click can therefore land on the non-actionable stage rather than the target. Those fixture constraints matter; pointer-events: none is not a universal event firewall.
Review A3 as four independent claims:
Route | Expected command count | Meaning |
Pointer | 0 | The target was not the pointer hit target in this fixture |
Enter | 1 | Native keyboard activation still reaches the click handler |
Space | 1 | Native keyboard activation still reaches the click handler |
Script | 1 | target.click() is unaffected by CSS hit testing |
A pointer zero can therefore match the oracle while the product decision remains REJECT_KEYBOARD_AND_SCRIPT_ACTION. Approving the component from a mouse-only check would confuse one blocked route with complete command suppression.
Separate keyboard reachability from command admission
Record the Before → target → After traversal independently from the command ledger. A3 should remain keyboard reachable, but reachability does not authorize an unavailable effect. Conversely, a target that was never reached provides no evidence that the guard would reject an attempted activation.
The same discipline of keeping input modes independent appears in Refonte Learning’s discussion of independent pointer and keyboard evidence, although that article solves a different move-completion problem. This laboratory uses the separation negatively: every supported route must fail to admit the unavailable preview command.
Put the guard immediately before the local effect
A4 keeps the focus-preserving ARIA state but adds the decisive branch inside the stable click handler:
if (state.policy === "guard" && !state.available) {
active.blocked += 1;
renderReport();
return;
}
active.commands.push({command: "apply-local-preview", sequence: active.commands.length + 1});The current Boolean is checked immediately before the declared local effect. For A4, pointer, Enter, Space and script should each produce zero commands. Depending on the route, receivedClicks can be nonzero; that does not make the case a failure. The blocked count identifies that the handler received an attempt and rejected it before command admission. The target remains reachable because the selected policy is discoverability through focus-preserving ARIA.
A5 is the positive partner. It uses the same handler and policy with available: true, renders aria-disabled="false" and expects exactly one command for every route. A4 without A5 could merely prove that the component was broken. A4 and A5 together show bounded rejection while unavailable and ordinary operation while available.
For an established A4 activation, the useful pattern is one received click, one blocked attempt and zero commands. For A5, the corresponding pattern is one received click, zero blocked attempts and one command. Review those counters together; a zero-command row with no received attempt belongs in HOLD, not ACCEPT_GUARDED.
preventDefault() is not a substitute for this return. Preventing a default action does not erase JavaScript statements that the handler executes afterward. Likewise, stopping propagation cannot undo a command already appended in the current listener. The branch must guard the effect itself.
Keep the claim narrow. This guard governs one synchronous local command in one handler. It is not server authorization, it does not prove absence of another command route elsewhere, and it does not make client-side availability a security boundary.
Revalidate both availability transitions
Static unavailable and available cases are necessary but incomplete. Components also fail when their semantic state changes without updating the Boolean used by the handler, or when the Boolean changes without updating the rendered state.
A8 starts guarded and unavailable, records that state, then synchronously changes to available and renders again before the attempted input. Each A8 route expects one command. Its states array should show two snapshots: first available: false with ariaDisabled: "true", then available: true with ariaDisabled: "false". Native disabled remains false and computed pointer events remain auto.
A9 performs the reverse transition. It begins available, records the first snapshot, then changes to unavailable before input. Every route expects zero commands, with the final state showing available: false and ariaDisabled: "true". The guard must use that final current Boolean rather than a stale value captured earlier.
These are synchronization controls, not race tests. Both render steps occur synchronously during arming, before the operator’s activation or the script call. There is no timer, request, promise or competing response. Their purpose is to prove that re-enabling restores the effect and disabling removes it without replacing the handler.
Review the state trace alongside the command count. A correct count with contradictory semantics is still a repair case because the product would communicate one state and enforce another. A correct ARIA value with the wrong Boolean is likewise unsafe for the declared contract.
Treat aria-disabled="false" as a value, not a truthy string
getAttribute("aria-disabled") returns a string when the attribute is present. The literal string "false" is truthy in JavaScript, so code such as if (button.getAttribute("aria-disabled")) would incorrectly treat both "true" and "false" as disabled.
The supplied guard does not parse the attribute. It reads the current state.available Boolean, while renderControl() emits String(!state.available) as the matching semantic value. That shared-state policy is the intended implementation. Introducing an attribute-truthiness guard would be a different defect and would require a new fixture revision rather than silently changing this one.
Compare the native disabled alternative
A6 uses the native Boolean disabled property with no additional application guard. The HTML Living Standard rules for enabling and disabling form controls define native disabled as a Boolean attribute and suppress queued user-interaction click dispatch for disabled controls. Under the documented paths represented here, pointer and target.click() should admit zero commands.
The focus policy is different from A4. In the chosen keyboard-navigation setup, sequential Tab navigation should move from Before directly to After, skipping the disabled target. A6 therefore has no target Enter or Space activation to count. That is why its oracle stores null for those command slots rather than pretending that a key was delivered and rejected.
A7 checks recovery. It first renders the native control disabled, then synchronously changes availability to true and renders again before input. The final native disabled property is false, the button returns to the tab sequence and each route should admit one command. This positive control shows that the native strategy can be re-enabled without rebinding the listener.
Native disabled is a valid alternative when the product has approved the skip policy. It is not automatically “more accessible” in every context, and aria-disabled is not automatically better because it preserves focus. The correct choice follows the documented navigation and discoverability contract.
Record skipped keyboard targets without inventing activations
For A6 Enter and Space, record Before → After and identify the target as UNREACHABLE/SKIP. Do not press Enter on After and count its zero preview commands as a target test. Do not call .focus() on the disabled target or use an automation force option to manufacture a route that ordinary sequential navigation does not provide.
If the platform’s keyboard-navigation setting produces a different baseline, preserve the exact environment and hold that focus claim. A changed tab sequence can be real platform evidence, but it cannot be reconciled by relabelling a different control’s key event as an activation of the target.
Keep programmatic clicks and automation decisions separate
The script route calls target.click() once and labels it as programmatic. The HTML Living Standard definition of HTMLElement.click() says the method returns early when the element is a disabled form control; otherwise it fires a synthetic click whose isTrusted value is false. The method is also not a focus test: calling it does not establish that Tab reached the element or that the element acquired focus. That path is useful for distinguishing native disabled from ARIA-only state, but it is not evidence of pointer hit testing, Tab order, real key processing, assistive-technology behavior or APIs that require user activation.
Do not treat isTrusted as a security decision or a complete statement about origin. In this laboratory it helps label the scripted route; command admission still follows the declared availability Boolean.
Do not replace the Enter or Space procedure with dispatchEvent(new KeyboardEvent(...)). Constructing and dispatching a keyboard-shaped event does not reproduce the browser’s trusted native activation sequence. Record it, if ever added, as a distinct script experiment with a revised oracle.
Automation adds another boundary. Current Playwright actionability documentation states that locator.click() checks conditions including Enabled and Receives Events before acting. An ARIA-disabled element can be treated as disabled for actionability, and pointer-events: none can fail the receives-events check. A timeout may therefore mean the tool never delivered an activation to the application. It is not proof that the component received and rejected an event.
A future Playwright adaptation should log three facts separately: the tool action requested, whether an action was actually delivered, and whether the command ledger changed. This article supplies no Playwright installation, version, forced-click run or automation observation. Keep the baseline file unchanged and version any adapter independently.
Reconcile all cases against the independent oracle
The full proposed matrix contains 36 unique case/route slots. Each number below is the expected number of apply-local-preview commands in one freshly armed trial. SKIP means the target is expected to be unreachable through that keyboard route, not that a target activation occurred with zero effects.
Case | Pointer | Enter | Space | Script | Expected focus policy | Product decision |
A1 Enabled baseline | 1 | 1 | 1 | 1 | Target reachable | ACCEPT_BASELINE |
A2 Unavailable: ARIA only | 1 | 1 | 1 | 1 | Target reachable | REJECT_UNAVAILABLE_ACTION |
A3 Unavailable: ARIA plus pointer CSS | 0 | 1 | 1 | 1 | Target reachable | REJECT_KEYBOARD_AND_SCRIPT_ACTION |
A4 Unavailable: guarded ARIA | 0 | 0 | 0 | 0 | Target reachable | ACCEPT_GUARDED |
A5 Available: guarded ARIA | 1 | 1 | 1 | 1 | Target reachable | ACCEPT_GUARDED |
A6 Unavailable: native disabled | 0 | SKIP | SKIP | 0 | Target skipped | NATIVE_ALTERNATIVE |
A7 Available: native disabled removed | 1 | 1 | 1 | 1 | Target reachable | NATIVE_ALTERNATIVE |
A8 Guarded: unavailable then available | 1 | 1 | 1 | 1 | Target reachable | ACCEPT_REENABLED |
A9 Guarded: available then unavailable | 0 | 0 | 0 | 0 | Target reachable | ACCEPT_DISABLED_TRANSITION |
Reconciliation requires exact case ID, route ID, command identity and count, state snapshots, focus result and relevant event sequence. A duplicate A4 pointer row does not replace a missing A4 Space row. Preserve mismatches, interrupted trials and incomplete rows; do not clean the dataset by deleting inconvenient evidence.
A2 and A3 are expected failure demonstrations. Matching them confirms the test can expose an unsafe implementation; it does not approve that implementation. A4, A5, A8 and A9 need both negative and positive evidence. A6 and A7 are acceptable only under the separately approved native focus policy.
Automated scanning may find semantic or structural issues, but it cannot substitute for this command ledger and route proof. The related discussion of the limits of automated accessibility checks provides broader context; no axe-core scan is installed or claimed in this laboratory.
Hold when the attempted route is unproven
The harness cannot automatically prove that the operator clicked the center, pressed the stated key exactly once or avoided autorepeat. Compare independent notes with focus and event records. A missing command is valid negative evidence only when the attempted route is established.
Wrong route, ambiguous focus, unexpected key repeat, missing environment details, a pending row, an exception or an untested assistive-technology claim remains HOLD for the affected conclusion. Absence of a row is not a pass. event.isTrusted can help distinguish the script click, but it does not certify the complete origin or accessibility of every interaction.
Review the semantic and visible state separately
Command suppression does not complete the component review. Inspect the target’s accessible name, native button role, disabled state, aria-describedby relationship and visible explanation as a separate evidence set. Confirm that the focus-preserving variant retains a usable focus indicator and that the unavailable reason changes with the same availability state.
Attribute presence is not an observed announcement. The fixture source contains aria-describedby="reason", and its state trace is designed to record the current aria-disabled value when the page runs. Without a named browser execution, neither point proves accessibility-tree exposure or the words spoken by a particular browser and assistive technology. When that claim matters, record the browser, assistive-technology version, settings, exact navigation procedure and actual result. Otherwise mark the integration unexecuted.
The fixture’s dashed border and focus outline are functional cues for the laboratory, not a design-system certification. It does not measure contrast, target size, zoom behavior, high-contrast rendering or every component state. Refonte Learning’s article on focus and disabled states in a design system is useful adjacent context for assigning visible-state ownership, but this test makes no legal conclusion and no site-wide WCAG claim.
Use a small semantic review record rather than a single “accessible” checkbox:
Record the computed role and accessible name for the target.
Record whether disabled state and description are exposed in the named browser/AT pairing, when that integration is in scope.
Confirm that the visible reason is present in both visual and accessibility review paths.
Confirm that focus is visible and follows the chosen reachable-or-skipped policy.
Keep command-ledger evidence attached to the same state and environment.
Keep the release question precise: does the chosen implementation communicate the intended unavailable state, follow the approved focus policy and suppress the declared command? A good semantic review cannot excuse an admitted unavailable command, and a correct guard cannot excuse contradictory or undiscoverable state communication.
Assign the release decision and regression triggers
Use the evidence to issue a bounded decision, not a universal accessibility verdict.
Evidence pattern | Release decision |
ARIA-only or pointer-CSS variant admits an unavailable command | Repair: add or restore the command guard |
Guarded unavailable cases reject all proven routes; enabled controls admit exactly one; focus policy matches | Accept within tested scope |
Native disabled suppresses documented routes; skip policy is approved; re-enabled control works | Accept native-disabled alternative |
Route, focus, environment or integration evidence is incomplete or contradictory | Hold |
Product and design own the discoverability decision: should the unavailable button stay in sequence or be skipped? Frontend owns rendering native/ARIA state from the current source of truth and guarding the local effect. QA owns route completeness, negative-evidence quality and reconciliation against the literal oracle.
Rerun affected cases when the element type, native disabled state, ARIA rendering, availability source, click handler, keyboard binding, pointer CSS, browser support matrix or component integration changes. A framework migration must preserve the command contract, not merely reproduce the attribute. Moving the effect to another function or introducing a second command path also invalidates the narrow proof.
When a regression appears, restore the previously verified guarded implementation or an approved native-disabled behavior, then rerun the changed routes and their positive controls. Keep rejected and provisional rows available for review. Recovery in this fixture means restoring a local component behavior; there is no server request or remote action to reverse.
The acceptance statement should name the boundary: one native light-DOM button, one stable handler, one synchronous local preview command and the recorded environments. It should not claim server authorization, absence of duplicate submissions, universal assistive-technology support or site-wide conformance.
Build the frontend foundations behind the contract
This exercise depends on ordinary platform skills: selecting the correct native HTML control, understanding CSS hit testing, writing a JavaScript guard at the effect boundary, and collecting accessibility evidence without confusing semantics with behavior. The overview of HTML, CSS and JavaScript foundations provides a useful starting point for engineers building those fundamentals.
Refonte Learning’s Frontend Development programme lists HTML5/CSS3, JavaScript ES6+, responsive design, React, Redux, APIs/AJAX, Git, accessibility, performance and a capstone, with an indicated workload of 10–12 hours per week. Its FAQ also names Git, npm, React and Visual Studio Code. Its completion information lists a Training Certificate and Certificate of Internship, and its career directions include Frontend Developer, UI Developer and Web Developer. The specialized command-admission laboratory in this article is an independent exercise, not a claimed syllabus module or promise of an individual placement.
Foundational instruction is most valuable when it leads to reviewable contracts such as this one: the native element supplies known interaction behavior, CSS is assessed at its actual hit-testing boundary, JavaScript owns the effect guard, and QA preserves evidence that can be rerun after change.
The immediate engineering next step is concrete: document each component’s focus policy and command-admission contract, then retain route-by-route negative and positive evidence whenever that contract changes.
