PATTERNS FEATURE
The composition model: parts
Components compose primitives through declared parts: #[UiPart] binds a primitive, #[ProvidesUiPart] computes its props, and props resolve in a fixed order.
The #[UiPart] declaration's baseline props for the bound primitive.
#[ProvidesUiPart]
A method computes structural props from the component's state — overrides defaults.
bind: 'value' threads the live value into the part — overrides the provider.
Anything the caller passes to ui_part() wins last — deterministic, always.
The composition model — parts
A component is not bespoke markup — it is a declared composition of primitives. The field and form components don't hand-assemble inputs and labels; they declare parts and let the model resolve them. That is what keeps composition predictable instead of ad-hoc template assembly.
Parts, providers, and a fixed resolution order
#[UiPart(name, uses: Primitive, bind?, defaults?)]declares a named part backed by a primitive (e.g.field.inputuses the input primitive).#[ProvidesUiPart(part)]marks a method that computes that part's structural props from the component's state.ui_part()/ui_part_props()resolve and render (or just resolve) a part atomically in Twig.
Props resolve in one fixed order, each step overriding the previous:
- Part defaults — the declaration's baseline.
- Provider — the
#[ProvidesUiPart]method. - Bind — the bound value (
bind: 'value'). - Caller overrides — anything passed to
ui_part()wins last.
So field.input is the input primitive, bound to the value, carrying the field's computed props — and a caller can still override any of it.
A declared, deterministically-resolved part model is what makes components consistent and composable instead of a pile of one-off templates. The same model underlies every component on this site. You read a component's parts to know its structure, and you extend behaviour by adding declarations — the Page Contract principle, one level down, at the component scale.
How it works
#[UiPart(name, uses: Primitive, bind?, defaults?)] declares a named part backed by a primitive; #[ProvidesUiPart(part)] marks a method that computes that part's structural props. At render, props resolve in a fixed order — part defaults → the provider method → the bound value → caller overrides — so behaviour is deterministic. The Twig helpers ui_part() / ui_part_props() resolve and render a part atomically. So field.input IS the input primitive, bound to value, carrying the field's computed props, and a caller can still override any of it.
Why it matters
A declared, deterministically-resolved part model is what makes components consistent and composable instead of a pile of one-off templates. The same model underlies every component on this site. You read a component's parts to know its structure, and you extend behaviour by adding declarations — the Page Contract principle, one level down, at the component scale.