Sitemap
ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Dissecting Expanded Dark Theme

with Claude Science and AOSP

6 min readJul 27, 2026

--

There’s a lot of excitement around customization, personalization, accessibility, and giving people more control over how interfaces adapt to their needs. I’m interested in what happens underneath, and dark mode hides a surprising amount of engineering.

A dark theme is more than a binary inversion. Colors can change, but the visual cues and relationships that give an interface meaning still need to carry through. Dark mode might feel like a preference for some people, but it can matter much more for those with low vision or photosensitivity.

Press enter or click to view image in full size
The Android robot comparing light and dark themes. Image generated by author.

For years, whether an app on your phone had dark mode depended on whether its developers built one. Android 10 (2019) introduced Force Dark, letting developers hand that work off to Android, which could generate a dark version from the app’s existing light theme. Then in late 2025, an Android 16 update introduced Expanded Dark Theme, putting that choice in users’ hands. Users can now ask Android to darken apps that still showed up light, even when those apps never provided their own dark theme. That shift toward user agency caught my attention.

I used Claude Science to probe Expanded Dark Theme: I built a light-only Android app, measured what changed across common interface patterns, and traced the results back through Android’s rendering code.

Setup

I tested fourteen interface patterns on one screen, in three versions. The original light theme, the dark version Android generated from it (I’ll call it auto-dark), and a dark theme I built by hand following Material 3, Google’s own design system (hand-built).

Press enter or click to view image in full size
Left to Right: Light mode, Android’s Expanded Dark Theme (auto-dark), and a hand-built Material 3 dark theme

What Android preserved

Could the transformation take readable text and make it unreadable? No. In my experiment, everything that passed the WCAG contrast floor in light mode still passed after darkening, and everything I deliberately built broken stayed broken. The ratio behind those verdicts is:

Contrast Ratio = (L_lighter + 0.05) / (L_darker + 0.05)

where L is each color’s relative luminance. White text on the orange banner failed at 2.8 in light mode and 2.7 in auto-dark. Android inherited my accessibility contrasts rather than auditing or changing them.

Color families held too, for the most part. Blues stayed blue, reds stayed red. The one exception was the orange banner, which came out red. The reason turned out to be buried in the rendering code, which comes later.

Press enter or click to view image in full size
Auto-dark mostly preserved color families. The orange brand color was the exception, shifting toward red

The next question I had was how close an automatic dark theme gets to one a person actually designed.

Automatic vs hand-built

On plain text and surfaces, the auto-dark theme came remarkably close to the hand-built one, with only minor color differences. On these simpler patterns, the OS came surprisingly close to my hand-built output without seeing a single design token. The gap surfaced in colorful, mid-toned elements (think link, errors, search, and banner); all read 1.8 to 2.5 times better when hand-built.

The brand banner shows the difference. My hand-built Material theme never touched the original orange. It stretched the brand color into a palette of tones, then picked tones by the banner’s role, so the dark scheme gave it a deep brown fill with pale peach text, the orange’s own dark and light ends, passing contrast at 6.6 to 1 by design.

Get Aryan K’s stories in your inbox

Join Medium for free to get updates from this writer.

These role labels, assigned by a developer, never reach Android’s renderer. In the auto-dark case, the banner is just a color, so all Android can do is run it through the darkening math, and it came out at 2.7 to 1 and redder.

Press enter or click to view image in full size
Six interface patterns as color strips in light, auto-dark, and hand-built dark. Plain text rows read about the same in both dark versions. The brand banner, error message, link, and search placeholder are 1.8 to 2.5 times higher contrast hand-built.
Auto-dark ties the hand-built theme on plain text and loses on colored elements, by 1.8 to 2.5 times
Press enter or click to view image in full size
Grouped bar chart of WCAG contrast ratios for nine patterns in light, auto-dark, and hand-built dark, with reference lines at 4.5 (AA), 7 (AAA), and 3 (large text). Auto-dark and hand-built are within half a point on all neutral text. Hand-built is far higher on the banner, error, link, and placeholder.
Contrast ratios for the nine cleanly measured patterns against the WCAG thresholds

The failures clustered around saturated colors and middle grays. To understand why those colors behaved differently, I traced how Android transforms them in the renderer.

Inside the renderer

For ordinary Android UI rendering, the app records drawing instructions that eventually pass through HWUI, Android’s hardware-accelerated UI renderer. Expanded Dark Theme can transform colors in those drawing instructions before they become pixels.

Press enter or click to view image in full size
Flow diagram in three boxes. An app produces drawing instructions, Expanded Dark Theme intercepts them, lightening text and darkening everything else, and the recolored instructions become the pixels on screen.
Expanded Dark Theme sits between an app’s drawing instructions and the pixels

Roughly, Android first decides whether a rendered chunk behaves like foreground or background. Text-containing nodes tend to become foreground and get pushed lighter; backgrounds get pushed darker, and the background guess is a heuristic the source itself labels a crude overlap check. Then one small piece of math picks the new color:

new lightness = min(110 − old lightness, 100)

applied only when it moves in the right direction. That guard is why my mid-grays never moved; a gray at lightness 50 maps to 60, wrong direction, left alone. Left alone is the trap. The gray stays put while everything behind it goes black, which is why the hand-built theme beat auto-dark on my placeholder; a designer re-lightens that gray, and the math never does. Here it is in the source:

SkColor makeDark(SkColor color) {
Lab lab = sRGBToLab(color);
float invertedL = std::min(110 - lab.L, 100.0f);
if (invertedL < lab.L) {
lab.L = invertedL;
return LabToSRGB(lab, SkColorGetA(color));
} else {
return color;
}
}

The math runs in CIELAB, a color system from 1976 that describes a color with three numbers:

L = lightness, 0 (black) to 100 (white)
a = position between green (−) and red (+)
b = position between blue (−) and yellow (+)

The system promises that lightness lives in L, so you can change it without touching the color. Android rewrites L and leaves a and b untouched.

That promise creates two problems in practice. First, it isn’t perfectly kept. The math bends for a few hues, orange among them, which is part of why newer systems like OKLab exist. Second, screens work in a fixed color range called sRGB, and when the math asks for a color outside that range, the screen substitutes the nearest one it has.

My orange banner hit both problems. The bend moved it about four degrees toward red. Then the darkened orange fell outside sRGB; there’s no such thing as a dark vivid orange, so the substitute did the remaining ten degrees. The standard bent a little, and the screen couldn’t deliver the rest.

Press enter or click to view image in full size
Hue shift after darkening. Orange moved 14 degrees

The point

Expanded Dark Theme is a useful fallback, especially for people who need or strongly prefer darker interfaces. But for developers, Google’s old advice stands: ship your own dark theme. Android can transform pixels. It doesn’t know that orange is your brand color, that gray is placeholder text, or that red communicates an error. Rely on auto-dark, and those decisions fall to renderer heuristics, color math, and the limits of sRGB.

For an automatic fallback, the results are surprisingly good. They are still no substitute for a theme designed with meaning in mind.

--

--

ProAndroidDev
ProAndroidDev

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.