patches/react-native-screens@4.26.2.patch

diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt
index fa1b77317f609983ae2434764174072e51130550..270c81c90971b0ff3038b999973dd4d128a9d140 100644
--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt
@@ -124,6 +124,34 @@ class ScreenStackHeaderConfigViewManager :
         config.setTitle(title)
     }
 
+    // The t3 patch adds these iOS glass-chrome props to the native spec; they have
+    // no Android implementation, but the codegen-generated interface requires the
+    // setters to exist. No-ops keep the Android build compiling.
+    override fun setSubtitle(
+        config: ScreenStackHeaderConfig,
+        subtitle: String?,
+    ) = Unit
+
+    override fun setLargeSubtitle(
+        config: ScreenStackHeaderConfig,
+        largeSubtitle: String?,
+    ) = Unit
+
+    override fun setNavigationItemStyle(
+        config: ScreenStackHeaderConfig,
+        navigationItemStyle: String?,
+    ) = Unit
+
+    override fun setHeaderCenterBarButtonItems(
+        config: ScreenStackHeaderConfig,
+        headerCenterBarButtonItems: ReadableArray?,
+    ) = Unit
+
+    override fun setHeaderToolbarItems(
+        config: ScreenStackHeaderConfig,
+        headerToolbarItems: ReadableArray?,
+    ) = Unit
+
     override fun setTitleFontFamily(
         config: ScreenStackHeaderConfig,
         titleFontFamily: String?,
diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
index 64f14f0c5ade16eb9519b8780d4ce013f88fbd3a..6cb1d69f97a95759c662a6006a9626925d9fefd9 100644
--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt
@@ -47,6 +47,12 @@ class ScreenStackHeaderSubviewManager :
         Log.w("[RNScreens]", "hidesSharedBackground prop is not available on Android")
     }
 
+    // Bar button transition identifiers only apply on iOS.
+    override fun setIdentifier(
+        view: ScreenStackHeaderSubview,
+        identifier: String?,
+    ) = Unit
+
     // synchronousShadowStateUpdatesEnabled is not available on Android atm,
     // however we must override their setters
     override fun setSynchronousShadowStateUpdatesEnabled(
diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.h
index a26d1254eafd89268f65d968988cf6a3b8c1e457..62eea42ac26c532801189556c5f51012c3df3a52 100644
--- a/ios/RNSBarButtonItem.h
+++ b/ios/RNSBarButtonItem.h
@@ -15,6 +15,10 @@ typedef void (^RNSBarButtonMenuItemAction)(NSString *menuId);
                         action:(RNSBarButtonItemAction)action
                     menuAction:(RNSBarButtonMenuItemAction)menuAction
                    imageLoader:(RCTImageLoader *)imageLoader;
+
++ (UIMenu *)initUIMenuWithDict:(NSDictionary<NSString *, id> *)dict
+                    menuAction:(RNSBarButtonMenuItemAction)menuAction
+                   imageLoader:(RCTImageLoader *)imageLoader;
 #endif // defined(__cplusplus)
 
 @end
diff --git a/ios/RNSBarButtonItem.mm b/ios/RNSBarButtonItem.mm
index 0eb1f09dee82edd99e3e614938233db5cdeaebfe..73298f10807520970f625e166d7f5dd1338206f1 100644
--- a/ios/RNSBarButtonItem.mm
+++ b/ios/RNSBarButtonItem.mm
@@ -110,6 +110,58 @@ - (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict
   if (dict[@"accessibilityHint"]) {
     self.accessibilityHint = dict[@"accessibilityHint"];
   }
+#if !TARGET_OS_TV && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+  NSNumber *glassEffectNum = dict[@"glassEffect"];
+  if (@available(iOS 26.0, *)) {
+    if ([glassEffectNum boolValue]) {
+      UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];
+      configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;
+      UIBackgroundConfiguration *background = configuration.background;
+      background.strokeWidth = 0.0;
+      background.strokeColor = UIColor.clearColor;
+      configuration.background = background;
+      configuration.contentInsets = NSDirectionalEdgeInsetsZero;
+      if (title != nil) {
+        configuration.title = title;
+      }
+
+      UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];
+      button.translatesAutoresizingMaskIntoConstraints = NO;
+      button.enabled = self.enabled;
+      button.accessibilityLabel = self.accessibilityLabel;
+      button.accessibilityHint = self.accessibilityHint;
+
+      CGFloat resolvedWidth = width != nil ? width.doubleValue : 56.0;
+      [button.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;
+      [button.heightAnchor constraintEqualToConstant:50.0].active = YES;
+
+      [[self class] resolveImageFromConfig:dict
+                               imageLoader:imageLoader
+                           completionBlock:^(UIImage *img) {
+                             UIButtonConfiguration *updatedConfiguration = button.configuration;
+                             updatedConfiguration.image = img;
+                             button.configuration = updatedConfiguration;
+                           }];
+
+      NSDictionary *menu = dict[@"menu"];
+      if (menu) {
+        button.menu = [[self class] initUIMenuWithDict:menu menuAction:menuAction imageLoader:imageLoader];
+        button.showsMenuAsPrimaryAction = YES;
+      } else {
+        NSString *buttonId = dict[@"buttonId"];
+        if (buttonId && action) {
+          UIAction *pressAction = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull sender) {
+            action(buttonId);
+          }];
+          [button addAction:pressAction forControlEvents:UIControlEventTouchUpInside];
+        }
+      }
+
+      self.customView = button;
+      return self;
+    }
+  }
+#endif
 
 #if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000
   if (@available(tvOS 17.0, *)) {
diff --git a/ios/RNSScreenStackHeaderConfig.h b/ios/RNSScreenStackHeaderConfig.h
index 8a41697223f6c4b5cb2dd6d450fc58cbb3b27536..309ba2f762068dcf452c172f7c7cbf2a55d03cb2 100644
--- a/ios/RNSScreenStackHeaderConfig.h
+++ b/ios/RNSScreenStackHeaderConfig.h
@@ -23,6 +23,8 @@
 NS_ASSUME_NONNULL_BEGIN
 
 @property (nonatomic, retain) NSString *title;
+@property (nonatomic, retain) NSString *subtitle;
+@property (nonatomic, retain) NSString *largeSubtitle;
 @property (nonatomic, retain) NSString *titleFontFamily;
 @property (nonatomic, retain) NSNumber *titleFontSize;
 @property (nonatomic, retain) NSString *titleFontWeight;
@@ -47,9 +49,12 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic) BOOL backButtonInCustomView;
 @property (nonatomic) UISemanticContentAttribute direction;
 @property (nonatomic) UINavigationItemBackButtonDisplayMode backButtonDisplayMode;
+@property (nonatomic) NSInteger navigationItemStyle;
 @property (nonatomic) RNSBlurEffectStyle blurEffect;
 @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerRightBarButtonItems;
 @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerLeftBarButtonItems;
+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerCenterBarButtonItems;
+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerToolbarItems;
 @property (nonatomic, readwrite) BOOL synchronousShadowStateUpdatesEnabled;
 
 NS_ASSUME_NONNULL_END
diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm
index 1c844846a5c66e31cfa530ca462774d2fb6bbea1..a60b96b44e85d3f4b29dc0d190b95ab60e185033 100644
--- a/ios/RNSScreenStackHeaderConfig.mm
+++ b/ios/RNSScreenStackHeaderConfig.mm
@@ -24,11 +24,46 @@
 #import "RNSSearchBar.h"
 #import "UINavigationBar+RNSUtility.h"
 
+#import <objc/runtime.h>
+
 namespace react = facebook::react;
 
 static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;
+
+#if !TARGET_OS_TV
+// Each placement keeps its own cache so changes to one side do not move live
+// items on the other side into new groups during a navigation transition.
+static char RNSAppliedLeadingHeaderItemsKey;
+static char RNSAppliedTrailingHeaderItemsKey;
+static char RNSAppliedCenterHeaderItemsKey;
+
+// Records a placement's owner and contents, returning whether they changed.
+static BOOL RNSUpdateHeaderItemsCache(UINavigationItem *navitem, const void *key, NSArray *itemsKey)
+{
+  if ([objc_getAssociatedObject(navitem, key) isEqual:itemsKey]) {
+    return NO;
+  }
+  objc_setAssociatedObject(navitem, key, itemsKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+  return YES;
+}
+#endif
+static char RNSAppliedToolbarConfigsKey;
 static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34;
 
+static NSInteger navigationItemStyleFromCppEquivalent(
+    react::RNSScreenStackHeaderConfigNavigationItemStyle navigationItemStyle)
+{
+  switch (navigationItemStyle) {
+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Browser:
+      return UINavigationItemStyleBrowser;
+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Editor:
+      return UINavigationItemStyleEditor;
+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Navigator:
+    default:
+      return UINavigationItemStyleNavigator;
+  }
+}
+
 @interface RCTImageLoader (Private)
 - (id<RCTImageCache>)imageCache;
 @end
@@ -46,6 +81,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string
 @end
 
 @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>
+#if !TARGET_OS_TV
+- (NSArray<UIBarButtonItemGroup *> *)barButtonItemGroupsFromItems:(NSArray<UIBarButtonItem *> *)items;
+#endif
 @end
 
 @implementation RNSScreenStackHeaderConfig {
@@ -80,6 +118,7 @@ - (void)initProps
   self.hidden = YES;
   _reactSubviews = [NSMutableArray new];
   _backTitleVisible = YES;
+  _navigationItemStyle = UINavigationItemStyleNavigator;
   _blurEffect = RNSBlurEffectStyleNone;
   _synchronousShadowStateUpdatesEnabled = YES;
 }
@@ -507,6 +546,10 @@ + (void)updateViewController:(UIViewController *)vc
 
   if (shouldHide) {
     navitem.title = config.title;
+    if (@available(iOS 26.0, *)) {
+      navitem.subtitle = config.subtitle;
+      navitem.largeSubtitle = config.largeSubtitle;
+    }
 
     // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.
     [navctr setNavigationBarHidden:YES animated:animated];
@@ -523,11 +566,19 @@ + (void)updateViewController:(UIViewController *)vc
   }
   navitem.largeTitleDisplayMode =
       config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
+
+  if (@available(iOS 16.0, *)) {
+    navitem.style = (UINavigationItemStyle)config.navigationItemStyle;
+  }
 #endif
 
   UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];
   navitem.standardAppearance = appearance;
   navitem.compactAppearance = appearance;
+  if (@available(iOS 26.0, *)) {
+    navitem.subtitle = config.subtitle;
+    navitem.largeSubtitle = config.largeSubtitle;
+  }
 
 // appearance does not apply to the tvOS so we need to use lagacy customization
 #if TARGET_OS_TV
@@ -559,8 +610,6 @@ + (void)updateViewController:(UIViewController *)vc
   navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
 #endif
   navitem.titleView = nil;
-  navitem.leftBarButtonItems = nil;
-  navitem.rightBarButtonItems = nil;
 
 #if !TARGET_OS_TV
   // We want to set navitem.searchController to nil only if we are sure
@@ -568,22 +617,18 @@ + (void)updateViewController:(UIViewController *)vc
   bool searchBarPresent = false;
 #endif /* !TARGET_OS_TV */
 
