fix(a11y): WCAG 2 AA contrast, labels and dropdown
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Moritz 2026-03-04 16:19:28 +01:00
parent 8025858060
commit 70c3ca82ea
Signed by: moritz
GPG key ID: 1020A035E5DD0824
8 changed files with 134 additions and 37 deletions

View file

@ -548,4 +548,96 @@
--color-secondary-content: oklch(98% 0 0);
}
/* ============================================
WCAG 2.2 AA: Tab list inactive tab text contrast (4.5:1)
============================================ */
#member-tablist .tab:not(.tab-active) {
color: oklch(0.35 0.02 285);
}
[data-theme="dark"] #member-tablist .tab:not(.tab-active) {
color: oklch(0.72 0.02 257);
}
/* ============================================
WCAG 2.2 AA: Link contrast - primary and accent
============================================ */
[data-theme="light"] .link.link-primary {
color: oklch(0.45 0.15 35);
}
[data-theme="light"] .link.link-primary:hover {
color: oklch(0.38 0.14 35);
}
[data-theme="dark"] .link.link-primary {
color: oklch(0.82 0.14 45);
}
[data-theme="dark"] .link.link-primary:hover {
color: oklch(0.88 0.12 45);
}
[data-theme="dark"] .link.link-accent {
color: oklch(0.82 0.18 292);
}
[data-theme="dark"] .link.link-accent:hover {
color: oklch(0.88 0.16 292);
}
/* ============================================
WCAG 2.2 AA: Danger zone heading contrast (dark theme)
============================================ */
[data-theme="dark"] #danger-zone-heading.text-error {
color: oklch(0.78 0.18 25);
}
/* ============================================
WCAG 2.2 AA: Blue link contrast in dark theme
============================================ */
[data-theme="dark"] a.text-blue-700,
[data-theme="dark"] a.text-blue-600,
[data-theme="dark"] a.hover\:text-blue-800 {
color: oklch(0.72 0.16 255);
}
[data-theme="dark"] a.text-blue-700:hover,
[data-theme="dark"] a.text-blue-600:hover {
color: oklch(0.82 0.14 255);
}
/* ============================================
WCAG 2.2 AA: Password / form label on light box in dark theme
============================================ */
[data-theme="dark"] .bg-gray-50 {
background-color: var(--color-base-200);
color: var(--color-base-content);
}
[data-theme="dark"] .bg-gray-50 .label,
[data-theme="dark"] .bg-gray-50 .mb-1.label,
[data-theme="dark"] .bg-gray-50 .text-gray-600,
[data-theme="dark"] .bg-gray-50 .text-gray-700,
[data-theme="dark"] .bg-gray-50 strong,
[data-theme="dark"] .bg-gray-50 p,
[data-theme="dark"] .bg-gray-50 li {
color: var(--color-base-content);
}
/* Dark mode: orange/red info boxes (admin note, OIDC warning) dark bg, light text */
[data-theme="dark"] .bg-orange-50 {
background-color: oklch(0.32 0.06 55);
border-color: oklch(0.42 0.08 55);
color: var(--color-base-content);
}
[data-theme="dark"] .bg-orange-50 .text-orange-800,
[data-theme="dark"] .bg-orange-50 p,
[data-theme="dark"] .bg-orange-50 strong {
color: var(--color-base-content);
}
[data-theme="dark"] .bg-red-50 {
background-color: oklch(0.32 0.08 25);
border-color: oklch(0.42 0.12 25);
color: var(--color-base-content);
}
[data-theme="dark"] .bg-red-50 .text-red-800,
[data-theme="dark"] .bg-red-50 .text-red-700,
[data-theme="dark"] .bg-red-50 p,
[data-theme="dark"] .bg-red-50 strong {
color: var(--color-base-content);
}
/* This file is for your main application CSS */

View file

