---
title: "Nutrient 12 migration guide"
canonical_url: "https://www.nutrient.io/guides/android/migration-guides/12-migration-guide/"
md_url: "https://www.nutrient.io/guides/android/migration-guides/12-migration-guide.md"
last_updated: "2026-09-29T00:00:00.000Z"
description: "Lists important public API changes for Nutrient Android SDK 12.0."
---

This guide outlines the changes to Nutrient Android SDK 12.0. Several independent areas changed. The toolbars come first, followed by the annotation inspector’s color picker, the settings dialog, the Material 3 modal surfaces, and the document overview grid. The smaller items come last: the `simple-compose` example app’s move into the Catalog and the document tab bar’s move to Compose. They also include the Kotlin conversion of `PdfFragment` and `PdfTabBar` and a new Instant enum case.

Nutrient 12 replaces every toolbar of `PdfActivity` and `PdfUiFragment` with a Jetpack Compose implementation. That covers the main toolbar, the annotation creation toolbar, the document editing and content editing toolbars, and the inline search bar. The Compose toolbar item model is now the single source of truth for toolbar content. This is a clean break with no deprecation window. The Android options-menu contract (`Menu`, menu XML, `onCreateOptionsMenu`, …) no longer drives the main toolbar, and the View-based `ContextualToolbar` hierarchy and `ToolbarCoordinatorLayout` are removed. This guide covers the [main toolbar](#menu-setup--toolbar-items-api) first. Then it covers the [contextual toolbars](#contextual-toolbars), the [text selection toolbar](#text-selection-toolbar), [custom layouts](#toolbarcoordinatorlayout-is-removed), the new [floating toolbars](#floating-toolbars), and the [behavior changes](#toolbar-behavior-changes) to check in existing integrations.

## Requirements

- **Compile against API 37.1.** The SDK targets API 37, and its Android Archive (AAR) metadata requires compiling against API level 37 with minor version 1. An app with a lower `compileSdk` fails its Gradle build until it sets `compileSdk = 37` and `compileSdkMinor = 1`. Your `targetSdk` can stay where it is.

- **Kotlin 2.3 or newer** for Kotlin projects. The SDK is built with Kotlin 2.4.10, and a Kotlin compiler reads metadata up to one version ahead of itself.

- **Jetpack Compose 1.12.0.** Gradle resolves an app’s Compose dependencies to at least 1.12.0 because the SDK depends on it.

## Overview of changes

| Old                                                                                 | New                                                                                                 |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `onCreateOptionsMenu`/`onPrepareOptionsMenu` overrides                              | `setToolbarItemsProvider(ToolbarItemsProvider)`                                                     |
| `onGenerateMenuItemIds` (`OnMenuItemsGenerateListener`)                             | `ToolbarItemsProvider` (reorder/insert in the returned list)                                        |
| `onOptionsItemSelected` (toolbar items)                                             | `setOnToolbarItemClickListener(OnToolbarItemClickListener)`                                         |
| `invalidateOptionsMenu()`/`supportInvalidateOptionsMenu()`/`PdfUi.invalidateMenu()` | `invalidateToolbarItems()`                                                                          |
| `OnToolbarMenuChangedListener` (`PdfUiFragment` hosts)                              | Same three APIs on `PdfUiFragment`/`PdfUi`                                                          |
| Menu XML resources for toolbar items                                                | `ToolbarItem.Button`/`Toggle`/`Submenu` built-in code                                               |
| `com.pspdfkit.ui.toolbar.MainToolbar` (View)                                        | Compose toolbar; custom hosts render `NutrientMainToolbar`                                          |
| `com.pspdfkit.ui.search.PdfSearchViewInline`                                        | Compose inline search bar inside the toolbar; `PdfUi.isInlineSearchActive()`                        |
| `getActiveViewType() == VIEW_SEARCH` (inline search)                                | `PdfUi.isInlineSearchActive()`                                                                      |
| Standalone document title overlay strip                                             | Rendered inside the main toolbar (themed with it, shown in portrait too)                            |
| XML theme attributes as the only toolbar theming                                    | Still the default, plus `setMainToolbarColorScheme(UiColorScheme)`/`NutrientTheme`                  |
| (No inset control)                                                                  | `ToolbarInsets` + `ToolbarCoordinator.setMainToolbarInsets`/`NutrientMainToolbar(windowInsets = …)` |
| `Toolbar.setNavigationIcon`/`setNavigationOnClickListener` on `pspdf__toolbar_main` | `setToolbarNavigationIcon`/`setOnToolbarNavigationClickListener` on `PdfUi` and `DocumentState`     |
| `MainToolbar.setForcedVisibility(GONE)`                                             | `ToolbarCoordinator.setMainToolbarHostVisible(false)`                                               |

## Menu setup → toolbar items API

This section lists what was removed from the options-menu contract and shows how to rebuild the same toolbar with the items API.

### What was removed

- `PdfActivity` no longer overrides `onCreateOptionsMenu`/`onPrepareOptionsMenu`/`onOptionsItemSelected` to build the toolbar; overriding them in a subclass no longer affects toolbar content. `invalidateOptionsMenu()` no longer refreshes toolbar items.

- `OnMenuItemsGenerateListener` (with `onGenerateMenuItemIds`) — Deleted.

- `OnToolbarMenuChangedListener` — Deleted.

- `PdfUi.invalidateMenu()` — Deleted.

- Menu XML isn’t inflated into the toolbar.

The `PdfActivity.MENU_OPTION_*` IDs (`R.id.pspdf__menu_option_*`) are unchanged. They’re now the `ToolbarItem.id` values your click listener receives, but they’re no longer View IDs. The Compose toolbar has no views to match, so `onView(withId(R.id.pspdf__menu_option_search))` and UI Automator lookups by resource ID find nothing. Each item carries a Compose test tag with the ID’s resource entry name instead, so an Espresso test moves to the Compose test APIs:

```kotlin

// Before:
onView(withId(R.id.pspdf__menu_option_search)).perform(click())

// After:
composeTestRule.onNodeWithTag("pspdf__menu_option_search").performClick()

```

A custom item gets its tag the same way, from its `id`’s resource entry name.

### Old

Before 12.0, a `PdfActivity` subclass added custom items by overriding the options-menu callbacks:

```kotlin

class CustomActionsActivity : PdfActivity() {

    override fun onGenerateMenuItemIds(menuItems: MutableList<Int>): List<Int> {
        val indexOfOutlineButton = menuItems.indexOf(PdfActivity.MENU_OPTION_OUTLINE)
        menuItems.addAll(indexOfOutlineButton + 1, listOf(R.id.custom_action1, R.id.custom_action2))
        return menuItems
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menu.findItem(R.id.custom_action1).apply {
            title = "Menu Item 1"
            setIcon(R.drawable.ic_arrow_left)
            setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
        }
        menu.findItem(R.id.custom_action2).apply {
            title = "Menu Item 2"
            setIcon(R.drawable.ic_arrow_right)
        }
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val handled = when (item.itemId) {
            R.id.custom_action1 -> { showToast("Action 1"); true }
            R.id.custom_action2 -> { showToast("Action 2"); true }
            else -> false
        }
        return handled || super.onOptionsItemSelected(item)
    }
}

```

### New

No overrides — set a provider and a click listener (for example, in `onCreate`). The provider receives the SDK’s default items in order and returns the list to render; ordering, insertion, and removal all happen on that list. `showAsAction` is replaced by each item’s `overflowBehavior`:

```kotlin

class CustomActionsActivity : PdfActivity() {

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

        setToolbarItemsProvider { defaultItems ->
            val customItems = listOf(
                ToolbarItem.Button(
                    actionId = "my.app.action1",           // Stable string, survives process death.
                    id = R.id.custom_action1,               // `R.id` used for clicks and test tags.
                    contentDescription = "Menu Item 1",     // Accessibility label + overflow text.
                    icon = ToolbarIcon.Resource(R.drawable.ic_arrow_left),
                    overflowBehavior = ToolbarItem.OverflowBehavior.AlwaysShow,
                ),
                ToolbarItem.Button(
                    actionId = "my.app.action2",
                    id = R.id.custom_action2,
                    contentDescription = "Menu Item 2",
                    icon = ToolbarIcon.Resource(R.drawable.ic_arrow_right),
                ),
            )
            // Insert after the outline button, exactly like the old `onGenerateMenuItemIds`.
            val outlineIndex = defaultItems.indexOfFirst { it.id == PdfActivity.MENU_OPTION_OUTLINE }
            defaultItems.toMutableList().apply { addAll(outlineIndex + 1, customItems) }
        }

        setOnToolbarItemClickListener { id ->
            when (id) {
                R.id.custom_action1 -> { showToast("Action 1"); true }
                R.id.custom_action2 -> { showToast("Action 2"); true }
                else -> false // `false` = let the SDK handle its default items.
            }
        }
    }
}

```

Key concepts of the item model (`com.pspdfkit.compose.toolbar.state`):

- **`ToolbarItem.Button`** — A plain action. `Toggle` has a selected state (annotation tools). `Submenu` groups nested `children`, which the contextual and floating toolbars open as a menu.

- The main toolbar renders a `Submenu` as a single button that reports its own `id` to the click listener. It never shows the children, so build main toolbar menus from separate items.

- **`actionId: String`** — A stable identity for dispatch and state save/restore. It survives process death even if the `R.id` integer changes between SDK versions. Namespace custom ones (`"my.app.sign"`).

- **`id: Int`** — The legacy `R.id`, used by the click listener and auto-derived test tags.

- **`state: ToolbarItemState`** — `isEnabled`/`isSelected`/`isVisible`. To disable or hide a default item, return a `copy` with a new state instead of mutating a `MenuItem`:

  ```kotlin

  setToolbarItemsProvider { defaultItems ->
      defaultItems.map { item ->
          if (item.id == PdfActivity.MENU_OPTION_EDIT_ANNOTATIONS) {
              item.copyWithState(item.state.copy(isVisible = false))
          } else item
      }
  }
  ```

- **`overflowBehavior`** — `AlwaysShow` (≈ `SHOW_AS_ACTION_ALWAYS`), `IfRoom` (≈ `SHOW_AS_ACTION_IF_ROOM`), `Never` (pinned to the overflow menu). No pregrouping needed.

- **`position: ItemEdge`** — `Start`/`End` edge grouping.

- **`icon: ToolbarIcon`** — `Resource(id, tint)`, `Vector(image, tint)`, `Composed { … }` for dynamic icons (such as a color-preview circle), or `None` for text-only.

### Dynamic updates

To refresh the toolbar when your state changes, call the new invalidation method instead of `invalidateOptionsMenu()`:

```kotlin

// Old: state changed → `invalidateOptionsMenu()`.
invalidateOptionsMenu()

// New: state changed → re-run the provider.
invalidateToolbarItems()

```

## PdfUiFragment in a custom activity

A `PdfUiFragment` hosted in your own activity used the same options-menu model and now uses the same items API as `PdfActivity`.

### Old

The hosting activity implemented `OnToolbarMenuChangedListener` (auto-discovered in `onAttach`) or set it explicitly, then mutated the `Menu`:

```kotlin

class MyActivity : AppCompatActivity(), OnToolbarMenuChangedListener {
    override fun onCreateToolbarMenu(menu: Menu) {
        menu.add(0, R.id.custom_action1, 0, "Share").setIcon(R.drawable.ic_share)
    }
    override fun onPrepareToolbarMenu(menu: Menu) {
        menu.findItem(R.id.custom_action1)?.isEnabled = documentLoaded
    }
    override fun onToolbarMenuItemClick(item: MenuItem): Boolean =
        if (item.itemId == R.id.custom_action1) { share(); true } else false
}

```

### New

`OnToolbarMenuChangedListener` is gone (including the `onAttach` auto-discovery). Use the same items API — it lives on `PdfUi`, so it’s identical on `PdfActivity` and `PdfUiFragment`:

```kotlin

val pdfUiFragment = supportFragmentManager.findFragmentByTag(TAG) as PdfUiFragment
pdfUiFragment.setToolbarItemsProvider { defaultItems ->
    defaultItems + ToolbarItem.Button(
        actionId = "my.app.share",
        id = R.id.custom_action1,
        contentDescription = "Share",
        icon = ToolbarIcon.Resource(R.drawable.ic_share),
    )
}
pdfUiFragment.setOnToolbarItemClickListener { id ->
    if (id == R.id.custom_action1) { share(); true } else false
}
// When your own state changes (the old `onPrepareToolbarMenu` moment):
pdfUiFragment.invalidateToolbarItems()

```

### Navigation icon

`findViewById(R.id.pspdf__toolbar_main)` returns `null` now: The SDK no longer inflates a `Toolbar`, so `setNavigationIcon` and `setNavigationOnClickListener` on it have no target. The navigation (up or back) icon is host state on `PdfUi`, identical on `PdfActivity` and `PdfUiFragment`:

```kotlin

pdfUiFragment.setToolbarNavigationIcon(ToolbarIcon.Resource(R.drawable.pspdf__ic_arrow_back), "Back")
pdfUiFragment.setOnToolbarNavigationClickListener { finish() }
// Remove it again:
pdfUiFragment.setToolbarNavigationIcon(null, null)

```

The icon is rendered at the leading edge as a standard toolbar item, with the 48dp target, the theme tint, and a tooltip. Passing `null` as the label falls back to AppCompat’s localized “Navigate up,” the same label the View `Toolbar` gave its navigation button. It survives configuration changes and the fragment’s own implementation swaps like the items provider does. The SDK has no default navigation behavior: Without a listener, the tap does nothing. Underneath, the icon lives on `ToolbarFrame.navigationIcon`, set through `ToolbarCoordinator.setMainToolbarNavigationIcon`. A tap dispatches `ToolbarAction.NavigationClicked`, which `ToolbarCoordinator.addNavigationClickHandler` wraps for Java callers.

### Hiding the toolbar without the SDK undoing it

The SDK rewrites the `ToolbarCoordinator.setMainToolbarVisible` flag on every visibility recompute (immersive mode, inline search, a contextual bar docking on top). A host’s hide through it is therefore gone at the next toggle. `setMainToolbarHostVisible(false)` is the host’s veto and replaces `MainToolbar.setForcedVisibility(GONE)`. What is published is the SDK’s request ANDed with it, and lifting the veto restores whatever the SDK last requested. Use it for a runtime hide; `defaultToolbarEnabled(false)` remains the configuration-time way to have no SDK toolbar at all.

```kotlin

val coordinator = pdfUiFragment.nutrientToolbarCoordinator()
coordinator.setMainToolbarHostVisible(false)

```

## Compose DocumentView hosts

Previously, the Compose `DocumentView` had no way to customize the main toolbar (the old experimental `com.pspdfkit.jetpack.compose.components.MainToolbar` composable is deleted). Now `DocumentState` exposes the same API, safe to call before the `DocumentView` attaches:

```kotlin

val documentState = rememberDocumentState(uri, configuration)

LaunchedEffect(documentState) {
    documentState.setToolbarItemsProvider { defaultItems -> defaultItems + myCustomItem }
    documentState.setOnToolbarItemClickListener { id ->
        if (id == R.id.custom_action1) { doThing(); true } else false
    }
    // The SDK-rendered bar's navigation icon; a host composing its own `NutrientMainToolbar`
    // fills the `navigationIcon` slot instead.
    documentState.setToolbarNavigationIcon(ToolbarIcon.Resource(R.drawable.ic_back), "Back")
    documentState.setOnToolbarNavigationClickListener { onBack() }
}
// Later, when state feeding the provider changes.
documentState.invalidateToolbarItems()

```

### If you used the Compose toolbar preview in 11.6 or 11.7

11.6 and 11.7 shipped an early public version of the Compose toolbar API. Its signatures changed in 12.0:

- `NutrientMainToolbar`’s `customTitle` and `customActions` parameters are removed. Use the `title` and `actions` slots described in the [slots and chrome](#slots-and-chrome) guide.

- `ToolbarIcon.Composed.content` now receives the tint the toolbar would apply in the item’s current state, `@Composable (tint: Color) -> Unit`, so a composed icon can match the normal, selected, and disabled colors.

- `ToolbarItem.Button` now defaults to `position = ItemEdge.End`, where 11.7 defaulted to `Start`. Pass `position = ItemEdge.Start` to keep a button on the leading edge.

- `NutrientToolbarLayout` takes an `ImmutableList<ToolbarItem>`; convert with `toImmutableList()`.

- `NutrientToolbarItemButton` gains the `onLongClick`, `invertedSelection`, `toolbarPosition`, and `indication` parameters listed in the [new API reference](#new-api-reference).

### Rendering the toolbar yourself

With `PdfActivityConfiguration.Builder.defaultToolbarEnabled(false)`, the host can place the toolbar anywhere. `DocumentState.toolbarCoordinator` publishes the fragment-scoped `ToolbarCoordinator` once the UI is ready (`null` until then):

```kotlin

val coordinator by documentState.toolbarCoordinator.collectAsState()

Box(Modifier.fillMaxSize()) {
    DocumentView(documentState = documentState, modifier = Modifier.fillMaxSize())

    coordinator?.let { c ->
        NutrientTheme(colorScheme = myColors()) {   // Themes items, title, and the overflow popup.
            NutrientMainToolbar(
                coordinator = c,
                modifier = Modifier.fillMaxWidth(),      // The bar is as wide as its content unless given a width.
                windowInsets = WindowInsets(0, 0, 0, 0), // Host already handles the status bar.
            )
        }
    }
}

```

The coordinator also exposes `searchSession` (inline-search activity as a flow) and `frame` (visibility that flips with immersive mode). Custom chrome can observe both to animate alongside the toolbar. See `MainToolbarCustomizationExample` in the Catalog app for a complete toolbar built this way, and `ComposePdfViewerExample` for a full Compose app hosting the bar over `DocumentView`.

### Slots and chrome

`NutrientMainToolbar` has the shape of Material 3’s `TopAppBar`. It takes `navigationIcon`, `title`, `actions`, and `searchBar` slots, or one `content` lambda for a bar that isn’t “navigation, title, actions” at all. It also takes flat `shape`, `shadowElevation`, `windowInsets`, `colors`, `itemIndication`, `enter`, and `exit` parameters, all defaulting to `NutrientMainToolbarDefaults`. The SDK’s own pieces are public composables (`NutrientMainToolbarTitle`, `NutrientToolbarItems`, `NutrientToolbarSearchBar`, and `NutrientMainToolbarContent`), so composing your own content never means giving up the coordinator’s items:

```kotlin

// A back button in front of the SDK title and items, drawn as a floating pill.
NutrientMainToolbar(
    coordinator = c,
    navigationIcon = {
        IconButton(onClick = onBack) {
            Icon(painterResource(R.drawable.ic_back), contentDescription = "Back", tint = UiTheme.colors.mainToolbar.iconColor)
        }
    },
    shape = NutrientMainToolbarDefaults.FloatingShape,
    shadowElevation = NutrientMainToolbarDefaults.FloatingShadowElevation,
    windowInsets = NutrientMainToolbarDefaults.NoWindowInsets,
    itemIndication = ToolbarItemIndication(shape = CircleShape),
)

// A bar that is not "navigation, title, actions": lay the SDK pieces out yourself.
NutrientMainToolbar(coordinator = c, modifier = Modifier.align(Alignment.TopEnd)) {
    NutrientToolbarItems(c, Modifier.weight(1f))
    NutrientMainToolbarTitle(c)
}

```

A few rules follow from that shape:

- `navigationIcon` defaults to `NutrientMainToolbarNavigationIcon(coordinator)`, which renders the icon set through `setToolbarNavigationIcon` and dispatches `ToolbarAction.NavigationClicked`. A composed slot replaces it.

- `title`, `actions`, `searchBar`, and `content` run in a `NutrientToolbarScope`, whose `weight()` and `align()` modifiers work like `RowScope`’s. The default `actions` carry `Modifier.weight(1f)`, so trailing items pin to the far edge of a full-bleed bar.

- `searchBar` replaces the slots while inline search is open, whatever the host composed. Its default is the SDK search bar filling the width.

- The toolbar is as wide as its content, the way `Row` is, and a width modifier wins. `Modifier.fillMaxWidth()` is the full-bleed bar the SDK’s own mount passes. Passing nothing gives a floating pill exactly as wide as its icons. The toolbar animates width changes itself; don’t add `Modifier.animateContentSize()`, which clips its shadow.

- `modifier` is applied to the outermost node, the show/hide animation, so `BoxScope.align`, padding, and `onSizeChanged` work from the call site. The subtree is removed once the toolbar has animated away. A modifier that must reserve space while the bar is hidden belongs on a container around it.

- A Material `Surface` draws `shape`, `shadowElevation`, and `windowInsets` inside that animation, so a pill slides away with the bar. A tap that lands on the toolbar but misses every button stops there instead of reaching the document.

- `colors` and `itemIndication` are provided to everything inside the bar, so slot content reads the same scheme through `UiTheme.colors.mainToolbar`. Every item, the SDK’s and yours, shows its pressed, hovered, and selected states with the same `ToolbarItemIndication`. The default is the 12dp round rect the View toolbar drew its selection with, now also bounding the ripple.

## The toolbar is no longer an ActionBar/View

- `com.pspdfkit.ui.toolbar.MainToolbar` (the View subclass) is deleted. Code that called `findViewById` on it, passed it to `setSupportActionBar`, or themed it as an `androidx.appcompat.widget.Toolbar` must move to the items/theming APIs above.

- `getSupportActionBar()` in a `PdfActivity` subclass no longer returns the main toolbar’s `ActionBar`. `ActionBar`-based title/home-button tweaks don’t apply to the Compose toolbar. Set the home or up button with `setToolbarNavigationIcon` + `setOnToolbarNavigationClickListener` (see [navigation icon](#navigation-icon)). For a runtime hide the SDK doesn’t undo, use `ToolbarCoordinator.setMainToolbarHostVisible(false)`, which replaces `MainToolbar.setForcedVisibility(GONE)`.

- `com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout` is deleted along with the main toolbar. Toolbar placement, docking, and drag chrome are owned by the Compose toolbar scaffold in every host. See [`ToolbarCoordinatorLayout` is removed](#toolbarcoordinatorlayout-is-removed) for custom layouts.

The contextual toolbars (annotation creation, document editing, content editing) moved to Compose as well and are covered in [their own section](#contextual-toolbars). The popup toolbars (`TextSelectionPopupToolbar` for selected text and `AnnotationPopupToolbar` for a selected annotation, customized through `OnPreparePopupToolbarListener`) are unchanged.

## Inline search

Inline search is now a Compose bar rendered inside the main toolbar’s own composition — it’s no longer a `View` in the activity layout:

- `com.pspdfkit.ui.search.PdfSearchViewInline` is deleted.

- For `SearchType.INLINE`, `PSPDFKitViews.getSearchView()` now returns `null`. `SearchType.MODULAR` is unchanged (`PdfSearchViewModular`/`PdfSearchViewLazy` still exist).

- Inline search no longer reports as `VIEW_SEARCH`: `getActiveViewType()` returns `VIEW_NONE` while the inline bar is open.

- Inline search state (query, active result) now survives rotation.

### Old

Code that observed the inline search View looked like this:

```kotlin

val searchView = pspdfKitViews.searchView
if (searchView is PdfSearchViewInline) {
    searchView.addOnVisibilityChangedListener(object : OnVisibilityChangedListener {
        override fun onShow(view: View) { onSearchOpened() }
        override fun onHide(view: View) { onSearchClosed() }
    })
}
// or:
val searchOpen = activeViewType == PSPDFKitViews.Type.VIEW_SEARCH

```

### New

Read the search state directly on `PdfUi`, or observe it through the coordinator in Compose hosts:

```kotlin

// `PdfActivity`/`PdfUiFragment` (`PdfUi`):
val searchOpen = isInlineSearchActive()

// Compose hosts — observe it reactively via the coordinator:
val searchSession by coordinator.searchSession.collectAsState()
val searchActive = searchSession?.isActive?.collectAsState()?.value == true

```

For a real search View with a rich API, switch to `SearchType.MODULAR`; for headless search, use `com.pspdfkit.document.search.TextSearch`.

## Document title overlay

The title overlay (`pspdf__activity_title_overlay`) is no longer a standalone View strip below the toolbar — it renders inside the main toolbar’s composition.

Behavior changes:

- It follows the main toolbar’s color theme (`DocumentTitleOverlayColors`, part of `MainToolbarColors` — defaults to the toolbar background/title colors).

- It’s now shown in portrait as well (previously landscape-only).

- No blank strip is reserved when the document has no title.

`PSPDFKitViews.getDocumentTitleOverlayView()` is removed, since the SDK no longer has a `TextView` to return. `PdfActivityConfiguration.Builder.documentTitleOverlayEnabled(...)` works as before. Custom Compose hosts replace the title area through the `title` slot, `NutrientMainToolbar(title = { … })`. They put a back button in front of it with `navigationIcon`.

## Theming

XML theme attributes (`pspdf__actionBarIconsStyle`, toolbar background/title attributes, …) remain the default and keep working — the Compose toolbar reads them via `getUiColors()`. The sections below cover the programmatic and Compose theming APIs that sit on top.

### Programmatic theming from PdfActivity

Set a color scheme on the activity to override the XML attributes at runtime:

```kotlin

// `null` clears the override and falls back to the XML attrs.
setMainToolbarColorScheme(myUiColorScheme)

```

The scheme colors the toolbar row, title, title overlay, and the overflow/submenu popup:

```kotlin

val scheme = defaults.copy( // e.g. `getUiColors()` captured in a composition, or a fully built `UiColorScheme`.
    mainToolbar = MainToolbarColors(
        backgroundColor = Color(0xFF1E2A44),
        textColor = Color.White,
        popup = ToolbarPopupColors(backgroundColor = Color(0xFF1E2A44)),
        titleTextColor = Color.White,
        // `iconColor` defaults to `textColor`; `iconSelectedColor`/`iconDisabledColor` derive from it.
    ),
)

```

### Compose hosts

Wrap the toolbar in `NutrientTheme` (see the section on rendering the toolbar yourself above). `getUiColors()` gives the XML-derived defaults, and `getComposeUiColors()` gives the pure-Compose defaults; `copy` the `mainToolbar` block.

## Inset management — New API

Previously, the toolbar assumed it was mounted edge-to-edge at the top of the window and reserved a status-bar strip unconditionally. In activities with custom layouts (a `DrawerLayout` wrapping the activity layout, or content positioned below the status bar), this desynced: Opening inline search clipped the search bar, and toolbars shifted. The toolbar now derives the reserved strip from its actual window position, so SDK-mounted hosts need no changes and get correct behavior. It also reserves the side system bars. In landscape with a three-button navigation bar, or beside a display cutout, the trailing overflow item no longer renders under the system buttons. Both insets are part of `NutrientMainToolbarDefaults.windowInsets(coordinator)`, so a host that passes its own `windowInsets` owns the side insets too.

### Controlling insets explicitly

`ToolbarInsets` (in `com.pspdfkit.compose.toolbar.state`) describes what the toolbar reserves:

- `ToolbarInsets.StatusBars` — The default; correct for edge-to-edge top mounting.

- `ToolbarInsets.None` — The toolbar sits away from the top edge or the host already spaces it.

- `ToolbarInsets.Custom(top, bottom, start, end)` — Fixed `Dp` insets; `ToolbarInsets.fixedTop(topPx, density)` is the Java-friendly factory.

Two ways to apply:

```kotlin

// 1. Host-agnostic — works for the SDK-mounted toolbar too (`PdfActivity`/`PdfUiFragment`,
//    e.g. an embedded `PdfFragment` sitting below other chrome):
coordinator.setMainToolbarInsets(ToolbarInsets.None)

// 2. Compose-only — parameter on the composable; overrides (1) when set:
NutrientMainToolbar(coordinator = c, windowInsets = WindowInsets(0, 0, 0, 0))

```

### Document viewport behavior change

The document no longer shifts when the inline search bar opens or content-editing mode starts. The viewport is only inset while UI actually overlaps it (content-editing styling bar, keyboard). If your integration compensated for the old jump (manual padding/scroll offsets around `SearchType.INLINE` or content editing), remove that workaround.

## Contextual toolbars

One generic `NutrientContextualToolbar`, driven by a `ToolbarCoordinator`, renders the annotation creation, document editing, and content editing toolbars in Jetpack Compose. The coordinator is scoped to its host: `ComponentActivity.nutrientToolbarCoordinator()` returns the activity’s, `Fragment.nutrientToolbarCoordinator()` returns the fragment’s, and a `PdfUiFragment` uses its own, so ask the host that shows the bar. In a `PdfActivity` or `PdfUiFragment`, the bar mounts automatically and is never constructed by the integration. Everything that used to be done on a toolbar _instance_ is done on the coordinator instead.

### What was removed

- `com.pspdfkit.ui.toolbar.ContextualToolbar` (the base class), `AnnotationToolbar`, `DocumentEditingToolbar`, `ContentEditingToolbar`, `ContextualToolbarMenuBar`, `ContextualToolbarSubMenu`, and `ToolbarCoordinatorLayoutController`.

- `ToolbarCoordinatorLayout.OnContextualToolbarLifecycleListener`, `OnContextualToolbarMovementListener`, and `OnContextualToolbarPositionListener`, and their setters on `PdfActivity`, `PdfUiFragment`, `PdfUi`, and `DocumentState`.

- `ToolbarCoordinatorLayout.displayContextualToolbar`/`removeContextualToolbar`/`getCurrentlyDisplayedContextualToolbar`/`attachContextualToolbar`/`detachContextualToolbar`.

- `AnnotationToolbar.ItemToAnnotationToolMapper` and `AnnotationToolbar.setShouldShowStylusButton()`.

- The `PSPDFKitPreferences.getLastToolbarPosition`/`setLastToolbarPosition` overloads that took a `ContextualToolbar`. The ID-based overloads remain, retyped to `ToolbarDockPosition` (see [preferences](#preferences)).

- `ContextualToolbar.MIN_TOOLBAR_CAPACITY` moved to `PresetMenuItemGroupingRule.MIN_TOOLBAR_CAPACITY`.

`ContextualToolbarMenuItem`, `MenuItemGroupingRule`, `PresetMenuItemGroupingRule`, and `DefaultMenuItemGroupingRule` still exist but are deprecated. They only carry the grouping-rule bridge described below and are removed with it in the next major version.

### Getting the coordinator

Every contextual toolbar customization starts from the host’s coordinator:

```kotlin

import com.pspdfkit.jetpack.compose.toolbar.nutrientToolbarCoordinator

// In any `ComponentActivity` or `Fragment` host:
val coordinator = nutrientToolbarCoordinator()

// Compose-first hosts collect it from the document state instead (`null` until the UI is ready):
val coordinator by documentState.toolbarCoordinator.collectAsState()

```

Every `add…` method on the coordinator returns a `ToolbarSubscription`, which is `AutoCloseable`. Close it in the host’s teardown callback.

### Reacting to a contextual toolbar showing or hiding

The lifecycle listener is replaced by a listener on the coordinator’s contextual state:

```kotlin

// Before — the lifecycle listener:
setOnContextualToolbarLifecycleListener(object : ToolbarCoordinatorLayout.OnContextualToolbarLifecycleListener {
    override fun onDisplayContextualToolbar(toolbar: ContextualToolbar<*>) { toolbarShown = true }
    override fun onRemoveContextualToolbar(toolbar: ContextualToolbar<*>) { toolbarShown = false }
    override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) = Unit
})

// After — observe the coordinator's contextual state; `null` means no bar is shown.
val subscription = coordinator.addContextualToolbarChangeListener { state ->
    toolbarShown = state!= null
    when (state?.kind) {
        ContextualToolbarKind.Annotation -> { /* Annotation creation bar. */ }
        ContextualToolbarKind.DocumentEditing -> { /* Document editing bar. */ }
        ContextualToolbarKind.ContentEditing -> { /* Content editing bar. */ }
        null -> { /* Hidden. */ }
    }
}

```

Coroutine hosts can collect `coordinator.contextualToolbar` (a `StateFlow<ContextualToolbarState?>`) instead. The state also carries the bar’s `items`, its `position`, and whether it `isDraggable`, so the movement and position listeners have the same replacement: `state.position` changes when the user docks the bar elsewhere. Drag start and end callbacks (`OnContextualToolbarMovementListener`) have no public replacement in SDK hosts; a host rendering its own bar receives them through `NutrientContextualToolbarDragListener`.

### Customizing items and handling clicks

Items are immutable state on the coordinator rather than mutable `ContextualToolbarMenuItem`s on a View. Transform them with `updateContextualItems`, and observe taps on the coordinator’s action bus:

```kotlin

// Before:
override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) {
    if (toolbar is AnnotationToolbar) {
        toolbar.setMenuItems(toolbar.menuItems + myCustomItem)
        toolbar.setOnMenuItemClickListener { _, item -> if (item.id == R.id.my_item) { doThing(); true } else false }
    }
}

// After — add the item whenever the annotation bar appears, and listen for the click:
coordinator.addContextualToolbarChangeListener { state ->
    if (state?.kind == ContextualToolbarKind.Annotation && state.items.none { it.id == R.id.my_item }) {
        coordinator.updateContextualItems { items -> items.add(myCustomToolbarItem) }
    }
}
coordinator.addActionListener { action ->
    if (action is ToolbarAction.ItemClicked && action.id == R.id.my_item) doThing()
}

```

Click actions carry the bar they came from in `action.kind` (`null` for the main toolbar). That tells an annotation-tool tap apart from a main-toolbar item with the same ID.

`addActionListener` observes only; the SDK’s own handler always runs too. To _consume_ a click, as returning `true` from the old `OnMenuItemClickListener` did, register an interceptor. It runs before every listener, and returning `true` stops the SDK from seeing the action:

```kotlin

coordinator.addActionInterceptor { action ->
    action is ToolbarAction.ItemClicked &&
        action.kind == ContextualToolbarKind.Annotation &&
        action.id in gatedToolIds // `true` = consumed, the tool is never activated.
}

```

Custom tool items activate their tool directly from the action listener with `fragment.enterAnnotatingMode(tool, variant)`; `ItemToAnnotationToolMapper` has no replacement. Long-press events aren’t exposed on the action bus, because a long press opens a group’s submenu.

To remove an item, filter it out in `updateContextualItems`. This is also how you hide the stylus toggle now that `setShouldShowStylusButton(false)` is gone. It appears automatically whenever a stylus is connected, and filtering out `R.id.pspdf__annotation_toolbar_item_stylus` removes it.

### Grouping

The preset-based grouping rules keep working through a deprecated bridge for one major version. Register the rule once, on the coordinator, instead of on each toolbar instance in `onPrepareContextualToolbar`:

```kotlin

// Before:
override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) {
    if (toolbar is AnnotationToolbar) toolbar.setMenuItemGroupingRule(MyPresetGroupingRule(this))
}

// After — deprecated bridge, scoped to one bar kind:
coordinator.setMenuItemGroupingRule(this, ContextualToolbarKind.Annotation, MyPresetGroupingRule(this))

// Replacement — one pure-data provider for every bar, branching on kind:
coordinator.setContextualToolbarGroupingProvider { kind, capacity, itemCount, defaultPreset ->
    if (kind == ContextualToolbarKind.Annotation) myPreset(capacity) else defaultPreset
}

```

Only the `getGroupPreset()` data of a `PresetMenuItemGroupingRule` is honored by the bridge. A custom `groupMenuItems(...)` override operates on View menu items and can’t be bridged; port it to a `ContextualToolbarGroupingProvider` returning `ToolbarPresetEntry` lists.

### Docking and draggability

Docking and draggability moved from the toolbar instance to the coordinator:

```kotlin

// Before — layout params and setters on the toolbar instance:
toolbar.setDraggable(false)
toolbar.layoutParams = ToolbarCoordinatorLayout.LayoutParams(Position.LEFT, EnumSet.of(Position.LEFT))

// After — coordinator-level, applies to the bar that is currently shown:
coordinator.setContextualToolbarPosition(ToolbarPosition.Start)
coordinator.setContextualToolbarDraggable(false)

```

`ToolbarPosition` is logical (`Top`, `Start`, `End`). It resolves against the layout direction, so a bar docked at `Start` sits on the right in a right-to-left layout. Both setters are silent no-ops while no contextual bar is shown, so call them from an `addContextualToolbarChangeListener` callback. The user-dragged position persists across sessions in SDK hosts, exactly as the View bar’s did.

To restrict which docks the user may drag a contextual bar to, what `ToolbarCoordinatorLayout.LayoutParams.allowedPositions` did, call `coordinator.setContextualToolbarAllowedPositions(listOf(ToolbarPosition.Top))`. It holds across every show and mode change, and applies to the shown bar and every later one. The host can only narrow the set. A bar’s own set is intersected with the host’s, and an empty intersection keeps the bar’s own docks so nothing becomes undockable. A bar sitting on a forbidden dock moves to the first allowed one. Passing `null` clears the restriction.

### Preferences {#preferences}

The physical dock (`TOP`, `LEFT`, `RIGHT`) moved from `ToolbarCoordinatorLayout.LayoutParams.Position` to the top-level `com.pspdfkit.ui.toolbar.ToolbarDockPosition`, with identical constants and ordinals, so persisted preferences restore unchanged:

```kotlin

// Before:
PSPDFKitPreferences.get(context).setLastToolbarPosition(R.id.pspdf__document_editing_toolbar, ToolbarCoordinatorLayout.LayoutParams.Position.TOP)

// After:
PSPDFKitPreferences.get(context).setLastToolbarPosition(R.id.pspdf__document_editing_toolbar, ToolbarDockPosition.TOP)

```

### Self-hosted toolbars with a plain PdfFragment

Hosts that build their own activity around a `PdfFragment` used to construct an `AnnotationToolbar` and hand it to `ToolbarCoordinatorLayout.displayContextualToolbar`. Now they mount the SDK’s Compose toolbar stack and bind it to the fragment:

```kotlin

// Before:
val toolbar = AnnotationToolbar(context)
toolbarCoordinatorLayout.displayContextualToolbar(toolbar, true)

// After — mount the toolbar stack (drag handle, dock slots, dock persistence included)
// and show the annotation bar whenever the fragment enters annotating mode:
val coordinator = nutrientToolbarCoordinator()
toolbarHostContainer.addView(nutrientToolbarsComposeView(this, coordinator))
val annotationToolbar = AnnotationToolbarBinder(coordinator)
fragment.addOnAnnotatingModeChangeListener(object : OnAnnotatingModeChangeListener {
    override fun onEnterAnnotatingMode(controller: AnnotatingController) = annotationToolbar.attach(controller)
    override fun onExitAnnotatingMode(controller: AnnotatingController) = annotationToolbar.detach()
    override fun onChangeAnnotatingMode(controller: AnnotatingController) = Unit
})

```

`AnnotationToolbarBinder.position` and `isDraggable` apply on every `attach()`. A host that also renders main toolbar items pairs this with `PdfFragmentToolbarBinder`. The binder keeps the coordinator’s undo, redo, and annotation tool items (`pspdf.undo`, `pspdf.redo`, `pspdf.annotation.{tool}` action IDs) in sync with the fragment. Call `attach(fragment)` once the fragment is attached and `detach()` when it goes away. Pass `showMainToolbar = true` to `nutrientToolbarsComposeView` to render the SDK’s main toolbar in the same stack.

Custom hosts own docking. The binders restore the last-used dock position, but only the host places the bar. `nutrientToolbarsComposeView` provides the same top, start, and end dock slots and drop-zone chrome `PdfActivity` has. A host that mounts `nutrientContextualToolbarComposeView` in its own container instead must either implement docking or pin the position (`binder.position = ToolbarPosition.Top`). Otherwise, a restored `Start` or `End` dock renders a vertical bar at the container’s origin with no way to move it. The drag handle only shows when the mount is passed a `NutrientContextualToolbarDragListener`.

Self-hosted setups also persist the dock themselves: Observe `coordinator.contextualToolbar`, store `state.position`, and set the binder’s `position` before the next `attach()`.

### Rendering the contextual bar yourself

A host that sets `defaultContextualToolbarEnabled(false)` and draws the contextual bar itself composes `NutrientContextualToolbar(coordinator)`. It follows the same `TopAppBar` shape as the main toolbar. It has a `closeButton` slot, whose `NutrientContextualToolbarScope` exposes the bar’s `state` and a `dispatch` that stamps the action with the bar’s kind. It also has flat `shape`, `border`, `sideDockedShape`, `sideDockedBorder`, `shadowElevation`, `windowInsets`, `colors`, `itemIndication`, `enter`, and `exit` parameters defaulting to `NutrientContextualToolbarDefaults`. Two shape and border pairs cover the bar’s two looks: a strip docked at the top and a rounded rail docked at a side.

```kotlin

NutrientContextualToolbar(
    coordinator = c,
    modifier = Modifier.align(Alignment.BottomCenter).padding(16.dp),
    shape = RoundedCornerShape(28.dp),
    sideDockedShape = RoundedCornerShape(28.dp),
    shadowElevation = 8.dp,
    colors = NutrientContextualToolbarDefaults.colors().copy(backgroundColor = MaterialTheme.colorScheme.primaryContainer),
)

```

A top-docked bar is as wide as its items unless given a width. Pass `Modifier.fillMaxWidth()` for the edge-to-edge strip `PdfActivity` shows, and nothing for a pill in a corner. A side-docked bar is always `NutrientContextualToolbarDefaults.ToolbarSize` thick. The default `windowInsets` are none, because `NutrientToolbarScaffold` and the SDK’s own mount already place the bar inside them. Pass a `dragListener` to show the drag handle; a bar that offers no docking shows none. `CustomToolbarsDocumentViewExample` and `CustomToolbarsPdfActivityExample` in the Catalog app render the bar this way.

### Theming

The generic `pspdf__contextualToolbarStyle` (background, border, icon, and activated-icon colors, submenu colors) themes every Compose contextual toolbar, as it did the View bars. The per-bar styleables are no longer read: `pspdf__annotationCreationToolbarIconsStyle`/`pspdf__AnnotationCreationToolbarIcons` (per-tool icons, `pspdf__iconsColor`, `pspdf__iconsColorActivated`, `pspdf__undoIcon`, `pspdf__redoIcon`), `pspdf__DocumentEditingToolbarIcons`, and `pspdf__ContentEditingToolbar`. Per-item icon replacement goes through `updateContextualItems`, swapping the item for a copy with a different `ToolbarIcon`.

`pspdf__toolbarCoordinatorLayoutStyle` (drag-target color, contextual toolbar height) outlives the deleted View class. The Compose toolbar scaffold reads the same attribute and styleable, so existing themes keep working.

### UI tests

The bars have no View IDs. `onView(withId(R.id.pspdf__annotation_toolbar))` and the per-item ID matchers find nothing. Compose test tags carry the same resource entry names, and the bar’s surface is tagged `pspdf__contextual_toolbar`:

```kotlin

// Before:
onView(withId(R.id.pspdf__document_editing_toolbar_item_done)).perform(click())

// After:
composeTestRule.onNodeWithTag("pspdf__document_editing_toolbar_item_done").performClick()

```

## Text selection toolbar

The deprecated docked `com.pspdfkit.ui.toolbar.TextSelectionToolbar` is removed. The popup toolbar (`com.pspdfkit.ui.toolbar.popup.TextSelectionPopupToolbar`), the default since it was introduced, is the only text selection toolbar.

- `textSelectionPopupToolbarEnabled()` on `PdfConfiguration.Builder` and `PdfActivityConfiguration.Builder`, and `PdfConfiguration.isTextSelectionPopupToolbarEnabled`, are removed. They could only mean `true` once the fallback was gone. Delete the calls.

- Customizing the popup toolbar is unchanged: `setOnPreparePopupToolbarListener` and `onPrepareTextSelectionPopupToolbar` keep working. See `PopupToolbarCustomItemsExample` in the Catalog app.

- `pspdf__textSelectionToolbarIconsStyle` and `PSPDFKit.TextSelectionToolbarIcons` only themed the removed View toolbar and are removed with it. Colors come from `pspdf__popupToolbarStyle` (`pspdf__backgroundColor`, `pspdf__itemTint`, `pspdf__itemTintDisabled`). Per-action icon overrides such as `pspdf__copyIcon` have no theme equivalent. In the prepare listener, replace the `PopupToolbarMenuItem` with one carrying the drawable instead.

- Espresso: The `pspdf__text_selection_toolbar` ID is unchanged, but the toolbar lives in a popup window, so match it with `inRoot(isPlatformPopup())`.

- Behavior: The removed View toolbar forced the user interface visible while text was selected. The popup toolbar keeps immersive mode active, matching the annotation selection popup.

## ToolbarCoordinatorLayout is removed

`com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout` and its `LayoutParams` are deleted. The Compose toolbar scaffold owns placement, docking, drag chrome, and the content inset in every host.

Custom activity layouts passed through `PdfActivityConfiguration.Builder.layout()` provide a `ComposeView` with the ID `pspdf__toolbar_compose_host` instead of the coordinator layout. It overlays the content view:

```xml

<!-- Before: -->

<com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout
    android:id="@+id/pspdf__toolbar_coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- After: -->

<androidx.compose.ui.platform.ComposeView
    android:id="@+id/pspdf__toolbar_compose_host"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

```

Custom `PdfFragment` hosts that called `toolbarCoordinatorLayout.bindNutrientContextualToolbar(coordinator)` add `nutrientToolbarsComposeView(context, coordinator)` to their container instead; see [self-hosted toolbars](#self-hosted-toolbars-with-a-plain-pdffragment).

`PdfActivity` no longer pads the document for a contextual toolbar when `defaultContextualToolbarEnabled(false)` is set and the host renders the contextual toolbar itself.

## Floating toolbars

New in 12.0, for hosts that draw their own chrome: `NutrientFloatingToolbar` renders the main toolbar’s items as a Material 3 Expressive floating pill, and `NutrientFloatingContextualToolbar` does the same for whichever contextual bar is shown. The `ToolbarCoordinator` drives both. You can drag and dock them to any window edge, and they lay out horizontally or vertically depending on the edge and animate with springs.

```kotlin

val coordinator by documentState.toolbarCoordinator.collectAsState()

Box(Modifier.fillMaxSize()) {
    DocumentView(documentState = documentState, modifier = Modifier.fillMaxSize())
    coordinator?.let { c ->
        NutrientFloatingToolbar(
            coordinator = c,
            state = rememberFloatingToolbarState(initialEdge = FloatingToolbarEdge.Bottom),
            collapsible = true, // Shrinks to the active item, expands on tap.
        )
        NutrientFloatingContextualToolbar(coordinator = c)
    }
}

```

`FloatingToolbarState` holds the current `edge`, and `allowedEdges` restricts where the pill can dock. The contextual pill also honors the bar’s own allowed docks and its close button placement. Items declared with `OverflowBehavior.Never` stay in the pill’s overflow menu. A selected item, such as the item of an open outline or an active tool, renders with a filled `primary` container. `NutrientFloatingToolbarItemButton`, `NutrientFloatingToolbarMenuItem`, and `NutrientFloatingToolbarSearchTheme` are the building blocks for a host laying out its own pills from coordinator items. Pair the composables with `defaultToolbarEnabled(false)` and `defaultContextualToolbarEnabled(false)` so the SDK doesn’t also draw its docked bars. See `MainToolbarFloatingExample` in the Catalog app. `MainToolbarFabMenuExample` shows the same coordinator items as a Material 3 Expressive floating action button (FAB) menu.

### Status bar scrim

A Compose host that disables the SDK main toolbar and floats its own pill below the status bar leaves the strip above it uncovered. The document then scrolls under the clock and system icons. `NutrientDocumentStatusBarScrim` blurs (API 31 and later) and tints that strip. It samples the document through a backdrop that `Modifier.nutrientBackdropSource` records on the `DocumentView`. It decides for itself when to show. It stays off while the SDK toolbar is mounted, in immersive mode, or while the page sits below the strip. It fades in as the page scrolls up into it.

```kotlin

val backdrop = rememberNutrientBackdropState()

Box(Modifier.fillMaxSize()) {
    // Must be the first child: the scrim samples whatever drew before it.
    DocumentView(documentState = documentState, modifier = Modifier.fillMaxSize().nutrientBackdropSource(backdrop))
    NutrientDocumentStatusBarScrim(documentState = documentState, state = backdrop, modifier = Modifier.align(Alignment.TopCenter))
    coordinator?.let { NutrientFloatingToolbar(coordinator = it, modifier = Modifier.statusBarsPadding()) }
}

```

The default tint follows the window’s status bar icon appearance, translucent white under dark icons and translucent black under light ones. You can override `color` and `blurRadius` through `NutrientStatusBarScrimDefaults`. `NutrientStatusBarScrim` is the same scrim with an explicit `visible` flag, for chrome that isn’t driven by a `DocumentState`. In `PdfActivity` and `nutrientToolbarsComposeView` hosts, the document is a sibling `View` and can’t be sampled, so the scrim falls back to a flat tint there. `MainToolbarCustomizationExample` mounts it.

## Toolbar behavior changes {#toolbar-behavior-changes}

None of these need code changes in an SDK-hosted integration, but they change what users see, and some of them make a workaround unnecessary.

- **Long-pressing an icon-only button shows a tooltip** naming the action. It appears on the docked bars, overflow and close buttons, submenu children, and floating pills, as the View toolbars did through `TooltipCompat`. The tooltip opens on the bar’s document side, so it never covers the bar or lands offscreen. A host composing its own buttons passes their dock, as in `NutrientToolbarItemButton(toolbarPosition = …)` and `NutrientFloatingToolbarItemButton(edge = …)`. Buttons that already spend their long press, such as a submenu group, show no tooltip.

- **A modifier passed to `NutrientToolbarItemButton` sizes the slot the button is centered in**, not the button itself. An item given an explicit width keeps the standard 48dp touch target and selection background.

- **The system bars stay while a contextual toolbar is onscreen.** Entering annotation creation mode with a side-docked bar, or in a host with `defaultToolbarEnabled(false)`, used to hide the user interface. The status and navigation bars went with it. Meanwhile, the bar the user was working in stayed onscreen. The system bars now follow the contextual toolbar’s presence. This was long-standing behavior of `hideUserInterfaceWhenCreatingAnnotations` meeting `immersiveMode`, both `true` by default. It also changes existing integrations with side-docked annotation toolbars.

- **The document no longer moves when a contextual toolbar appears or changes dock.** With the SDK main toolbar disabled, the document used to be reinset whenever a contextual bar showed or closed. It was also reinset when the user dragged the bar to another edge. Its pages were refit each time too. The content inset now depends only on the SDK’s own main toolbar. The SDK also no longer hides its main toolbar or insets the document for a contextual bar it doesn’t draw. A `defaultContextualToolbarEnabled(false)` host therefore doesn’t get a blank strip, and its status bar and thumbnail bar stay where they are.

- **The toolbar keeps clear of the landscape navigation bar.** With a three-button navigation bar in landscape, the trailing overflow item rendered under the system buttons and couldn’t be tapped. The main toolbar now reserves the side system bar and display cutout insets. It reads them live, so a rotation applies in the same frame. See [inset management](#inset-management--new-api) if you pass your own `windowInsets`.

- **The main toolbar’s items change shape while the page grid is open.** While the document overview is displayed, the row becomes the grid’s own. It holds the document editor item (`PdfActivity.MENU_OPTION_DOCUMENT_EDITOR`), the grid item that closes it, and the overflow-only settings and document info items. The items that act on the document view behind the grid aren’t in it. A `ToolbarItemsProvider` receives this shorter list as its `defaults`. The SDK calls it again on every refresh, including when the grid opens and closes. A provider that indexes into `defaults` must check for the item it sits beside and append when it’s absent.

## Removed API reference

| Removed                                                                                                                                                                                   | Replacement                                                                                                                                          |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `com.pspdfkit.ui.toolbar.MainToolbar`                                                                                                                                                     | Compose toolbar (`NutrientMainToolbar` for custom hosts)                                                                                             |
| `com.pspdfkit.ui.search.PdfSearchViewInline`                                                                                                                                              | Compose inline search; `isInlineSearchActive()`/`SearchType.MODULAR`/`TextSearch`                                                                    |
| `com.pspdfkit.listeners.OnMenuItemsGenerateListener`                                                                                                                                      | `ToolbarItemsProvider`                                                                                                                               |
| `com.pspdfkit.listeners.OnToolbarMenuChangedListener`                                                                                                                                     | `setToolbarItemsProvider` + `setOnToolbarItemClickListener` on `PdfUiFragment`                                                                       |
| `PdfUi.invalidateMenu()`                                                                                                                                                                  | `invalidateToolbarItems()`                                                                                                                           |
| `PdfActivity.onCreateOptionsMenu`/`onPrepareOptionsMenu` overrides (toolbar path)                                                                                                         | `ToolbarItemsProvider`                                                                                                                               |
| `PdfActivity.onGenerateMenuItemIds`                                                                                                                                                       | `ToolbarItemsProvider` (list order)                                                                                                                  |
| `PdfActivity.onGetShowAsAction`/`PdfUiFragment.onGetShowAsAction`                                                                                                                         | `ToolbarItem.overflowBehavior`                                                                                                                       |
| `com.pspdfkit.jetpack.compose.components.MainToolbar` (experimental composable)                                                                                                           | `NutrientMainToolbar` + `DocumentState.toolbarCoordinator`                                                                                           |
| `com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout` and `LayoutParams`, `R.id.pspdf__toolbar_coordinator`                                                                                  | `ComposeView` with ID `pspdf__toolbar_compose_host`; `nutrientToolbarsComposeView`                                                                   |
| `ToolbarCoordinatorLayout.LayoutParams.Position`                                                                                                                                          | `com.pspdfkit.ui.toolbar.ToolbarDockPosition`                                                                                                        |
| `ContextualToolbar`, `AnnotationToolbar`, `DocumentEditingToolbar`, `ContentEditingToolbar`, `ContextualToolbarMenuBar`, `ContextualToolbarSubMenu`, `ToolbarCoordinatorLayoutController` | `ToolbarCoordinator` (`contextualToolbar`, `updateContextualItems`, `addActionListener`, `addActionInterceptor`)                                     |
| `OnContextualToolbarLifecycleListener`, `OnContextualToolbarMovementListener`, `OnContextualToolbarPositionListener`, and their setters                                                   | `ToolbarCoordinator.addContextualToolbarChangeListener`/`contextualToolbar`                                                                          |
| `ToolbarCoordinatorLayout.displayContextualToolbar`/`removeContextualToolbar`/`bindNutrientContextualToolbar`                                                                             | `nutrientToolbarsComposeView` + `PdfFragmentToolbarBinder`/`AnnotationToolbarBinder`                                                                 |
| `AnnotationToolbar.ItemToAnnotationToolMapper`, `setShouldShowStylusButton`                                                                                                               | Action listener + `enterAnnotatingMode`; filter `pspdf__annotation_toolbar_item_stylus`                                                              |
| `PSPDFKitPreferences.get/setLastToolbarPosition(ContextualToolbar, …)`                                                                                                                    | ID-based overloads with `ToolbarDockPosition`                                                                                                        |
| `com.pspdfkit.ui.toolbar.TextSelectionToolbar`, `textSelectionPopupToolbarEnabled()`, `isTextSelectionPopupToolbarEnabled`                                                                | `TextSelectionPopupToolbar` (always used)                                                                                                            |
| `pspdf__textSelectionToolbarIconsStyle`, `PSPDFKit.TextSelectionToolbarIcons`                                                                                                             | `pspdf__popupToolbarStyle`                                                                                                                           |
| `pspdf__annotationCreationToolbarIconsStyle`, `pspdf__DocumentEditingToolbarIcons`, `pspdf__ContentEditingToolbar` (no longer read)                                                       | `pspdf__contextualToolbarStyle`; per-item icons via `updateContextualItems`                                                                          |
| `Toolbar.setNavigationIcon`/`setNavigationOnClickListener` on `R.id.pspdf__toolbar_main` (now `null`)                                                                                     | `setToolbarNavigationIcon`/`setOnToolbarNavigationClickListener` on `PdfUi`, `DocumentState`; `navigationIcon` slot                                  |
| `PSPDFKitViews.getDocumentTitleOverlayView()`                                                                                                                                             | None; the overlay is part of the main toolbar (`title` slot for custom hosts)                                                                        |
| `MainToolbar.setForcedVisibility(GONE)`                                                                                                                                                   | `ToolbarCoordinator.setMainToolbarHostVisible(false)`                                                                                                |
| `ToolbarCoordinatorLayout.LayoutParams.allowedPositions`                                                                                                                                  | `ToolbarCoordinator.setContextualToolbarAllowedPositions(List<ToolbarPosition>?)`                                                                    |
| `RecyclerView` under `R.id.pspdf__tabs_bar_list` (adapters, decorations, `ItemTouchHelper`); `pspdf__tab_item.xml` and its IDs                                                            | `NutrientTabBar`: `TabBarColors`, `NutrientTabBarDefaults.dimensions()`, the `tabContent` slot, `NutrientTabBarDefaults.TabContent(closeButton = …)` |
| `SettingsOptions.snapToPoint`, `snapToSelf`, and `showSmartGuides` — along with their constructor parameters                                                                              | `PSPDFKitPreferences.setMeasurementSnappingEnabled`/`setSnapToSelfEnabled`/`setSmartGuidesEnabled`                                                   |
| `pspdf__settings_preset_still_image_horizontal`, `pspdf__settings_preset_still_image_vertical`                                                                                            | None; the settings presets are removed                                                                                                               |

## New API reference

| API                                                                                                                                                                                                                                                                                                                                                                | Where                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| `setToolbarItemsProvider`/`setOnToolbarItemClickListener`/`invalidateToolbarItems`                                                                                                                                                                                                                                                                                 | `PdfUi` (→ `PdfActivity`, `PdfUiFragment`), `DocumentState`    |
| `ToolbarItem` (`Button`/`Toggle`/`Submenu`), `ToolbarItemState`, `ToolbarIcon`, `ItemEdge`, `OverflowBehavior`                                                                                                                                                                                                                                                     | `com.pspdfkit.compose.toolbar.state`                           |
| `NutrientMainToolbar(coordinator, modifier, navigationIcon, title, actions, searchBar, shape, shadowElevation, windowInsets, colors, itemIndication, enter, exit)` and the `content` overload; `NutrientMainToolbarDefaults`; `NutrientToolbarScope`; `NutrientMainToolbarTitle`, `NutrientToolbarItems`, `NutrientToolbarSearchBar`, `NutrientMainToolbarContent` | `com.pspdfkit.compose.toolbar.composables`                     |
| `DocumentState.toolbarCoordinator: StateFlow<ToolbarCoordinator?>`                                                                                                                                                                                                                                                                                                 | `com.pspdfkit.jetpack.compose.interactors`                     |
| `ToolbarCoordinator.setMainToolbarInsets(ToolbarInsets)`, `.searchSession`, `.frame`                                                                                                                                                                                                                                                                               | `com.pspdfkit.compose.toolbar.adapter`                         |
| `ToolbarInsets` (`StatusBars`/`None`/`Custom`, `fixedTop`)                                                                                                                                                                                                                                                                                                         | `com.pspdfkit.compose.toolbar.state`                           |
| `PdfUi.isInlineSearchActive()`                                                                                                                                                                                                                                                                                                                                     | `com.pspdfkit.ui`                                              |
| `PdfActivity.setMainToolbarColorScheme(UiColorScheme)`                                                                                                                                                                                                                                                                                                             | `com.pspdfkit.ui`                                              |
| `MainToolbarColors.documentTitleOverlay: DocumentTitleOverlayColors`                                                                                                                                                                                                                                                                                               | `com.pspdfkit.compose.theme`                                   |
| `ComponentActivity.nutrientToolbarCoordinator()`, `Fragment.nutrientToolbarCoordinator()`, `nutrientToolbarsComposeView`, `PdfFragmentToolbarBinder`, `AnnotationToolbarBinder`                                                                                                                                                                                    | `com.pspdfkit.jetpack.compose.toolbar`                         |
| `ToolbarCoordinator.contextualToolbar`, `addContextualToolbarChangeListener`, `updateContextualItems`, `addActionListener`, `addActionInterceptor`, `setContextualToolbarPosition`, `setContextualToolbarDraggable`, `setContextualToolbarGroupingProvider`; `ToolbarSubscription`                                                                                 | `com.pspdfkit.compose.toolbar.adapter`                         |
| `ContextualToolbarState`, `ContextualToolbarKind`, `ToolbarPosition`, `ToolbarAction`, `ToolbarPresetEntry`, `ContextualToolbarGroupingProvider`                                                                                                                                                                                                                   | `com.pspdfkit.compose.toolbar.state`                           |
| `ToolbarDockPosition`                                                                                                                                                                                                                                                                                                                                              | `com.pspdfkit.ui.toolbar`                                      |
| `NutrientToolbars`, `NutrientToolbarScaffold`, `LocalToolbarContentInset`, `rememberNutrientToolbarCoordinator`, `DocumentAnchoredPopup`, `NutrientContextualToolbarDragListener`, `nutrientContextualToolbarComposeView`                                                                                                                                          | `com.pspdfkit.compose.toolbar.composables`                     |
| `NutrientContextualToolbar(coordinator, modifier, closeButton, shape, border, sideDockedShape, sideDockedBorder, shadowElevation, windowInsets, colors, itemIndication, enter, exit, …)`; `NutrientContextualToolbarScope`; `NutrientContextualToolbarCloseButton`; `NutrientContextualToolbarDefaults`                                                            | `com.pspdfkit.compose.toolbar.composables`                     |
| `ToolbarItemIndication`, `LocalToolbarItemIndication`; `NutrientToolbarItemButton(item, onAction, modifier, onLongClick, invertedSelection, toolbarPosition, indication)`                                                                                                                                                                                          | `com.pspdfkit.compose.toolbar.composables`                     |
| `setToolbarNavigationIcon(ToolbarIcon?, String?)`/`setOnToolbarNavigationClickListener(OnToolbarNavigationClickListener?)`                                                                                                                                                                                                                                         | `PdfUi` (→ `PdfActivity`, `PdfUiFragment`), `DocumentState`    |
| `ToolbarCoordinator.setMainToolbarNavigationIcon`, `.addNavigationClickHandler`, `ToolbarFrame.navigationIcon`/`navigationContentDescription`; `NutrientMainToolbarNavigationIcon(coordinator)`                                                                                                                                                                    | `com.pspdfkit.compose.toolbar.adapter`/`.state`/`.composables` |
| `ToolbarCoordinator.setMainToolbarHostVisible(Boolean)`, `.setContextualToolbarAllowedPositions(List<ToolbarPosition>?)`                                                                                                                                                                                                                                           | `com.pspdfkit.compose.toolbar.adapter`                         |
| `NutrientStatusBarScrim`, `NutrientStatusBarScrimDefaults`, `NutrientBackdropState`, `rememberNutrientBackdropState`, `Modifier.nutrientBackdropSource`                                                                                                                                                                                                            | `com.pspdfkit.compose.toolbar.composables`                     |
| `NutrientDocumentStatusBarScrim(documentState, state, …)`; `DocumentState.contentTopInWindowPx`                                                                                                                                                                                                                                                                    | `com.pspdfkit.jetpack.compose.toolbar`/`.interactors`          |
| `NutrientTabBar(documentCoordinator, …)` and `NutrientTabBar(tabs, selectedTabId, onTabClick, onCloseClick, …, onTabMove, tabContent)`; `NutrientTabBarTab`, `NutrientTabBarDimensions`, `NutrientTabBarTabScope`; `NutrientTabBarDefaults` (`dimensions()`, `TabContent`, `CloseButton`)                                                                          | `com.pspdfkit.compose.tabs.composables`                        |
| `TabBarColors`, `TabBarColors.from(MainToolbarColors)`, `UiColorScheme.tabBar`; `getTabBarColors(...)`                                                                                                                                                                                                                                                             | `com.pspdfkit.compose.theme`; `io.nutrient.ui.theme`           |
| `DocumentState.documentCoordinator: StateFlow<DocumentCoordinator?>`                                                                                                                                                                                                                                                                                               | `com.pspdfkit.jetpack.compose.interactors`                     |
| `NutrientFloatingToolbar`, `NutrientFloatingContextualToolbar`, `FloatingToolbarState`, `FloatingToolbarEdge`, `rememberFloatingToolbarState`, `NutrientFloatingToolbarItemButton`, `NutrientFloatingToolbarMenuItem`, `NutrientFloatingToolbarSearchTheme`                                                                                                        | `com.pspdfkit.compose.toolbar.composables`                     |
| `onConfigurationUpdated(PdfActivityConfiguration)`                                                                                                                                                                                                                                                                                                                 | `PdfActivityListener` (→ `PdfActivity`, `PdfUiFragment`)       |
| `pspdf__sheetHeight` theme attribute                                                                                                                                                                                                                                                                                                                               | `PSPDFKit.SettingsDialog` style                                |

## Working examples

All of these examples are in the Catalog app, under _Toolbars and Menus_ unless noted. That section is ordered by the surface being customized: main toolbar, annotation toolbar, popup toolbar, and then the examples that host every toolbar themselves.

- `MainToolbarCustomizationExample` (_Main Toolbar: Custom Layout and Items_) — `NutrientMainToolbar` in a rounded, elevated container. It adds a custom color scheme, custom items, click routing, inset opt-out, search and immersive animations, and the status bar scrim.

- `MainToolbarFloatingExample` (_Main Toolbar: Floating Pill_) — `NutrientFloatingToolbar` and `NutrientFloatingContextualToolbar`, with the collapsible variant behind a toggle.

- `MainToolbarFabMenuExample` (_Main Toolbar: FAB Menu_) — The coordinator’s items as a Material 3 Expressive FAB menu.

- `AnnotationToolbarUserEditableExample`, `AnnotationToolbarCustomItemsExample`, and `AnnotationToolbarGroupingExample` — User-arranged tools saved across sessions, custom items bound to tools and variants, and grouping on the contextual bars. `ConstructionExample` (_Industry_) registers a preset grouping rule through the bridge.

- `PopupToolbarCustomItemsExample` — The text selection, annotation selection, long-press, and content editing popup toolbars.

- `CustomToolbarsPdfActivityExample`, `CustomToolbarsPdfFragmentExample`, `CustomToolbarsPdfFragmentViewExample`, and `CustomToolbarsDocumentViewExample` — The hosting recipe for each host type: SDK bars off with host Compose bars on the activity coordinator, `NutrientToolbars` around a bare `PdfFragment`, the toolbar stack as a `ComposeView` in a classic View layout, and both bars replaced over the Compose `DocumentView`.

- `ComposePdfViewerExample` (_Jetpack Compose_) — A complete Compose viewer app with a host-rendered `NutrientMainToolbar` over `DocumentView`. See [the simple-compose example moved into the Catalog](#the-simple-compose-example-moved-into-the-catalog).

- `DocumentPagerExample` (_Multi-document_) — Collecting `documentState.toolbarCoordinator` in a Compose-first host.

- `DocumentTabsComposeExample` (_Multi-document_, _Tabbed Documents (Compose)_) — A floating-pill `NutrientMainToolbar` with a `NutrientTabBar` below it. Both overlay a `DocumentView` and animate out together in immersive mode.

The pre-12 examples were consolidated into these. _Custom ActionBar Actions_, _Custom Main Toolbar_, and _Floating Toolbar Layout_ became `MainToolbarCustomizationExample`. The Java _Custom Activity Toolbars_ example became `CustomToolbarsPdfFragmentExample`, and _Collapsible Floating Toolbar_ became `MainToolbarFloatingExample`. The example `res/menu/*.xml` files were deleted in favor of the items API.

## Annotation inspector color picker

This change is independent of the toolbar rewrite above, and like it, it’s a clean break rather than a deprecation.

The annotation inspector used to ship two separate color-picker detail views, and which one you got depended on whether custom colors were enabled:

- `ColorPickerInspectorDetailView` — A flat grid of the available colors.

- `CustomColorPickerInspectorDetailView` — A palette with recently used colors, color variations, and Hex/HSL/RGB custom entry.

They’re now one. `CustomColorPickerInspectorDetailView` is removed, and `ColorPickerInspectorDetailView` is reimplemented in Jetpack Compose to cover both. The name stays, but the constructor changes. The old class took a trailing `singleLine` flag and the new one takes no fourth argument. Any call still using the old form fails to compile rather than changing behavior silently.

### Do I need to do anything?

**If you never touched the color picker’s configuration or classes: no.** The inspector picks the new view up automatically. You’ll see a redesigned picker: one view with a palette (recents, defaults, variations) and a “Custom” tab. The tab carries the Hex/HSL/RGB entry that used to be a separate screen.

**You need to act if either of these applies:**

- You called `setCustomColorPickerEnabled(...)` on any color configuration builder, or read or implemented `customColorPickerEnabled()`. They’re replaced by `setRestrictToAvailableColors(...)` and `restrictToAvailableColors()`, with the opposite meaning.

- You construct either detail-view class directly, typically to pass a custom detail view to `ColorPickerInspectorView`.

Both are compile errors, so the build will find every affected call site for you.

### setCustomColorPickerEnabled is now setRestrictToAvailableColors

This affects every integration that configured the flag, not just those constructing the views directly.

`setCustomColorPickerEnabled(boolean)` is removed from `AnnotationColorConfiguration.Builder`, `AnnotationFillColorConfiguration.Builder`, and `AnnotationOutlineColorConfiguration.Builder`, and `setRestrictToAvailableColors(boolean)` replaces it, with the meaning inverted and the default flipped from `true` to `false`:

```java

// Before — hold the user to the configured palette..setAvailableColors(brandColors).setCustomColorPickerEnabled(false)

// After:.setAvailableColors(brandColors).setRestrictToAvailableColors(true)

```

If you passed `true`, the old default that allowed custom colors, drop the call, since `false` is the new default.

The getter is renamed the same way. `boolean customColorPickerEnabled()` on `AnnotationColorConfiguration`, `AnnotationFillColorConfiguration`, `AnnotationOutlineColorConfiguration`, and `ContentEditingFillColorConfiguration` is now `boolean restrictToAvailableColors()`. Code that reads the flag must invert the check. A class implementing one of these interfaces, including a custom `ContentEditingFillColorConfiguration`, must rename the method and return the inverse of its old value.

The rename follows a behavior fix worth knowing about. `customColorPickerEnabled = false` only hid the custom-entry UI. The recently used colors and the generated color variations stayed onscreen, and both offer colors outside `availableColors`. Restricting the palette therefore never actually restricted it. `setRestrictToAvailableColors(true)` hides all three, so the picker offers the available colors and nothing else. If you used the old flag to hold annotations to an approved brand or accessibility palette, that starts working as intended in this release.

### Replacing CustomColorPickerInspectorDetailView

Its behavior is the surviving class’s default, so no flag call is needed:

```java

// Before:
final var detailView = new CustomColorPickerInspectorDetailView(context, colors, defaultColor);

// After:
final var detailView = new ColorPickerInspectorDetailView(context, colors, defaultColor);

```

### Updating ColorPickerInspectorDetailView call sites

The class keeps its name but drops the trailing `singleLine` argument. To get the old flat grid, call `setRestrictToAvailableColors(true)`:

```java

// Before:
final var detailView =
        new ColorPickerInspectorDetailView(context, colors, defaultColor, /* singleLine = */ false);
detailView.setShowSelectionIndicator(false);

// After:
final var detailView = new ColorPickerInspectorDetailView(context, colors, defaultColor);
detailView.setRestrictToAvailableColors(true);
detailView.setShowSelectionIndicator(false);

```

There’s no `singleLine` equivalent. The old flat grid could be forced onto one row, whereas the new swatch grid always wraps to a fixed column count. Drop the argument rather than translating it.

`setShowSelectionIndicator(boolean)` carries over unchanged, as do `setOnColorPickedListener`, `getState`/`setState`, and the rest of the `ColorPickerInspectorView.ColorPickerDetailView` contract.

Both old classes accepted the color list as either `int[]` or `List<Integer>`. The surviving class takes a `List<Integer>` only, so `int[]` call sites need updating.

## Document overview grid

The document overview’s page grid (`PdfThumbnailGrid`) is rendered in Jetpack Compose. Its public methods, its `RelativeLayout` base class, and the `pspdf__ThumbnailGrid` theme attributes are unchanged. Code that reached into its views or relied on its floating action button needs updating:

- **The grid content is no longer a `RecyclerView`.** The view with the `pspdf__thumbnail_grid_recycler_view` ID stays for layout-based repositioning but is now a `ComposeView`. Code that casts it to `RecyclerView`, or attaches adapters, item decorations, or an `ItemTouchHelper` to it, fails at runtime. Cast it to `View` instead.

- **The floating action button is gone.** In `PdfActivity`, the main toolbar shows a document editor item (`PdfActivity.MENU_OPTION_DOCUMENT_EDITOR`) while the grid is displayed. Theme it through `pspdf__documentEditorIcon` and `pspdf__documentEditorIconActivated`. The document editing toolbar gains an Add Page item. A `PdfThumbnailGrid` used outside `PdfActivity` shows no entry point at all, so start editing from your own UI.

- **The button’s theming no longer applies.** `pspdf_fabIconColor` and the `PSPDFKit.FabStyle` shape overlay are still declared, so existing themes compile but have no effect.

```kotlin

// Before: the grid's FAB entered document editing, and the grid was a `RecyclerView`.
val list = thumbnailGrid.findViewById<RecyclerView>(R.id.pspdf__thumbnail_grid_recycler_view)

// After: start editing and add pages from your own UI.
val content: View = thumbnailGrid.findViewById(R.id.pspdf__thumbnail_grid_recycler_view)
editButton.setOnClickListener { thumbnailGrid.enterDocumentEditingMode() }
addPageButton.setOnClickListener { thumbnailGrid.createNewPage() }
val editing = thumbnailGrid.isDocumentEditingModeActive()

```

`PdfThumbnailGrid.setDocumentEditorEnabled(false)` can now be called while the grid is displayed: It leaves document editing mode and removes the document editor item. Reordering pages by drag creates a single undo step instead of one per swap.

## Settings dialog

The settings sheet is simpler in 12.0, and its layout settings take effect without a restart.

### Layout changes apply without a restart

Changing the scroll direction, page transition, or page layout in the settings sheet updates the open document as the user taps. The activity no longer restarts when the sheet closes. `PdfUi.setConfiguration()` does the same when only `scrollDirection`, `scrollMode`, or `layoutMode` differ from the current configuration. Any other change — the theme for example — still restarts the activity.

So these three values can now change while the activity is running. If you read one of them once, in `onCreate` for example, override the new `PdfActivityListener.onConfigurationUpdated()` to read it again. `PdfActivity` and `PdfUiFragment` implement `PdfActivityListener`, so the callback is available in a subclass of either:

```kotlin

// Before: correct only until the user changed the scroll direction.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    scrollBar.setScrollDirection(configuration.configuration.scrollDirection)
}

// After: also follow changes made while the activity is running.
override fun onConfigurationUpdated(configuration: PdfActivityConfiguration) {
    super.onConfigurationUpdated(configuration)
    scrollBar.setScrollDirection(configuration.configuration.scrollDirection)
}

```

A change that still restarts the activity doesn’t call `onConfigurationUpdated()`. The activity is recreated with the new configuration instead.

### The presets and the keep-screen-on switch are removed

The vertical and horizontal preset buttons at the top of the sheet are gone. They were shortcuts for the page transition and scroll direction rows below them. The keep-screen-on switch is gone too.

`SettingsMenuItemType.PRESETS` and `SettingsMenuItemType.SCREEN_AWAKE` are deprecated and ignored, so a configuration that lists them keeps compiling and working. A set with only these two no longer shows the settings item. To keep the screen on, set the timeout yourself:

```kotlin

// Before: listing SCREEN_AWAKE gave users a keep-screen-on switch in the settings sheet.
PdfActivityConfiguration.Builder(context).setSettingsMenuItems(EnumSet.of(SettingsMenuItemType.THEME, SettingsMenuItemType.SCREEN_AWAKE))

// After: keep the screen on from your own code or UI.
pdfUi.setScreenTimeout(PdfUi.TIMEOUT_INFINITE)

```

The presets’ theme attributes are removed:

- `pspdf__settings_preset_animation_url_horizontal`

- `pspdf__settings_preset_animation_url_vertical`

- `pspdf__settings_preset_still_image_horizontal`

- `pspdf__settings_preset_still_image_vertical`

If your theme sets any of them, remove those entries. `pspdf__settings_preset_unselected_border_color`, `pspdf__settings_preset_selected_border_width`, and `pspdf__settings_preset_unselected_border_width` are public, so they’re still declared and themes that set them still build, but they have no effect. `pspdf__settings_preset_label_textcolor` still sets `SettingsColorScheme.unselectedTextColor` for your own settings UI, but the built-in sheet no longer uses it.

### Snapping moved to the annotation inspector

The Snap to Point, Snap to Self, and Show Smart Guides switches moved out of the settings sheet. All three are in the measurement tools inspector. Snap to Self and Show Smart Guides also apply to drawing lines, polylines, and polygons, so the inspectors for those tools show them too.

`SettingsOptions.snapToPoint`, `snapToSelf`, and `showSmartGuides` — along with their constructor parameters — are removed. Read and change snapping through `PSPDFKitPreferences` instead:

```kotlin

// Before:
val options = SettingsOptions(
    scrollDirection, scrollMode, layoutMode, themeMode, screenTimeoutMillis, visibleItems,
    snapToPoint = true,
    snapToSelf = true,
    showSmartGuides = false,
)

// After:
val options = SettingsOptions(scrollDirection, scrollMode, layoutMode, themeMode, screenTimeoutMillis, visibleItems)

PSPDFKitPreferences.get(context).apply {
    setMeasurementSnappingEnabled(true)
    setSnapToSelfEnabled(true)
    setSmartGuidesEnabled(false)
}

```

### Smaller changes

- The sheet fits its controls instead of opening at the full window height. The new `pspdf__sheetHeight` attribute on `PSPDFKit.SettingsDialog` gives it a fixed height instead.

- Scroll direction is listed right after page transition.

- The page appearance is picked with color circles, as on iOS, instead of segmented buttons.

- The automatic page layout is labeled “Adaptive,” which uses the new `pspdf__adaptive` string. `pspdf__settings_menu_auto` and `pspdf__settings_menu_keep_screen_on` are public, so the SDK keeps them but no longer uses them.

## Material 3 modal surfaces

This change is independent of the ones above. Four surfaces moved from Android Views to Jetpack Compose and Material 3. All four now present as modal sheets: the document info screen, the outline container, the settings dialog, and the share menu. The outline container’s tabs are outline, bookmarks, annotations, document info, and attachments.

`PdfOutlineView`, `SettingsDialog`, `ActionMenu`, `SharingMenu`, and `DefaultSharingMenu` keep their public API, so most integrations need no change. The items below are the ones that do.

### The outline content is no longer in the view hierarchy

This is the change most likely to break an integration quietly, because it still compiles.

A modal sheet in its own window, not `PdfOutlineView`, now presents the outline container’s content. The IDs are still in `R`, so code referencing them compiles exactly as before, and `findViewById` returns `null`:

```kotlin

// Before: returned the pager.
// After: compiles, returns `null`.
val pager = pdfOutlineView.findViewById<ViewPager>(R.id.pspdf__outline_pager)

```

`R.id.pspdf__activity_outline_view`, `R.id.pspdf__outline_list_view`, and `R.id.pspdf__outline_pager` all still exist, so nothing fails at build time. There’s no public replacement for reaching into the pager. If you depend on that, open an issue describing what you need. We can then add API for it rather than have integrations rely on the internal view tree.

`R.id.pspdf__outline_list_search_view` is removed, because the outline’s search field is a Compose `TextField` now. It was never a `<public>` ID, but it did exist in `R` and compiled.

Espresso matchers scoped to the activity’s root no longer find the outline’s views, and Compose text is invisible to `onView(withText(...))` regardless. Assert against Compose semantics instead:

```kotlin

// Before:
onView(withId(R.id.pspdf__outline_list_view)).check(matches(isDisplayed()))

// After:
composeTestRule.onNodeWithTag("Outline_List").assertIsDisplayed()

```

The search field’s test tag is `Outline_Search`.

### Sheet width and presentation

The outline, document info, and settings present as a Material 3 modal bottom sheet. On expanded windows (840dp wide and up, such as a landscape tablet or a large foldable), they present as an end-anchored side sheet. It has full height and a clamped width, and slides in from the trailing edge. Users dismiss it by dragging it back off that edge, tapping the scrim, or pressing back. In a right-to-left layout, the side sheet anchors to the left and the drag direction mirrors with it.

The share menu stays a bottom sheet at every width and is clamped to the same width. On an expanded window, it’s centered rather than stretched.

Two theme attributes configure this, both on the outline and settings styles:

| Attribute                      | Meaning                                                                                                                                               |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pspdf__maxWidth`              | The width the sheet is clamped to: the side sheet’s exact width, and the bottom sheet’s maximum. Defaults to `@dimen/pspdf__sheet_max_width` (480dp). |
| `pspdf__sheetPresentationMode` | One of `auto` (the default), `bottom`, or `side`.                                                                                                     |

```xml

<style name="MyOutlineView" parent="PSPDFKit.OutlineView">
    <!-- Keep the pre-12.0 shape: always a bottom sheet. -->

    <item name="pspdf__sheetPresentationMode">bottom</item>
    <item name="pspdf__maxWidth">640dp</item>
</style>

```

Width resolves in three tiers, and the first one silently wins, which makes the other two look broken if your theme already sets it:

1. `pspdf__width` on the outline style, if set to a positive value. This is the attribute the old end-anchored panel used. A theme written for that panel almost certainly sets it, so it still wins and your existing sidebar width keeps working. Its meaning has narrowed, from sizing a panel directly to clamping the sheet. Set it to `-1dp` to fall through.

2. `pspdf__maxWidth` on the outline or settings style. Use this for new themes. It’s the only one of the three that applies to the settings sheet.

3. The `pspdf__sheet_max_width` dimension, overridden in your own `dimens.xml`, moves both sheets at once. Prefer this over overriding `pspdf__bottom_sheet_max_width`, because the action menu, annotation inspector, modular search view, and AI assistant also read it.

The outline tab bar shows labels only when the widest visible label fits the width each tab gets. Otherwise it shows icons only, with the tab names as the icons’ accessibility labels. The labels are measured rather than compared against a fixed width per tab, so the decision is correct in every locale.

### Theming that changed default

The bars at the bottom of the annotations and bookmarks tabs are surface-colored with a top divider rather than solid primary. Four public attributes kept their names and changed their defaults, so these bars look different if you never set them:

| Attribute                              | Before                         | After                      |
| -------------------------------------- | ------------------------------ | -------------------------- |
| `pspdf__annotationsBarBackgroundColor` | `?colorPrimary`                | `?android:colorBackground` |
| `pspdf__annotationsBarIconColor`       | `@color/pspdf__onPrimaryLight` | `?colorPrimary`            |
| `pspdf__bookmarksBarBackgroundColor`   | `?colorPrimary`                | `?android:colorBackground` |
| `pspdf__bookmarksBarIconColor`         | `@color/pspdf__onPrimaryLight` | `?colorPrimary`            |

In the dark theme, the two backgrounds resolve to `@color/pspdf__dark` and the two icon colors to `?colorAccent`. Set all four to the “Before” values to keep the old look. Anything you set explicitly still wins.

### Removed theming and API

`SettingsTopbar` is removed from `com.pspdfkit.ui.settings.components`. The Material 3 settings sheet uses a drag handle and its own title, so it renders no top bar. It was public, but its only parameter type was internal, so there was no supported way to call it.

The settings presets are removed, along with their theme attributes. See information about [the presets and the keep-screen-on switch](#the-presets-and-the-keep-screen-on-switch-are-removed) above.

The settings dialog’s floating window mode is gone. It used to size its window to a centered panel on large screens and go fullscreen on phones. The window is now fullscreen and transparent in both cases, and the sheet supplies its own scrim and sizing. Use `pspdf__maxWidth` and `pspdf__sheetPresentationMode` instead of sizing that window.

### The page label API is nullable

`PdfDocument.getPageLabel` has always documented a `null` return. It has always returned `null` for a document with no label for the page when `substituteWithPlainLabel` is `false`. It carried no `@Nullable`, so Kotlin callers saw `String` and received a `null` through a non-null type. It’s annotated now, which is a Kotlin source break:

```kotlin

// Before: compiled, and threw at runtime on a document with no page labels.
val label: String = document.getPageLabel(3, false)

// After: pick what suits the call site.
val label: String? = document.getPageLabel(3, false)
val label: String = document.getPageLabel(3, false)?: ""
val label: String = document.getPageLabel(3, true)!! // Substituted: never `null` at runtime, but still typed `String?`.

```

Java callers are unaffected, since the annotation only adds a nullability warning.

`OutlineElement.Builder.setPageLabel` takes `String?` now, so you can pass that result straight in, and `null` means “this element has no page label.” Widening a parameter breaks no caller. The Java virtual machine (JVM) signature is unchanged, so that half is source and binary compatible in both languages.

### Reusing the settings content with NutrientSettingsView

Three approaches put settings in front of a user, and the middle one is new in 12.0.

1. Show the built-in sheet. Leave `settingsMenuEnabled` on and let the toolbar’s settings item open it.

2. Compose `NutrientSettingsView`. It keeps the SDK’s rows and theming while you own where they appear. You can also replace or decorate an individual row.

3. Render your own UI from the same data, reading and writing through `SettingsOptions`.

`NutrientSettingsView` is public in `io.nutrient.ui.settings`, next to `SettingsOptions`, and [customizing the settings UI](https://www.nutrient.io/guides/android/user-interface/custom-settings-ui.md) covers all three approaches. It renders content only, with no sheet, scrim, or dialog, so presentation stays yours. Give it a bounded height, because the rows scroll inside it.

```kotlin

NutrientSettingsView(
    options = options,
    onOptionsChanged = { options = it },
    // Replace just the scroll direction row, and keep every other row as the SDK draws it.
    settingsItem = { type, default ->
        if (type == SettingsMenuItemType.SCROLL_DIRECTION) MyOwnScrollDirectionRow() else default()
    },
)

```

`SettingsOptions.visibleItems` decides which rows appear, the same way it does for the built-in sheet. `settingsItem` receives each row’s `SettingsMenuItemType` and the SDK’s default rendering for it. Returning `default()` keeps the row, ignoring it replaces the row, and calling `default()` inside your own layout decorates it. `title` is a slot too, defaulting to the localized “Settings” title.

### Custom DocumentOutlineProvider

Three behavior changes, all of them fixes:

- **The outline is requeried when the document changes.** The load is keyed on the document instance. Reopening a document after a save therefore reloads the outline instead of keeping the presave titles and page targets.

- **A provider that reports an error no longer crashes.** The tab shows its empty state. It tries again the next time the outline is opened. This covers a provider that throws on the call as well as one that signals `Single.error`.

- **The provider isn’t retained.** `setDocumentOutlineProvider` again keeps its documented promise not to retain the provider across an activity restart. An anonymous provider capturing your activity no longer leaks it across a rotation.

### What didn’t change

- `PdfOutlineView`’s public API: `show()`, `hide()`, `isDisplayed`, the visibility listeners, `setDocumentOutlineProvider`, `setBookmarkViewAdapter`, and the tab-enablement setters.

- `SettingsDialog`’s public API, and the `ActionMenu`, `SharingMenu`, and `DefaultSharingMenu` classes.

- `PdfActivityConfiguration`’s outline, bookmark, annotation list, and document info flags.

- Outline state still survives configuration changes. It deliberately doesn’t survive process death, since the outline is a transient modal sheet.

## The simple-compose example moved into the Catalog

The standalone `simple-compose` example app, and the `pspdfkit-jetpack-compose-pdf-viewer` repository that mirrored it, are retired. The app’s functionality ships inside the Catalog app as the **Compose PDF Viewer** example (`ComposePdfViewerExample`, under _Jetpack Compose_). No SDK API changed, so nothing in your integration code needs to change. What changes is where you look for a complete, copy-pasteable Compose viewer.

`simple-compose` was a separate Gradle build with its own dependency set, Koin for dependency injection (DI), and Room for persistence, and its own app shell. That made it expensive to keep compiling against each SDK release. Because the integration was spread across repositories, it was also a poor answer to “show me how to use `DocumentView`.” Inside the Catalog, it builds and is smoke-tested on every SDK change alongside every other example. It also demonstrates the toolbar hosting APIs the standalone app predated.

| Old (`examples/simple-compose`)                                    | New (`examples/catalog`)                                                                     |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| Separate Gradle project with its own `gradlew`                     | Part of the Catalog build                                                                    |
| `pspdfkit-jetpack-compose-pdf-viewer` repository                   | Catalog → _Jetpack Compose_ → **Compose PDF Viewer**                                         |
| `com.pspdfkit.example`                                             | `com.pspdfkit.catalog.examples.kotlin.composeviewer`                                         |
| `MainActivity`, `Root`, `Navigation`                               | `ComposePdfViewerActivity` and `ViewerNavHost` in `ComposePdfViewerExample.kt`               |
| `MainScreen`, `PdfScreen`, `SettingsScreen`, and their view models | `LibraryScreen.kt`, `ViewerScreen.kt`, `SettingsScreen.kt`, and one `ComposeViewerViewModel` |
| `CustomTopbar`                                                     | `NutrientMainToolbar` rendered by the host in `ViewerScreen.kt`                              |
| Koin modules, Room `AppDatabase`, the repositories                 | No DI framework; recents persist as a JSON array in a DataStore preference                   |
| `ui/theme/*`                                                       | `ComposeViewerTheme`: baseline Material 3 plus dynamic color                                 |
| `assets/demo.pdf`                                                  | The Catalog’s bundled welcome document                                                       |

The example keeps everything the old app did: a recents library with page thumbnails; file import through the system picker; and light, dark, and dynamic-color settings. It also covers Compose integration surface the standalone app never did. A host-rendered `NutrientMainToolbar` sits over `DocumentView` with the inset bookkeeping a custom main toolbar needs. The example also shows custom `ToolbarItem`s, `DefaultListeners` wiring, and `DocumentState.setCustomPdfSource` with its asynchronous completion contract. Two behaviors differ from the old app. Persistence is a DataStore preference rather than a Room database, so it has no schema and no migration. Clearing app data resets the library. _Clear imported documents_ in the settings removes only documents imported through the file picker. It keeps the bundled demo document, so the library never lands on an empty screen.

If you forked `pspdfkit-jetpack-compose-pdf-viewer`, the fork keeps working against the SDK version it pinned but receives no updates. Clone the [Catalog](https://github.com/PSPDFKit/pspdfkit-android-catalog) instead and open the Compose PDF Viewer example.

## Document tab bar

`PdfActivity` shows the document tab bar, a row of tabs, above a document when several documents are open through a `DocumentCoordinator`. Jetpack Compose now renders it. `PdfTabBar` keeps its public API, its `LinearLayout` base class, and the `pspdf__TabBar` theme attributes. The `RecyclerView` under it is gone. The same Compose UI is public as `NutrientTabBar`. An app that renders its own chrome around a `DocumentView` can show a tab bar that follows the same theming and slot conventions as `NutrientMainToolbar`.

| Old                                                                   | New                                                                                                                    |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `RecyclerView` under the `pspdf__tabs_bar_list` ID inside `PdfTabBar` | A `ComposeView`; the ID is still declared but no view carries it                                                       |
| `pspdf__tab_item.xml` layout and its view IDs                         | Removed; tab content is a Compose slot                                                                                 |
| No tab bar for Compose `DocumentView` hosts                           | `NutrientTabBar(documentCoordinator = …)`, bound through `DocumentState.documentCoordinator`                           |
| Styling only through the `pspdf__TabBar` XML attributes               | Still the default, plus `TabBarColors` (`UiColorScheme.tabBar`), `NutrientTabBarDefaults.dimensions()`, shape, spacing |
| `pspdf__tabIconColor` declared but never applied                      | Tints the close button of unselected tabs (visible in `PdfTabBarCloseMode.CLOSE_ENABLED` only)                         |
| Rapid taps on several tabs loaded each document in turn               | Only the last tapped document is loaded                                                                                |

### PdfTabBar keeps working

`PdfTabBar` is still a `LinearLayout` with the same public methods (`bindToDocumentCoordinator`, `setCloseMode`, `getTabs`, `getTabBarItem`, the `OnTabClickedListener` and `OnTabsChangedListener` collections). Java subclasses and the documented ways of extending it compile unchanged. `PdfTabBar` is now written in Kotlin, so a Kotlin subclass that overrode `getTabs()` or `getSize()` overrides the `tabs` and `size` properties instead. See [`PdfFragment` and `PdfTabBar` are Kotlin classes](#pdffragment-and-pdftabbar-are-kotlin-classes). Inserting your own view into the bar keeps working, because the Compose child is measured at the width left after its siblings:

```kotlin

val addTabButton: View = …
pspdfKitViews.tabBar?.addView(addTabButton, 0)

```

What no longer works is reaching into the bar for a `RecyclerView`: `findViewById(R.id.pspdf__tabs_bar_list)` returns `null`, and adapters, layout managers, item decorations, and `ItemTouchHelper` have nothing to attach to. The tab item layout `pspdf__tab_item.xml` and its IDs are gone as well. Everything those hooks were used for (colors, dimensions, and the tab interior) has a themed or Compose counterpart below.

Two behavior changes to be aware of:

- **`pspdf__tabIconColor` is honored.** The View bar tinted every close button with `pspdf__tabIconColorSelected`. Now, unselected tabs use `pspdf__tabIconColor`, which only shows in `PdfTabBarCloseMode.CLOSE_ENABLED`, where unselected tabs have a close button. Set both attributes to the same color to keep the old look.

- **Rapid taps switch to the last tapped document only.** Tapping tab B and then tab C loads C alone. The document switch follows the tab animation by 250 ms. Closing a tab drops a pending switch to it.

### A tab bar for Compose DocumentView hosts

Previously, the Compose `DocumentView` had no tab bar. `DocumentState` now publishes the fragment’s `DocumentCoordinator` once the UI is ready (`null` until then), and `NutrientTabBar` binds to it:

```kotlin

val documentState = rememberDocumentState(uri, configuration)
val coordinator by documentState.documentCoordinator.collectAsState()

Box(Modifier.fillMaxSize()) {
    DocumentView(documentState = documentState, modifier = Modifier.fillMaxSize())

    coordinator?.let { NutrientTabBar(documentCoordinator = it, modifier = Modifier.align(Alignment.TopCenter)) }
}

```

Hide the SDK’s own bar with `PdfActivityConfiguration.Builder.setTabBarHidingMode(TabBarHidingMode.HIDE)` when rendering your own. Otherwise, both show. Add documents to the coordinator the same way as before. The bar mirrors it, and a tap or a close calls `setVisibleDocument` and `removeDocument` on it.

The bound bar doesn’t hide itself in immersive mode. Tie it to the same visibility as the rest of your chrome. The `DocumentTabsComposeExample` in the Catalog app observes `ToolbarCoordinator.frame.isVisible` and animates the main toolbar and the tab bar out together.

The stateless overload takes the tabs and reports what the user did for a bar that isn’t backed by a `DocumentCoordinator`:

```kotlin

var tabs by remember { mutableStateOf(listOf(NutrientTabBarTab(id = 1L, title = "Report"), NutrientTabBarTab(id = 2L, title = "Invoice"))) }
var selectedId by remember { mutableStateOf<Long?>(1L) }

NutrientTabBar(
    tabs = tabs,
    selectedTabId = selectedId,
    onTabClick = { selectedId = it.id },
    onCloseClick = { tab -> tabs = tabs - tab },
    onTabMove = { from, to -> tabs = tabs.toMutableList().apply { add(to, removeAt(from)) }; true },
)

```

`NutrientTabBarTab.id` must be unique within the bar and stable across reorders and title changes. `onTabMove` is called live during a long-press drag and returns whether the move was applied; `null` disables drag-reorder.

### Theming

The `pspdf__TabBar` XML attributes remain the default for `PdfTabBar` and for a `NutrientTabBar` inside an SDK-themed context: `getUiColors()` reads them into `UiColorScheme.tabBar`. New on top:

- **`TabBarColors`** (`com.pspdfkit.compose.theme`) — `backgroundColor` behind the tabs, `tabColor` for unselected tabs, and `selectedTabColor` for the selected tab and the thin separator between touching tabs. The bar also takes `textColor`/`selectedTextColor` and `iconColor`/`selectedIconColor` for the close button, defaulting to the text colors. `TabBarColors.from(mainToolbarColors)` derives a bar that blends with a main toolbar. `UiColorScheme.tabBar` defaults to that, so existing `UiColorScheme(...)` callers compile unchanged. `getTabBarColors(...)` builds one from the Material theme.

- **`NutrientTabBarDefaults.dimensions(...)`** — `height`, `tabMinWidth`, `textSize`, `separatorWidth`, `tabHorizontalPadding`, and `closeButtonPadding`, each defaulting to the `pspdf__tab_bar_*` value the SDK bar uses.

- **`tabShape`**, **`tabSpacing`**, and **`contentPadding`** on the composable. A non-zero spacing replaces the separator between touching tabs.

- **`windowInsets`** defaults to the horizontal safe-drawing insets, like Material’s app bars: The background extends into them, and the tabs stay clear. Pass `WindowInsets(0, 0, 0, 0)` when your layout already handles them.

```kotlin

NutrientTabBar(
    documentCoordinator = coordinator,
    colors = getTabBarColors(
        backgroundColor = Color.Transparent,
        tabColor = MaterialTheme.colorScheme.surface,
        selectedTabColor = MaterialTheme.colorScheme.primary,
        selectedTextColor = MaterialTheme.colorScheme.onPrimary,
    ),
    dimensions = NutrientTabBarDefaults.dimensions(height = 52.dp, tabMinWidth = 96.dp),
    tabShape = RoundedCornerShape(28.dp),
    tabSpacing = 8.dp,
    contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
)

```

### Custom tab content

`tabContent` replaces the inside of every tab; the bar keeps drawing the background, shape, selection, separator, ripple, and drag handling. The slot receives a `NutrientTabBarTabScope` with the `tab`, `isSelected`, `isCloseable`, the bar’s `colors` and `dimensions`, and `closeTab()`. Calling `closeTab()` is a no-op for tabs the close mode doesn’t make closeable.

To change only the close button, keep the stock content, `NutrientTabBarDefaults.TabContent`, and pass it a `closeButton`. `NutrientTabBarDefaults.CloseButton` keeps the tap handling, ripple, and accessibility semantics of the stock button while taking your own `painter`, `contentDescription`, `tint`, and `size`. Add spacing through its `modifier`:

```kotlin

NutrientTabBar(
    documentCoordinator = coordinator,
    tabContent = {
        NutrientTabBarDefaults.TabContent(
            this,
            closeButton = {
                NutrientTabBarDefaults.CloseButton(
                    this,
                    painter = rememberVectorPainter(Icons.Rounded.Cancel),
                    tint = if (isSelected) colors.selectedIconColor else colors.iconColor,
                    modifier = Modifier.padding(end = 8.dp),
                )
            },
        )
    },
)

```

The bar sizes its tabs for the stock 18dp button plus `closeButtonPadding`. A much larger button should come with a larger `tabMinWidth`.

## PdfFragment and PdfTabBar are Kotlin classes

`PdfFragment` and `PdfTabBar` were converted from Java to Kotlin. The SDK keeps the JVM signatures and the static constants, such as `PdfFragment.DOCUMENT_VIEW_ID`, so Java callers and Java subclasses are unaffected. Kotlin code sees two differences:

- **Nullability is explicit.** Members that were platform types now declare nullability (for example, `PdfFragment.document` is `PdfDocument?`). Kotlin code that treated a result as non-null needs a null check.

- **Getters are properties, and some members stay functions.** A Kotlin subclass overrides properties with `override val` (for example `document`, `undoManager`, and `PdfTabBar.tabs`). It sets `isAnnotationOverlayEnabled` instead of calling `setAnnotationOverlayEnabled()`. `isImageDocument()`, `getImageDocument()`, `getContentEditingUndoManager()`, and `getAudioModeManager()` remain functions, so property syntax such as `fragment.isImageDocument` no longer compiles. Call them as functions.

```kotlin

// Before (`PdfFragment` in Java, seen from Kotlin):
class MyFragment : PdfFragment() {
    override fun getDocument(): PdfDocument? = super.getDocument()
}
val isImage = fragment.isImageDocument

// After:
class MyFragment : PdfFragment() {
    override val document: PdfDocument? get() = super.document
}
val isImage = fragment.isImageDocument()

```

## Instant: Editing comments

Instant comments can be edited when the layer’s collaboration permissions grant `comments:edit`. To report the edit, `InstantAnnotationProvider.NonAnnotationChange` gains a `COMMENT_UPDATED` case. A Kotlin `when` over that enum used as an expression must handle it, or it stops compiling:

```kotlin

when (change) {
    //...existing cases...
    NonAnnotationChange.COMMENT_UPDATED -> refreshComments()
}

```

For more information, refer to the [changelog](https://www.nutrient.io/guides/android/changelog.md#12.0.0).
---

## Related pages

- [Upgrading](/guides/android/advanced-integration/upgrading.md)
- [10 3 Migration Guide](/guides/android/migration-guides/10-3-migration-guide.md)
- [10 4 Migration Guide](/guides/android/migration-guides/10-4-migration-guide.md)
- [10 5 Migration Guide](/guides/android/migration-guides/10-5-migration-guide.md)
- [2024 8 Migration Guide](/guides/android/migration-guides/2024-8-migration-guide.md)
- [2024 9 Migration Guide](/guides/android/migration-guides/2024-9-migration-guide.md)
- [Migrate to electronic signatures](/guides/android/migration-guides/migrating-to-electronic-signatures.md)
- [Nutrient 10 Migration Guide](/guides/android/migration-guides/nutrient-10-migration-guide.md)
- [Pspdfkit 2 1 Migration Guide](/guides/android/migration-guides/pspdfkit-2-1-migration-guide.md)
- [Pspdfkit 2024 1 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-1-migration-guide.md)
- [Pspdfkit 2024 2 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-2-migration-guide.md)
- [Pspdfkit 2024 3 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-3-migration-guide.md)
- [Pspdfkit 2024 4 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-4-migration-guide.md)
- [Pspdfkit 2024 6 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-6-migration-guide.md)
- [Pspdfkit 2024 7 Migration Guide](/guides/android/migration-guides/pspdfkit-2024-7-migration-guide.md)
- [Pspdfkit 3 1 Migration Guide](/guides/android/migration-guides/pspdfkit-3-1-migration-guide.md)
- [Migrate to Nutrient 3.0 with ease](/guides/android/migration-guides/pspdfkit-3-migration-guide.md)
- [Pspdfkit 4 4 Migration Guide](/guides/android/migration-guides/pspdfkit-4-4-migration-guide.md)
- [Migration guide for Android SDK 4 features](/guides/android/migration-guides/pspdfkit-4-migration-guide.md)
- [Pspdfkit 5 1 Migration Guide](/guides/android/migration-guides/pspdfkit-5-1-migration-guide.md)
- [Pspdfkit 5 2 Migration Guide](/guides/android/migration-guides/pspdfkit-5-2-migration-guide.md)
- [Pspdfkit 5 3 Migration Guide](/guides/android/migration-guides/pspdfkit-5-3-migration-guide.md)
- [Pspdfkit 5 5 Migration Guide](/guides/android/migration-guides/pspdfkit-5-5-migration-guide.md)
- [Pspdfkit 5 Migration Guide](/guides/android/migration-guides/pspdfkit-5-migration-guide.md)
- [Pspdfkit 6 1 Migration Guide](/guides/android/migration-guides/pspdfkit-6-1-migration-guide.md)
- [Pspdfkit 6 2 Migration Guide](/guides/android/migration-guides/pspdfkit-6-2-migration-guide.md)
- [Pspdfkit 6 3 Migration Guide](/guides/android/migration-guides/pspdfkit-6-3-migration-guide.md)
- [Pspdfkit 6 4 Migration Guide](/guides/android/migration-guides/pspdfkit-6-4-migration-guide.md)
- [Pspdfkit 6 6 Migration Guide](/guides/android/migration-guides/pspdfkit-6-6-migration-guide.md)
- [Pspdfkit 6 Migration Guide](/guides/android/migration-guides/pspdfkit-6-migration-guide.md)
- [Pspdfkit 7 Migration Guide](/guides/android/migration-guides/pspdfkit-7-migration-guide.md)
- [Pspdfkit 8 6 Migration Guide](/guides/android/migration-guides/pspdfkit-8-6-migration-guide.md)
- [Pspdfkit 8 7 Migration Guide](/guides/android/migration-guides/pspdfkit-8-7-migration-guide.md)
- [Pspdfkit 8 8 Migration Guide](/guides/android/migration-guides/pspdfkit-8-8-migration-guide.md)
- [Pspdfkit 8 9 Migration Guide](/guides/android/migration-guides/pspdfkit-8-9-migration-guide.md)
- [Pspdfkit 8 Migration Guide](/guides/android/migration-guides/pspdfkit-8-migration-guide.md)