+  NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array];
+  NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array];
   for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
     // This code should be kept in sync on Fabric with analogous switch statement in
     // `- [RNSScreenStackHeaderConfig replaceNavigationBarViewsWithSnapshotOfSubview:]` method.
     switch (subview.type) {
       case RNSScreenStackHeaderSubviewTypeLeft: {
-        NSArray<UIBarButtonItem *> *currentItems = navitem.leftBarButtonItems ?: @[];
-        NSMutableArray<UIBarButtonItem *> *mutableItems = [currentItems mutableCopy];
-        [mutableItems addObject:[subview getUIBarButtonItem]];
-        navitem.leftBarButtonItems = mutableItems;
+        [subviewLeftItems addObject:[subview getUIBarButtonItem]];
         break;
       }
       case RNSScreenStackHeaderSubviewTypeRight: {
-        NSArray<UIBarButtonItem *> *currentItems = navitem.rightBarButtonItems ?: @[];
-        NSMutableArray<UIBarButtonItem *> *mutableItems = [currentItems mutableCopy];
-        [mutableItems addObject:[subview getUIBarButtonItem]];
-        navitem.rightBarButtonItems = mutableItems;
+        [subviewRightItems addObject:[subview getUIBarButtonItem]];
         break;
       }
       case RNSScreenStackHeaderSubviewTypeCenter:
@@ -648,10 +693,479 @@ + (void)updateViewController:(UIViewController *)vc
   // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).
   // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)
   navitem.title = config.title;
-  navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
-                                                withCurrentItems:navitem.leftBarButtonItems];
-  navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
-                                                 withCurrentItems:navitem.rightBarButtonItems];
+  // Retain the config in the key so its address cannot be reused while cached.
+  // Items capture this config's event emitter in their press handlers, so a
+  // remounted header-config view with value-equal configs must still rebuild
+  // instead of dispatching presses into the old config's emitter.
+  // getUIBarButtonItem is stable for the lifetime of its React header
+  // subview. Include those native identities in the key so React-backed
+  // items can use the same cache without reusing a group after its view was
+  // replaced.
+  // A bar button item can belong to only one group. Even if the item itself
+  // is reused, constructing a new group removes it from the old group that
+  // UIKit may still be animating. Preserve each unchanged placement's group,
+  // including the empty leading group beside the system back button.
+#if !TARGET_OS_TV
+  if (@available(iOS 16.0, *)) {
+    NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];
+    NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];
+    NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];
+    if (RNSUpdateHeaderItemsCache(
+            navitem, &RNSAppliedLeadingHeaderItemsKey, @[ config, headerLeftConfigs, subviewLeftItems ])) {
+      NSArray *items = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
+                                      withCurrentItems:subviewLeftItems
+                                        navigationItem:navitem
+                              allowsSearchBarPlacement:NO];
+      navitem.leadingItemGroups = [config barButtonItemGroupsFromItems:items];
+    }
+    if (RNSUpdateHeaderItemsCache(
+            navitem, &RNSAppliedTrailingHeaderItemsKey, @[ config, headerRightConfigs, subviewRightItems ])) {
+      NSArray *items = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
+                                      withCurrentItems:subviewRightItems
+                                        navigationItem:navitem
+                              allowsSearchBarPlacement:NO];
+      navitem.trailingItemGroups = [config barButtonItemGroupsFromItems:items];
+    }
+    if (@available(iOS 26.0, *)) {
+      if (RNSUpdateHeaderItemsCache(navitem, &RNSAppliedCenterHeaderItemsKey, @[ config, headerCenterConfigs ])) {
+        NSArray *items = [config barButtonItemsFromConfigs:config.headerCenterBarButtonItems
+                                        withCurrentItems:@[]
+                                          navigationItem:navitem
+                                allowsSearchBarPlacement:NO];
+        navitem.centerItemGroups = [config barButtonItemGroupsFromItems:items];
+      }
+    }
+  } else
+#endif
+  {
+    NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems
+                                                                      withCurrentItems:subviewLeftItems
+                                                                        navigationItem:navitem
+                                                              allowsSearchBarPlacement:NO];
+    NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems
+                                                                       withCurrentItems:subviewRightItems
+                                                                         navigationItem:navitem
+                                                               allowsSearchBarPlacement:NO];
+    navitem.leftBarButtonItems = leftBarButtonItems;
+    navitem.rightBarButtonItems = rightBarButtonItems;
+  }
+  NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;
+  NSMutableArray<NSDictionary<NSString *, id> *> *nonMailSearchToolbarConfigs = [NSMutableArray array];
+  for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {
+    if (toolbarConfig[@"mailSearchToolbar"]) {
+      if (mailSearchToolbarConfig == nil) {
+        mailSearchToolbarConfig = toolbarConfig;
+      }
+    } else {
+      [nonMailSearchToolbarConfigs addObject:toolbarConfig];
+    }
+  }
+  NSArray<NSDictionary<NSString *, id> *> *navigationToolbarConfigs = [nonMailSearchToolbarConfigs copy];
+  if (mailSearchToolbarConfig == nil) {
+    for (NSDictionary<NSString *, id> *rightConfig in config.headerRightBarButtonItems) {
+      if ([rightConfig[@"bottomMailSearchToolbar"] boolValue]) {
+        mailSearchToolbarConfig = rightConfig;
+        break;
+      }
+    }
+  }
+
+  static NSInteger const RNSMailSearchToolbarViewTag = 260628;
+  UIView *chromeHostView = vc.view;
+  UIView *existingMailSearchToolbar = [chromeHostView viewWithTag:RNSMailSearchToolbarViewTag];
+  if (existingMailSearchToolbar == nil) {
+    existingMailSearchToolbar = [vc.view viewWithTag:RNSMailSearchToolbarViewTag];
+  }
+  // Reuse the live toolbar when nothing it was built from changed: rebuilding
+  // replaces the fallback UISearchTextField with a fresh empty one, dropping
+  // the user's in-progress search text and first responder on every unrelated
+  // header update. Keyed on the config instance (the button/search blocks
+  // capture its event emitter), the toolbar config values, and the host width
+  // (the width constraint constant is resolved from it at build time).
+  static char RNSAppliedMailSearchToolbarConfigKey;
+  NSArray *mailSearchToolbarKey = mailSearchToolbarConfig != nil
+      ? @[ @((uintptr_t)config), mailSearchToolbarConfig, @(chromeHostView.bounds.size.width) ]
+      : nil;
+  BOOL reuseMailSearchToolbar = existingMailSearchToolbar != nil && mailSearchToolbarKey != nil &&
+      [objc_getAssociatedObject(existingMailSearchToolbar, &RNSAppliedMailSearchToolbarConfigKey)
+          isEqual:mailSearchToolbarKey];
+  if (!reuseMailSearchToolbar) {
+    [existingMailSearchToolbar removeFromSuperview];
+  }
+
+  if (mailSearchToolbarConfig != nil && !reuseMailSearchToolbar) {
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+    if (@available(iOS 26.0, *)) {
+      CGFloat horizontalInset = 18.0;
+      NSNumber *width = mailSearchToolbarConfig[@"width"];
+      CGFloat hostWidth = chromeHostView.bounds.size.width;
+      if (hostWidth <= 0.0) {
+        hostWidth = vc.view.bounds.size.width;
+      }
+      if (hostWidth <= 0.0) {
+        hostWidth = [UIScreen mainScreen].bounds.size.width;
+      }
+      CGFloat fallbackWidth = MIN(560.0, MAX(300.0, hostWidth - (horizontalInset * 2.0)));
+      CGFloat resolvedWidth = width != nil ? width.doubleValue : fallbackWidth;
+      if (hostWidth > 0.0) {
+        resolvedWidth = MIN(resolvedWidth, MAX(260.0, hostWidth - 12.0));
+      }
+
+      CGFloat toolbarHeight = 48.0;
+      CGFloat toolbarRadius = toolbarHeight / 2.0;
+      CGFloat buttonSize = toolbarHeight;
+      CGFloat sideButtonReserve = buttonSize + 8.0;
+      CGFloat searchFieldHeight = 42.0;
+      CGFloat toolbarBottomSpacing = 4.0;
+      UIFont *searchFont = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle3];
+      NSDictionary<NSAttributedStringKey, id> *placeholderAttributes = @{
+        NSForegroundColorAttributeName : UIColor.secondaryLabelColor,
+        NSFontAttributeName : searchFont,
+      };
+      UIKeyboardLayoutGuide *keyboardLayoutGuide = chromeHostView.keyboardLayoutGuide;
+
+      UIView *toolbarHost = [[UIView alloc] init];
+      toolbarHost.tag = RNSMailSearchToolbarViewTag;
+      objc_setAssociatedObject(
+          toolbarHost,
+          &RNSAppliedMailSearchToolbarConfigKey,
+          mailSearchToolbarKey,
+          OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+      toolbarHost.translatesAutoresizingMaskIntoConstraints = NO;
+      [chromeHostView addSubview:toolbarHost];
+      // The screen stays mounted beneath pushed routes, so its keyboard layout
+      // guide can track a keyboard owned by another screen. Keep the toolbar at
+      // rest unless its own search field is editing.
+      NSLayoutConstraint *keyboardAvoidConstraint =
+          [toolbarHost.bottomAnchor constraintEqualToAnchor:keyboardLayoutGuide.topAnchor
+                                                   constant:-toolbarBottomSpacing];
+      keyboardAvoidConstraint.priority = UILayoutPriorityDefaultLow;
+      NSLayoutConstraint *restingBottomConstraint =
+          [toolbarHost.bottomAnchor constraintEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor
+                                                   constant:-toolbarBottomSpacing];
+      restingBottomConstraint.priority = UILayoutPriorityDefaultHigh;
+      [NSLayoutConstraint activateConstraints:@[
+        [toolbarHost.centerXAnchor constraintEqualToAnchor:chromeHostView.centerXAnchor],
+        [toolbarHost.bottomAnchor constraintLessThanOrEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor
+                                                           constant:-toolbarBottomSpacing],
+        keyboardAvoidConstraint,
+        restingBottomConstraint,
+        [toolbarHost.widthAnchor constraintEqualToConstant:resolvedWidth],
+        [toolbarHost.heightAnchor constraintEqualToConstant:toolbarHeight],
+      ]];
+      [chromeHostView bringSubviewToFront:toolbarHost];
+
+      UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular];
+      glassEffect.interactive = YES;
+      UIVisualEffectView *glassView = [[UIVisualEffectView alloc] initWithEffect:glassEffect];
+      glassView.clipsToBounds = YES;
+      glassView.layer.cornerRadius = toolbarRadius;
+      glassView.translatesAutoresizingMaskIntoConstraints = NO;
+      [toolbarHost addSubview:glassView];
+      BOOL hasFilterButton =
+          mailSearchToolbarConfig[@"filterButtonId"] != nil || mailSearchToolbarConfig[@"filterMenu"] != nil;
+      BOOL hasComposeButton =
+          mailSearchToolbarConfig[@"composeButtonId"] != nil || mailSearchToolbarConfig[@"composeMenu"] != nil;
+      CGFloat glassLeadingInset = hasFilterButton ? sideButtonReserve : 0.0;
+      CGFloat glassTrailingInset = hasComposeButton ? -sideButtonReserve : 0.0;
+      NSLayoutConstraint *glassLeadingConstraint =
+          [glassView.leadingAnchor constraintEqualToAnchor:toolbarHost.leadingAnchor constant:glassLeadingInset];
+      NSLayoutConstraint *glassTrailingConstraint =
+          [glassView.trailingAnchor constraintEqualToAnchor:toolbarHost.trailingAnchor constant:glassTrailingInset];
+      [NSLayoutConstraint activateConstraints:@[
+        glassLeadingConstraint,
+        glassTrailingConstraint,
+        [glassView.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],
+        [glassView.heightAnchor constraintEqualToConstant:toolbarHeight],
+      ]];
+
+      void (^emitButtonPress)(NSString *) = ^(NSString *buttonId) {
+        auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
+            config->_eventEmitter);
+        if (eventEmitter && buttonId) {
+          eventEmitter->onPressHeaderBarButtonItem(
+              facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonItem{
+                  .buttonId = std::string([buttonId UTF8String])});
+        }
+      };
+
+      void (^emitMenuPress)(NSString *) = ^(NSString *menuId) {
+        auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
+            config->_eventEmitter);
+        if (eventEmitter && menuId) {
+          eventEmitter->onPressHeaderBarButtonMenuItem(
+              facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonMenuItem{
+                  .menuId = std::string([menuId UTF8String])});
+        }
+      };
+
+      UIButton *(^makeGlassButton)(NSString *, NSString *, NSDictionary *) =
+          ^UIButton *(NSString *systemImageName, NSString *buttonId, NSDictionary *menuConfig) {
+        UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];
+        configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;
+        configuration.image = [UIImage systemImageNamed:systemImageName ?: @"circle"];
+        configuration.baseForegroundColor = UIColor.labelColor;
+        UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];
+        if (menuConfig != nil) {
+          button.menu = [RNSBarButtonItem initUIMenuWithDict:menuConfig
+                                                  menuAction:emitMenuPress
+                                                 imageLoader:config->_imageLoader];
+          button.showsMenuAsPrimaryAction = YES;
+        } else if (buttonId != nil) {
+          UIAction *action = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull action) {
+            emitButtonPress(buttonId);
+          }];
+          [button addAction:action forControlEvents:UIControlEventTouchUpInside];
+        }
+        return button;
+      };
+
+      BOOL useFallbackSearchField = [mailSearchToolbarConfig[@"useFallbackSearchField"] boolValue];
+      UISearchBar *searchBar =
+          !useFallbackSearchField && navitem.searchController != nil ? navitem.searchController.searchBar : nil;
+      NSString *placeholder = mailSearchToolbarConfig[@"placeholder"];
+      UITextField *resolvedSearchTextField = nil;
+      if (searchBar != nil) {
+        if (placeholder != nil) {
+          searchBar.placeholder = placeholder;
+        }
+        searchBar.hidden = NO;
+        searchBar.alpha = 1.0;
+        searchBar.userInteractionEnabled = YES;
+        searchBar.searchBarStyle = UISearchBarStyleMinimal;
+        searchBar.backgroundImage = [UIImage new];
+        searchBar.searchTextField.hidden = NO;
+        searchBar.searchTextField.alpha = 1.0;
+        searchBar.searchTextField.backgroundColor = UIColor.clearColor;
+        searchBar.searchTextField.font = searchFont;
+        searchBar.searchTextField.adjustsFontForContentSizeCategory = YES;
+        searchBar.searchTextField.textColor = UIColor.labelColor;
+        searchBar.searchTextField.tintColor = UIColor.labelColor;
+        resolvedSearchTextField = searchBar.searchTextField;
+        if (placeholder != nil) {
+          searchBar.searchTextField.attributedPlaceholder =
+              [[NSAttributedString alloc] initWithString:placeholder attributes:placeholderAttributes];
+        }
+        searchBar.translatesAutoresizingMaskIntoConstraints = NO;
+        [glassView.contentView addSubview:searchBar];
+        [NSLayoutConstraint activateConstraints:@[
+          [searchBar.leadingAnchor constraintEqualToAnchor:glassView.contentView.leadingAnchor constant:8.0],
+          [searchBar.trailingAnchor constraintEqualToAnchor:glassView.contentView.trailingAnchor constant:-8.0],
+          [searchBar.centerYAnchor constraintEqualToAnchor:glassView.contentView.centerYAnchor],
+          [searchBar.heightAnchor constraintEqualToConstant:searchFieldHeight],
+        ]];
+      } else {
+        UISearchTextField *searchField = [UISearchTextField new];
+        if (placeholder != nil) {
+          searchField.placeholder = placeholder;
+          searchField.attributedPlaceholder =
+              [[NSAttributedString alloc] initWithString:placeholder attributes:placeholderAttributes];
+        }
+        searchField.borderStyle = UITextBorderStyleNone;
+        searchField.font = searchFont;
+        searchField.adjustsFontForContentSizeCategory = YES;
+        searchField.textColor = UIColor.labelColor;
+        searchField.tintColor = UIColor.labelColor;
+        resolvedSearchTextField = searchField;
+        searchField.translatesAutoresizingMaskIntoConstraints = NO;
+        [glassView.contentView addSubview:searchField];
+        [NSLayoutConstraint activateConstraints:@[
+          [searchField.leadingAnchor constraintEqualToAnchor:glassView.contentView.leadingAnchor constant:14.0],
+          [searchField.trailingAnchor constraintEqualToAnchor:glassView.contentView.trailingAnchor constant:-14.0],
+          [searchField.centerYAnchor constraintEqualToAnchor:glassView.contentView.centerYAnchor],
+          [searchField.heightAnchor constraintEqualToConstant:searchFieldHeight],
+        ]];
+      }
+
+      // A bare UISearchTextField keeps UITextField defaults (sentence
+      // capitalization, autocorrect), unlike UISearchBar. Search queries are
+      // identifiers, so match the autoCapitalize "none" the JS fallbacks use.
+      if (resolvedSearchTextField != nil) {
+        resolvedSearchTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
+        resolvedSearchTextField.autocorrectionType = UITextAutocorrectionTypeNo;
+        resolvedSearchTextField.spellCheckingType = UITextSpellCheckingTypeNo;
+      }
+
+      NSString *searchTextChangeId = mailSearchToolbarConfig[@"searchTextChangeId"];
+      NSString *searchTextChangeActionIdentifier = @"org.react-native-screens.mail-search-toolbar.text-change";
+      if (resolvedSearchTextField != nil) {
+        [resolvedSearchTextField removeActionForIdentifier:searchTextChangeActionIdentifier
+                                          forControlEvents:UIControlEventEditingChanged];
+      }
+      if (searchTextChangeId != nil && resolvedSearchTextField != nil) {
+        [resolvedSearchTextField
+            addAction:[UIAction actionWithTitle:@""
+                                          image:nil
+                                     identifier:searchTextChangeActionIdentifier
+                                        handler:^(__kindof UIAction *_Nonnull action) {
+                                                  UITextField *field = (UITextField *)action.sender;
+                                                  emitButtonPress(
+                                                      [NSString stringWithFormat:@"%@:%@", searchTextChangeId, field.text ?: @""]);
+                                                }]
+            forControlEvents:UIControlEventEditingChanged];
+      }
+
+      UIButton *filterButton = nil;
+      if (hasFilterButton) {
+        filterButton = makeGlassButton(
+            mailSearchToolbarConfig[@"filterSystemImageName"] ?: @"line.3.horizontal.decrease",
+            mailSearchToolbarConfig[@"filterButtonId"],
+            mailSearchToolbarConfig[@"filterMenu"]);
+        filterButton.translatesAutoresizingMaskIntoConstraints = NO;
+        [toolbarHost addSubview:filterButton];
+        [NSLayoutConstraint activateConstraints:@[
+          [filterButton.leadingAnchor constraintEqualToAnchor:toolbarHost.leadingAnchor],
+          [filterButton.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],
+          [filterButton.widthAnchor constraintEqualToConstant:buttonSize],
+          [filterButton.heightAnchor constraintEqualToConstant:buttonSize],
+        ]];
+      }
+
+      UIButton *composeButton = nil;
+      if (hasComposeButton) {
+        composeButton = makeGlassButton(
+            mailSearchToolbarConfig[@"composeSystemImageName"] ?: @"square.and.pencil",
+            mailSearchToolbarConfig[@"composeButtonId"],
+            mailSearchToolbarConfig[@"composeMenu"]);
+        composeButton.translatesAutoresizingMaskIntoConstraints = NO;
+        [toolbarHost addSubview:composeButton];
+        [NSLayoutConstraint activateConstraints:@[
+          [composeButton.trailingAnchor constraintEqualToAnchor:toolbarHost.trailingAnchor],
+          [composeButton.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],
+          [composeButton.widthAnchor constraintEqualToConstant:buttonSize],
+          [composeButton.heightAnchor constraintEqualToConstant:buttonSize],
+        ]];
+      }
+
+      BOOL showsSearchDismissButton =
+          [mailSearchToolbarConfig[@"showsSearchDismissButton"] boolValue] && resolvedSearchTextField != nil;
+      UIButton *searchDismissButton = nil;
+      if (showsSearchDismissButton) {
+        searchDismissButton = makeGlassButton(@"xmark", nil, nil);
+        searchDismissButton.accessibilityLabel = @"Dismiss search keyboard";
+        searchDismissButton.alpha = 0.0;
+        searchDismissButton.hidden = YES;
+        searchDismissButton.translatesAutoresizingMaskIntoConstraints = NO;
+        [toolbarHost addSubview:searchDismissButton];
+        [NSLayoutConstraint activateConstraints:@[
+          [searchDismissButton.trailingAnchor constraintEqualToAnchor:toolbarHost.trailingAnchor],
+          [searchDismissButton.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],
+          [searchDismissButton.widthAnchor constraintEqualToConstant:buttonSize],
+          [searchDismissButton.heightAnchor constraintEqualToConstant:buttonSize],
+        ]];
+        __weak UITextField *weakSearchTextField = resolvedSearchTextField;
+        [searchDismissButton
+            addAction:[UIAction actionWithHandler:^(__kindof UIAction *_Nonnull action) {
+                        [weakSearchTextField resignFirstResponder];
+                      }]
+            forControlEvents:UIControlEventTouchUpInside];
+      }
+
+      __weak UIButton *weakFilterButton = filterButton;
+      __weak UIButton *weakComposeButton = composeButton;
+      __weak UIButton *weakSearchDismissButton = searchDismissButton;
+      __weak NSLayoutConstraint *weakGlassLeadingConstraint = glassLeadingConstraint;
+      __weak NSLayoutConstraint *weakGlassTrailingConstraint = glassTrailingConstraint;
+      __weak NSLayoutConstraint *weakKeyboardAvoidConstraint = keyboardAvoidConstraint;
+      __weak NSLayoutConstraint *weakRestingBottomConstraint = restingBottomConstraint;
+      __weak UIView *weakToolbarHost = toolbarHost;
+      __weak UITextField *weakSearchTextField = resolvedSearchTextField;
+      void (^setSearchEditingAppearance)(BOOL, BOOL) = ^(BOOL isEditing, BOOL animated) {
+        weakKeyboardAvoidConstraint.priority =
+            isEditing ? UILayoutPriorityDefaultHigh : UILayoutPriorityDefaultLow;
+        weakRestingBottomConstraint.priority =
+            isEditing ? UILayoutPriorityDefaultLow : UILayoutPriorityDefaultHigh;
+
+        if (showsSearchDismissButton) {
+          if (isEditing) {
+            weakSearchDismissButton.hidden = NO;
+          } else {
+            weakFilterButton.hidden = NO;
+            weakComposeButton.hidden = NO;
+          }
+          weakGlassLeadingConstraint.constant = isEditing ? 0.0 : glassLeadingInset;
+          weakGlassTrailingConstraint.constant = isEditing ? -sideButtonReserve : glassTrailingInset;
+
+          void (^changes)(void) = ^{
+            weakFilterButton.alpha = isEditing ? 0.0 : 1.0;
+            weakComposeButton.alpha = isEditing ? 0.0 : 1.0;
+            weakSearchDismissButton.alpha = isEditing ? 1.0 : 0.0;
+            [weakToolbarHost layoutIfNeeded];
+          };
+          void (^completion)(BOOL) = ^(BOOL finished) {
+            if (!finished || weakSearchTextField.isFirstResponder != isEditing) {
+              return;
+            }
+            weakFilterButton.hidden = isEditing;
+            weakComposeButton.hidden = isEditing;
+            weakSearchDismissButton.hidden = !isEditing;
+          };
+          if (animated) {
+            [UIView animateWithDuration:0.2
+                                  delay:0.0
+                                options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
+                             animations:changes
+                             completion:completion];
+          } else {
+            changes();
+            completion(YES);
+          }
+        }
+        [weakToolbarHost setNeedsLayout];
+      };
+
+      setSearchEditingAppearance(resolvedSearchTextField.isFirstResponder, NO);
+      NSString *beginActionIdentifier = @"org.react-native-screens.mail-search-toolbar.keyboard-begin";
+      NSString *endActionIdentifier = @"org.react-native-screens.mail-search-toolbar.keyboard-end";
+      [resolvedSearchTextField removeActionForIdentifier:beginActionIdentifier
+                                       forControlEvents:UIControlEventEditingDidBegin];
+      [resolvedSearchTextField removeActionForIdentifier:endActionIdentifier
+                                       forControlEvents:UIControlEventEditingDidEnd];
+      [resolvedSearchTextField
+          addAction:[UIAction actionWithTitle:@""
+                                        image:nil
+                                   identifier:beginActionIdentifier
+                                      handler:^(__kindof UIAction *_Nonnull action) {
+                                                setSearchEditingAppearance(YES, YES);
+                                              }]
+          forControlEvents:UIControlEventEditingDidBegin];
+      [resolvedSearchTextField
+          addAction:[UIAction actionWithTitle:@""
+                                        image:nil
+                                   identifier:endActionIdentifier
+                                      handler:^(__kindof UIAction *_Nonnull action) {
+                                                setSearchEditingAppearance(NO, YES);
+                                              }]
+          forControlEvents:UIControlEventEditingDidEnd];
+    }
+#endif
+  }
+
+  NSArray *toolbarConfigsKey = navigationToolbarConfigs ?: @[];
+  // Same reuse rule as the header item groups above (including the config
+  // identity — cached toolbar items capture this config's event emitter).
+  // The top-view-controller and hidden-state checks scope the skip to
+  // same-screen refreshes, so transitions between screens with different
+  // toolbars still reapply.
+  NSArray *toolbarCacheKey = @[ @((uintptr_t)config), toolbarConfigsKey ];
+  BOOL reuseToolbarItems = navctr.topViewController == vc &&
+      navctr.isToolbarHidden == (toolbarConfigsKey.count == 0) &&
+      [objc_getAssociatedObject(navitem, &RNSAppliedToolbarConfigsKey) isEqual:toolbarCacheKey];
+  if (!reuseToolbarItems) {
+    NSArray<UIBarButtonItem *> *toolbarItems = [config barButtonItemsFromConfigs:navigationToolbarConfigs
+                                                                 withCurrentItems:@[]
+                                                                   navigationItem:navitem
+                                                         allowsSearchBarPlacement:YES];
+    if (toolbarItems.count > 0) {
+      vc.toolbarItems = toolbarItems;
+      [navctr setToolbarHidden:NO animated:animated];
+    } else {
+      vc.toolbarItems = nil;
+      [navctr setToolbarHidden:YES animated:animated];
+    }
+    objc_setAssociatedObject(
+        navitem, &RNSAppliedToolbarConfigsKey, toolbarCacheKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+  }
 
   // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items
   // (setting nav bar visibility should be done after `navitem.*BarButtonItems`).
@@ -784,6 +1289,8 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
 
 - (NSArray<UIBarButtonItem *> *)barButtonItemsFromConfigs:(NSArray<NSDictionary<NSString *, id> *> *)dicts
                                          withCurrentItems:(NSArray<UIBarButtonItem *> *)currentItems
+                                           navigationItem:(UINavigationItem *)navitem
+                                 allowsSearchBarPlacement:(BOOL)allowsSearchBarPlacement
 {
   if (dicts.count == 0) {
     return currentItems;
@@ -792,7 +1299,200 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
   [items addObjectsFromArray:currentItems];
   for (NSUInteger i = 0; i < dicts.count; i++) {
     NSDictionary *dict = dicts[i];
-    if (dict[@"buttonId"] || dict[@"menu"]) {
+    if (dict[@"mailSearchToolbar"]) {
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+      if (@available(iOS 26.0, *)) {
+        NSNumber *width = dict[@"width"];
+        CGFloat resolvedWidth = width != nil ? width.doubleValue : MAX(320.0, [UIScreen mainScreen].bounds.size.width - 56.0);
+        CGFloat resolvedHeight = 58.0;
+        UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, resolvedWidth, resolvedHeight)];
+        container.translatesAutoresizingMaskIntoConstraints = NO;
+        [container.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;
+        [container.heightAnchor constraintEqualToConstant:resolvedHeight].active = YES;
+
+        UIStackView *stackView = [[UIStackView alloc] initWithFrame:container.bounds];
+        stackView.axis = UILayoutConstraintAxisHorizontal;
+        stackView.alignment = UIStackViewAlignmentCenter;
+        stackView.distribution = UIStackViewDistributionFill;
+        stackView.spacing = 10.0;
+        stackView.translatesAutoresizingMaskIntoConstraints = NO;
+        [container addSubview:stackView];
+        [NSLayoutConstraint activateConstraints:@[
+          [stackView.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
+          [stackView.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
+          [stackView.centerYAnchor constraintEqualToAnchor:container.centerYAnchor],
+          [stackView.heightAnchor constraintEqualToConstant:50.0],
+        ]];
+
+        void (^emitButtonPress)(NSString *) = ^(NSString *buttonId) {
+          auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
+              self->_eventEmitter);
+          if (eventEmitter && buttonId) {
+            eventEmitter->onPressHeaderBarButtonItem(
+                facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonItem{
+                    .buttonId = std::string([buttonId UTF8String])});
+          }
+        };
+
+        void (^emitMenuPress)(NSString *) = ^(NSString *menuId) {
+          auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
+              self->_eventEmitter);
+          if (eventEmitter && menuId) {
+            eventEmitter->onPressHeaderBarButtonMenuItem(
+                facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonMenuItem{
+                    .menuId = std::string([menuId UTF8String])});
+          }
+        };
+
+        UIButton *(^makeGlassButton)(NSString *, NSString *, NSDictionary *) =
+            ^UIButton *(NSString *systemImageName, NSString *buttonId, NSDictionary *menuConfig) {
+          UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];
+          configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;
+          configuration.image = [UIImage systemImageNamed:systemImageName ?: @"circle"];
+
+          UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];
+          button.translatesAutoresizingMaskIntoConstraints = NO;
+          [button.widthAnchor constraintEqualToConstant:58.0].active = YES;
+          [button.heightAnchor constraintEqualToConstant:50.0].active = YES;
+          if (menuConfig != nil) {
+            button.menu = [RNSBarButtonItem initUIMenuWithDict:menuConfig
+                                                    menuAction:emitMenuPress
+                                                   imageLoader:self->_imageLoader];
+            button.showsMenuAsPrimaryAction = YES;
+          } else if (buttonId != nil) {
+            UIAction *action = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull action) {
+              emitButtonPress(buttonId);
+            }];
+            [button addAction:action forControlEvents:UIControlEventTouchUpInside];
+          }
+          return button;
+        };
+
+        BOOL hasFilterButton = dict[@"filterButtonId"] != nil || dict[@"filterMenu"] != nil;
+        BOOL hasComposeButton = dict[@"composeButtonId"] != nil || dict[@"composeMenu"] != nil;
+
+        if (hasFilterButton) {
+          UIButton *filterButton = makeGlassButton(
+              dict[@"filterSystemImageName"] ?: @"line.3.horizontal.decrease",
+              dict[@"filterButtonId"],
+              dict[@"filterMenu"]);
+          [stackView addArrangedSubview:filterButton];
+        }
+
+        UISearchBar *searchBar = navitem.searchController != nil ? navitem.searchController.searchBar : [UISearchBar new];
+        NSString *placeholder = dict[@"placeholder"];
+        if (placeholder != nil) {
+          searchBar.placeholder = placeholder;
+        }
+        searchBar.searchBarStyle = UISearchBarStyleMinimal;
+        searchBar.translatesAutoresizingMaskIntoConstraints = NO;
+        [searchBar.heightAnchor constraintEqualToConstant:50.0].active = YES;
+        [stackView addArrangedSubview:searchBar];
+
+        if (hasComposeButton) {
+          UIButton *composeButton = makeGlassButton(
+              dict[@"composeSystemImageName"] ?: @"square.and.pencil",
+              dict[@"composeButtonId"],
+              dict[@"composeMenu"]);
+          [stackView addArrangedSubview:composeButton];
+        }
+
+        UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:container];
+        [items addObject:item];
+      }
+#endif
+    } else if (dict[@"searchField"]) {
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+      if (@available(iOS 26.0, *)) {
+        NSNumber *width = dict[@"width"];
+        CGFloat resolvedWidth = width != nil ? width.doubleValue : 280.0;
+        UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, resolvedWidth, 44.0)];
+        container.translatesAutoresizingMaskIntoConstraints = NO;
+        [container.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;
+        [container.heightAnchor constraintEqualToConstant:44.0].active = YES;
+
+        UISearchTextField *searchField = [UISearchTextField new];
+        NSString *placeholder = dict[@"placeholder"];
+        if (placeholder != nil) {
+          searchField.placeholder = placeholder;
+        }
+        searchField.translatesAutoresizingMaskIntoConstraints = NO;
+        [container addSubview:searchField];
+        [NSLayoutConstraint activateConstraints:@[
+          [searchField.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
+          [searchField.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
+          [searchField.centerYAnchor constraintEqualToAnchor:container.centerYAnchor],
+          [searchField.heightAnchor constraintEqualToConstant:40.0],
+        ]];
+
+        UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:container];
+        [items addObject:item];
+      }
+#endif
+    } else if (dict[@"searchBarPlacement"]) {
+      if (!allowsSearchBarPlacement) {
+        continue;
+      }
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+      if (@available(iOS 26.0, *)) {
+        NSNumber *width = dict[@"width"];
+        UIBarButtonItem *item = nil;
+        if ([dict[@"activatesSearchController"] boolValue]) {
+          UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];
+          configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;
+          configuration.image = [UIImage systemImageNamed:@"magnifyingglass"];
+          configuration.baseForegroundColor = UIColor.labelColor;
+          UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];
+          button.translatesAutoresizingMaskIntoConstraints = NO;
+          [button.widthAnchor constraintEqualToConstant:56.0].active = YES;
+          [button.heightAnchor constraintEqualToConstant:56.0].active = YES;
+          UIAction *action = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull action) {
+            if (navitem.searchController != nil) {
+              [navitem.searchController setActive:YES];
+              [navitem.searchController.searchBar becomeFirstResponder];
+            }
+          }];
+          [button addAction:action forControlEvents:UIControlEventTouchUpInside];
+          item = [[UIBarButtonItem alloc] initWithCustomView:button];
+        } else if ([dict[@"customView"] boolValue] && navitem.searchController != nil) {
+          UISearchBar *searchBar = navitem.searchController.searchBar;
+          CGFloat resolvedWidth = width != nil ? width.doubleValue : searchBar.frame.size.width;
+          CGFloat resolvedHeight = searchBar.frame.size.height > 0 ? searchBar.frame.size.height : 44.0;
+          UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, resolvedWidth, resolvedHeight)];
+          container.translatesAutoresizingMaskIntoConstraints = NO;
+          [container.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;
+          [container.heightAnchor constraintEqualToConstant:resolvedHeight].active = YES;
+          searchBar.frame = container.bounds;
+          searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+          [container addSubview:searchBar];
+          item = [[UIBarButtonItem alloc] initWithCustomView:container];
+        } else {
+          item = navitem.searchBarPlacementBarButtonItem;
+          if (width != nil) {
+            item.width = width.doubleValue;
+          }
+        }
+        NSNumber *index = dict[@"index"];
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+        if (@available(iOS 26.0, *)) {
+          NSNumber *sharesBackground = dict[@"sharesBackground"];
+          if (sharesBackground != nil) {
+            item.sharesBackground = sharesBackground.boolValue;
+          }
+          NSNumber *hidesSharedBackground = dict[@"hidesSharedBackground"];
+          if (hidesSharedBackground != nil) {
+            item.hidesSharedBackground = hidesSharedBackground.boolValue;
+          }
+        }
+#endif
+        if (index != nil && index.integerValue >= 0 && index.integerValue < items.count) {
+          [items insertObject:item atIndex:index.integerValue];
+        } else {
+          [items addObject:item];
+        }
+      }
+#endif
+    } else if (dict[@"buttonId"] || dict[@"menu"]) {
       RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict
           action:^(NSString *buttonId) {
             auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
@@ -814,19 +1514,23 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
           }
           imageLoader:_imageLoader];
       NSNumber *index = dict[@"index"];
