Setup

Theme the whole stack once.

app/_layout.tsx
import {useStackScreenOptions} from '@hzblj/kara-ui/header'
import {Stack} from 'expo-router'

export default function Layout() {
  const screenOptions = useStackScreenOptions()

  return <Stack screenOptions={screenOptions} />
}

Actions

Pass your screen’s navigation to useHeader.

tsx
import {type HeaderAction, useHeader} from '@hzblj/kara-ui/header'
import {useNavigation} from 'expo-router' // or @react-navigation/native

export default function Product() {
  const navigation = useNavigation()

  const trailingActions = useMemo<HeaderAction[]>(() => [
    {type: 'icon', icon: 'share', label: 'Share', onPress: share},
    {type: 'menu', icon: 'more', label: 'More', items: [
      {type: 'action', label: 'Delete', destructive: true, onPress: remove},
    ]},
  ], [])

  useHeader(navigation, {title: 'Product', trailingActions})

  return <ProductDetail />
}
TypeWhat it is
back, closeThe platform’s chevron or cross. icon swaps in your own.
iconOne of your icons. loading, badge (iOS 26), selected, color.
textA label: “Edit”, “Done”. prominent on iOS.
menuAn icon that opens a menu. Rows, sections (submenu), checked, destructive.
customAny React Native element.
  • leadingActions and trailingActions, in screen order.
  • separated: true gives an action its own glass on iOS 26.
  • backHidden: true hides the back button. On Android a trailing close moves to the start.
  • Memoize the arrays.

Actions the screen has from the start can go into static options:

tsx
const header = useHeaderOptions({title: 'Product', trailingActions})

<Stack.Screen options={header.options} />

Titles

ParamWhat it does
titleThe title in the bar.
subtitleTitle and subtitle on the leading side.
largeTitleA large title that collapses into the bar.
titleRevealProgressFades the title in, 0 to 1. For a title that shows after scrolling.
titleHiddenThe bar without a title.

Android has no native large title. Render header.largeTitle as the first row and feed header.onScroll the scroll offset.

tsx
const header = useHeader(navigation, {title: 'Countries', largeTitle: true})

const onScroll = useAnimatedScrollHandler({
  onScroll: event => header.onScroll(event.contentOffset.y),
})

<Animated.FlatList
  contentInsetAdjustmentBehavior={header.contentInsetAdjustmentBehavior}
  ListHeaderComponent={header.largeTitle}
  onScroll={onScroll}
  data={countries}
  renderItem={renderCountry}
/>

Hero header

A transparent bar over a full-width image. The title, the bar and the status bar come in as the image scrolls away. Render hero.background after the list.

tsx
// layout: <Stack.Screen name="place" options={HERO_HEADER_SCREEN_OPTIONS} />

export default function Place() {
  const navigation = useNavigation()
  const hero = useHeroHeader(navigation, {title: 'Northern lights', heroHeight: 460})

  const onScroll = useAnimatedScrollHandler({
    onScroll: event => hero.onScroll(event.contentOffset.y),
  })

  return (
    <>
      <Animated.ScrollView contentInsetAdjustmentBehavior="never" onScroll={onScroll}>
        <Image h={460} source={photo} />
        <Story />
      </Animated.ScrollView>
      {hero.background}
    </>
  )
}

revealedTrailingActions join the bar once the title shows. overHeroStyle: "dark" for a light image.

Helpers

ExportUse it for
HeaderInsetContent right under a transparent header.
useHeaderBarInset()The header’s height, as a number.
useHeaderScrollToTop(ref, {largeTitle})Pass to useScrollToTop. A tab press lands on the expanded title.
LARGE_TITLE_SCREEN_OPTIONSStatic options for a large-title screen.
HERO_HEADER_SCREEN_OPTIONSStatic options for a hero screen.
HIDDEN_HEADER_SCREEN_OPTIONSNo header.

Needs react-native-screens and react-native-safe-area-context. Every native stack has them already.