Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-06-28 - Enhance Disabled State Accessibility
**Learning:** Simply disabling a button using the `disabled` property doesn't always provide sufficient visual feedback, especially if existing hover states apply regardless of the disabled attribute, potentially confusing users who expect interactive elements. Adding `aria-busy="true"` or updating ARIA attributes during loading states is also critical for screen readers.
**Action:** When creating or modifying button states, explicitly add `.btn:disabled` styling (like opacity drops and `cursor: not-allowed`), update hover pseudo-classes to exclude the disabled state (`:not(:disabled)`), and pair visual feedback with relevant ARIA attributes (`aria-busy`) when the button is inactive due to async operations.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ private static String viewerShellHtml(UUID docId, String initialState) {

<div class="actions" aria-label="Actions">
<button type="button" class="btn btn-primary" id="retry-btn">Refresh</button>
<a class="btn btn-secondary" id="open-json-link" href="#" hidden>Open JSON bootstrap</a>
<a class="btn btn-secondary" id="open-json-link" href="#"
target="_blank" rel="noopener noreferrer" hidden
aria-label="Open JSON bootstrap (new tab)">
Open JSON bootstrap
</a>
</div>
</section>

Expand Down
9 changes: 7 additions & 2 deletions src/main/resources/static/assets/viewer/viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ a:hover {
color: #ffffff;
}

.btn-primary:hover {
.btn-primary:hover:not(:disabled) {
filter: brightness(0.95);
}

Expand All @@ -205,10 +205,15 @@ a:hover {
color: var(--ink);
}

.btn-secondary:hover {
.btn-secondary:hover:not(:disabled) {
background: #fbfcfe;
}

.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}

.preview {
border-radius: 12px;
border: 1px dashed rgba(16, 32, 50, 0.26);
Expand Down
16 changes: 15 additions & 1 deletion src/main/resources/static/assets/viewer/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,21 @@ function setLoading(message) {
el.error.hidden = true;
el.liveStatus.textContent = message;
el.preview.setAttribute("aria-busy", "true");
if (el.retryBtn) {
el.retryBtn.disabled = true;
el.retryBtn.setAttribute("aria-busy", "true");
}
}

function showError(message) {
el.error.hidden = false;
el.errorMessage.textContent = message;
el.liveStatus.textContent = "";
el.preview.setAttribute("aria-busy", "false");
if (el.retryBtn) {
el.retryBtn.disabled = false;
el.retryBtn.setAttribute("aria-busy", "false");
}
el.errorTitle.focus();
}

Expand All @@ -72,7 +80,9 @@ function renderPreviewLink(path) {
link.href = path;
link.textContent = "Open artifact";
link.className = "btn btn-secondary";
link.rel = "noopener";
link.target = "_blank";
link.rel = "noopener noreferrer";
link.setAttribute("aria-label", "Open artifact (opens in a new tab)");
el.preview.appendChild(link);
}

Expand Down Expand Up @@ -156,6 +166,10 @@ async function poll(docId, abortSignal) {

el.preview.setAttribute("aria-busy", "false");
el.liveStatus.textContent = "Ready.";
if (el.retryBtn) {
el.retryBtn.disabled = false;
el.retryBtn.setAttribute("aria-busy", "false");
}

clearPreview();
const path = bootstrap.data.previewResourcePath;
Expand Down
Loading