-      if (index.integerValue < items.count) {
+      if (index != nil && index.integerValue >= 0 && index.integerValue < items.count) {
         [items insertObject:item atIndex:index.integerValue];
       } else {
         [items addObject:item];
       }
     } else if (dict[@"spacing"]) {
-      UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
-                                                                            target:nil
-                                                                            action:nil];
+      BOOL flexible = [dict[@"flexible"] boolValue];
+      UIBarButtonItem *item = [[UIBarButtonItem alloc]
+          initWithBarButtonSystemItem:(flexible ? UIBarButtonSystemItemFlexibleSpace : UIBarButtonSystemItemFixedSpace)
+                                target:nil
+                                action:nil];
       NSNumber *spacingValue = dict[@"spacing"];
-      item.width = [spacingValue doubleValue];
+      if (!flexible) {
+        item.width = [spacingValue doubleValue];
+      }
       NSNumber *index = dict[@"index"];
-      if (index.integerValue < items.count) {
+      if (index != nil && index.integerValue >= 0 && index.integerValue < items.count) {
         [items insertObject:item atIndex:index.integerValue];
       } else {
         [items addObject:item];
@@ -836,6 +1540,47 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
   return items;
 }
 
+#if !TARGET_OS_TV
+- (NSArray<UIBarButtonItemGroup *> *)barButtonItemGroupsFromItems:(NSArray<UIBarButtonItem *> *)items
+{
+  if (items.count == 0) {
+    return @[];
+  }
+
+  NSMutableArray<UIBarButtonItemGroup *> *groups = [NSMutableArray array];
+  NSMutableArray<UIBarButtonItem *> *sharedRun = [NSMutableArray array];
+
+  void (^flushSharedRun)(void) = ^{
+    if (sharedRun.count == 0) {
+      return;
+    }
+    [groups addObject:[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
+                                                                       items:[sharedRun copy]]];
+    [sharedRun removeAllObjects];
+  };
+
+  for (UIBarButtonItem *item in items) {
+    BOOL shouldShareBackground = NO;
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+    if (@available(iOS 26.0, *)) {
+      shouldShareBackground = item.sharesBackground && !item.hidesSharedBackground;
+    }
+#endif
+
+    if (shouldShareBackground) {
+      [sharedRun addObject:item];
+      continue;
+    }
+
+    flushSharedRun();
+    [groups addObject:[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil items:@[ item ]]];
+  }
+
+  flushSharedRun();
+  return groups;
+}
+#endif
+
 RNS_IGNORE_SUPER_CALL_BEGIN
 - (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
 {
@@ -1023,6 +1768,8 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
   }
 
   _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);
+  _subtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitle);
+  _largeSubtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.largeSubtitle);
   if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {
     _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily);
   }
