From 5a1fa26b340df0c79a1f30800696e657d21d2b7a Mon Sep 17 00:00:00 2001 From: Smyile <84925446+xsmyile@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:37:51 +0200 Subject: [PATCH 1/3] fix: raise contrast of tertiary text to WCAG AA --- ui/src/components/DetailRail.tsx | 36 ++++++++++++++--------------- ui/src/components/FilterBar.tsx | 20 ++++++++-------- ui/src/components/Header.tsx | 2 +- ui/src/components/Inbox.tsx | 12 +++++----- ui/src/components/MessageDetail.tsx | 6 ++--- ui/src/components/PaneRead.tsx | 2 +- ui/src/components/Settings.tsx | 2 +- ui/src/components/StatusBar.tsx | 6 ++--- ui/src/components/UndoToast.tsx | 2 +- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/ui/src/components/DetailRail.tsx b/ui/src/components/DetailRail.tsx index 0e8fe77..669b03a 100644 --- a/ui/src/components/DetailRail.tsx +++ b/ui/src/components/DetailRail.tsx @@ -94,7 +94,7 @@ function Section(props: { return (
-

+

{props.label}

{props.aside} @@ -159,7 +159,7 @@ export default function DetailRail(props: { +

Every header the message carries, as received.

} @@ -185,23 +185,23 @@ export default function DetailRail(props: { function SummaryList(props: { message: Message }) { return (
-
From
+
From
{props.message.sender || "(no sender)"}
-
To
+
To
{(r) =>
{r}
}
-
Date
+
Date
{formatDateTime(props.message.created_at)}
-
Size
+
Size
{formatSize(props.message.size)}
-
Tags
+
Tags
@@ -224,7 +224,7 @@ function AuthList(props: { results: AuthResults }) { +

No authentication headers found.

DKIM, SPF, and DMARC headers are typically added by receiving mail @@ -279,7 +279,7 @@ function AttachmentsSection(props: { aside={ {(list) => ( - + {list().length} )} @@ -300,7 +300,7 @@ function AttachmentsSection(props: { 0} fallback={ -

+

No attachments

} @@ -320,7 +320,7 @@ function AttachmentsSection(props: { {(size) => ( - + {formatSize(size())} )} @@ -364,7 +364,7 @@ function LinksSection(props: { message: Message }) {
+ {links().length} } @@ -372,7 +372,7 @@ function LinksSection(props: { message: Message }) { 0} fallback={ -

No links

+

No links

} >
    @@ -441,11 +441,11 @@ function LinkRow(props: { link: MessageLink }) { {props.link.host} - + {props.link.path} 1}> - + ×{props.link.count} @@ -457,7 +457,7 @@ function LinkRow(props: { link: MessageLink }) { - + {props.link.text} @@ -482,7 +482,7 @@ function LinkRow(props: { link: MessageLink }) { {props.link.href} - + {MANUAL_COPY_HINT}

    @@ -568,7 +568,7 @@ function TagEditor(props: { message: MessageSummary }) { removeTag(tags()[tags().length - 1]); } }} - class="bg-transparent text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-400 dark:placeholder-zinc-600 outline-none min-w-[80px] flex-1 rounded-md px-1.5 py-0.5 transition focus:bg-zinc-100 dark:focus:bg-zinc-800/60" + class="bg-transparent text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-500 dark:placeholder-zinc-400 outline-none min-w-[80px] flex-1 rounded-md px-1.5 py-0.5 transition focus:bg-zinc-100 dark:focus:bg-zinc-800/60" />
); diff --git a/ui/src/components/FilterBar.tsx b/ui/src/components/FilterBar.tsx index 5ca30b5..2b7869a 100644 --- a/ui/src/components/FilterBar.tsx +++ b/ui/src/components/FilterBar.tsx @@ -31,7 +31,7 @@ function Chip(props: { class={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[11px] font-medium border transition-colors cursor-pointer select-none ${ props.active ? props.activeClass - : "bg-zinc-100 dark:bg-zinc-800/50 text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700/50 hover:bg-zinc-200 dark:hover:bg-zinc-700/50 hover:text-zinc-600 dark:hover:text-zinc-400" + : "bg-zinc-100 dark:bg-zinc-800/50 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700/50 hover:bg-zinc-200 dark:hover:bg-zinc-700/50 hover:text-zinc-700 dark:hover:text-zinc-300" }`} > @@ -111,7 +111,7 @@ function TagDropdown() { class={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[11px] font-medium border transition-colors cursor-pointer select-none ${ filters().tags.length > 0 ? "bg-orange-50 dark:bg-orange-500/15 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-500/30" - : "bg-zinc-100 dark:bg-zinc-800/50 text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700/50 hover:bg-zinc-200 dark:hover:bg-zinc-700/50 hover:text-zinc-600 dark:hover:text-zinc-400" + : "bg-zinc-100 dark:bg-zinc-800/50 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700/50 hover:bg-zinc-200 dark:hover:bg-zinc-700/50 hover:text-zinc-700 dark:hover:text-zinc-300" }`} > 0} fallback={ -
+
No tags yet
} @@ -176,7 +176,7 @@ function TagDropdown() { value={tagQuery()} onInput={(e) => setTagQuery(e.currentTarget.value)} placeholder="Filter tags..." - class="w-full rounded-md border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2 py-1 text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-400 dark:placeholder-zinc-500 outline-none focus:border-zinc-400 dark:focus:border-zinc-600" + class="w-full rounded-md border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2 py-1 text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-500 dark:placeholder-zinc-400 outline-none focus:border-zinc-400 dark:focus:border-zinc-600" />
@@ -184,7 +184,7 @@ function TagDropdown() { 0} fallback={ -
+
No matching tags
} @@ -237,7 +237,7 @@ function TagDropdown() {
-
+
Up to {MAX_TAG_FILTERS} tags at a time
@@ -248,7 +248,7 @@ function TagDropdown() { clearTagFilters(); setOpen(false); }} - class="text-[11px] text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300 transition cursor-pointer" + class="text-[11px] text-zinc-500 dark:text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 transition cursor-pointer" > Reset tags @@ -294,7 +294,7 @@ export default function FilterBar() { placeholder="Search emails..." value={search()} onInput={(e) => onSearchInput(e.currentTarget.value)} - class="w-full rounded-md border border-zinc-200 dark:border-zinc-700/60 bg-zinc-50 dark:bg-zinc-950/40 pl-8 pr-3 py-1.5 text-sm text-zinc-800 dark:text-zinc-200 placeholder-zinc-400 dark:placeholder-zinc-500 outline-none focus:border-zinc-400 dark:focus:border-zinc-500 transition" + class="w-full rounded-md border border-zinc-200 dark:border-zinc-700/60 bg-zinc-50 dark:bg-zinc-950/40 pl-8 pr-3 py-1.5 text-sm text-zinc-800 dark:text-zinc-200 placeholder-zinc-500 dark:placeholder-zinc-400 outline-none focus:border-zinc-400 dark:focus:border-zinc-500 transition" /> @@ -323,12 +323,12 @@ export default function FilterBar() {
- + {listSize()} {listSize() === 1 ? "match" : "matches"} diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx index b4f1ef7..2240996 100644 --- a/ui/src/components/Header.tsx +++ b/ui/src/components/Header.tsx @@ -25,7 +25,7 @@ export default function Header() {

RustMail

- + {total()} {total() === 1 ? "message" : "messages"}
diff --git a/ui/src/components/Inbox.tsx b/ui/src/components/Inbox.tsx index d9b22cd..5bd6768 100644 --- a/ui/src/components/Inbox.tsx +++ b/ui/src/components/Inbox.tsx @@ -169,7 +169,7 @@ export default function Inbox() {
-
+

No messages yet

-

+

Send an email to the SMTP port to get started

@@ -256,7 +256,7 @@ export default function Inbox() {
-
+
Loading…
@@ -321,14 +321,14 @@ function MessageRow(props: { msg: Accessor; index: number }) { {msg().subject || "(no subject)"} {formatDate(msg().created_at)}
{routeLine(msg())} @@ -345,7 +345,7 @@ function MessageRow(props: { msg: Accessor; index: number }) { )} 3}> - + +{msg().tags.length - 3} diff --git a/ui/src/components/MessageDetail.tsx b/ui/src/components/MessageDetail.tsx index 0ac5a43..7e163d2 100644 --- a/ui/src/components/MessageDetail.tsx +++ b/ui/src/components/MessageDetail.tsx @@ -225,7 +225,7 @@ export default function MessageDetail() { +

Details of the selected message show here.

} @@ -361,7 +361,7 @@ function MessageHeader(props: { message: Message; reads: RailReads }) { <{sender().address}> - {"→"} + {"→"} {meta()}

@@ -443,7 +443,7 @@ function SegmentedSwitch(props: { classList={{ "bg-white dark:bg-zinc-700 text-zinc-900 dark:text-zinc-50 shadow-sm": props.value === option.id, - "text-zinc-500 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200": + "text-zinc-600 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200": props.value !== option.id, }} > diff --git a/ui/src/components/PaneRead.tsx b/ui/src/components/PaneRead.tsx index 2419e49..315b098 100644 --- a/ui/src/components/PaneRead.tsx +++ b/ui/src/components/PaneRead.tsx @@ -112,7 +112,7 @@ export function ReadState(props: { {(drift) => (

This page does not match the server it is talking to.

-

+

{drift().message}

From 901143bbf2772fec26a4658d3c803657b74bb8e3 Mon Sep 17 00:00:00 2001 From: Smyile <84925446+xsmyile@users.noreply.github.com> Date: Wed, 7 Oct 2026 01:01:53 +0200 Subject: [PATCH 2/3] fix: raise contrast of the remaining controls to WCAG AA --- ui/src/components/DetailRail.tsx | 6 +++--- ui/src/components/FilterBar.tsx | 4 ++-- ui/src/components/Inbox.tsx | 4 ++-- ui/src/components/MessageDetail.tsx | 6 +++--- ui/src/components/Notices.tsx | 2 +- ui/src/components/Settings.tsx | 12 ++++++------ ui/src/components/StatusBar.tsx | 2 +- ui/src/components/UndoToast.tsx | 2 +- ui/src/index.css | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ui/src/components/DetailRail.tsx b/ui/src/components/DetailRail.tsx index 669b03a..3823643 100644 --- a/ui/src/components/DetailRail.tsx +++ b/ui/src/components/DetailRail.tsx @@ -150,7 +150,7 @@ export default function DetailRail(props: { @@ -381,7 +381,7 @@ function LinksSection(props: { message: Message }) { @@ -472,7 +472,7 @@ function LinkRow(props: { link: MessageLink }) { : `Copy the link to ${props.link.host}` } title={feedback() === "copied" ? "Copied" : "Copy"} - class="mt-1 shrink-0 rounded p-1 text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition cursor-pointer" + class="mt-1 shrink-0 rounded p-1 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition cursor-pointer" > diff --git a/ui/src/components/FilterBar.tsx b/ui/src/components/FilterBar.tsx index 2b7869a..cde7323 100644 --- a/ui/src/components/FilterBar.tsx +++ b/ui/src/components/FilterBar.tsx @@ -110,7 +110,7 @@ function TagDropdown() { onClick={() => setOpen(!open())} class={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[11px] font-medium border transition-colors cursor-pointer select-none ${ filters().tags.length > 0 - ? "bg-orange-50 dark:bg-orange-500/15 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-500/30" + ? "bg-orange-50 dark:bg-orange-500/15 text-orange-700 dark:text-orange-400 border-orange-200 dark:border-orange-500/30" : "bg-zinc-100 dark:bg-zinc-800/50 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700/50 hover:bg-zinc-200 dark:hover:bg-zinc-700/50 hover:text-zinc-700 dark:hover:text-zinc-300" }`} > @@ -303,7 +303,7 @@ export default function FilterBar() { label="Starred" icon={"\u2605"} active={f().starred} - activeClass="bg-amber-50 dark:bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-200 dark:border-amber-500/30" + activeClass="bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-200 dark:border-amber-500/30" onClick={() => toggleFilter("starred")} /> @@ -308,7 +308,7 @@ function MessageRow(props: { msg: Accessor; index: number }) { title={msg().is_starred ? "Unstar" : "Star"} > diff --git a/ui/src/components/MessageDetail.tsx b/ui/src/components/MessageDetail.tsx index 7e163d2..66189b8 100644 --- a/ui/src/components/MessageDetail.tsx +++ b/ui/src/components/MessageDetail.tsx @@ -389,8 +389,8 @@ function MessageHeader(props: { message: Message; reads: RailReads }) { onClick={() => starMessage(props.message.id, !starred())} class={ACTION_CLASS} classList={{ - "text-amber-400 hover:text-amber-500": starred(), - "text-zinc-500 dark:text-zinc-400 hover:text-amber-400": + "text-amber-700 hover:text-amber-800 dark:text-amber-400 dark:hover:text-amber-200": starred(), + "text-zinc-500 dark:text-zinc-400 hover:text-amber-800 dark:hover:text-amber-400": !starred(), }} title={starred() ? "Unstar" : "Star"} @@ -410,7 +410,7 @@ function MessageHeader(props: { message: Message; reads: RailReads }) { setSelectedId(null); deleteWithUndo(props.message.id); }} - class={`${ACTION_CLASS} text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400`} + class={`${ACTION_CLASS} text-zinc-500 dark:text-zinc-400 hover:text-red-700 dark:hover:text-red-300`} title="Delete" > diff --git a/ui/src/components/Notices.tsx b/ui/src/components/Notices.tsx index b8bc8cc..8f004d5 100644 --- a/ui/src/components/Notices.tsx +++ b/ui/src/components/Notices.tsx @@ -22,7 +22,7 @@ export default function Notices() { diff --git a/ui/src/components/UndoToast.tsx b/ui/src/components/UndoToast.tsx index 1911418..27bb64b 100644 --- a/ui/src/components/UndoToast.tsx +++ b/ui/src/components/UndoToast.tsx @@ -22,7 +22,7 @@ export default function UndoToast() { diff --git a/ui/src/index.css b/ui/src/index.css index 125412d..7c9fca0 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -401,7 +401,7 @@ .btn-destructive { border-color: var(--color-red-200); background-color: var(--color-red-50); - color: var(--color-red-600); + color: var(--color-red-700); } .btn-destructive:hover { background-color: var(--color-red-100); From dac9f59d269564820d0287bf735a1ee1424141ea Mon Sep 17 00:00:00 2001 From: Smyile <84925446+xsmyile@users.noreply.github.com> Date: Wed, 7 Oct 2026 01:11:06 +0200 Subject: [PATCH 3/3] fix: keep tertiary text at WCAG AA on hover and focus surfaces --- ui/src/components/DetailRail.tsx | 14 +++++++------- ui/src/components/Inbox.tsx | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/src/components/DetailRail.tsx b/ui/src/components/DetailRail.tsx index 3823643..c93f3e3 100644 --- a/ui/src/components/DetailRail.tsx +++ b/ui/src/components/DetailRail.tsx @@ -312,7 +312,7 @@ function AttachmentsSection(props: { @@ -320,7 +320,7 @@ function AttachmentsSection(props: { {(size) => ( - + {formatSize(size())} )} @@ -435,17 +435,17 @@ function LinkRow(props: { link: MessageLink }) { target="_blank" rel="noopener noreferrer" title={props.link.href} - class="min-w-0 flex-1 rounded-md px-2 py-1.5 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition" + class="group/link min-w-0 flex-1 rounded-md px-2 py-1.5 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition" > {props.link.host} - + {props.link.path} 1}> - + ×{props.link.count} @@ -457,7 +457,7 @@ function LinkRow(props: { link: MessageLink }) { - + {props.link.text} @@ -568,7 +568,7 @@ function TagEditor(props: { message: MessageSummary }) { removeTag(tags()[tags().length - 1]); } }} - class="bg-transparent text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-500 dark:placeholder-zinc-400 outline-none min-w-[80px] flex-1 rounded-md px-1.5 py-0.5 transition focus:bg-zinc-100 dark:focus:bg-zinc-800/60" + class="bg-transparent text-xs text-zinc-700 dark:text-zinc-300 placeholder-zinc-500 focus:placeholder-zinc-600 dark:placeholder-zinc-400 dark:focus:placeholder-zinc-400 outline-none min-w-[80px] flex-1 rounded-md px-1.5 py-0.5 transition focus:bg-zinc-100 dark:focus:bg-zinc-800/60" />
); diff --git a/ui/src/components/Inbox.tsx b/ui/src/components/Inbox.tsx index 0f42007..8792ebf 100644 --- a/ui/src/components/Inbox.tsx +++ b/ui/src/components/Inbox.tsx @@ -287,7 +287,7 @@ function MessageRow(props: { msg: Accessor; index: number }) { aria-setsize={listSize()} data-id={msg().id} onClick={() => selectMessage(msg())} - class={`w-full text-left rounded-lg px-2.5 py-2 transition cursor-pointer ${ + class={`group/row w-full text-left rounded-lg px-2.5 py-2 transition cursor-pointer ${ isSelected() ? "inbox-row-selected" : "hover:bg-zinc-100/80 dark:hover:bg-zinc-800/50" @@ -321,14 +321,14 @@ function MessageRow(props: { msg: Accessor; index: number }) { {msg().subject || "(no subject)"} {formatDate(msg().created_at)}
{routeLine(msg())} @@ -345,7 +345,7 @@ function MessageRow(props: { msg: Accessor; index: number }) { )} 3}> - + +{msg().tags.length - 3}