Let the navigation bar hide on a scroll down

Plan step 25 (navigation Missing): M3's "hides on scroll-down, reappears on
scroll-up" had no implementation. `<x-navigation-bar hide-on-scroll>` slides the
bar out on the default spatial spring — an instant swap under reduced motion,
which zeroes the duration token — and never while a snackbar, a bottom sheet or
a drawer is anchored to its edge; focus reaching the bar brings it back.
`<x-app-shell hide-bar-on-scroll>` picks it, and --material-bottom-bar follows
the bar down and up so a `fab` button and the snackbar keep their offsets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:36:57 +02:00
co-authored by Claude Fable 5.1
parent 4d00d87cd2
commit ce1cd6eec0
8 changed files with 167 additions and 11 deletions
+25 -2
View File
@@ -43,8 +43,9 @@
* rail starts expanded instead of collapsed. The bar's own item layout is a *container* query at
* the same 37.5rem, so a bar in a narrow column lays out by its own width.
*
* Everything here is in `@layer components` except the last block, which restyles a FAB the
* application put in the rail: that one has to beat a utility, and a layer never does.
* Everything here is in `@layer components` except the blocks after it, which reach past the
* component into what an application wrote — the bottom offset <x-app-shell> publishes, a FAB put
* in the rail: those have to beat a utility, and a layer never does.
*/
@custom-variant rail-collapsed {
@@ -116,6 +117,19 @@
min-height: 5rem;
}
/* "Scrolling: hides on scroll-down, reappears on scroll-up" (the reference's Navigation bar
§ Behaviour). It slides out of the window on the default spatial spring, which reduced
motion zeroes to an instant swap along with every other duration token. Whether it may hide
at all is resources/js/navigation.js's call: never while a bottom sheet, a drawer or a
snackbar is on screen, since those are anchored to the bar's edge. */
[data-navigation-bar][data-hide-on-scroll] {
transition: translate var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default);
}
[data-navigation-bar][data-hide-on-scroll][data-hidden] {
translate: 0 100%;
}
[data-navigation-bar-item] {
--navigation-layer: 0;
position: relative;
@@ -664,6 +678,15 @@
}
}
/* A bar that has slid off the bottom of the window is no longer there to clear, so the offset
* everything pinned to the bottom reads drops to the bottom safe area and whatever the application
* has docked on the bar — a `fab` button, the snackbar and the page's own bottom padding all follow
* it down and come back up with the bar. Unlayered on purpose: <x-app-shell> publishes the variable
* with a utility, and a rule in any layer loses to one. */
[data-app-shell]:has([data-navigation-bar][data-hide-on-scroll][data-hidden]) {
--material-bottom-bar: calc(var(--material-safe-bottom, env(safe-area-inset-bottom)) + var(--material-bottom-extra, 0px));
}
/* A FAB in the rail's header rests at elevation 0, not the 3 a standalone FAB has
* (docs/reference/m3/components-navigation-selection-inputs.md § Navigation rail: "when nested
* within another component, such as the navigation rail, the FAB's resting elevation should be