@@ -1048,6 +1795,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
   _disableBackButtonMenu = newScreenProps.disableBackButtonMenu;
   _backButtonDisplayMode =
       [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];
+  _navigationItemStyle = navigationItemStyleFromCppEquivalent(newScreenProps.navigationItemStyle);
 
   if (newScreenProps.userInterfaceStyle != oldScreenProps.userInterfaceStyle) {
     _userInterfaceStyle = [RNSConvert UIUserInterfaceStyleFromCppEquivalent:newScreenProps.userInterfaceStyle];
@@ -1094,6 +1842,30 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
     _headerRightBarButtonItems = array;
   }
 
+  if (newScreenProps.headerCenterBarButtonItems != oldScreenProps.headerCenterBarButtonItems) {
+    const auto &vec = newScreenProps.headerCenterBarButtonItems;
+    NSMutableArray<NSDictionary<NSString *, id> *> *array = [NSMutableArray arrayWithCapacity:vec.size()];
+    for (const auto &item : vec) {
+      NSDictionary *dict = [RNSConvert idFromFollyDynamic:item];
+      if ([dict isKindOfClass:[NSDictionary class]]) {
+        [array addObject:dict];
+      }
+    }
+    _headerCenterBarButtonItems = array;
+  }
+
+  if (newScreenProps.headerToolbarItems != oldScreenProps.headerToolbarItems) {
+    const auto &vec = newScreenProps.headerToolbarItems;
+    NSMutableArray<NSDictionary<NSString *, id> *> *array = [NSMutableArray arrayWithCapacity:vec.size()];
+    for (const auto &item : vec) {
+      NSDictionary *dict = [RNSConvert idFromFollyDynamic:item];
+      if ([dict isKindOfClass:[NSDictionary class]]) {
+        [array addObject:dict];
+      }
+    }
+    _headerToolbarItems = array;
+  }
+
   [self updateViewControllerIfNeeded];
 
   if (needsNavigationControllerLayout) {
diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm
--- a/ios/RNSScreenStack.mm
+++ b/ios/RNSScreenStack.mm
@@ -1074,6 +1074,15 @@ RNS_IGNORE_SUPER_CALL_END
   return scrollView.contentSize.width > scrollView.frame.size.width;
 }
 