@ -562,13 +562,17 @@ defmodule MvWeb.CoreComponents do
phx-target={@phx_target}
>
<%= if @checkboxes do %>
<input
type="checkbox"
checked={Map.get(@selected, item.value, true)}
class="checkbox checkbox-sm checkbox-primary pointer-events-none"
tabindex="-1"
<%!-- Visual-only indicator: do not nest an interactive control (checkbox) inside the button for screen reader and focus correctness (WCAG 2.1.2). --%>
<span
class={
if Map.get(@selected, item.value, true),
do: "text-primary",
else: "text-base-300"
}
aria-hidden="true"
/>
>
<.icon name="hero-check" class="size-4 shrink-0" />
</span>
<% end %>
<span>{item.label}</span>
</button>

View file

@ -32,9 +32,9 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
<.icon name="hero-arrow-left" class="size-4" />
{gettext("Back")}
</.button>
<h3 class="card-title">
<h2 class="card-title text-xl">
{if @custom_field, do: gettext("Edit Data Field"), else: gettext("New Data Field")}
</h3>
</h2>
</div>
<.form

View file

@ -304,22 +304,20 @@ defmodule MvWeb.GlobalSettingsLive do
}
/>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-2">
<.input
field={@form[:oidc_only]}
type="checkbox"
class="checkbox checkbox-sm"
disabled={@oidc_only_env_set or not @oidc_configured}
label={
if @oidc_only_env_set do
gettext("Only OIDC sign-in (hide password login)") <>
" (" <> gettext("From OIDC_ONLY") <> ")"
else
gettext("Only OIDC sign-in (hide password login)")
end
}
/>
<span class="label-text">
{gettext("Only OIDC sign-in (hide password login)")}
<%= if @oidc_only_env_set do %>
<span class="label-text-alt text-base-content/70 ml-1">
({gettext("From OIDC_ONLY")})
</span>
<% end %>
</span>
</label>
<p class="label-text-alt text-base-content/70 mt-1">
{gettext(
"When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button."

View file

@ -88,16 +88,17 @@ defmodule MvWeb.ImportLive.Components do
phx-submit="start_import"
data-testid="csv-upload-form"
>
<fieldset class="mb-2 fieldset w-md">
<label for="csv_file">
<span class="mb-1 label">{gettext("CSV File")}</span>
</label>
<fieldset class="mb-2 fieldset w-md" aria-labelledby="csv_file_label">
<label id="csv_file_label" class="label block">
<span class="mb-1 label text-base-content">{gettext("CSV File")}</span>
<.live_file_input
upload={@uploads.csv_file}
id="csv_file"
class="file-input file-input-bordered"
class="file-input file-input-bordered block"
aria-describedby="csv_file_help"
aria-label={gettext("CSV File")}
/>
</label>
<p class="text-sm text-base-content/60 mt-2" id="csv_file_help">
{gettext("CSV files only, maximum %{size} MB", size: @csv_import_max_file_size_mb)}
</p>

View file

@ -50,9 +50,9 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
<.icon name="hero-arrow-left" class="size-4" />
{gettext("Back")}
</.button>
<h3 class="card-title">
<h2 class="card-title text-xl">
{gettext("Edit Field: %{field}", field: @field_label)}
</h3>
</h2>
</div>
<.form

View file

@ -215,14 +215,16 @@ defmodule MvWeb.MemberLive.Form do
<.form_section title={gettext("Membership Fee")}>
<div class="space-y-4">
<div>
<label class="label">
<label for={@form[:membership_fee_type_id].id} class="label">
<span class="label-text font-semibold">{gettext("Membership Fee Type")}</span>
</label>
<select
id={@form[:membership_fee_type_id].id}
class="select select-bordered w-full"
name={@form[:membership_fee_type_id].name}
phx-change="validate"
value={@form[:membership_fee_type_id].value || ""}
aria-label={gettext("Membership Fee Type")}
>
<%!-- No "None" option: a membership fee type is required (validated in Member resource). --%>
<option value="">{gettext("Select a membership fee type")}</option>

View file

@ -90,7 +90,7 @@ defmodule MvWeb.UserLive.Show do
{MvWeb.Helpers.MemberHelpers.display_name(@user.member)}
</.link>
<% else %>
<span class="italic text-gray-500">{gettext("No member linked")}</span>
<span class="italic text-base-content/70">{gettext("No member linked")}</span>
<% end %>
</:item>
</.list>