Skip to content
LogoLogo

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:

ValueMeaning
omitted0 — the default for contribute() and for a façade fill
negativebefore everything that left order unset
10, 20, 30the 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:

  1. order — lower first.
  2. Plugin position — where the plugin sits in the list handed to resolvePlugins.
  3. 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:

#ContributionorderWhy it is here
1search/search-box10lowest order
2filters/menu20filters is earlier in the list than exportCsv
3export-csv/export20first in exportCsv's contributes
4export-csv/import20second 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

No fill mounted

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.