+// Whether a horizontally scrollable ScrollView is resting at its leading edge, so a
+// pan in the back-gesture direction has nothing left to scroll. Checked in the
+// ScrollView's own coordinate space: React Native mirrors the view for RTL layout,
+// which keeps the leading edge at the minimum content offset in both directions.
+- (BOOL)scrollViewIsAtLeadingEdge:(UIScrollView *)scrollView
+{
+  return scrollView.contentOffset.x <= -scrollView.adjustedContentInset.left + 1.0;
+}
+
 // Custom method for compatibility with iOS < 13.4
 // RNSScreenStackView is a UIGestureRecognizerDelegate for three types of gesture recognizers:
 // RNSPanGestureRecognizer, RNSScreenEdgeGestureRecognizer, _UIParallaxTransitionPanGestureRecognizer
@@ -1167,9 +1176,11 @@ RNS_IGNORE_SUPER_CALL_END
     if (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
         [self isScrollViewPanGestureRecognizer:otherGestureRecognizer]) {
       // ScrollView should take precedence when scrolling horizontally (it should be required to fail).
-      // However, if it does not allow for horizontal scrolling, there should be no such restriction,
+      // However, if it does not allow for horizontal scrolling, or is already resting at its leading
+      // edge so a back swipe has nothing left to scroll, there should be no such restriction,
       // and swiping horizontally should dismiss the screen.
-      return [self scrollViewHasHorizontalPart:static_cast<UIScrollView *>(otherGestureRecognizer.view)];
+      UIScrollView *scrollView = static_cast<UIScrollView *>(otherGestureRecognizer.view);
+      return [self scrollViewHasHorizontalPart:scrollView] && ![self scrollViewIsAtLeadingEdge:scrollView];
     }
   }
 
@@ -1189,6 +1200,15 @@ RNS_IGNORE_SUPER_CALL_END
     // gestureRecognizer:shouldRequireFailureOfGestureRecognizer
     isEdgeSwipeGestureRecognizer =
         isEdgeSwipeGestureRecognizer && gestureRecognizer != _controller.interactiveContentPopGestureRecognizer;
+
+    // The exception is a horizontal ScrollView resting at its leading edge: its pan recognizer would
+    // still begin (and merely bounce) on a back swipe, so it must yield to the pop gesture the same
+    // way plain content does.
+    if (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
+        [self isScrollViewPanGestureRecognizer:otherGestureRecognizer]) {
+      UIScrollView *scrollView = static_cast<UIScrollView *>(otherGestureRecognizer.view);
+      return [self scrollViewHasHorizontalPart:scrollView] && [self scrollViewIsAtLeadingEdge:scrollView];
+    }
   }
 #endif // check for iOS >= 26
 
diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm
index add33c4807e038dcd846f6c72b2fc472ded02809..489afa5fa2da09c1ec6986d1832b58bb595396ae 100644
--- a/ios/RNSScreenStackHeaderSubview.mm
+++ b/ios/RNSScreenStackHeaderSubview.mm
@@ -20,7 +20,9 @@ @implementation RNSScreenStackHeaderSubview {
   // This is a strong reference to UIBarButtonItem which creates a retain cycle.
   // The cycle is cleared via `invalidateUIBarButtonItem` method, called by `invalidate` callback.
   UIBarButtonItem *_barButtonItem;
+  NSString *_barButtonItemIdentifier;
   BOOL _hidesSharedBackground;
+  UILabel *_titleScrollEdgeEffectGuide;
 }
 
 #pragma mark - Common
@@ -102,5 +104,24 @@ - (void)updateShadowStateWithFrame:(CGRect)frame
 - (void)layoutSubviews
 {
   [super layoutSubviews];
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+  if (@available(iOS 26.0, *)) {
+    if (_type == RNSScreenStackHeaderSubviewTypeTitle || _type == RNSScreenStackHeaderSubviewTypeCenter) {
+      // UIKit shapes the navigation bar's scroll-edge fade around native labels,
+      // but does not recognize Fabric's custom text views. An empty, non-interactive
+      // label supplies the title's geometry without drawing or becoming a bar item.
+      if (_titleScrollEdgeEffectGuide == nil) {
+        _titleScrollEdgeEffectGuide = [UILabel new];
+        _titleScrollEdgeEffectGuide.accessibilityElementsHidden = YES;
+        // Append after Fabric's children so their mount/unmount indices stay intact.
+        [self addSubview:_titleScrollEdgeEffectGuide];
+      }
+      _titleScrollEdgeEffectGuide.frame = self.bounds;
+    } else {
+      [_titleScrollEdgeEffectGuide removeFromSuperview];
+      _titleScrollEdgeEffectGuide = nil;
+    }
+  }
+#endif
   [self updateShadowStateInContextOfAncestorView:[self findNavigationBar]];
 }
@@ -124,6 +145,7 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
   const auto &newHeaderSubviewProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderSubviewProps>(props);
 
   [self setType:[RNSConvert RNSScreenStackHeaderSubviewTypeFromCppEquivalent:newHeaderSubviewProps.type]];
+  [self setBarButtonItemIdentifier:RCTNSStringFromStringNilIfEmpty(newHeaderSubviewProps.identifier)];
   [self setHidesSharedBackground:newHeaderSubviewProps.hidesSharedBackground];
   [self setSynchronousShadowStateUpdatesEnabled:newHeaderSubviewProps.synchronousShadowStateUpdatesEnabled];
   [super updateProps:props oldProps:oldProps];
@@ -276,7 +298,14 @@ - (void)configureBarButtonItem
 #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
   if (@available(iOS 26.0, *)) {
     if (_barButtonItem != nil) {
-      [_barButtonItem setHidesSharedBackground:_hidesSharedBackground];
+      NSString *currentIdentifier = _barButtonItem.identifier;
+      if (currentIdentifier != _barButtonItemIdentifier &&
+          ![currentIdentifier isEqualToString:_barButtonItemIdentifier]) {
+        _barButtonItem.identifier = _barButtonItemIdentifier;
+      }
+      if (_barButtonItem.hidesSharedBackground != _hidesSharedBackground) {
+        _barButtonItem.hidesSharedBackground = _hidesSharedBackground;
+      }
     }
   }
 #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
@@ -284,10 +313,22 @@ - (void)configureBarButtonItem
 
 - (void)setHidesSharedBackground:(BOOL)hidesSharedBackground
 {
+  if (_hidesSharedBackground == hidesSharedBackground) {
+    return;
+  }
   _hidesSharedBackground = hidesSharedBackground;
   [self configureBarButtonItem];
 }
 
+- (void)setBarButtonItemIdentifier:(nullable NSString *)identifier
+{
+  if (_barButtonItemIdentifier == identifier || [_barButtonItemIdentifier isEqualToString:identifier]) {
+    return;
+  }
+  _barButtonItemIdentifier = [identifier copy];
+  [self configureBarButtonItem];
+}
+
 #pragma mark - Dynamic frameworks support
 
 // Needed because of this: https://github.com/facebook/react-native/pull/37274
diff --git a/lib/commonjs/components/ScreenStackHeaderConfig.js b/lib/commonjs/components/ScreenStackHeaderConfig.js
index 8c509f2657105cfd86448347c1a01fb73651ff9e..e668e089a1788973cb122a150be7846364a2a2cf 100644
--- a/lib/commonjs/components/ScreenStackHeaderConfig.js
+++ b/lib/commonjs/components/ScreenStackHeaderConfig.js
@@ -26,17 +26,42 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_
   } = (0, _EdgeInsetApplicationContext.useEdgeInsetApplication)(!props.hidden, props.disableTopInsetApplication ?? false, props.disableLeftInsetApplication ?? false, props.disableRightInsetApplication ?? false, props.disableBottomInsetApplication ?? false);
   const {
     headerLeftBarButtonItems,
-    headerRightBarButtonItems
+    headerRightBarButtonItems,
+    headerCenterBarButtonItems,
+    headerToolbarItems
   } = props;
   const preparedHeaderLeftBarButtonItems = headerLeftBarButtonItems && _utils.isHeaderBarButtonsAvailableForCurrentPlatform ? (0, _prepareHeaderBarButtonItems.prepareHeaderBarButtonItems)(headerLeftBarButtonItems, 'left') : undefined;
   const preparedHeaderRightBarButtonItems = headerRightBarButtonItems && _utils.isHeaderBarButtonsAvailableForCurrentPlatform ? (0, _prepareHeaderBarButtonItems.prepareHeaderBarButtonItems)(headerRightBarButtonItems, 'right') : undefined;
-  const hasHeaderBarButtonItems = _utils.isHeaderBarButtonsAvailableForCurrentPlatform && (preparedHeaderLeftBarButtonItems?.length || preparedHeaderRightBarButtonItems?.length);
+  const preparedHeaderCenterBarButtonItems = headerCenterBarButtonItems && _utils.isHeaderBarButtonsAvailableForCurrentPlatform ? (0, _prepareHeaderBarButtonItems.prepareHeaderBarButtonItems)(headerCenterBarButtonItems, 'center') : undefined;
+  const preparedHeaderToolbarItems = headerToolbarItems && _utils.isHeaderBarButtonsAvailableForCurrentPlatform ? (0, _prepareHeaderBarButtonItems.prepareHeaderBarButtonItems)(headerToolbarItems, 'toolbar') : undefined;
+  const hasHeaderBarButtonItems = _utils.isHeaderBarButtonsAvailableForCurrentPlatform && (preparedHeaderLeftBarButtonItems?.length || preparedHeaderRightBarButtonItems?.length || preparedHeaderCenterBarButtonItems?.length || preparedHeaderToolbarItems?.length);
 
   // Handle bar button item presses
   const onPressHeaderBarButtonItem = hasHeaderBarButtonItems ? event => {
-    const pressedItem = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? [])].find(item => item && 'buttonId' in item && item.buttonId === event.nativeEvent.buttonId);
+    const buttonId = event.nativeEvent.buttonId;
+    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? []), ...(preparedHeaderCenterBarButtonItems ?? []), ...(preparedHeaderToolbarItems ?? [])];
+    const pressedItem = allItems.find(item => item && 'buttonId' in item && item.buttonId === buttonId);
     if (pressedItem && pressedItem.type === 'button' && pressedItem.onPress) {
       pressedItem.onPress();
+      return;
+    }
+    for (const item of allItems) {
+      if (!item || item.type !== 'mailSearchToolbar') {
+        continue;
+      }
+      if (item.filterButtonId === buttonId) {
+        item.onFilterPress?.();
+        return;
+      }
+      if (item.composeButtonId === buttonId) {
+        item.onComposePress?.();
+        return;
+      }
+      const searchTextChangePrefix = item.searchTextChangeId ? `${item.searchTextChangeId}:` : undefined;
+      if (searchTextChangePrefix && buttonId.startsWith(searchTextChangePrefix)) {
+        item.onSearchTextChange?.(buttonId.slice(searchTextChangePrefix.length));
+        return;
+      }
     }
   } : undefined;
 
@@ -59,7 +84,7 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_
     };
 
     // Check each bar-button item with a menu
-    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? [])];
+    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? []), ...(preparedHeaderCenterBarButtonItems ?? []), ...(preparedHeaderToolbarItems ?? [])];
     for (const item of allItems) {
       if (item && item.type === 'menu' && item.menu) {
         const action = findInMenu(item.menu, event.nativeEvent.menuId);
@@ -67,6 +92,15 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_
           action.onPress();
           return;
         }
+      } else if (item && item.type === 'mailSearchToolbar') {
+        const toolbarMenus = [item.filterMenu, item.composeMenu].filter(Boolean);
+        for (const toolbarMenu of toolbarMenus) {
+          const action = findInMenu(toolbarMenu, event.nativeEvent.menuId);
+          if (action) {
+            action.onPress();
+            return;
+          }
+        }
       }
     }
   } : undefined;
@@ -74,6 +108,8 @@ const ScreenStackHeaderConfig = exports.ScreenStackHeaderConfig = /*#__PURE__*/_
     userInterfaceStyle: props.experimental_userInterfaceStyle,
     headerLeftBarButtonItems: preparedHeaderLeftBarButtonItems,
     headerRightBarButtonItems: preparedHeaderRightBarButtonItems,
+    headerCenterBarButtonItems: preparedHeaderCenterBarButtonItems,
+    headerToolbarItems: preparedHeaderToolbarItems,
     onPressHeaderBarButtonItem: onPressHeaderBarButtonItem,
     onPressHeaderBarButtonMenuItem: onPressHeaderBarButtonMenuItem,
     ref: ref,
diff --git a/lib/commonjs/components/helpers/prepareHeaderBarButtonItems.js b/lib/commonjs/components/helpers/prepareHeaderBarButtonItems.js
index ab93f62e4a7049d63c6681cecbd6cf8b1d07ce10..9dca2ab640e92161eb647b819a989acd0d41d9fe 100644
--- a/lib/commonjs/components/helpers/prepareHeaderBarButtonItems.js
+++ b/lib/commonjs/components/helpers/prepareHeaderBarButtonItems.js
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.prepareHeaderBarButtonItems = void 0;
 var _reactNative = require("react-native");
