---
title: "Theme the Android PDF viewer from code with color schemes | Nutrient"
canonical_url: "https://www.nutrient.io/guides/android/user-interface/theming-from-code/"
md_url: "https://www.nutrient.io/guides/android/user-interface/theming-from-code.md"
last_updated: "2026-11-03T00:00:00.000Z"
description: "Theme most Nutrient Android SDK screens from code with a UiColorScheme: Start from Material 3 or your XML theme, set an accent, follow light and dark mode, and match your Compose content."
---

# Theming from code

Since Nutrient Android SDK 12.1, you can theme the SDK’s screens from code with a Compose [`UiColorScheme`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.compose.theme/-ui-color-scheme/index.html) instead of XML theme attributes. One call to [`setColorScheme()`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui/set-color-scheme.html) on [`PdfActivity`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity/index.html) or [`PdfUiFragment`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui-fragment/index.html) colors the toolbars, the outline view, document info, and the tab bar. It also colors the share menu, the settings sheet, AI Assistant, the thumbnail bar, and the thumbnail grid.

## XML theme or color scheme

Both approaches theme most of the same screens. Pick the one that matches how your app defines its colors:

- **XML theme attributes** stay the default. Use them when your app already has an XML theme. They also style anything a color scheme doesn’t cover, such as annotation hints, the password view, and the signature layout. See [appearance styling](https://www.nutrient.io/guides/android/customizing-the-interface/appearance-styling.md) for every attribute.

- **`setColorScheme()`** suits apps that define their colors in code — for example, in a Compose `MaterialTheme`. It also enables you to change the colors at runtime without recreating the activity.

A scheme set in code wins over the XML theme for the screens it covers. Call `setColorScheme(null)` to return to the XML theme.

The SDK stores the scheme on the hosting activity. Two `PdfUiFragment` instances in one activity share it, and the last call wins. `PdfUiFragment` also accepts the call before it’s attached and applies the scheme once it is.

## Choosing a starting point

A `UiColorScheme` has a Material 3 `ColorScheme` base, `material`, and a color class per screen. Every color you leave unset takes its Material role from `material`, so you only set the colors that should differ. Start from one of these:

| Function                              | Starts from                                     | Composable |
| ------------------------------------- | ----------------------------------------------- | ---------- |
| `UiColorScheme.fromMaterial(colors)`  | The roles of a Material 3 `ColorScheme`         | No         |
| `UiColorScheme.fromXmlTheme(context)` | The colors your XML theme attributes give today | No         |
| `UiColorScheme.default()`             | The ambient `MaterialTheme.colorScheme`         | Yes        |

`fromMaterial()` and `fromXmlTheme(context)` are plain functions, so you can call them in `onCreate()`:

```kotlin

class MyPdfActivity : PdfActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Every screen takes its colors from your app's Material 3 roles.
        setColorScheme(UiColorScheme.fromMaterial(lightColorScheme(primary = Color(0xFF1E5AA8))))
    }
}

```

Use `fromXmlTheme(context)` when you only want to change some screens. A scheme copied from it keeps the XML colors of every screen it doesn’t change. `fromXmlTheme()` derives its Material base from the outline view’s background and text. The theme’s `colorAccent` becomes `primary`, the main toolbar background becomes `surfaceContainer`, and the outline’s delete color becomes `error`.

Starting from `fromMaterial()` or `default()` instead moves every screen to Material colors, including the ones you don’t set.

`default()` returns a resolved scheme, so every color in it is already set. Replacing `material` on its result doesn’t change the screens’ colors. To start from other Material colors, pass them to `fromMaterial()` instead.

## What each part colors

Use `copy()` to replace the parts you want to change. Each part is a color class whose properties all default to unset:

| `UiColorScheme` property  | Class                     | Screen                                                           |
| ------------------------- | ------------------------- | ---------------------------------------------------------------- |
| `mainToolbar`             | `MainToolbarColors`       | Main toolbar, its overflow menu, and the document title          |
| `contextualToolbar`       | `ContextualToolbarColors` | Annotation creation toolbar and its submenus                     |
| `outline`                 | `OutlineColors`           | Outline view with its bookmark, annotation, and attachment lists |
| `documentInfoColorScheme` | `DocumentInfoColorScheme` | Document info                                                    |
| `tabBar`                  | `TabBarColors`            | Document tab bar                                                 |
| `actionMenu`              | `ActionMenuColors`        | Share menu                                                       |
| `settingsColorScheme`     | `SettingsColorScheme`     | Settings sheet and the custom stamp creator’s switches           |
| `aiAssistantColorScheme`  | `AiAssistantColorScheme`  | AI Assistant                                                     |
| `thumbnailBar`            | `ThumbnailBarColors`      | Floating, pinned, and scrollable thumbnail bars                  |
| `thumbnailGrid`           | `ThumbnailGridColors`     | Thumbnail grid (document overview)                               |

An unset color resolves to a Material role of `material`. These are the main ones:

| Part                      | Background                                                | Content                                                     | Selection                                              |
| ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------ |
| `mainToolbar`             | `surfaceContainer`                                        | `onBackground` for text and title; icons follow the text    | Selected icons follow the icon color                   |
| `contextualToolbar`       | `surfaceContainer`                                        | `onSurfaceVariant` icons                                    | `primary`                                              |
| `outline`                 | `background`                                              | `onBackground` text, `onSurfaceVariant` unselected tabs     | `primary`                                              |
| `documentInfoColorScheme` | `background`                                              | `onBackground` titles, `onSurfaceVariant` values            | `primary` group titles, `primaryContainer` button      |
| `tabBar`                  | `background`                                              | `onSurfaceVariant` titles                                   | `secondaryContainer` tab, `onSecondaryContainer` title |
| `actionMenu`              | `background`                                              | `onBackground` labels                                       | `primary` icon background, `onPrimary` icon            |
| `settingsColorScheme`     | `background`                                              | `onBackground` titles and labels, `outlineVariant` dividers | `primary`, with `secondary` for unselected options     |
| `thumbnailBar`            | `surfaceContainer` (the scrollable bar stays transparent) | `outline` thumbnail borders                                 | `primary` current page                                 |
| `thumbnailGrid`           | `surfaceDim`                                              |                                                             | `primary` current page and selection check             |

Some colors follow another color of the same part instead of a role. For example, an unset `MainToolbarColors.iconColor` follows `textColor`, so setting `textColor` also changes the icon color. The title doesn’t follow `textColor`, so set `titleTextColor` as well:

```kotlin

setColorScheme(
    UiColorScheme.fromXmlTheme(this).copy(
        mainToolbar = MainToolbarColors(
            backgroundColor = Color(0xFF1E2A44),
            textColor = Color.White,
            titleTextColor = Color.White,
            popup = ToolbarPopupColors(backgroundColor = Color(0xFF1E2A44)),
        ),
        // A scheme copied from `fromXmlTheme()` keeps the XML annotation creation toolbar.
        // Set it too when the two toolbars should match.
        contextualToolbar = ContextualToolbarColors(
            backgroundColor = Color(0xFF1E2A44),
            iconColor = Color.White,
        ),
    ),
)

```

The `mainToolbar` icon colors also color the SDK’s built-in toolbar items and the overflow button. A scheme from `fromXmlTheme()` takes them from `pspdf__iconsColor` and `pspdf__iconsColorActivated` in `pspdf__actionBarIconsStyle`, so the toolbar keeps its XML look until you change them. See [appearance styling](https://www.nutrient.io/guides/android/customizing-the-interface/appearance-styling.md) for the icon style.

The SDK fills in unset colors only when it resolves the scheme. Inside composition, read colors through `NutrientTheme.colorScheme`, which is already resolved. Outside composition, call `resolve()` on the scheme first.

A component’s own colors still win over the scheme for that component. For example, the `colors` parameter of [`NutrientMainToolbar`](https://www.nutrient.io/guides/android/jetpack-compose/components/main-toolbar/) wins for that toolbar. Once you call `PdfThumbnailBar.setBackgroundColor()`, it wins for the floating and pinned bar. That method takes a color, not `null`, so you can’t hand the background back to the scheme afterward.

## Setting one accent color

`withAccent(accent, onAccent)` returns a copy of a scheme with one accent on the selection colors of every screen. `accent` becomes `primary`, `primaryContainer`, `secondaryContainer`, and `inversePrimary` in `material`, with `onAccent` as the content color on them. Surfaces and most text stay as they are, so it works well on top of `fromXmlTheme()`. AI Assistant is the exception: Its text, toolbar text, and icons follow `primary`, so they take the accent too:

### KOTLIN

```kotlin

setColorScheme(UiColorScheme.fromXmlTheme(this).withAccent(Color(0xFF1E5AA8), Color.White))

```

### JAVA

```java

// `withAccent()` takes packed ARGB colors from Java.
setColorScheme(UiColorScheme.fromXmlTheme(this).withAccent(0xFF1E5AA8, Color.WHITE));

```

A toolbar keeps its selected icon color when the accent wouldn’t be readable on its background. Replace `material` before you call `withAccent()`, not after. `withAccent()` fixes AI Assistant’s reply bubble and button colors to the values they have when you call it, so a `material` you set afterward doesn’t reach them.

## Light and dark mode

A scheme set with `setColorScheme(UiColorScheme)` applies in both light and dark mode. To follow the theme, pass a pair: either two Material 3 `ColorScheme`s, which the SDK wraps with `fromMaterial()`, or two `UiColorScheme`s:

```kotlin

// Two Material 3 color schemes.
setColorScheme(
    lightColorScheme(primary = brandColor),
    darkColorScheme(primary = brandColorDark),
)

// Dynamic color, on Android 12 and later.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    setColorScheme(dynamicLightColorScheme(this), dynamicDarkColorScheme(this))
}

// Two `UiColorScheme`s, when you also set parts of the scheme.
setColorScheme(
    UiColorScheme.fromMaterial(lightColors).withAccent(brandColor, Color.White),
    UiColorScheme.fromMaterial(darkColors).withAccent(brandColorDark, Color.Black),
)

```

The SDK shows the light scheme when the activity’s theme declares itself light with `isLightTheme`. It shows the dark scheme when the theme declares itself dark. Every AppCompat and Material Components theme declares it. Any other theme follows the configuration’s night mode. It switches on its own, so you don’t need to set the scheme again after a configuration change. In practice:

- A DayNight theme switches the scheme with the device’s dark mode.

- [`ThemeMode.NIGHT`](https://www.nutrient.io/guides/android/viewer/viewing-options/night-theme/) shows the dark scheme whatever the device’s mode, because it applies a dark theme.

- The SDK’s default theme is light only. If you keep it, the light scheme shows even on a dark device. To show the dark scheme, pass a DayNight theme to [`PdfActivityConfiguration.Builder#theme(int)`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.configuration.activity/-pdf-activity-configuration/-builder/theme.html) or use `ThemeMode.NIGHT`.

To build the pair from two XML themes, wrap the activity in a `ContextThemeWrapper` for each:

### KOTLIN

```kotlin

setColorScheme(
    UiColorScheme.fromXmlTheme(ContextThemeWrapper(this, R.style.MyTheme)),
    UiColorScheme.fromXmlTheme(ContextThemeWrapper(this, R.style.MyTheme_Dark)),
)

```

### JAVA

```java

setColorScheme(
    UiColorScheme.fromXmlTheme(new ContextThemeWrapper(this, R.style.MyTheme)),
    UiColorScheme.fromXmlTheme(new ContextThemeWrapper(this, R.style.MyTheme_Dark)));

```

A `ContextThemeWrapper` reads resources in the activity’s current night mode. If a theme’s colors come from `-night` resources, call `applyOverrideConfiguration()` on its wrapper with a configuration that sets `uiMode`. Use `UI_MODE_NIGHT_YES` for the dark theme and `UI_MODE_NIGHT_NO` for the light theme.

## Compose content

[`NutrientTheme`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.compose.theme/-nutrient-theme.html) provides the SDK’s colors to Compose content, such as a host-rendered [`NutrientMainToolbar`](https://www.nutrient.io/guides/android/jetpack-compose/components/main-toolbar/) or [`NutrientTabBar`](https://www.nutrient.io/guides/android/jetpack-compose/components/tab-bar/). It picks its scheme from the first of these that is set:

1. The `colorScheme` passed to it, or to a `NutrientTheme` around it. It applies to that content only. The SDK’s own sheets and dialogs keep the activity’s scheme.

2. The scheme set with `setColorScheme()`, the light or dark one of a pair.

3. The activity’s XML theme attributes, or those of a theme overlay around the composition, such as `android:theme` or a `ContextThemeWrapper`.

So `NutrientTheme` without a `colorScheme` matches the SDK screens around it. Read the colors with `NutrientTheme.colorScheme` and the icons with `NutrientTheme.iconScheme`:

```kotlin

NutrientTheme {
    Icon(
        painter = painterResource(R.drawable.ic_share),
        contentDescription = null,
        tint = NutrientTheme.colorScheme.mainToolbar.iconColor,
    )
}

```

`NutrientTheme` also installs the scheme’s `material` base as the `MaterialTheme` of its content. Stock Material 3 components inside it, such as switches, ripples, and text fields, therefore follow the SDK’s colors. Typography, shapes, and the text style stay those of the enclosing composition.

If you wrap SDK composables in your own `MaterialTheme` and want its colors to apply, pass them in:

```kotlin

MaterialTheme(colorScheme = appColors) {
    NutrientTheme(colorScheme = UiColorScheme.fromMaterial(MaterialTheme.colorScheme)) {
        NutrientMainToolbar(coordinator = coordinator)
    }
}

```

Inside composition, the composable `UiColorScheme.fromXmlTheme()` returns the XML theme’s scheme for the current context, so you can copy it and change one part.

`PdfActivity` and `PdfUiFragment` update every `NutrientTheme` in the activity after `Activity.setTheme()` or a configuration change, without recreating the activity. Colors read from `NutrientTheme.colorScheme` update on their own. If your code remembers a value read from the XML theme, key it on `LocalNutrientThemeKey` as well, because `LocalContext` and `LocalConfiguration` don’t change on `setTheme()`:

```kotlin

val context = LocalContext.current
val accent = remember(context, LocalNutrientThemeKey.current) {
    val value = TypedValue()
    context.theme.resolveAttribute(androidx.appcompat.R.attr.colorAccent, value, true)
    Color(value.data)
}

```

## Using color schemes from Java

`setColorScheme()` and the scheme functions that don’t take Compose colors work from Java:

- `UiColorScheme.fromXmlTheme(context)`, including with a `ContextThemeWrapper` for a light and dark pair.

- `withAccent(int accent, int onAccent)`, which takes packed alpha, red, green, and blue (ARGB) colors.

- `setColorScheme(light, dark)` and `setColorScheme(null)`.

- `UiColorScheme.fromMaterial(colors)` and `setColorScheme(lightColors, darkColors)`, which take Material 3 `ColorScheme`s.

- `resolve()`, and the getters of every part, such as `getMainToolbar()`.

Compose Material 3 compiles its dynamic color functions into `DynamicTonalPaletteKt`, so Java can call them:

```java

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    setColorScheme(
        DynamicTonalPaletteKt.dynamicLightColorScheme(this),
        DynamicTonalPaletteKt.dynamicDarkColorScheme(this));
}

```

`lightColorScheme()` and `darkColorScheme()` take Compose `Color` values, which Java can’t pass. Neither can the constructors of the color classes, and `UiColorScheme.copy()` has no Java overloads. `UiColorScheme.default()` and `NutrientTheme` are composable. Write a small Kotlin function that returns the scheme, and call it from Java. Use this to build a `ColorScheme` from your own colors or to set individual colors.

## Migrating from the deprecated theming APIs

Nutrient Android SDK 12.1 deprecates the earlier theming APIs. Switch to the replacements below. Most of them keep working, with one exception. The `NutrientTheme` overload that takes a `controller` is hidden, not only deprecated. Code compiled against 12.0 still runs, but Kotlin source that passes `controller` no longer compiles, so remove the argument when you update:

| Deprecated                                                     | Replacement                                                                                       |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `PdfActivity.setMainToolbarColorScheme(scheme)`                | `setColorScheme(scheme)`                                                                          |
| `NutrientTheme(controller = …, colorScheme = scheme)`          | `NutrientTheme(colorScheme = scheme)`. Source that passes `controller` no longer compiles         |
| `NutrientThemeController`, `rememberNutrientThemeController()` | Nothing. Remove calls to `invalidate()`, and key remembered XML values on `LocalNutrientThemeKey` |
| `UiTheme.colors`/`UiTheme.icons`                               | `NutrientTheme.colorScheme`/`NutrientTheme.iconScheme`                                            |
| `SdkTheme(colors, icons)`, `SdkTheme.default()`                | `NutrientTheme(colorScheme = colors, iconScheme = icons) { … }`                                   |
| `getUiColors()`                                                | `UiColorScheme.fromXmlTheme()`                                                                    |
| `getComposeUiColors()`                                         | `UiColorScheme.default()`                                                                         |
| `NutrientFloatingToolbarSearchTheme { … }`                     | `NutrientTheme(colorScheme = floatingToolbarSearchColorScheme()) { … }`                           |

A scheme set with `setMainToolbarColorScheme()` still wins over `setColorScheme()` on the main toolbar. When you switch, stop calling it, or call it with `null`, or the main toolbar keeps its old colors.

`setColorScheme()` colors more screens than `setMainToolbarColorScheme()` did. If your scheme was copied from `UiColorScheme.fromXmlTheme()`, the other screens keep their colors. If it was copied from `UiColorScheme.default()`, they move to Material 3 colors, so set the parts you want to keep. See the [12.1 migration guide](https://www.nutrient.io/guides/android/releases/12-1.md) for the other changes to how schemes resolve.

## Examples

These Catalog examples build a scheme from `UiColorScheme.fromXmlTheme()` and pass it to `NutrientTheme` around host-rendered toolbars:

- **Custom Toolbars: PdfActivity**, **Custom Toolbars: PdfFragment**, **Custom Toolbars: PdfFragment (View Interop)**, and **Custom Toolbars: DocumentView** (`CustomToolbarsExamples.kt`)

- **Tabbed Documents (Compose)** (`DocumentTabsComposeExample.kt`)

- **Annotation Toolbar: User-Editable Tools** (`AnnotationToolbarUserEditableExample.kt`)
---

## Related pages

- [Customizing PDF viewer styling on Android](/guides/android/customizing-the-interface/appearance-styling.md)
- [Customizing the electronic signature UI on Android](/guides/android/customizing-the-interface/configuring-the-signaturepickerfragment.md)
- [Customize the main toolbar on Android](/guides/android/customizing-the-interface/customizing-menus.md)
- [Customizing the note editor on Android](/guides/android/customizing-the-interface/customizing-the-note-editor.md)
- [Customizing the thumbnail file viewer on Android](/guides/android/customizing-the-interface/customizing-the-thumbnail-bar.md)
- [Customizing dialog modals on Android](/guides/android/customizing-the-interface/modal-dialogs-styling.md)
- [Hide or customize scrollbars in our Android viewer](/guides/android/customizing-the-interface/scrollbars.md)
- [Show or hide the UI in our Android viewer](/guides/android/customizing-the-interface/user-interface-visibility.md)
- [Customizing the form editing toolbar on Android](/guides/android/customizing-the-interface/using-form-ui-within-fragment.md)
- [Localization: Change languages in our Android PDF viewer](/guides/android/features/localization.md)
- [Overlay views on Android](/guides/android/features/overlay-views.md)
- [Customizing our PDF viewer on Android](/guides/android/user-interface.md)
- [Customize the settings UI](/guides/android/user-interface/custom-settings-ui.md)
- [Separate the document info view in our Android viewer](/guides/android/user-interface/separate-document-info.md)

