All articles

Tutorial

Sync Android Dark Theme with Your PWA in a TWA

August 28, 2026 · 7 min read

Providing a cohesive user experience is one of the most critical aspects of publishing a web app to the Google Play Store. When web applications are wrapped inside an Android Trusted Web Activity (TWA), they look and feel like native apps. However, visual discrepancies can quickly break this illusion. One of the most common friction points is dark mode synchronisation.

If an Android user has system-wide dark theme enabled, they expect every app they open to respect this preference instantly. If your TWA launches with a bright white splash screen before rendering your web app's dark interface, or if your web app fails to adapt to the system setting altogether, it degrades the user experience. This step-by-step tutorial explains how to perfectly synchronise the Android system-level dark theme with your PWA inside a TWA.

The Two Layers of TWA Dark Theme Management

To successfully implement dark mode synchronisation, you must configure two distinct layers of your application. These layers operate at different stages of the application lifecycle and require different handling mechanisms:

The first is the Native Layer. This includes the Android launcher, native styles, and the initial splash screen that displays immediately after the user taps your app icon. At this point, the web rendering engine has not yet initialised, meaning your CSS files cannot influence the layout. The native system must handle this phase.

The second is the Web Layer. Once the browser engine initialises and loads your web application, the responsibility shifts to your web code. Your CSS and JavaScript must detect the system preference and render the appropriate dark style smoothly without flash-of-unstyled-content (FOUC) issues.

Step 1: Configuring Native Android Styles for Dark Theme

The native Android wrapper of your TWA contains a configuration file called AndroidManifest.xml and a set of resource files under the res/values/ directory. To support dark theme natively, you must leverage Android's resource qualifier system.

Android allows you to define alternative resources for different system states. By default, your main styles are defined in res/values/styles.xml. To define styles that only apply when dark theme is enabled, you create an identical folder named res/values-night/ and place a modified styles.xml file inside it.

In your default res/values/styles.xml, you define your light theme assets:

<style name="Theme.LauncherActivity" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@color/background_light</item>
    <item name="android:statusBarColor">@color/status_bar_light</item>
</style>

In your dark-themed resource file, located at res/values-night/styles.xml, you override these values to match your app's dark aesthetic:

<style name="Theme.LauncherActivity" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowBackground">@color/background_dark</item>
    <item name="android:statusBarColor">@color/status_bar_dark</item>
</style>

When the system launches your TWA, it automatically detects whether the device is in dark mode and reads the properties from the correct directory. This ensures that your app's initial launch screen matches the user's system preferences immediately.

Step 2: Leveraging CSS media queries inside the Web App

Once the web rendering engine takes over from the native splash screen, your web application must take charge of styling. Modern Chromium engines, which power TWAs on Android devices, have excellent support for media queries that detect system preferences.

The primary mechanism for this is the prefers-color-scheme CSS media query. This media query automatically detects whether the operating system is requesting a light or dark theme and applies the appropriate styles without requiring any JavaScript.

The most efficient way to manage this is using CSS custom properties (variables). This approach allows you to define a single set of utility classes while switching values dynamically based on system state:

:root {
    --background-color: #ffffff;
    --text-color: #1a1a1a;
    --primary-color: #0066cc;
}

@media (prefers-color-scheme: dark) {
    :root {
        --background-color: #121212;
        --text-color: #f5f5f5;
        --primary-color: #3399ff;
    }
}

By structuring your CSS in this manner, your web application shifts style rules instantly as soon as it loads inside the TWA container, providing a seamless visual handoff from the native splash screen to the web content.

Step 3: Synchronising the Browser UI elements (Status and Navigation Bars)

A common mistake when converting a PWA is styling the web document's background while forgetting about the surrounding system chrome, such as the Android status bar at the top and the navigation bar at the bottom. In a TWA, these elements are managed by the system but can be controlled by your web application.

You can dynamically control the status bar colour from your web code using the HTML theme-color meta tag. To support dark mode, you should define multiple meta tags in your index.html file, using the media attribute to target different colour schemes:

<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#121212" media="(prefers-color-scheme: dark)">

When running inside the TWA container, Chrome parses these tags and dynamically adjusts the status bar and navigation bar background colours to match. This ensures your app looks fully integrated with the OS, rather than feeling like a website running inside a window.

Step 4: Handling Manual Theme Toggles in JavaScript

While auto-detecting system dark mode is excellent, many users prefer a manual toggle in the app's settings menu to force a light or dark theme regardless of global Android settings. To handle this, your web application must manage theme state dynamically.

When a user selects a manual preference, you should save this choice in localStorage. On application start, your script should check for a saved preference first; if none exists, it defaults to the system preferences using the JavaScript window.matchMedia API.

Here is how to structure this check in your main web script:

const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) {
    document.documentElement.classList.add('dark-mode');
} else {
    document.documentElement.classList.remove('dark-mode');
}

By combining this JavaScript fallback logic with your native resource files, you provide an extremely robust theme management engine that handles both system defaults and manual overrides gracefully.

Dark Theme Sync Strategies

The following table outlines the different strategies for handling theme changes and where they should be configured for the best user experience:

App PhaseResponsibilityConfiguration MethodImpact
App Launch (Splash Screen)Native Android Wrapperres/values-night/styles.xmlPrevents initial blinding white flash.
Web Content LoadingCSS Media Query@media (prefers-color-scheme: dark)Instantly styles the main body background.
System UI IntegrationHTML Meta Tags<meta name="theme-color"> with media queriesMatches native status and navigation bars.
User Preferences OverrideJavaScript and StoragelocalStorage check & class injectionAllows users to force theme preferences.

Testing and Fine-Tuning Your Theme Transitions

Once you have configured both your native style sheets and your web application CSS, testing is essential to ensure there are no rendering lag issues during theme changes. To test your setup, install your TWA on an Android device or emulator.

Go to the Android system settings, toggle Dark Theme on and off, and launch your application. Verify that the launch screen and splash screen match the chosen mode. Once the app loads, ensure there are no white borders or unstyled native views. Ensuring this fluid transition elevates your app's quality, making it indistinguishable from a natively compiled Java or Kotlin application on Google Play.

Ready to ship your Android app?

Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.

Build my app