-const prepareMenu = (menu, index, side, path = '') => {
+const prepareMenu = (menu, index, placement, path = '') => {
   return {
     ...menu,
     items: menu.items.map((menuItem, menuIndex) => {
@@ -26,7 +26,7 @@ const prepareMenu = (menu, index, side, path = '') => {
           xcassetName,
           imageSource,
           templateSource,
-          ...prepareMenu(menuItem, index, side, currentPath)
+          ...prepareMenu(menuItem, index, placement, currentPath)
         };
       }
       return {
@@ -35,16 +35,40 @@ const prepareMenu = (menu, index, side, path = '') => {
         xcassetName,
         imageSource,
         templateSource,
-        menuId: `${currentPath}-${index}-${side}`
+        menuId: `${currentPath}-${index}-${placement}`
       };
     })
   };
 };
-const prepareHeaderBarButtonItems = (barButtonItems, side) => {
-  return barButtonItems?.map((item, index) => {
+const prepareHeaderBarButtonItems = (barButtonItems, placement) => {
+  const items = Array.isArray(barButtonItems) ? barButtonItems : barButtonItems && typeof barButtonItems === 'object' && 'type' in barButtonItems ? [barButtonItems] : undefined;
+  return items?.map((item, index) => {
     if (item.type === 'spacing') {
       return item;
     }
+    if (item.type === 'searchBarPlacement') {
+      if (placement !== 'toolbar') {
+        return null;
+      }
+      return {
+        ...item,
+        searchBarPlacement: true
+      };
+    }
+    if (item.type === 'searchField') {
+      return {
+        ...item,
+        searchField: true
+      };
+    }
+    if (item.type === 'mailSearchToolbar') {
+      return {
+        ...item,
+        mailSearchToolbar: true,
+        filterMenu: item.filterMenu ? prepareMenu(item.filterMenu, index, placement, 'filter') : undefined,
+        composeMenu: item.composeMenu ? prepareMenu(item.composeMenu, index, placement, 'compose') : undefined
+      };
+    }
     let imageSource, templateSource;
     if (item.icon?.type === 'imageSource') {
       imageSource = _reactNative.Image.resolveAssetSource(item.icon.imageSource);
@@ -77,17 +101,17 @@ const prepareHeaderBarButtonItems = (barButtonItems, side) => {
     if (item.type === 'button') {
       return {
         ...processedItem,
-        buttonId: `${index}-${side}`
+        buttonId: `${index}-${placement}`
       };
     }
     if (item.type === 'menu') {
       return {
         ...processedItem,
-        menu: prepareMenu(item.menu, index, side)
+        menu: prepareMenu(item.menu, index, placement)
       };
     }
     return null;
-  });
+  }).filter(item => item !== null);
 };
 exports.prepareHeaderBarButtonItems = prepareHeaderBarButtonItems;
 //# sourceMappingURL=prepareHeaderBarButtonItems.js.map
\ No newline at end of file
diff --git a/lib/module/components/ScreenStackHeaderConfig.js b/lib/module/components/ScreenStackHeaderConfig.js
index 68e3ba6a78314aba908d07098bb6982d11e6ee80..db5d17bfdac19ed3e664b55b6a73ce49766ac7c5 100644
--- a/lib/module/components/ScreenStackHeaderConfig.js
+++ b/lib/module/components/ScreenStackHeaderConfig.js
@@ -22,17 +22,42 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref
   } = useEdgeInsetApplication(!props.hidden, props.disableTopInsetApplication ?? false, props.disableLeftInsetApplication ?? false, props.disableRightInsetApplication ?? false, props.disableBottomInsetApplication ?? false);
   const {
     headerLeftBarButtonItems,
-    headerRightBarButtonItems
+    headerRightBarButtonItems,
+    headerCenterBarButtonItems,
+    headerToolbarItems
   } = props;
   const preparedHeaderLeftBarButtonItems = headerLeftBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform ? prepareHeaderBarButtonItems(headerLeftBarButtonItems, 'left') : undefined;
   const preparedHeaderRightBarButtonItems = headerRightBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform ? prepareHeaderBarButtonItems(headerRightBarButtonItems, 'right') : undefined;
-  const hasHeaderBarButtonItems = isHeaderBarButtonsAvailableForCurrentPlatform && (preparedHeaderLeftBarButtonItems?.length || preparedHeaderRightBarButtonItems?.length);
+  const preparedHeaderCenterBarButtonItems = headerCenterBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform ? prepareHeaderBarButtonItems(headerCenterBarButtonItems, 'center') : undefined;
+  const preparedHeaderToolbarItems = headerToolbarItems && isHeaderBarButtonsAvailableForCurrentPlatform ? prepareHeaderBarButtonItems(headerToolbarItems, 'toolbar') : undefined;
+  const hasHeaderBarButtonItems = isHeaderBarButtonsAvailableForCurrentPlatform && (preparedHeaderLeftBarButtonItems?.length || preparedHeaderRightBarButtonItems?.length || preparedHeaderCenterBarButtonItems?.length || preparedHeaderToolbarItems?.length);
 
   // Handle bar button item presses
   const onPressHeaderBarButtonItem = hasHeaderBarButtonItems ? event => {
-    const pressedItem = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? [])].find(item => item && 'buttonId' in item && item.buttonId === event.nativeEvent.buttonId);
+    const buttonId = event.nativeEvent.buttonId;
+    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? []), ...(preparedHeaderCenterBarButtonItems ?? []), ...(preparedHeaderToolbarItems ?? [])];
+    const pressedItem = allItems.find(item => item && 'buttonId' in item && item.buttonId === buttonId);
     if (pressedItem && pressedItem.type === 'button' && pressedItem.onPress) {
       pressedItem.onPress();
+      return;
+    }
+    for (const item of allItems) {
+      if (!item || item.type !== 'mailSearchToolbar') {
+        continue;
+      }
+      if (item.filterButtonId === buttonId) {
+        item.onFilterPress?.();
+        return;
+      }
+      if (item.composeButtonId === buttonId) {
+        item.onComposePress?.();
+        return;
+      }
+      const searchTextChangePrefix = item.searchTextChangeId ? `${item.searchTextChangeId}:` : undefined;
+      if (searchTextChangePrefix && buttonId.startsWith(searchTextChangePrefix)) {
+        item.onSearchTextChange?.(buttonId.slice(searchTextChangePrefix.length));
+        return;
+      }
     }
   } : undefined;
 
@@ -55,7 +80,7 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref
     };
 
     // Check each bar-button item with a menu
-    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? [])];
+    const allItems = [...(preparedHeaderLeftBarButtonItems ?? []), ...(preparedHeaderRightBarButtonItems ?? []), ...(preparedHeaderCenterBarButtonItems ?? []), ...(preparedHeaderToolbarItems ?? [])];
     for (const item of allItems) {
       if (item && item.type === 'menu' && item.menu) {
         const action = findInMenu(item.menu, event.nativeEvent.menuId);
@@ -63,6 +88,15 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref
           action.onPress();
           return;
         }
+      } else if (item && item.type === 'mailSearchToolbar') {
+        const toolbarMenus = [item.filterMenu, item.composeMenu].filter(Boolean);
+        for (const toolbarMenu of toolbarMenus) {
+          const action = findInMenu(toolbarMenu, event.nativeEvent.menuId);
+          if (action) {
+            action.onPress();
+            return;
+          }
+        }
       }
     }
   } : undefined;
