244. A child may say where it sits
Date: 2026-09-05
Status
Accepted. Closes the TODO.md entry opened by
ADR-0111, and takes one of the
two things stack is blocked on.
Context
The entry names the gap and prices it:
align-selfis not in §8’s subset, which a tab strip’s+found: a child shorter than its row sits at the top of it and there is no per-child way to say otherwise. It is the companion ofalign-items, which is in the subset, and Yoga’ssetAlignSelfis already bound — what it costs is a component inComputedStyleand one inBox, both of which are records whose every wither would have to be revisited.
Two corrections to that framing, both in the toolkit’s favour:
- §8 has listed it all along.
docs/ARCHITECTURE.md§8’s layout list readsalign-items/self/content, and the sentence naming what is unimplemented says onlyflex-basis. So the document claimed this worked. What was missing was the implementation, not the sanction. Align.AUTOwas already waiting for it. The enum’s own comment says “AUTOonly means anything foralign-self” — a value that existed for a property that did not.
The price is real: ComputedStyle has 22 hand-written withers and Box 27, each
rebuilding its record positionally. Adding a component means editing 47 argument
lists, and alignItems and alignSelf are the same type, so a swap between
them compiles, runs, and is wrong.
Decision
One component on each record, alignSelf, defaulting to Align.AUTO.
ComputedStylegains the component, the wither and analign-selfcase inwith.Boxgains the component, a builder method, and carries it throughstyle(ComputedStyle)so a resolved declaration reaches the box.RenderObjectcallsnode.setAlignSelfbeside the existingsetAlignItems, guarded by the same “only if it changed” comparison every other property uses.
Align.AUTO is the default on both, which is Yoga’s and CSS’s: a child that says
nothing is aligned by its parent’s align-items alone. It is also the only value
that reads differently on a child than on a parent — “defer to my container” — so
align-self: auto is a real declaration that undoes a more general rule rather
than a missing one.
The mechanical edit was scripted, and the safety net already existed
47 argument lists is not a hand edit. The clean sites — 22 in ComputedStyle, 25
in Box — are one identifier per argument, so they were rewritten by a script
that splits the argument list and inserts at a fixed index. The four that are not
clean carry inline comments containing commas, and were edited by hand.
What makes that safe is that RecordWitherTest already exists, from ADR-0181
when Limits was added, and it is exactly the check this needs: it asks every
wither to set its component to the value it already holds and requires the record
back unchanged. A wither that writes its argument into the wrong slot, reads the
wrong component into a slot, or passes one component twice all fail it. Its
premise — that no two components of one type hold equal values — is asserted by
componentsAreDistinct, which is why the fixtures give alignItems
Align.FLEX_END and alignSelf Align.CENTER.
So the risk this entry priced was already insured, by a test written the last time somebody paid it.
Alternatives considered
- Group the align properties into a sub-record, the way
InsetsandLimitsgroup their four. It is the structural answer to long argument lists andRecordWitherTest’s own comment says so — and it would renamebox.alignItems()at every call site in the toolkit for a benefit the test already delivers completely. - Put it only on
Boxand not onComputedStyle. A widget could then set it inrenderand no stylesheet could, which inverts the whole arrangement: the point of the entry is that a document can say where a child sits. - Add
align-contentat the same time, since §8 lists all three. Nothing in the catalog wants one — it decides how wrapped lines share the cross axis, which matters only to a wrapped row in a box with a fixed height — and that is the rule §8’s subset has grown by all along. - Ship a consumer with it. The tab strip’s
+is the case that found the gap, and changing it moves goldens for a reason unrelated to the mechanism. Better as its own diff.
Consequences
stackis one blocker lighter. Its entry named two: the layering half, whichWindowRootalready does withposition: absoluteand Yoga insets, and the alignment half, which was this. What remains isstackitself.- Five layout tests, four of which fail against the old code, asserted
against Yoga’s own output rather than against the record — the property is
one line in
RenderObjectand the whole risk is whether that line runs, so a test readingbox.alignSelf()back would pass on a box nothing laid out. autois asserted to be indistinguishable from saying nothing, and beside it a test that a non-autovalue really does move the child — because the reason those two agree must not be that nothing is wired up at all.RecordWitherTest’s two fixtures gained a value each, and its coverage count went up by two on its own.- No golden moved, because nothing in the catalog declares
align-selfyet. That is the honest state of a property added for the widget that will want it. docs/ARCHITECTURE.md§8 is now true where it was optimistic: its list includedalign-items/self/contentwhile only the first resolved.