Skip to content
Open
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-03-10 - Consistent Focus States and ARIA labels
**Learning:** Interactive elements like icon-only buttons need explicit 'aria-label' for screen readers. While 'focus-visible' is great for keyboard users to avoid clutter for mouse users, text inputs and select elements should still use 'focus:ring' because mouse users also expect a clear visual indicator of the active field.
**Action:** Apply 'aria-label' to all icon-only buttons and use a combination of 'focus:ring' and 'focus-visible:ring-offset' for inputs to ensure a balanced UX for all input methods.
6 changes: 4 additions & 2 deletions web/src/components/AccountCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ export function AccountCard({ account, index, onDelete, proxies, onProxyChange }
</span>
<button
onClick={handleDelete}
class="p-1.5 text-slate-400 dark:text-text-dim hover:text-red-500 transition-colors rounded-md hover:bg-red-50 dark:hover:bg-red-900/20"
class="p-1.5 text-slate-400 dark:text-text-dim hover:text-red-500 transition-colors rounded-md hover:bg-red-50 dark:hover:bg-red-900/20 focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark outline-none"
title={t("deleteAccount")}
aria-label={t("deleteAccount")}
>
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
Expand Down Expand Up @@ -143,7 +144,8 @@ export function AccountCard({ account, index, onDelete, proxies, onProxyChange }
onChange={(e) =>
onProxyChange(account.id, (e.target as HTMLSelectElement).value)
}
class="text-xs px-2 py-1 rounded-md border border-gray-200 dark:border-border-dark bg-white dark:bg-bg-dark text-slate-700 dark:text-text-main focus:outline-none focus:ring-1 focus:ring-primary cursor-pointer"
aria-label={t("proxyAssignment")}
class="text-xs px-2 py-1 rounded-md border border-gray-200 dark:border-border-dark bg-white dark:bg-bg-dark text-slate-700 dark:text-text-main focus:outline-none focus:ring-1 focus:ring-primary focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark cursor-pointer transition-shadow"
>
<option value="global">{t("globalDefault")}</option>
<option value="direct">{t("directNoProxy")}</option>
Expand Down
5 changes: 3 additions & 2 deletions web/src/components/AddAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ export function AddAccount({ visible, onSubmitRelay, addInfo, addError }: AddAcc
value={input}
onInput={(e) => setInput((e.target as HTMLInputElement).value)}
placeholder={t("pasteCallback")}
class="flex-1 px-3 py-2.5 bg-slate-50 dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-sm font-mono text-slate-600 dark:text-text-main focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-colors"
aria-label={t("pasteCallback")}
class="flex-1 px-3 py-2.5 bg-slate-50 dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-sm font-mono text-slate-600 dark:text-text-main focus:ring-2 focus:ring-primary/50 focus:border-primary focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark outline-none transition-shadow"
/>
<button
onClick={handleSubmit}
disabled={submitting}
class="px-4 py-2.5 bg-white dark:bg-card-dark border border-gray-200 dark:border-border-dark rounded-lg text-sm font-medium text-slate-700 dark:text-text-main hover:bg-slate-50 dark:hover:bg-border-dark transition-colors"
class="px-4 py-2.5 bg-white dark:bg-card-dark border border-gray-200 dark:border-border-dark rounded-lg text-sm font-medium text-slate-700 dark:text-text-main hover:bg-slate-50 dark:hover:bg-border-dark focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{submitting ? t("submitting") : t("submit")}
</button>
Expand Down
5 changes: 3 additions & 2 deletions web/src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function CopyButton({ getText, class: className, titleKey, variant = "ico
return (
<button
onClick={handleCopy}
class={`flex items-center gap-1.5 px-3 py-1.5 ${bgClass} text-white rounded text-xs font-medium transition-colors ${className || ""}`}
class={`flex items-center gap-1.5 px-3 py-1.5 ${bgClass} text-white rounded text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark outline-none ${className || ""}`}
>
<svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="9" y="9" width="13" height="13" rx="2" />
Expand All @@ -66,14 +66,15 @@ export function CopyButton({ getText, class: className, titleKey, variant = "ico
return (
<button
onClick={handleCopy}
class={`p-1.5 transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark ${
class={`p-1.5 transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 dark:focus-visible:ring-offset-bg-dark outline-none ${
state === "ok"
? "text-primary"
: state === "fail"
? "text-red-500"
: "text-slate-400 dark:text-text-dim hover:text-primary"
} ${className || ""}`}
title={titleKey ? t(titleKey as TranslationKey) : undefined}
aria-label={titleKey ? t(titleKey as TranslationKey) : t("copy")}
>
{state === "ok" ? SVG_CHECK : state === "fail" ? SVG_FAIL : SVG_COPY}
</button>
Expand Down