@@ -70,6 +104,8 @@ export const ScreenStackHeaderConfig = /*#__PURE__*/React.forwardRef((props, ref
     userInterfaceStyle: props.experimental_userInterfaceStyle,
     headerLeftBarButtonItems: preparedHeaderLeftBarButtonItems,
     headerRightBarButtonItems: preparedHeaderRightBarButtonItems,
+    headerCenterBarButtonItems: preparedHeaderCenterBarButtonItems,
+    headerToolbarItems: preparedHeaderToolbarItems,
     onPressHeaderBarButtonItem: onPressHeaderBarButtonItem,
     onPressHeaderBarButtonMenuItem: onPressHeaderBarButtonMenuItem,
     ref: ref,
diff --git a/lib/module/components/helpers/prepareHeaderBarButtonItems.js b/lib/module/components/helpers/prepareHeaderBarButtonItems.js
index 8a70ffd78617147418d628c03c580bb7ff9a8a72..bca930372d2a7c3f8e0a35c3dcf28474f899f21c 100644
--- a/lib/module/components/helpers/prepareHeaderBarButtonItems.js
+++ b/lib/module/components/helpers/prepareHeaderBarButtonItems.js
@@ -1,5 +1,5 @@
 import { Image, processColor } from 'react-native';
-const prepareMenu = (menu, index, side, path = '') => {
+const prepareMenu = (menu, index, placement, path = '') => {
   return {
     ...menu,
     items: menu.items.map((menuItem, menuIndex) => {
@@ -20,7 +20,7 @@ const prepareMenu = (menu, index, side, path = '') => {
           xcassetName,
           imageSource,
           templateSource,
-          ...prepareMenu(menuItem, index, side, currentPath)
+          ...prepareMenu(menuItem, index, placement, currentPath)
         };
       }
       return {
@@ -29,16 +29,40 @@ const prepareMenu = (menu, index, side, path = '') => {
         xcassetName,
         imageSource,
         templateSource,
-        menuId: `${currentPath}-${index}-${side}`
+        menuId: `${currentPath}-${index}-${placement}`
       };
     })
   };
 };
-export const prepareHeaderBarButtonItems = (barButtonItems, side) => {
-  return barButtonItems?.map((item, index) => {
+export const prepareHeaderBarButtonItems = (barButtonItems, placement) => {
+  const items = Array.isArray(barButtonItems) ? barButtonItems : barButtonItems && typeof barButtonItems === 'object' && 'type' in barButtonItems ? [barButtonItems] : undefined;
+  return items?.map((item, index) => {
     if (item.type === 'spacing') {
       return item;
     }
+    if (item.type === 'searchBarPlacement') {
+      if (placement !== 'toolbar') {
+        return null;
+      }
+      return {
+        ...item,
+        searchBarPlacement: true
+      };
+    }
+    if (item.type === 'searchField') {
+      return {
+        ...item,
+        searchField: true
+      };
+    }
+    if (item.type === 'mailSearchToolbar') {
+      return {
+        ...item,
+        mailSearchToolbar: true,
+        filterMenu: item.filterMenu ? prepareMenu(item.filterMenu, index, placement, 'filter') : undefined,
+        composeMenu: item.composeMenu ? prepareMenu(item.composeMenu, index, placement, 'compose') : undefined
+      };
+    }
     let imageSource, templateSource;
     if (item.icon?.type === 'imageSource') {
       imageSource = Image.resolveAssetSource(item.icon.imageSource);
@@ -71,16 +95,16 @@ export const prepareHeaderBarButtonItems = (barButtonItems, side) => {
     if (item.type === 'button') {
       return {
         ...processedItem,
-        buttonId: `${index}-${side}`
+        buttonId: `${index}-${placement}`
       };
     }
     if (item.type === 'menu') {
       return {
         ...processedItem,
-        menu: prepareMenu(item.menu, index, side)
+        menu: prepareMenu(item.menu, index, placement)
       };
     }
     return null;
-  });
+  }).filter(item => item !== null);
 };
 //# sourceMappingURL=prepareHeaderBarButtonItems.js.map
\ No newline at end of file
diff --git a/lib/typescript/components/helpers/prepareHeaderBarButtonItems.d.ts b/lib/typescript/components/helpers/prepareHeaderBarButtonItems.d.ts
index c41ef943e554754f599f2fcd157dd0dc61d78b7e..ed02d44f78ee3f1387ba93bf292be2b99bb0e3b3 100644
--- a/lib/typescript/components/helpers/prepareHeaderBarButtonItems.d.ts
+++ b/lib/typescript/components/helpers/prepareHeaderBarButtonItems.d.ts
@@ -1,5 +1,5 @@
 import { HeaderBarButtonItem } from 'react-native-screens/types';
-export declare const prepareHeaderBarButtonItems: (barButtonItems: HeaderBarButtonItem[], side: "left" | "right") => (import("react-native-screens/types").HeaderBarButtonItemSpacing | {
+export declare const prepareHeaderBarButtonItems: (barButtonItems: HeaderBarButtonItem[], side: "left" | "right" | "center" | "toolbar") => (import("react-native-screens/types").HeaderBarButtonItemSpacing | {
     buttonId: string;
     imageSource: import("react-native").ImageResolvedAssetSource | undefined;
     templateSource: import("react-native").ImageResolvedAssetSource | undefined;
@@ -161,5 +161,5 @@ export declare const prepareHeaderBarButtonItems: (barButtonItems: HeaderBarButt
     identifier?: string | undefined;
     accessibilityLabel?: string | undefined;
     accessibilityHint?: string | undefined;
-} | null)[];
+})[];
 //# sourceMappingURL=prepareHeaderBarButtonItems.d.ts.map
\ No newline at end of file
diff --git a/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts b/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts
index a51d8648567cff29cd2be0d5262bd15c6fc69c61..8b7439aff4d7e6fe8a72bf0bac93c3c19568b1a6 100644
--- a/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts
+++ b/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts
@@ -9,6 +9,7 @@ type OnPressHeaderBarButtonMenuItemEvent = Readonly<{
     menuId: string;
 }>;
 type BackButtonDisplayMode = 'minimal' | 'default' | 'generic';
+type NavigationItemStyle = 'navigator' | 'browser' | 'editor';
 type BlurEffect = 'none' | 'extraLight' | 'light' | 'dark' | 'regular' | 'prominent' | 'systemUltraThinMaterial' | 'systemThinMaterial' | 'systemMaterial' | 'systemThickMaterial' | 'systemChromeMaterial' | 'systemUltraThinMaterialLight' | 'systemThinMaterialLight' | 'systemMaterialLight' | 'systemThickMaterialLight' | 'systemChromeMaterialLight' | 'systemUltraThinMaterialDark' | 'systemThinMaterialDark' | 'systemMaterialDark' | 'systemThickMaterialDark' | 'systemChromeMaterialDark';
 type UserInterfaceStyle = 'unspecified' | 'light' | 'dark';
 export interface NativeProps extends ViewProps {
@@ -32,18 +33,23 @@ export interface NativeProps extends ViewProps {
     largeTitleColor?: ColorValue | undefined;
     translucent?: boolean | undefined;
     title?: string | undefined;
+    subtitle?: string | undefined;
+    largeSubtitle?: string | undefined;
     titleFontFamily?: string | undefined;
     titleFontSize?: CT.Int32 | undefined;
     titleFontWeight?: string | undefined;
     titleColor?: ColorValue | undefined;
     disableBackButtonMenu?: boolean | undefined;
     backButtonDisplayMode?: CT.WithDefault<BackButtonDisplayMode, 'default'>;
+    navigationItemStyle?: CT.WithDefault<NavigationItemStyle, 'navigator'>;
     hideBackButton?: boolean | undefined;
     backButtonInCustomView?: boolean | undefined;
     blurEffect?: CT.WithDefault<BlurEffect, 'none'>;
     topInsetEnabled?: boolean | undefined;
     headerLeftBarButtonItems?: CT.UnsafeMixed[] | undefined;
     headerRightBarButtonItems?: CT.UnsafeMixed[] | undefined;
+    headerCenterBarButtonItems?: CT.UnsafeMixed[] | undefined;
+    headerToolbarItems?: CT.UnsafeMixed[] | undefined;
     onPressHeaderBarButtonItem?: CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent> | undefined;
     onPressHeaderBarButtonMenuItem?: CT.DirectEventHandler<OnPressHeaderBarButtonMenuItemEvent> | undefined;
     synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, true>;
diff --git a/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts b/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
index 1ca926e6753294861f5f79ab6267dc7999f70639..94e676fdb08ff9d1cf81a1cf06a45e931b5461f1 100644
--- a/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
+++ b/lib/typescript/fabric/ScreenStackHeaderSubviewNativeComponent.d.ts
@@ -3,6 +3,7 @@ export type HeaderSubviewTypes = 'back' | 'right' | 'left' | 'title' | 'center'
 export interface NativeProps extends ViewProps {
     type?: CT.WithDefault<HeaderSubviewTypes, 'left'>;
     hidesSharedBackground?: boolean | undefined;
+    identifier?: string | undefined;
     synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, true>;
 }
 declare const _default: import("react-native").HostComponent<NativeProps>;
diff --git a/lib/typescript/types.d.ts b/lib/typescript/types.d.ts
index 10a31341f4eff258a7f8ea6abae3eddf0dd21218..bab5031f5f9b246f15e5396f3f2fee90b91d826a 100644
--- a/lib/typescript/types.d.ts
+++ b/lib/typescript/types.d.ts
@@ -10,6 +10,7 @@ export type SearchBarCommands = {
     cancelSearch: () => void;
 };
 export type BackButtonDisplayMode = 'default' | 'generic' | 'minimal';
+export type NavigationItemStyle = 'navigator' | 'browser' | 'editor';
 export type StackPresentationTypes = 'push' | 'modal' | 'transparentModal' | 'containedModal' | 'containedTransparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet';
 export type StackAnimationTypes = 'default' | 'fade' | 'fade_from_bottom' | 'flip' | 'none' | 'simple_push' | 'slide_from_bottom' | 'slide_from_right' | 'slide_from_left' | 'ios_from_right' | 'ios_from_left';
 export type BlurEffectTypes = BlurEffect;
@@ -631,6 +632,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
      * @platform ios
      */
     backButtonDisplayMode?: BackButtonDisplayMode | undefined;
+    /**
+     * Controls the UIKit `UINavigationItem.style` used by native iOS headers.
+     *
+     * `editor` matches modern document/editor-style bars with leading-aligned compact titles and glass controls.
+     *
+     * @platform ios
+     */
+    navigationItemStyle?: NavigationItemStyle | undefined;
     /**
      * Array of UIBarButtomItems to the left side of the header.
      *
@@ -643,6 +652,26 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
      * @platform ios
      */
     headerRightBarButtonItems?: HeaderBarButtonItem[] | undefined;
+    /**
+     * Array of UIBarButtonItems to the centered item group in modern iOS headers.
+     *
+     * This maps to `UINavigationItem.centerItemGroups` on iOS 26+ and lets
+     * document/editor-style chrome keep primary actions visually centered while
+     * leaving trailing search separate.
+     *
+     * @platform ios
+     */
+    headerCenterBarButtonItems?: HeaderBarButtonItem[] | undefined;
+    /**
+     * Array of UIBarButtonItems to display in the native toolbar attached to
+     * the current screen's navigation controller.
+     *
+     * On iOS 26+, this can contain a `searchBarPlacement` item to let UIKit
+     * render integrated Mail-style search chrome using the attached SearchBar.
+     *
+     * @platform ios
+     */
+    headerToolbarItems?: HeaderBarButtonItem[] | undefined;
     /**
      * When set to true the header will be hidden while the parent Screen is on the top of the stack. The default value is false.
      */
@@ -704,6 +733,20 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
      * String that can be displayed in the header as a fallback for `headerTitle`.
      */
     title?: string | undefined;
+    /**
+     * String displayed below the compact navigation title on iOS 26+.
+     *
+     * @platform ios
+     */
+    subtitle?: string | undefined;
+    /**
+     * String displayed below the large navigation title on iOS 26+.
+     *
+     * Falls back to `subtitle` when omitted.
+     *
+     * @platform ios
+     */
+    largeSubtitle?: string | undefined;
     /**
      * Allows for setting text color of the title.
      */
@@ -999,6 +1042,13 @@ export interface SearchBarProps {
     shouldShowHintSearchIcon?: boolean | undefined;
 }
 export interface ScreenStackHeaderSubviewProps {
+    /**
+     * An identifier used to match this item across navigation bar transitions.
+     * Only applicable to type="right" and type="left" subviews on iOS 26.0 and later.
+     *
+     * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
+     */
+    identifier?: string | undefined;
     /**
      * A boolean value indicating whether the background this item may share with other items in the bar should be hidden.
      * Only applicable to type="right" and type="left" subviews.
@@ -1037,6 +1087,11 @@ interface SharedHeaderBarButtonItem {
      * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
      */
     variant?: 'plain' | 'done' | 'prominent' | undefined;
+    /**
+     * Render the item with UIButtonConfiguration.glassButtonConfiguration.
+     * Only available from iOS 26.0 and later.
+     */
+    glassEffect?: boolean | undefined;
     /**
      * The tint color to apply to the item.
      *
@@ -1181,8 +1236,38 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
 export interface HeaderBarButtonItemSpacing {
     type: 'spacing';
     spacing: number;
+    flexible?: boolean | undefined;
+}
+export interface HeaderBarButtonSearchBarPlacementItem {
+    type: 'searchBarPlacement';
+    activatesSearchController?: boolean | undefined;
+    customView?: boolean | undefined;
+    index?: number | undefined;
+    width?: number | undefined;
+}
+export interface HeaderBarButtonSearchFieldItem {
+    type: 'searchField';
+    placeholder?: string | undefined;
+    width?: number | undefined;
+}
+export interface HeaderBarButtonMailSearchToolbarItem {
+    type: 'mailSearchToolbar';
+    composeButtonId?: string | undefined;
+    composeMenu?: HeaderBarButtonItemWithMenu['menu'] | undefined;
+    composeSystemImageName?: string | undefined;
+    filterButtonId?: string | undefined;
+    filterMenu?: HeaderBarButtonItemWithMenu['menu'] | undefined;
+    filterSystemImageName?: string | undefined;
+    onComposePress?: (() => void) | undefined;
+    onFilterPress?: (() => void) | undefined;
+    onSearchTextChange?: ((text: string) => void) | undefined;
+    placeholder?: string | undefined;
+    searchTextChangeId?: string | undefined;
+    showsSearchDismissButton?: boolean | undefined;
+    useFallbackSearchField?: boolean | undefined;
+    width?: number | undefined;
 }
-export type HeaderBarButtonItem = HeaderBarButtonItemWithAction | HeaderBarButtonItemWithMenu | HeaderBarButtonItemSpacing;
+export type HeaderBarButtonItem = HeaderBarButtonItemWithAction | HeaderBarButtonItemWithMenu | HeaderBarButtonMailSearchToolbarItem | HeaderBarButtonSearchFieldItem | HeaderBarButtonSearchBarPlacementItem | HeaderBarButtonItemSpacing;
 /**
  * Custom Screen Transition
  */
diff --git a/src/components/ScreenStackHeaderConfig.tsx b/src/components/ScreenStackHeaderConfig.tsx
index c3a88b6f6c8362cf717372a35cdc35a6b397bdd0..9e8173da567933fd3f6819fb26bf4a0e87640767 100644
--- a/src/components/ScreenStackHeaderConfig.tsx
+++ b/src/components/ScreenStackHeaderConfig.tsx
@@ -48,7 +48,12 @@ export const ScreenStackHeaderConfig = React.forwardRef<
     props.disableBottomInsetApplication ?? false,
   );
 
-  const { headerLeftBarButtonItems, headerRightBarButtonItems } = props;
+  const {
+    headerLeftBarButtonItems,
+    headerRightBarButtonItems,
+    headerCenterBarButtonItems,
+    headerToolbarItems,
+  } = props;
 
   const preparedHeaderLeftBarButtonItems =
     headerLeftBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform
@@ -58,22 +63,36 @@ export const ScreenStackHeaderConfig = React.forwardRef<
     headerRightBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform
       ? prepareHeaderBarButtonItems(headerRightBarButtonItems, 'right')
       : undefined;
+  const preparedHeaderCenterBarButtonItems =
+    headerCenterBarButtonItems && isHeaderBarButtonsAvailableForCurrentPlatform
+      ? prepareHeaderBarButtonItems(headerCenterBarButtonItems, 'center')
+      : undefined;
+  const preparedHeaderToolbarItems =
+    headerToolbarItems && isHeaderBarButtonsAvailableForCurrentPlatform
+      ? prepareHeaderBarButtonItems(headerToolbarItems, 'toolbar')
+      : undefined;
   const hasHeaderBarButtonItems =
     isHeaderBarButtonsAvailableForCurrentPlatform &&
     (preparedHeaderLeftBarButtonItems?.length ||
-      preparedHeaderRightBarButtonItems?.length);
+      preparedHeaderRightBarButtonItems?.length ||
+      preparedHeaderCenterBarButtonItems?.length ||
+      preparedHeaderToolbarItems?.length);
 
   // Handle bar button item presses
   const onPressHeaderBarButtonItem = hasHeaderBarButtonItems
     ? (event: NativeSyntheticEvent<{ buttonId: string }>) => {
-        const pressedItem = [
+        const buttonId = event.nativeEvent.buttonId;
+        const allItems = [
           ...(preparedHeaderLeftBarButtonItems ?? []),
           ...(preparedHeaderRightBarButtonItems ?? []),
-        ].find(
+          ...(preparedHeaderCenterBarButtonItems ?? []),
+          ...(preparedHeaderToolbarItems ?? []),
+        ];
+        const pressedItem = allItems.find(
           item =>
             item &&
             'buttonId' in item &&
-            item.buttonId === event.nativeEvent.buttonId,
+            item.buttonId === buttonId,
         );
         if (
           pressedItem &&
@@ -81,6 +100,32 @@ export const ScreenStackHeaderConfig = React.forwardRef<
           pressedItem.onPress
         ) {
           pressedItem.onPress();
+          return;
+        }
+        for (const item of allItems) {
+          if (!item || item.type !== 'mailSearchToolbar') {
+            continue;
+          }
+          if (item.filterButtonId === buttonId) {
+            item.onFilterPress?.();
+            return;
+          }
+          if (item.composeButtonId === buttonId) {
+            item.onComposePress?.();
+            return;
+          }
+          const searchTextChangePrefix = item.searchTextChangeId
+            ? `${item.searchTextChangeId}:`
+            : undefined;
+          if (
+            searchTextChangePrefix &&
+            buttonId.startsWith(searchTextChangePrefix)
+          ) {
+            item.onSearchTextChange?.(
+              buttonId.slice(searchTextChangePrefix.length),
+            );
+            return;
+          }
         }
       }
     : undefined;
@@ -111,6 +156,8 @@ export const ScreenStackHeaderConfig = React.forwardRef<
         const allItems = [
           ...(preparedHeaderLeftBarButtonItems ?? []),
           ...(preparedHeaderRightBarButtonItems ?? []),
+          ...(preparedHeaderCenterBarButtonItems ?? []),
+          ...(preparedHeaderToolbarItems ?? []),
         ];
         for (const item of allItems) {
           if (item && item.type === 'menu' && item.menu) {
@@ -119,6 +166,17 @@ export const ScreenStackHeaderConfig = React.forwardRef<
               action.onPress();
               return;
             }
+          } else if (item && item.type === 'mailSearchToolbar') {
+            const toolbarMenus = [item.filterMenu, item.composeMenu].filter(
+              Boolean,
+            );
+            for (const toolbarMenu of toolbarMenus) {
+              const action = findInMenu(toolbarMenu, event.nativeEvent.menuId);
+              if (action) {
+                action.onPress();
+                return;
+              }
+            }
           }
         }
       }
@@ -130,6 +188,8 @@ export const ScreenStackHeaderConfig = React.forwardRef<
       userInterfaceStyle={props.experimental_userInterfaceStyle}
       headerLeftBarButtonItems={preparedHeaderLeftBarButtonItems}
       headerRightBarButtonItems={preparedHeaderRightBarButtonItems}
+      headerCenterBarButtonItems={preparedHeaderCenterBarButtonItems}
+      headerToolbarItems={preparedHeaderToolbarItems}
       onPressHeaderBarButtonItem={onPressHeaderBarButtonItem}
       onPressHeaderBarButtonMenuItem={onPressHeaderBarButtonMenuItem}
       ref={ref}
diff --git a/src/components/helpers/prepareHeaderBarButtonItems.ts b/src/components/helpers/prepareHeaderBarButtonItems.ts
index be2c24dfee77f5883b5ab1d7473be80933b5dc6f..2008a342f13806641909c19a9f4ab6829f239016 100644
--- a/src/components/helpers/prepareHeaderBarButtonItems.ts
+++ b/src/components/helpers/prepareHeaderBarButtonItems.ts
@@ -7,7 +7,7 @@ import {
 const prepareMenu = (
   menu: HeaderBarButtonItemWithMenu['menu'],
   index: number,
-  side: 'left' | 'right',
+  placement: 'left' | 'right' | 'center' | 'toolbar',
   path: string = '',
 ): HeaderBarButtonItemWithMenu['menu'] => {
   return {
@@ -34,7 +34,7 @@ const prepareMenu = (
           xcassetName,
           imageSource,
           templateSource,
-          ...prepareMenu(menuItem, index, side, currentPath),
+          ...prepareMenu(menuItem, index, placement, currentPath),
         };
       }
       return {
@@ -43,20 +43,53 @@ const prepareMenu = (
         xcassetName,
         imageSource,
         templateSource,
-        menuId: `${currentPath}-${index}-${side}`,
+        menuId: `${currentPath}-${index}-${placement}`,
       };
     }),
   };
 };
 
 export const prepareHeaderBarButtonItems = (
-  barButtonItems: HeaderBarButtonItem[],
-  side: 'left' | 'right',
+  barButtonItems:
+    | HeaderBarButtonItem[]
+    | HeaderBarButtonItem
+    | null
+    | undefined,
+  placement: 'left' | 'right' | 'center' | 'toolbar',
 ) => {
-  return barButtonItems?.map((item, index) => {
+  const items = Array.isArray(barButtonItems)
+    ? barButtonItems
+    : barButtonItems && typeof barButtonItems === 'object' && 'type' in barButtonItems
+      ? [barButtonItems]
+      : undefined;
+
+  return items?.map((item, index) => {
     if (item.type === 'spacing') {
       return item;
     }
+    if (item.type === 'searchBarPlacement') {
+      if (placement !== 'toolbar') {
+        return null;
+      }
+      return {
+        ...item,
+        searchBarPlacement: true,
+      };
+    }
+    if (item.type === 'searchField') {
+      return {
+        ...item,
+        searchField: true,
+      };
+    }
+    if (item.type === 'mailSearchToolbar') {
+      return {
+        ...item,
+        mailSearchToolbar: true,
+        filterMenu: item.filterMenu ? prepareMenu(item.filterMenu, index, placement, 'filter') : undefined,
+        composeMenu: item.composeMenu ? prepareMenu(item.composeMenu, index, placement, 'compose') : undefined,
+      };
+    }
     let imageSource, templateSource;
     if (item.icon?.type === 'imageSource') {
       imageSource = Image.resolveAssetSource(item.icon.imageSource);
@@ -91,15 +124,15 @@ export const prepareHeaderBarButtonItems = (
     if (item.type === 'button') {
       return {
         ...processedItem,
-        buttonId: `${index}-${side}`,
+        buttonId: `${index}-${placement}`,
       };
     }
     if (item.type === 'menu') {
       return {
         ...processedItem,
-        menu: prepareMenu(item.menu, index, side),
+        menu: prepareMenu(item.menu, index, placement),
       };
     }
     return null;
-  });
+  }).filter(item => item !== null);
 };
diff --git a/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/src/fabric/ScreenStackHeaderConfigNativeComponent.ts
index a80d9fef0c0ab5ce8221c5e0c7fae65162bc6164..fc16f51425702a418c09de553a54753331f19d15 100644
--- a/src/fabric/ScreenStackHeaderConfigNativeComponent.ts
+++ b/src/fabric/ScreenStackHeaderConfigNativeComponent.ts
@@ -14,6 +14,7 @@ type OnPressHeaderBarButtonItemEvent = Readonly<{ buttonId: string }>;
 type OnPressHeaderBarButtonMenuItemEvent = Readonly<{ menuId: string }>;
 
 type BackButtonDisplayMode = 'minimal' | 'default' | 'generic';
+type NavigationItemStyle = 'navigator' | 'browser' | 'editor';
 
 type BlurEffect =
   | 'none'
@@ -61,12 +62,15 @@ export interface NativeProps extends ViewProps {
   largeTitleColor?: ColorValue | undefined;
   translucent?: boolean | undefined;
   title?: string | undefined;
+  subtitle?: string | undefined;
+  largeSubtitle?: string | undefined;
   titleFontFamily?: string | undefined;
   titleFontSize?: CT.Int32 | undefined;
   titleFontWeight?: string | undefined;
   titleColor?: ColorValue | undefined;
   disableBackButtonMenu?: boolean | undefined;
   backButtonDisplayMode?: CT.WithDefault<BackButtonDisplayMode, 'default'>;
+  navigationItemStyle?: CT.WithDefault<NavigationItemStyle, 'navigator'>;
   hideBackButton?: boolean | undefined;
   backButtonInCustomView?: boolean | undefined;
   blurEffect?: CT.WithDefault<BlurEffect, 'none'>;
@@ -74,6 +78,8 @@ export interface NativeProps extends ViewProps {
   topInsetEnabled?: boolean | undefined;
   headerLeftBarButtonItems?: CT.UnsafeMixed[] | undefined;
   headerRightBarButtonItems?: CT.UnsafeMixed[] | undefined;
+  headerCenterBarButtonItems?: CT.UnsafeMixed[] | undefined;
+  headerToolbarItems?: CT.UnsafeMixed[] | undefined;
   onPressHeaderBarButtonItem?:
     | CT.DirectEventHandler<OnPressHeaderBarButtonItemEvent>
     | undefined;
diff --git a/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts b/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
index 7d3fbfe08c70461936832a40a1ce8d9c11b22457..1f9caca6271b68d182b70a180f180d6a4ce3988c 100644
--- a/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
+++ b/src/fabric/ScreenStackHeaderSubviewNativeComponent.ts
@@ -14,6 +14,7 @@ export type HeaderSubviewTypes =
 export interface NativeProps extends ViewProps {
   type?: CT.WithDefault<HeaderSubviewTypes, 'left'>;
   hidesSharedBackground?: boolean | undefined;
+  identifier?: string | undefined;
   synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, true>;
 }
 
diff --git a/src/types.tsx b/src/types.tsx
index 9c3ab4c3653ee1769addda8a6464cdf49695a601..6f242ad2bbd7152ea9f873ef7754ad47caf2466d 100644
--- a/src/types.tsx
+++ b/src/types.tsx
@@ -26,6 +26,7 @@ export type SearchBarCommands = {
 };
 
 export type BackButtonDisplayMode = 'default' | 'generic' | 'minimal';
+export type NavigationItemStyle = 'navigator' | 'browser' | 'editor';
 export type StackPresentationTypes =
   | 'push'
   | 'modal'
@@ -735,6 +736,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
    * @platform ios
    */
   backButtonDisplayMode?: BackButtonDisplayMode | undefined;
+  /**
+   * Controls the UIKit `UINavigationItem.style` used by native iOS headers.
+   *
+   * `editor` matches modern document/editor-style bars with leading-aligned compact titles and glass controls.
+   *
+   * @platform ios
+   */
+  navigationItemStyle?: NavigationItemStyle | undefined;
   /**
    * Array of UIBarButtomItems to the left side of the header.
    *
@@ -747,6 +756,26 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
    * @platform ios
    */
   headerRightBarButtonItems?: HeaderBarButtonItem[] | undefined;
+  /**
+   * Array of UIBarButtonItems to the centered item group in modern iOS headers.
+   *
+   * This maps to `UINavigationItem.centerItemGroups` on iOS 26+ and lets
+   * document/editor-style chrome keep primary actions visually centered while
+   * leaving trailing search separate.
+   *
+   * @platform ios
+   */
+  headerCenterBarButtonItems?: HeaderBarButtonItem[] | undefined;
+  /**
+   * Array of UIBarButtonItems to display in the native toolbar attached to
+   * the current screen's navigation controller.
+   *
+   * On iOS 26+, this can contain a `searchBarPlacement` item to let UIKit
+   * render integrated Mail-style search chrome using the attached SearchBar.
+   *
+   * @platform ios
+   */
+  headerToolbarItems?: HeaderBarButtonItem[] | undefined;
   /**
    * When set to true the header will be hidden while the parent Screen is on the top of the stack. The default value is false.
    */
@@ -808,6 +837,20 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
    * String that can be displayed in the header as a fallback for `headerTitle`.
    */
   title?: string | undefined;
+  /**
+   * String displayed below the compact navigation title on iOS 26+.
+   *
+   * @platform ios
+   */
+  subtitle?: string | undefined;
+  /**
+   * String displayed below the large navigation title on iOS 26+.
+   *
+   * Falls back to `subtitle` when omitted.
+   *
+   * @platform ios
+   */
+  largeSubtitle?: string | undefined;
   /**
    * Allows for setting text color of the title.
    */
@@ -1120,6 +1163,13 @@ export interface SearchBarProps {
 }
 
 export interface ScreenStackHeaderSubviewProps {
+  /**
+   * An identifier used to match this item across navigation bar transitions.
+   * Only applicable to type="right" and type="left" subviews on iOS 26.0 and later.
+   *
+   * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/identifier
+   */
+  identifier?: string | undefined;
   /**
    * A boolean value indicating whether the background this item may share with other items in the bar should be hidden.
    * Only applicable to type="right" and type="left" subviews.
@@ -1161,6 +1211,11 @@ interface SharedHeaderBarButtonItem {
    * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/style-swift.property
    */
   variant?: 'plain' | 'done' | 'prominent' | undefined;
+  /**
+   * Render the item with UIButtonConfiguration.glassButtonConfiguration.
+   * Only available from iOS 26.0 and later.
+   */
+  glassEffect?: boolean | undefined;
   /**
    * The tint color to apply to the item.
    *
@@ -1315,11 +1370,47 @@ export interface HeaderBarButtonItemWithMenu extends SharedHeaderBarButtonItem {
 export interface HeaderBarButtonItemSpacing {
   type: 'spacing';
   spacing: number;
+  flexible?: boolean | undefined;
+}
+
+export interface HeaderBarButtonSearchBarPlacementItem {
+  type: 'searchBarPlacement';
+  activatesSearchController?: boolean | undefined;
+  customView?: boolean | undefined;
+  index?: number | undefined;
+  width?: number | undefined;
+}
+
+export interface HeaderBarButtonSearchFieldItem {
+  type: 'searchField';
+  placeholder?: string | undefined;
+  width?: number | undefined;
+}
+
+export interface HeaderBarButtonMailSearchToolbarItem {
+  type: 'mailSearchToolbar';
+  composeButtonId?: string | undefined;
+  composeMenu?: HeaderBarButtonItemWithMenu['menu'] | undefined;
+  composeSystemImageName?: string | undefined;
+  filterButtonId?: string | undefined;
+  filterMenu?: HeaderBarButtonItemWithMenu['menu'] | undefined;
+  filterSystemImageName?: string | undefined;
+  onComposePress?: (() => void) | undefined;
+  onFilterPress?: (() => void) | undefined;
+  onSearchTextChange?: ((text: string) => void) | undefined;
+  placeholder?: string | undefined;
+  searchTextChangeId?: string | undefined;
+  showsSearchDismissButton?: boolean | undefined;
+  useFallbackSearchField?: boolean | undefined;
+  width?: number | undefined;
 }
 
 export type HeaderBarButtonItem =
   | HeaderBarButtonItemWithAction
   | HeaderBarButtonItemWithMenu
+  | HeaderBarButtonMailSearchToolbarItem
+  | HeaderBarButtonSearchFieldItem
+  | HeaderBarButtonSearchBarPlacementItem
   | HeaderBarButtonItemSpacing;
 
 /**