Scroll-aware AppBar
Pre-alphaThe registry and the CLI are not published yet.Roadmap
Scroll

Scroll-aware AppBar

An AppBar that stays in place and changes its look as content scrolls under it.

Draft

Specified, not implemented yet. The API is described in useScrollAwareAppBar and may change.

The bar doesn’t move and doesn’t change size. At the top of the screen it is transparent and has no title, so the content shows through. As content scrolls under it, it gets a background and a hairline, and the title appears once the title in the page has scrolled out of view.

The last lighthouse keeper

6 min read · Coast

For forty years, the light on the point was kept by one person. Every evening at dusk, the lamp was lit by hand, the lens wound, and the log filled in.

Automation came in 1998. The keeper stayed on anyway, repainting the rails and answering letters from sailors who remembered the beam.

The log books tell the story in short lines: wind, visibility, ships passed. On stormy nights, a single word: kept.

Today the lighthouse is a small museum. The lamp still turns, driven by a motor the size of a shoebox.

Visitors climb the 132 steps and read the last entry, left open on the desk: light on, all well.

The keeper lives in the village now, a short walk away, and still looks up at dusk.

Every spring, the old logbooks go on display for a week. People come looking for the night a relative’s boat came home.

Most entries are dull on purpose. A quiet log meant a quiet sea, and a quiet sea meant everyone came back.

The lens was made in Paris in 1884. It weighs two tonnes and floats on a bath of mercury, so a hand can turn it.

When the power fails, a small battery takes over. When the battery fails, someone from the village still has a key.

The keeper was asked once what the job was really about. The answer fit in the log: being there.

The last lighthouse keeper
offset 0 · elevation 0.00 · title 0.00

Scroll the article. Turn on Guides to see the bottom of the bar and the in-content title that drives the compact one.

When to use it

  • Detail screens that open on a cover image, a map or a hero: article, product, profile, place.
  • Any screen whose content starts with its own title, so the bar doesn’t repeat it at the top.
  • Plain lists, with only the hairline: it appears once a row is under the bar, telling the user there is content above.

The three scroll behaviors change different things:

BehaviorThe bar…Follows
Scroll-aware AppBarstays in place, changes its lookposition
Collapsible headerchanges its heightposition
Hide-on-scrollleaves the screendelta

They combine: a collapsible header is scroll-aware once collapsed, and a scroll-aware bar can also hide on scroll.

How it works

Two values, both derived from the scroll position:

elevation = clamp(offset / distance, 0, 1);
title = clamp((offset - (titleTop - barHeight)) / titleHeight, 0, 1);
offset      0 ──────────── distance ─────── titleTop − bar ─── + titleHeight ──►
elevation   0 transparent ──► 1 background + hairline
title                                        0 hidden ────────► 1 shown
  • elevation fills the bar. With a cover, distance is the cover height minus the bar height, so the bar is opaque exactly when the cover is gone. On a plain list, a short distance (16pt) is enough.
  • title starts when the in-content title reaches the bottom of the bar and completes when it is fully under it. The two titles are never both readable.
RuleWhy
Never move the barBack and actions stay under the thumb and in the same place.
Keep controls readable over the coverEach button sits on a disc that fades out as the bar fills.
Title from the content’s own layoutThe compact title appears when the real title leaves, whatever its length or the font size.
No snap, no animation of its ownBoth values follow the finger. The bar is in the right state whenever scrolling stops.

Implementation

Axiom implements it with one hook, useScrollAwareAppBar, and two AppBar props that read its values.

LibraryUsed for
react-native-reanimatedoffset, elevation and title as shared values computed in useAnimatedScrollHandler. interpolateColor for the background and the hairline. No React render while scrolling.
React Native layoutonLayout on the in-content title gives titleTop and titleHeight. The bar is positioned over the content, which starts at the top of the screen.

In a screen

Conceptual
const bar = useScrollAwareAppBar({ distance: COVER_HEIGHT - 44 });

<Animated.ScrollView {...bar.scrollProps}>
  <Cover />
  <Text variant="title" {...bar.titleProps}>The last lighthouse keeper</Text>
</Animated.ScrollView>

<AppBar elevationProgress={bar.elevation} titleProgress={bar.title} style={styles.overlay}>
  <AppBar.Title>The last lighthouse keeper</AppBar.Title>
</AppBar>

Installation, options and return value are documented in useScrollAwareAppBar.

Accessibility

  • Keep one heading for screen readers: the in-content title. Hide the compact one with accessibilityElementsHidden / importantForAccessibility="no-hide-descendants", since it only repeats it.
  • Check contrast at elevation 0: icons over a photo need their disc, or a gradient under the bar.
  • The values follow the scroll, so Reduce Motion needs no change.