Ordering
Every contribution carries an order. The resolver sorts each slot's entries
by it — order, then plugin position, then declaration position — and a host
renders them in that resolved order. The façade ranks its fills on the same
kind of number, inside its own store.
A priority, not an index
order is a priority. Nothing is indexed by it, nothing is reserved, and two
contributions may share one.
Leave gaps — 10, 20, 30 — so a plugin can be slotted between two existing
ones later without renumbering anything.
// `order` is a priority, not an array index. Leave gaps so a plugin can be
// inserted later without renumbering anything.
export const = ({
: "search",
: [
.("search-box", { : 10, : }),
],
})
export const = ({
: "filters",
: [
.("menu", { : 20, : }),
],
})order is a plain number, so the whole range is available:
| Value | Meaning |
|---|---|
| omitted | 0 — the default for contribute() and for a façade fill |
| negative | before everything that left order unset |
10, 20, 30 | the useful convention: room to insert later |
Nothing is reserved and nothing is indexed by it, so a value you never use costs nothing and a gap you never fill is not a hole in the output.
Ties are stable, and both render
Two contributions that share one order both render. Neither replaces the
other.
// Two contributions may share one `order`. Both render; neither replaces the
// other. The tie is stable: plugin position first, then declaration order.
export const = ({
: "export-csv",
: [
.("export", { : 20, : }),
.("import", { : 20, : }),
],
})The tie-break is deterministic, in this order:
order— lower first.- Plugin position — where the plugin sits in the list handed to
resolvePlugins. - Declaration position — where the contribution sits in that plugin's
contributes.
Every part of that is derived from data the application controls, so the same
inputs always produce the same visual order — on the server and on the client.
Each resolved entry also carries its final position as seq.
A worked example
Given resolvePlugins([search, filters, exportCsv]), a host renders this:
| # | Contribution | order | Why it is here |
|---|---|---|---|
| 1 | search/search-box | 10 | lowest order |
| 2 | filters/menu | 20 | filters is earlier in the list than exportCsv |
| 3 | export-csv/export | 20 | first in exportCsv's contributes |
| 4 | export-csv/import | 20 | second in exportCsv's contributes |
Move filters after exportCsv in the list and rows 2–4 reorder. Change
nothing and the result is byte-identical on every render, on both sides of a
hydration.
Overrides can re-rank
The integrator has the last word: an override patches one contribution's
order by its full id, without forking the plugin that declared it.
// The application re-ranks a contribution it does not own by its full id.
// Typed patches come from the slot: `override` is where a replacement
// component would be checked against the slot's props.
export const = .("export-csv/import", { : 5 })Pass it to resolvePlugins in options.overrides. A target that nothing
carries comes back as an
unknown-override-target diagnostic, not a silent
no-op.
Read once
A façade fill reads its order once, when it mounts. Changing the value
afterwards leaves the fill exactly where it is.
const = ()
export function ({ }: { : number }) {
// A façade fill reads `order` once, when it mounts. Changing `rank`
// afterwards leaves the fill exactly where it is — pass the value that is
// already final.
return (
< ={}>
< ="button">Pinned</>
</>
)
}That is deliberate: the alternative is a contribution that jumps position while
a user is looking at it. Pass the value that is already final, or remount the
fill to apply a new one. Fills that share an order render in mount order.
A declared contribution has no such caveat. Its order is data in the
manifest — resolve again and the new rank applies.
See both rules at once
Change a declared order and the registry host reorders on the next Resolution. Change the fill's order and nothing moves until you remount it.
Registry host — declared orders
Façade host — one runtime fill
Change a declared order and the registry host reorders on the next Resolution — immediately. Change the fill's order and nothing moves — the façade read it once, on mount. Remount the fill to apply the new value.
History
In 2.x, order was an array index: two fills that shared one silently replaced
each other, and order bands, strides and duplicate-order detectors existed to
work around it. 3.0 made order a priority on both channels; 4.0 moved the
declared ranking into resolvePlugins, where overrides can re-rank it. See
Migrating to 4.0.