fix(thema): een selectievakje toonde zijn stand niet in donkere modus (#744) #749

Merged
brenno merged 1 commit from fix/primary-donker-b-744 into main 2026-07-23 14:26:10 +00:00
Owner

Route B uit #744, na route A in #746. Hiermee is het issue af.

Wat er mis was

Route A repareerde tekst. Bij het narekenen bleek er iets ergers onder te
zitten: een onleesbare tekst is vervelend, maar een bediening die zijn eigen
stand niet toont is onbruikbaar.

Gemeten in het profiel Donker, vóór deze wijziging:

primary #111827 op surface #1E293B 1,21 : 1
onPrimary #122F60 op primary #111827 1,35 : 1
cursorColor niet gezet → colorScheme.primary

De vulling van een aangevinkt vakje was #111827, met het vinkje erop in
#122F60. Dat vinkje verdween in zijn eigen vulling: je kon een aangevinkt
vakje niet van een leeg vakje onderscheiden. En omdat er nergens een
TextSelectionThemeData stond, nam Flutter colorScheme.primary voor de
tekstcursor — #111827 op een invoerveld van #1E293B. In een programma
waarin je schrijft, wist je niet waar je typte.

Waarom het niet "geef het profiel een lichte primaryColor" was

Omdat primary twee onverenigbare rollen droeg.

profile.primaryColor is de merk- en balkkleur: appBarTheme schildert de
bovenbalk ermee, en in een donker profiel hoort die donker te zijn. Material
behandelt ColorScheme.primary tegelijk als het accent voor interactieve
onderdelen
, waar het in donkere modus juist licht moet zijn. Eén waarde kan
niet allebei, en de voor de hand liggende reparatie lost het niet op maar
verplaatst het: een lichte primaryColor geeft een lichte bovenbalk.

De rollen zijn nu gescheiden. In donkere modus volgt ColorScheme.primary het
accent dat het profiel al heeft (accentColor, #60A5FA — dat de focusrand en
de primaire knop ook al gebruikten); de balk houdt de merkkleur. De kiem van het
schema blijft óók de merkkleur, zodat de rest van de harmonie (containers,
outlines, onPrimary) niet verschuift.

Na afloop:

Donker
primary op surface 1,21 → 5,75 : 1
onPrimary op primary 1,35 → 5,16 : 1
cursorColor #60A5FA, expliciet

Beide lichte profielen komen er byte-identiek uit — primary, onPrimary,
appBar, alles. De cursorkleur is daar nu expliciet, met dezelfde waarde die
Flutter er impliciet al pakte.

De regressietest

Drie toetsen erbij, per ingebouwd profiel:

  1. de vulling van een aangevinkt onderdeel haalt 3:1 op zijn oppervlak
    (WCAG 1.4.11 — het is een grafisch object, geen tekst);
  2. het vinkje haalt 4,5:1 op díe vulling — dat is de stand zelf;
  3. de cursor haalt 3:1 op de vulling van het invoerveld, en er is een
    expliciete cursorkleur (anders valt Flutter terug op primary).

En een vierde die de verkeerde uitweg dichthoudt: de bovenbalk moet de merkkleur
van het profiel houden met een leesbare titel erop. Wie dit later "oplost"
door het profiel een lichte primaryColor te geven, laat die toets vallen in
plaats van het oog.

Alle drie de mutaties rood gezien:

Mutatie Wat rood werd
interactive terug op primary "de vulling van een aangevinkt vakje verdwijnt in het oppervlak (Donker)"
textSelectionTheme weghalen "geen expliciete cursorkleur" — op alle drie de profielen
profiel Donker een lichte primaryColor "de titel in de bovenbalk leest niet (Donker)"

Met eigen ogen

flutter run -d macos in donkere modus: de aan-schakelaar is nu een lichte baan
met donkere duim (hetzelfde primary/onPrimary-paar dat het vinkje tekent),
de focusrand van het zoekveld staat er, en een tekstselectie is een zichtbare
blauwe markering in plaats van bijna-zwart op donker.

Poorten

make check exit 0, make check-secrets 0, make sast 0 bevindingen. DAST niet
gedraaid — advisory, geen geserveerd oppervlak geraakt. Geen nieuwe l10n.d('…'),
geen afhankelijkheid, geen SBOM-gevolg. CHANGELOG en docs/ACCESSIBILITY.md bij.

Eén ding dat blijft staan

Dit dekt de drie ingebouwde profielen. Wie zelf een donker profiel maakt met
een donkere accentColor krijgt hetzelfde probleem terug. Ik heb er bewust geen
stille correctie op gezet: een kleur die de gebruiker kiest en die de app dan
negeert, is een ander soort verrassing. Als dat moet worden opgevangen hoort het
in de profielbewerker zichtbaar te gebeuren — een waarschuwing zoals de
diakwaliteitscontrole die voor het dek van de gebruiker al geeft. Aparte
afweging, geen onderdeel van deze reparatie.

Route B uit #744, na route A in #746. Hiermee is het issue af. ## Wat er mis was Route A repareerde tekst. Bij het narekenen bleek er iets ergers onder te zitten: een onleesbare tekst is vervelend, maar een bediening die zijn eigen stand niet toont is onbruikbaar. Gemeten in het profiel *Donker*, vóór deze wijziging: | | | |---|---| | `primary` `#111827` op `surface` `#1E293B` | 1,21 : 1 | | `onPrimary` `#122F60` op `primary` `#111827` | **1,35 : 1** | | `cursorColor` | niet gezet → `colorScheme.primary` | De vulling van een aangevinkt vakje was `#111827`, met het vinkje erop in `#122F60`. Dat vinkje verdween in zijn eigen vulling: je kon een aangevinkt vakje niet van een leeg vakje onderscheiden. En omdat er nergens een `TextSelectionThemeData` stond, nam Flutter `colorScheme.primary` voor de tekstcursor — `#111827` op een invoerveld van `#1E293B`. In een programma waarin je schrijft, wist je niet waar je typte. ## Waarom het niet "geef het profiel een lichte primaryColor" was Omdat `primary` twee onverenigbare rollen droeg. `profile.primaryColor` is de **merk- en balkkleur**: `appBarTheme` schildert de bovenbalk ermee, en in een donker profiel hoort die donker te zijn. Material behandelt `ColorScheme.primary` tegelijk als het **accent voor interactieve onderdelen**, waar het in donkere modus juist licht moet zijn. Eén waarde kan niet allebei, en de voor de hand liggende reparatie lost het niet op maar verplaatst het: een lichte `primaryColor` geeft een lichte bovenbalk. De rollen zijn nu gescheiden. In donkere modus volgt `ColorScheme.primary` het accent dat het profiel al heeft (`accentColor`, `#60A5FA` — dat de focusrand en de primaire knop ook al gebruikten); de balk houdt de merkkleur. De kiem van het schema blijft óók de merkkleur, zodat de rest van de harmonie (containers, outlines, `onPrimary`) niet verschuift. Na afloop: | | Donker | |---|---| | `primary` op `surface` | 1,21 → **5,75 : 1** | | `onPrimary` op `primary` | 1,35 → **5,16 : 1** | | `cursorColor` | `#60A5FA`, expliciet | Beide lichte profielen komen er byte-identiek uit — `primary`, `onPrimary`, `appBar`, alles. De cursorkleur is daar nu expliciet, met dezelfde waarde die Flutter er impliciet al pakte. ## De regressietest Drie toetsen erbij, per ingebouwd profiel: 1. de vulling van een aangevinkt onderdeel haalt 3:1 op zijn oppervlak (WCAG 1.4.11 — het is een grafisch object, geen tekst); 2. het vinkje haalt 4,5:1 op díe vulling — dat is de stand zelf; 3. de cursor haalt 3:1 op de vulling van het invoerveld, en er *is* een expliciete cursorkleur (anders valt Flutter terug op `primary`). En een vierde die de verkeerde uitweg dichthoudt: de bovenbalk moet de merkkleur van het profiel houden **met een leesbare titel erop**. Wie dit later "oplost" door het profiel een lichte `primaryColor` te geven, laat die toets vallen in plaats van het oog. Alle drie de mutaties rood gezien: | Mutatie | Wat rood werd | |---|---| | `interactive` terug op `primary` | "de vulling van een aangevinkt vakje verdwijnt in het oppervlak (Donker)" | | `textSelectionTheme` weghalen | "geen expliciete cursorkleur" — op alle drie de profielen | | profiel *Donker* een lichte `primaryColor` | "de titel in de bovenbalk leest niet (Donker)" | ## Met eigen ogen `flutter run -d macos` in donkere modus: de aan-schakelaar is nu een lichte baan met donkere duim (hetzelfde `primary`/`onPrimary`-paar dat het vinkje tekent), de focusrand van het zoekveld staat er, en een tekstselectie is een zichtbare blauwe markering in plaats van bijna-zwart op donker. ## Poorten `make check` exit 0, `make check-secrets` 0, `make sast` 0 bevindingen. DAST niet gedraaid — advisory, geen geserveerd oppervlak geraakt. Geen nieuwe `l10n.d('…')`, geen afhankelijkheid, geen SBOM-gevolg. CHANGELOG en `docs/ACCESSIBILITY.md` bij. ## Eén ding dat blijft staan Dit dekt de drie **ingebouwde** profielen. Wie zelf een donker profiel maakt met een donkere `accentColor` krijgt hetzelfde probleem terug. Ik heb er bewust geen stille correctie op gezet: een kleur die de gebruiker kiest en die de app dan negeert, is een ander soort verrassing. Als dat moet worden opgevangen hoort het in de profielbewerker zichtbaar te gebeuren — een waarschuwing zoals de diakwaliteitscontrole die voor het dek van de gebruiker al geeft. Aparte afweging, geen onderdeel van deze reparatie.
Route B. De vulling van een aangevinkt vakje was primary #111827 met
een vinkje van onPrimary #122F60 erop: 1,35:1 op zijn eigen vulling. Je
kon niet zien of het aan stond. En zonder TextSelectionThemeData nam
Flutter colorScheme.primary voor de tekstcursor — #111827 op een veld
van #1E293B.

De oorzaak is dat primary twee onverenigbare rollen droeg. appBarTheme
schildert de bovenbalk ermee en daar hoort hij donker; Material gebruikt
hem als accent voor interactieve onderdelen en daar hoort hij licht. Het
profiel een lichte primaryColor geven verplaatst het probleem naar een
lichte bovenbalk, dus de rollen zijn gescheiden: ColorScheme.primary
volgt in donkere modus het accent van het profiel, de balk houdt de
merkkleur. Beide lichte profielen bewegen niet.

De redenering staat in _interactiveColor en niet in fromProfile: die
laatste liep anders over de methodelengte-ratchet heen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit bf8e55d372 into main 2026-07-23 14:26:10 +00:00
Sign in to join this conversation.
No description provided.