Context-Driven Navigation in Practice
Navigation is rarely designed. It emerges.
At first, it’s trivial: a sidebar, a few links, a simple hierarchy. Everything fits.
Then the system grows.
Features accumulate. Permissions fragment. Tenants appear. Admin modes are introduced. Support workflows require impersonation.
Nothing looks broken. But something feels fragile.
Not visually – structurally.
Menus start to encode more than navigation. They imply permissions. They hint at roles. They compensate for missing system clarity.
Over time, the menu turns into a shadow model of the system. One that no one owns explicitly.
Small changes feel risky. Multiple teams touch the same structure. Frontend code starts asking questions it cannot answer.
Navigation stops being a convenience. It becomes a liability.
This is usually treated as a UI problem. Better grouping. Better labels. Better hierarchy.
But those are downstream optimizations.
The real issue is upstream.
Navigation breaks down when the system does not clearly express who the user is acting as, what the system allows right now, and which actions are meaningful in this context.
This article builds on the architectural essay “Navigation as a Projection of Context” which starts from a different assumption:
Navigation should not encode system knowledge. It should project it.
Navigation is treated as a read-only snapshot of system context. The backend exposes truth. The frontend renders a projection.
The rest of this article shows how to implement this concretely, using explicit contracts, intent-based entries, contributor models, and deterministic composition, with Laravel and UI5 as a real-world example.
Audience: Senior developers, architects, and lead engineers
Focus: Architecture, contracts, and composition
Navigation as a Projection of Context
The essay established the core premise: navigation is not a UI structure, but a projection of system context.
This article does not revisit that argument. It assumes it.
From here on, we treat navigation as a derived artifact – something the system exposes, not something the UI assembles.
This requires a shift in perspective.
Navigation is no longer authored. It is composed.
The system provides an explicit description of its current context: who is acting,
under which identity,
with which capabilities,
and which intents are meaningful right now.
Navigation is the read-only snapshot of that description.
There is no menu state.
There is no frontend logic deciding visibility.
There is no negotiation between UI and backend.
If the context changes, a new snapshot is produced.
To make this composable and testable, the system must expose this snapshot through a stable contract: NavigationContext.
The NavigationContext is a plain object graph that describes what exists in the current context.
At a structural level, it consists of identity information, a set of intent-based navigation entries, and clearly defined structural slots.
The frontend does not interpret this context. It projects it.
The following sections introduce the building blocks from which such a NavigationContext is composed.
The NavigationContext
The NavigationContext is the sole interface between system context and frontend navigation. Nothing else is required. Nothing else is allowed.
It is a plain, immutable data model.
The NavigationContext describes what exists in the current context.
Its purpose is structural decoupling: the backend exposes a complete snapshot, the frontend renders it without interpretation.
At a conceptual level, the model consists of four areas.
Branding
System identity and framing: product name, environment markers, visual identity.
Entries
A set of intent-based navigation entries representing the actions that are meaningful in the current context.
Avatar
An explicit identity projection: who the user is acting as, under which authority.
Footer
Session-scoped actions such as logout, settings, or support.
These areas are structural surfaces. They are fully derived from system context.
The frontend does not compose this structure. It does not enrich it. It does not fill gaps.
The NavigationContext is complete by definition.
If something is missing, it is missing because the system did not expose it or the current context does not allow it. Never because the UI guessed wrong.
This completeness is what keeps navigation honest as systems scale.
The most important part of this model is the entry, because entries do not represent pages, but intent.
Entries and Intents
The most important element of the NavigationContext is the entry.
An entry does not represent a page or a route. It represents an intent.
Intents describe the user’s actionable world, what can be done in the current context, independent of how it is fulfilled.
An entry is therefore a small, explicit data object.
It contains a label, an optional icon, a target intent, and optionally an active marker.
On purpose, it contains no behavior, no routing logic and no frontend knowledge.
The entry declares what should happen, but not how it happens.
Intents are semantic identifiers. They express meaning, not structure.
Examples include:
app::usersdashboard::admindialog::profileaction::logout
Each intent names an action or destination in the language of the system, not in the language of delivery.
How an intent is fulfilled is resolved elsewhere.
That resolution may change. The intent does not.
Because intents are semantic, they can be reused across entry points.
The same intent can back primary navigation, contextual links, command palettes, global search, or keyboard shortcuts.
Navigation is no longer a special case. It becomes one projection among many.
There is one intent language. Everything else is projection.
Contributors
Once entries are defined as intents, the question of ownership becomes explicit.
Entries are not defined by a menu. They are declared by the parts of the system that own the underlying capability.
This leads to a contributor-based model.
A contributor does not edit navigation. It contributes intent declarations within a clearly defined scope.
Each contributor declares which intents it exposes, under which contextual conditions they apply, and to which structural surfaces they belong.
No contributor owns navigation as a whole. Each contributor owns only its contribution.
Navigation is the result of composition.
Contributors express ownership boundaries. They mirror how responsibility is already distributed across teams, modules, or applications.
To make these boundaries explicit, contributors are expressed through interfaces.
Typical examples include NavigationContributor, AvatarContributor, SessionContributor, BrandingContributor.
Each interface represents a structural responsibility.
A contributor implements only the interfaces corresponding to what it owns. Nothing more.
This keeps coupling low and responsibility unambiguous.
At runtime, the system aggregates all contributors into a single NavigationContext.
No contributor has knowledge of the full navigation. No contributor needs to.
Local ownership remains local.
Global coherence emerges through composition.
Navigation stops being a coordination artifact.
It becomes an architectural outcome.
Slotting
Once navigation is contributor-based, composition becomes the central concern.
The system must decide where contributed entries belong.
Slotting is not an ordering problem.
It is a structural decision.
Instead of a single, mutable menu structure, the system defines explicit slots.
Slots are structural surfaces. They represent distinct contextual responsibilities, not visual groupings.
Typical slots include primary navigation, identity-related entries, and session-scoped actions.
A contributor does not negotiate placement.
It declares intent and surface.
Slot knowledge belongs to the contributor.
Slotting is therefore not inferred during aggregation. It is declared at the point of ownership.
Aggregation becomes mechanical collect contributions, group by slot, preserve declared order, produce a stable result.
There is no ranking.
No rebalancing.
No optimization logic.
Composition is deterministic by design.
Slot Declarations
Slot assignment must be explicit and structural.
Free-form metadata is insufficient, because it introduces ambiguity and requires interpretation.
Instead, slot responsibility is expressed through marker interfaces.
Each interface represents a structural surface of the NavigationContext.
A contributor implements only the interfaces corresponding to what it owns.
The system does not parse configuration.
It inspects declared capabilities.
Slotting becomes unambiguous, compile-time visible, and impossible to misinterpret.
Declaration replaces inference.
Why Heuristics Are Excluded by Design
Heuristics attempt to infer slot intent from incomplete information. Inference shifts responsibility from ownership to aggregation.
This is incompatible with deterministic composition.
Any logic that tries to “decide” placement reintroduces hidden coupling and weakens ownership boundaries.
In this model, heuristics are not avoided accidentally.
They are excluded structurally.
Slotting as an Ownership Boundary
Slots define where responsibility ends and composition begins.
A contributor owns which intents it exposes, under which conditions they apply, and which structural surface they belong to.
The system owns only aggregation.
If an entry appears in the wrong slot, the error is local and explicit: the contributor declared the wrong surface.
This feedback loop is intentional.
Slotting remains predictable because responsibility is never ambiguous.
Identity as a Slot Boundary
Identity is a structural boundary, not a UI concern.
It defines who actions are executed as, and under which authority.
In navigation, identity is not inferred.
It is projected explicitly.
Actor vs. Principal
Identity consists of two distinct roles.
The actor is the authenticated user, the person initiating actions.
The principal is the identity the actor is currently operating as.
In simple systems, both coincide.
In real systems, they frequently diverge.
This divergence changes which intents are meaningful in the current context.
Identity Projection via the Avatar Slot
Identity is exposed to navigation through a dedicated structural surface.
The avatar is a pure data object.
It describes the current principal, the acting authority, and the active identity context.
It contains no logic.
It performs no checks.
It triggers no side effects.
The frontend renders this projection.
It does not interpret it.
Dummy Avatars and Structural Consistency
Identity projection is always present.
When no special identity context exists, a dummy avatar is provided.
This ensures a consistent shape of the NavigationContext.
The frontend does not branch on identity presence. It always renders identity – sometimes trivial, sometimes rich.
This allows identity complexity to evolve without structural refactoring.
Identity Is Never Interpreted in the Frontend
Identity semantics belong to the system.
The frontend does not infer permissions. It does not hide actions heuristically. It does not decide whether impersonation is allowed.
It receives an identity projection, a set of intents, and a complete NavigationContext.
It renders what exists.
Nothing more.
Active State
Active state is not a UI concern.
It is a property of the current system context.
An entry is not active because a route matches or because a component is mounted.
It is active because the system is currently fulfilling the intent that entry represents.
Active state therefore belongs to the NavigationContext.
Active State Is Not Derived in the Frontend
The frontend does not determine active state.
It does not compare URLs.
It does not parse routes.
It does not infer context from UI state.
All of these are heuristics.
Active state is declared by the system as part of the navigation snapshot.
If the context changes, a new snapshot is produced.
Active State as a Declarative Flag
Each navigation entry may expose a simple, declarative signal:
active: true | falseThis flag has no behavior.
It carries no logic.
It encodes no UI rules.
It states one fact: this intent is currently being fulfilled.
Responsibility Lies with the Contributor
Active state is owned by the same contributor that declares the entry.
The contributor knows under which conditions its intent should be considered active.
Aggregation does not infer this.
The frontend does not guess it.
If active state is wrong, the error is local and explicit: the contributor declared it incorrectly.
Structural Consistency Across Entry Points
Because active state is intent-based, it applies consistently across projections.
The same signal can drive navigation highlighting, aria-current attributes, command palette state, or keyboard navigation. No additional logic is required.
Active state remains correct regardless of how the intent is fulfilled or which UI surface renders it.
Active State as a Trust Signal
Active state is subtle, but structurally important.
When navigation reflects the current context accurately, users remain oriented.
When it does not, navigation becomes misleading.
By keeping active state inside the NavigationContext, navigation remains honest as the system grows in complexity.
Aggregation
Aggregation is the final step in producing a NavigationContext.
It orchestrates what has already been declared.
The Role of the Aggregator
The aggregator has a single responsibility:
to assemble a complete NavigationContext from contributed declarations.
It does not decide what is allowed, what is visible, or what is important.
All of that has already been resolved by contributors and system context.
Deterministic Assembly
Aggregation follows a fixed sequence.
It collects branding contributions, identity projections, navigation entries, and session-level actions.
It groups entries by declared slots.
It preserves declared order.
It produces a single, immutable snapshot.
There is no ranking. There is no rebalancing. There is no post-processing logic.
No Business Logic by Design
A healthy aggregator is intentionally boring.
If aggregation requires conditionals, permission checks, role switches, or ordering rules, then responsibility is misplaced.
The missing information belongs upstream.
Failure Is Local and Explicit
If navigation appears incorrect, the cause is never hidden.
An entry is missing because it was not contributed.
An entry is misplaced because it declared the wrong slot.
An entry is inactive because its contributor marked it so.
Aggregation itself cannot be the source of ambiguity.
Immutability and Snapshot Semantics
The NavigationContext produced by aggregation is immutable.
It represents a snapshot of the current system context.
If context changes, a new snapshot is produced.
Nothing is mutated in place.
Nothing is reconciled.
Because aggregation is deterministic contributors can evolve independently, changes remain predictable, navigation diffs are meaningful, and refactoring is safe.
Aggregation remains infrastructure, not a coordination surface.
Navigation coherence emerges from composition, not control.
Frontend Projection
Once the NavigationContext has been aggregated, the frontend’s role is deliberately limited.
It does not decide what exists.
It does not infer meaning.
It does not negotiate permissions or identity.
It projects.
The frontend receives a complete, immutable snapshot of navigation context and renders it as-is. There is no menu state to manage and no conditional logic to evaluate. What appears is what the system has declared to be true.
Structural surfaces are mapped to layout regions. Entries are rendered according to their declared intent and active state. Identity is displayed exactly as projected. Session-level actions are placed where they belong.
The frontend does not enrich this information. It does not fill gaps or compensate for missing context. If something is absent, it is absent by design.
When the system context changes, a new NavigationContext is produced. The frontend replaces the previous snapshot with the new one. Nothing is reconciled. Nothing is mutated.
This keeps the frontend predictable. There is no hidden coupling between UI state and navigation structure. Rendering becomes a pure function of context.
As a result, frontend code becomes smaller, calmer, and easier to reason about. Navigation no longer requires special handling. It is simply another projection of system truth.
Once this boundary is respected, navigation stops being a source of complexity. It becomes a mirror that reflects whether the system itself is coherent.
Consequences
At this point, the architectural picture is complete. The remaining question is pragmatic:
Why is this worth doing?
The answer is not “better navigation.” It is less accidental complexity.
Fewer Special Cases
Because navigation is derived:
- no role-specific menus
- no frontend permission checks
- no feature-flagged UI fragments
If something should not appear, it simply doesn’t exist in the context.
Clearer Tests
Because the NavigationContext is a contract:
- backend tests validate context composition
- frontend tests render static snapshots
- no integration gymnastics required
Navigation becomes one of the easiest parts of the system to test.
Easier Extension
New capabilities are added by:
- declaring intent
- implementing a contributor
- letting aggregation do the rest
No global refactors. No menu rewrites. No migration projects.
Better Team Collaboration
Teams work independently:
- local ownership
- explicit contracts
- no shared menu file
Navigation stops being a coordination tax.
No “Menu Migration” Later
Perhaps most importantly: there is no point where you have to “redo navigation properly.”
You don’t accumulate menu debt. You don’t outgrow your structure.
You stop maintaining navigation — you derive it.
That is the real payoff.
Outro
This deep dive did not introduce a new idea.
It made one idea concrete.
The flagship essay argued that:
Navigation is not a menu — it is a projection of context.
Everything in this article follows directly from that premise.
- context is explicit
- identity is visible
- intent is declared
- composition is deterministic
- UI is a projection, not a decision-maker
There is no contradiction between architecture and implementation only alignment.
Once navigation becomes a projection of context:
- systems become more honest
- UIs become simpler
- teams become more autonomous
And perhaps most importantly, navigation stops being a problem you have to solve.
It becomes a mirror that tells you, continuously, whether your system still makes sense.
Once navigation becomes a projection of context, both system design and UI start to simplify.