A single reference for what the recent feature set can and can’t do, on both the Android
(Jetpack Compose) and React (aznavrail-react, React Native / Web) libraries. Behaviour is kept at
parity between the two unless noted.
Nothing new was added to the dependency surface for the recent features.
Android (aznavrail)
minSdk 26, Java 17, Jetpack Compose (the project’s Compose BOM).androidx.compose.animation:animation-core — already a declared dependency; powers the kinetic
typography (CubicBezierEasing, Animatable, tween, spring).kotlinx-coroutines-core (already on the classpath via Compose; already used across the library) —
the host-expansion fix uses merge / flow / distinctUntilChanged from it. No new artifact.React (aznavrail-react)
react, react-native, react-native-web. The kinetic typography uses RN’s
built-in Animated / Easing; host auto-expansion uses a built-in setInterval. No new packages.Config-driven motion for menu words — preset enums, no free-composable escape hatch.
Capabilities
AzEntrance: None | Fade | SlideUp | Turnstile) and exit (AzExit:
None | Fade | Turnstile), staggered per item.itemTextStyle — override merged over the label so menu words can be big/light/wide Metro type.AzNavHost boundary title sweeps in each time the active screen changes.AzDropdownMenu, and the screen title. The
dropdown’s app-icon trigger also gets an automatic margin.AzEntrance.None /
AzExit.None (rail: azKinetics(...), dropdown: azConfig(...), React: settings / props).Limitations
entranceDurationMs if it feels heavy.transformOrigin is honored
on web and ignored on native.expandWhen / initiallyExpanded)A host item can auto-expand its sub-items reactively.
Capabilities
expandWhen: () -> Boolean — rising edge (false → true) expands, falling edge collapses.initiallyExpanded — expands once on first appearance.mutableStateOf) / React-state
condition reacts instantly; a non-reactive source (StateFlow.value, LiveData.value, a plain
var, an external store) still works via a low-rate poll.expandWhen and initiallyExpanded may coexist; the first observation only ever expands, so it
never clobbers an initiallyExpanded/manual state.Limitations
expandWhen host keeps a small recurring timer alive while mounted (one boolean eval per host
per ~300 ms). Negligible, but not zero. Hosts without expandWhen start no timer.onExpandedChange fires only for manual expand/collapse — never for expandWhen /
initiallyExpanded-driven changes.Historical note: this previously appeared “broken” because the watcher was keyed on the whole item list, so any item-value change relaunched the watchers and swallowed the rising edge. It is now keyed on the stable host-id set. See
docs/DSL.md→ Reactive Host Expansion.
Capabilities
docs/) from its GitHub repo, derived
from the app namespace (com.<owner>.<repo> → github.com/<owner>/<repo>); appRepositoryUrl
overrides it..azignore (and .aiexclude) in the repo root excludes listed docs from the table of contents
(gitignore-style patterns; * globs and trailing-slash dir prefixes).og:image); a blank or GitHub-avatar URL
falls back to the app’s initials — the owner’s GitHub avatar is never shown as an app icon.noMenu rails
that have no drawer footer. It persists but is not fixed: azAboutRailItem(...) /
<AzAboutRailItem> places and styles your own, azAbout(aboutRailItem = false) drops it.translucentBackground supplies the hue, never the alpha). The drop-down’s dropped panel
does the same, and additionally replaces a library-chosen accent that fails WCAG 3:1 against that
panel with plain ink.Limitations
appRepositoryUrl; the
library never falls back to its own docs in a consuming app..azignore is read from the repo’s contents listing (so it resolves on any default branch); a
repo unreachable over the API means no filtering that session.Capabilities
enableRailDragging), or swipe-openable, and it consumes
the pointer only on the branch that actually undocks the rail or moves the menu.dimBehindMenu is on; the scrim is inset to
exclude the rail, so taps on the rail still reach the rail.Limitations
| Area | Android | React |
|---|---|---|
| Kinetic entrance/exit/tilt/title | ✅ | ✅ (tilt web-only) |
expandWhen / host initiallyExpanded |
✅ instant + ~300 ms poll | ✅ render-eval + ~300 ms poll |
About .azignore filtering |
✅ | ✅ |
| Pinned “More from Az”, tap-to-open cards, real app icon | ✅ | ✅ |
| Clear Help/Tutorial while About open | ✅ | ✅ |
About (?) rail item, auto-appended + overridable |
✅ | ✅ |
Borderless shapes with a base footprint (NONE_SQUARE/NONE_CIRCLE) |
✅ | ✅ |
| Shared rail palette for every AzNavRail surface | ✅ LocalAzRailPalette |
✅ AzRailPaletteContext |
Outside-tap collapses the drawer without dimBehindMenu |
✅ | ✅ |
| Feature | Android (aznavrail) |
CMP (aznavrail-cmp) |
React (aznavrail-react) |
|---|---|---|---|
| Drop-down trigger set + title-row placement | ✅ | ✅ | ❌ not ported |
Unattached hosts (azUnattachedHostItem) |
✅ | ✅ | ❌ not ported |
| Floating-host screen-edge & rail-to-rail docking | ✅ | ✅ | ❌ n/a (no unattached hosts) |
Per-item badge / loading (azItemState) |
✅ | ✅ | ❌ not ported |
Popups (azPopup) + warning triangle |
✅ | ✅ | ❌ not ported |
| Pinned “More from Az” rail item | ✅ | ✅ | ✅ |
| Three highlights (active / focus / secondary) | ✅ | ✅ | ✅ |
| Per-item highlight colours | ✅ azHighlight |
✅ azHighlight |
✅ item props |
| About de-duplication across surfaces | ✅ | ✅ | ✅ |
| About content warmed in the background | ✅ | ✅ | ✅ |
| Auto-sizing footer labels | ✅ | ✅ | ✅ |
Floating windows (AzWindow) — movable + minimizable |
✅ | ✅ | ✅ |
| Hidden menu drawn in a window | ✅ | ✅ | ✅ |
System overlay (overlayService) |
✅ | Android target only | ❌ n/a |
| Dissolve overlay on item tap | ✅ | ❌ not ported | ❌ |
| Unit tests | ✅ | ✅ (DSL-level only) | ✅ |
AzWindow — landed on all three at once, but the older gaps remain.
aznavrail-react is versioned separately (package.json) from the Gradle artifact, so the two do
not move together.@Az annotations and KSP processor now exist (:aznavrail-annotations,
:aznavrail-processor) and generate a working AzGraph; see docs/API.md §1 and
SampleApp/.../AzGraphDemoActivity.kt. They are Android-only — the generated graph calls
setContent on a ComponentActivity, which has no Compose Multiplatform analogue. CMP consumers
use the DSL directly.AzRailLayoutHelper and its test both encode the documented rule.FLOATING unattached hosts now dock independently (aznavrail and aznavrail-cmp) instead of
sharing a single draggable stack: each one can be dragged to the top, bottom, or opposite screen
edge, or flush against another FLOATING host to attach to it (capped at two columns side by side,
and per column at whatever fits with everything fully expanded at once). A host with at least one
other host attached to it grows a grab bar for dragging the whole group. Rail-to-rail attachments
are not persisted across launches — only each host’s own resting spot is; see
AZNAVRAIL_COMPLETE_GUIDE.md §4.The library is measured against the Conveyance manifesto. What that means in practice, and what changed to honour it:
| Principle | How the rail expresses it |
|---|---|
| Guide by example | Toggles/cyclers are label-is-state-is-control. An alerted item morphs into the warning triangle and back. Menus unfold; taps dissolve outward across the screen. |
| Resourceful minimalism | AzLoad is a morphing shape, not a ring plus the word “loading…”. Per-item loading rather than a screen-blanking overlay. The rail is also the FAB, the drag handle and the app identity. |
| Eradicate explicit instruction | Auto-generated guidance (“Open the menu”, “Tap Settings”) is off by default (azAdvanced(autoGuidanceEdges = true) opts in). Affordance captions (“Tap to continue”, “Tap to collapse”) are gone. |
| Physics and motion | Haptics now answer every commit, not only FAB activation. New surfaces animate in rather than appearing. |
AzLoad no longer renders the text “loading…“ and no longer takes showLabel. It draws a
filled shape morphing through rounded polygons. If you asserted on that string, assert on
AzLoadContentDescription instead.azAdvanced(autoGuidanceEdges = true).vibrate is on, where previously only FAB activation did.Popup
whose window is sized to its content, so the travelling label was clipped at the rail’s edge and
appeared to vanish. It is now drawn by the host at the window root.