Skip to content

spec(native): a container label does not merge its children; targets measure in their environment - #8135

Draft
imdreamrunner wants to merge 2 commits into
mainfrom
spec/native-swift-a11y-merge-and-target-environment
Draft

imdreamrunner wants to merge 2 commits into
mainfrom
spec/native-swift-a11y-merge-and-target-environment

Conversation

@imdreamrunner

@imdreamrunner imdreamrunner commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Exact decision needed

Two claims in the Swift accessibility draft, packages/native/specs/swift/library/accessibility.md:

  • X5 states the outcome — one accessibility element per thing a person reads — without the mechanism that produces it, and the implementation a reader reaches for first does not produce it.
  • X7 requires a 44 by 44 point target without saying in what environment that target is measured.

Existing authority searched

  • packages/native/specs/swift/library/accessibility.md, X5 and X7: the draft that owns both claims and the only place either is stated.
  • docs/specs/AST-093-native-component-guidelines/spec.md FR3 (hit targets, Dynamic Type and VoiceOver follow the Apple Human Interface Guidelines) and FR5 (accessibility assertions for label, traits and value, plus a check at the largest Dynamic Type size). Both name the outcome and leave the mechanism and the measuring environment to this library spec.
  • docs/specs/AST-082-native-first-surfaces/spec.md: owns the core contract's accessibility semantics and their per-platform mapping; it carries no element-merging or hit-target claim.
  • docs/specs/AST-009/spec.md (core accessibility): no element-merging or minimum-target claim.
  • Open pull requests under packages/native/specs/swift/library/ (styles, component API, slots) and the AST-093 spec-location change: none of them touches accessibility.md.

No current claim settles either point, so X5 and X7 keep them.

Decision

X5 states its mechanism. A label on a container does not merge what the container holds: a text child inside a labelled ancestor keeps its own accessibility element and its static-text role, and that element publishes the ancestor's label in place of the child's own words — so the same name is reachable twice and the words the child draws are never read. A component that reads as one thing merges its children explicitly rather than resting on a container label: accessibilityElement(children: .combine), .ignore with a label of its own, and accessibilityRepresentation each do this, as does any equivalent that exposes one element named with the component's own words.

X7 states its environment. A control's rendered size follows the environment its ancestor establishes, so a target is measured in the environment the component ships in, at the default and the largest text sizes, and not in isolation. The largest size alone is where text-scaled controls are biggest, so a control under 44 points at the default size would pass it.

The acceptance checks gain a row for X5 — a text child inside a labelled container, exactly one element, reachable once, named with the words the component reads as — and the X7 row names the environment and both text sizes. performAccessibilityAudit() remains the screen-level check shared by X4 and X5; whether it reports a duplicate name is not tested here, so the new row says only that the audit does not establish the claim on its own and that the test asserts it.

Rejected alternatives

Resting on a container label to make a component read as one thing. It stays as one clause inside X5 rather than a separate note, because it is the implementation the requirement's old wording invited.

Claim scope and non-goals

  • Swift only. A React Native mirror of the same shape fails differently, and that platform is outside this record.
  • X1–X4, X6, the summary, the current-state section and the open question are unchanged.
  • The fixture's measured numbers stay out of the record; it carries only the durable constraints they support.
  • Whether performAccessibilityAudit() flags a duplicate name is untested and unclaimed.
  • Both additions come from one measurement pass over this one draft and land together; neither rescues the other.

Evidence and implementation relationship

This is a specification clarification, not a code change. No Swift view exists for it to govern yet, which the record's Current state section already says.

Measured on a SwiftUI fixture on a simulator:

  • With an accessibility label on an ancestor, a plain text child kept its own accessibility element and its static-text role, and that element published the ancestor's label instead of the text's own content. The same name was reachable twice and the text's own words were never read; the text re-rendered visibly while the published name did not follow it.
  • The same control's rendered size changed with the environment its ancestor established — roughly 81 by 34 points in one environment and 97 by 50 in another, with the same effect at other value states.
  • A React Native mirror of the first shape produced duplicate button-role rows: a different mechanism, the same hazard for a person using a screen reader.

Writing rubric results

  • R1 narration: none found. Both additions are present-tense statements about the system, and a scan of the record for proposes, we decided, previously, a port of and used to returned nothing.
  • R2 research in the record: none. The fixture measurements and their numbers live in this description; the record keeps only the two constraints they support.
  • R3 evidence naming a change: none. The new acceptance row names the assertion a test makes, and the X7 row names the environment it measures in; neither cites a pull request.
  • R4 rejected alternatives over one line or outside a decision: one found — resting on a container label — kept as a single clause inside X5, because naming it is what stops the mistake recurring.
  • R5 pull request or issue references remaining: none. Searched the record for # followed by digits, /pull/ and /issues/.
  • R6 ruled open questions still listing options: none. OQ1 is unanswered, untouched, and carries a recommendation rather than a list of options.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract. The modifiers named in X5 are Apple's own public SwiftUI API, given as examples of how a component merges its children; any equivalent that yields one element, read once, with the component's own words satisfies the requirement.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge — pass: "Knowledge templates and records are aligned."
  • pnpm check:repo — pass.
  • Prettier 3.9.3 over the changed file — no change.
  • git diff --check — clean.
  • pnpm vitest run --project node packages/native — 6 files, 34 tests, all pass.
  • Not run: swift build / swift test, and no captures. This change touches no Swift source and adds no view to capture.

