Skip to content

Contributions

A NavBar needs its entries from wherever they are declared - a dozen features scattered across a dozen modules, each adding the one link it owns. A slot on its own is the wrong tool for this: a slot is one parent placing one block of content into one region, not many children contributing into a shared collection. contribute to is the many-to-one counterpart.

Slots live on the application’s layout and on every screen template and dialog template, and any of them can declare it accepts contributions under a name:

layout <Name>
<slot-name> contributes <ContributionPoint>
<slot-name>
module <Name>
screen template <TemplateName>
<slot-name> contributes <ContributionPoint>
<slot-name>

Anything anywhere in the module/feature tree contributes to it, without knowing or caring who is listening:

contribute to <ContributionPoint>
navigate to <Screen> [by <param>]
label "<text>"
order <number>
  • <slot-name> contributes <ContributionPoint> - marks a slot as the target for contributions under that name. A slot without contributes behaves exactly as it always has.
  • contribute to <ContributionPoint> - one contributed item. Declared directly on a module (alongside screen template, dialog template, form and feature) or on a feature at any nesting depth. navigate, label and order are all optional. label accepts an unquoted $strings.<key> token in place of a literal - see Internationalization.
module Invoicing
screen template AppShell
navbar contributes Navigation
main
feature InvoiceManagement
slice StateView InvoiceList
screen InvoiceList
contribute to Navigation
navigate to InvoiceList
label "Invoices"
order 10

A contribution attaches to the nearest enclosing structure that declares a matching contribution point, walking outward the same way a bare name already resolves elsewhere in the document - just through the module/feature containment tree rather than by declaration scope. Concretely: a contribution first looks for a contributes <ContributionPoint> slot among its own module’s templates. A module with its own matching slot stops contributions inside it from bubbling any further - that module owns the point. Only when the module has no matching slot does the search continue outward, across every other module in the document, and finally to the application’s own layouts, which belong to no module and are the outermost shell of all.

This gives three real tiers of nesting: application-wide (the layout declares the point, so every module in the document contributes into the one shell - the natural home of Navigation), cross-module (a contribution resolves to some other module’s template because its own module has none that matches) and module-level (a module’s own template claims every contribution inside it, however deeply nested in that module’s features). A fourth tier - a feature declaring its own sub-shell that only its own slices contribute to - would need a template scoped to a feature rather than a module, which does not exist yet.

Unresolved and ambiguous contribution points are warnings, the same as every other reference in the document: a name may still resolve to something outside the document, and the point is that the gap stays visible.

module Payments
contribute to Navigation
navigate to InvoiceList

If Payments has no template of its own, and two other modules each declare a contributes Navigation slot, the contribution is ambiguous - both are equally near, and nothing in the document says which one it means.

navigate to <Screen> by <param> is not string interpolation. It reuses the exact typed navigate binding a screen’s action and on row-click already use, checked by the compiler the same way any other bare name is. Turning that into a URL, a query string, or a native deep link is a rendering concern the widget consuming the contribution point owns - not something the language decides.

Navigation is the mechanism’s first user, not the whole of it - a widget declares whatever contribution point name and shape it needs, and contribute to <Name> opens a property bag shaped by that name. This version of the mechanism ships with navigate, label and order; ordering by a group beyond a flat list, and an explicit override for the rare case where nearest-enclosing is not the contribution point a contributor means, are intentionally left for a later iteration.