…measure in their environment

X5 states the outcome, one element per thing a person reads, without the
mechanism that produces it. A text child inside a labelled ancestor keeps its
own element and publishes the ancestor's label instead of its own words, so the
same name is reachable twice and the child's words are never read. X5 now says
a component that reads as one thing merges its children explicitly, and the
acceptance checks assert it rather than leaving it to the audit.

A control's rendered size follows the environment its ancestor establishes, so
X7 measures a hit target in the environment the component ships in, at the
largest text size.
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 24, 2026 8:11am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 28, 2026, 1:05 AM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Preview availability: Storybook and Sandbox are not both ready on the exact-head Vercel preview.

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

46 of 496 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
RichTextEditor Markdown parity: side by side probe dark 94,866
RichTextEditor Markdown parity: side by side probe light 89,762
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232
ProgressBar With Target Mark probe dark 7,895
ProgressBar With Target Mark probe light 7,895
Markdown Default neutral dark 7,573
Markdown Default neutral light 7,566

and 26 more.


Generated by PR Enrichment workflow | View full report

@imdreamrunner
imdreamrunner marked this pull request as draft October 24, 2026 07:21

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner deciding the two draft requirements this pull request rewrites in the Swift accessibility contract: X5's explicit-merge rule with its new acceptance row, and X7's measuring condition with its amended row. An engineering owner records that decision by commenting /approve-spec <full head SHA> on the pull request, and the author may do so if they are an engineering owner. Separately from that decision, two wording fixes are needed, and because a new commit resets approval, making them first lets the decision land on the final head:

  • X7 and its acceptance row measure the 44-point target only at the largest text size, where text-scaled controls are biggest, so a target under 44 points at the default size can pass; measure at the default and the largest text sizes.
  • X5 names accessibilityElement(children: .combine) and .ignore with a label as the only ways to merge, which excludes equivalents such as X1's own accessibilityRepresentation; present them as examples, or add "or any equivalent that exposes one element named with its own words".

[Automated review]

Comment on lines +62 to +64
smaller. A control's rendered size follows the environment its ancestor
establishes, so a target is measured in the environment the component ships
in, at the largest text size, and not in isolation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This measures the 44-point target only at the largest text size, which is where text-scaled controls are biggest, so a control under 44 points at the default size in its shipping environment can pass (the fixture in the description measured a 34-point-tall control). Consider "measured in the environment the component ships in, at the default and the largest text sizes, and not in isolation", with the same wording in the X7 acceptance row.

Comment on lines +48 to +50
read. A component that reads as one thing merges its children explicitly,
with `accessibilityElement(children: .combine)` or with `.ignore` and a label
of its own, rather than resting on a container label. Every action reachable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As worded, these two modifiers read as the only permitted ways to merge, though the description says any equivalent satisfies the requirement, and X1 itself requires accessibilityRepresentation, which yields one element through neither. Consider "for example with accessibilityElement(children: .combine) or with .ignore and a label of its own, or with any equivalent that exposes one element named with its own words".

… examples

X7 and its acceptance row measure the 44-point target at the default and the
largest text sizes, not only the largest, where text-scaled controls are
biggest. X5 gives the merge modifiers as examples and admits any equivalent
that exposes one element named with the component's own words.
@imdreamrunner

imdreamrunner commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Both wording fixes from the review are in 1e7066d:

  • X7 measures at both text sizes. The requirement and its acceptance row now measure the 44-point target at the default and the largest text sizes. Measuring only at the largest size let a control that is under 44 points at the default size pass, since text-scaled controls are biggest there. The environment condition is unchanged: the target is still measured in the environment the component ships in, not in isolation.
  • X5 gives its merge modifiers as examples. accessibilityElement(children: .combine) and .ignore with a label of its own now read as examples alongside accessibilityRepresentation, and the requirement admits any equivalent that exposes one element named with the component's own words. The rule itself is unchanged: a component that reads as one thing merges its children explicitly rather than resting on a container label.

One file changed, packages/native/specs/swift/library/accessibility.md. pnpm check:knowledge, pnpm check:repo, pnpm vitest run --project node packages/native and Prettier over the changed file all pass; git diff --check is clean. The description is updated to match the new wording, and the writing rubric was re-run with no answer changed.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incremental review of 04bc7e5..1e7066d; prior findings carried: spec_authority, context, user_impact, mechanism, evidence_by_spec_claim, disposition

Both wording fixes are in: X7 and its acceptance row now measure the 44-point target at the default and the largest text sizes, and X5 gives its merge modifiers as examples beside any equivalent. Approval now waits only on an engineering owner deciding the two draft requirements this pull request rewrites, X5's explicit-merge rule with its new acceptance row and X7's measuring condition with its amended row, because no current spec states either. An engineering owner records that decision by commenting /approve-spec <full head SHA> on the pull request, and the author may do so if they are an engineering owner. Open pull requests #8288 and #8500 convert this same file into packages/native/specs/swift/accessibility.spec.md with the earlier X5 and X7 wording, so whichever lands second needs to carry the approved text.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — 1e7066d0 Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant