Skip to content

spec(List): under a coarse pointer, rows offer a 44 px hit target (FR10) - #8571

Closed
vjeux wants to merge 4 commits into
meta:mainfrom
vjeux:uikit/c11-list-fr2-touch-rows
Closed

vjeux wants to merge 4 commits into
meta:mainfrom
vjeux:uikit/c11-list-fr2-touch-rows

Conversation

@vjeux

@vjeux vjeux commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Top note (current state; the original description follows unchanged). Decided (Cindy, 2026-10-26, D25 = c): no List row-height change. Under a coarse pointer only, every row offers a 44 px hit target (padding or an invisible hit area, no visual change), and a fine pointer keeps today's target. FR10, the FR2 note, the compatibility line, the padded row, and the FR10 verification row are reshaped to that. The 52/62 px minimums below are superseded.

Exact decision needed

List's FR2 keeps padded row geometry everywhere. Under a finger, may a default or spacious row be at least 52 px tall (62 px with a description), the iOS 26.2 table row heights, while compact rows and fine pointers keep theirs?

Existing authority searched

component:List (FR2 rules a touch row height out; FR7, variations, states, verification map) and spec:AST-094 FR13 (the touch query). No open pull request amends core's List.spec.md; component:swift/List is a separate record. #8085 implements this.

Decision

FR2 keeps padded geometry apart from a new FR10: under a coarse primary pointer, a default or spacious row is at least 52 px tall (62 px with a description), its content aligned as before; compact rows and fine pointers keep their geometry; a theme's min-height on list-item wins. The variations, the padded state and the verification map name FR10.

Rejected alternatives

  • A 44 px minimum: a hit-target floor, not the 52 pt table row.
  • Raising compact rows: dense lists keep their density (owner direction), above WCAG 2.5.8's 24 px.

Claim scope and non-goals

Touch row minimum heights only; not Dynamic Type, padding, dividers or markers.

Evidence and implementation relationship

iOS 26.2 UITableView rows read from the views: 52 pt, 62 pt with a subtitle. #8085 implements it under @media (pointer: coarse) with unit, stylesheet and browser proofs, after an engineering owner's spec approval of this record. Prettier re-pads the FR table for FR10; only FR2 and FR10 change text.

Writing rubric results

Re-run on the current record (FR2's carve-out, FR10, the allowed-variation bullet, the padded state, FR10's verification row):

  • R1 narration: none; each sentence states what rows do under a coarse or fine pointer.
  • R2 research: none; no measurement or survey is in the record.
  • R3 evidence naming a change: none; FR10's basis is the owner's dated decision.
  • R4 rejected alternatives: none in the record.
  • R5 references: none in the record.
  • R6 open questions: none.

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.
  • 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 (aligned), Prettier and git diff --check: clean.

List's FR2 keeps the `padded` treatment's row geometry everywhere. Under a
coarse primary pointer, a new FR10 lets a default or spacious ListItem row
reach the iOS 26.2 UITableView row heights: at least 52 px, and 62 px with a
description. Compact rows and fine pointers keep their geometry, and a
theme's `min-height` on the `list-item` target still wins. FR2, the allowed
variations, the `padded` representative state and the verification map name
the exception.
@vercel

vercel Bot commented Oct 26, 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 27, 2026 2:42am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 27, 2026, 3:26 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 26, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 26, 2026

@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.

This spec change needs an engineering owner's decision before it can be approved: FR10 and the narrowed FR2 change the current component:List row-geometry contract, including whether a theme min-height on list-item becomes a guaranteed override, and this head has no owner approval yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; because a new commit resets that approval, it is worth making the fixes below first.

  • Compatibility and migration (line 66) and the Contract at a glance row (line 48) still say omission keeps current ListItem geometry, so they should state that balanced and spacious rows grow under a coarse pointer and that a theme min-height on list-item keeps the released height.
  • The FR10 verification row (line 205) fills three of the map's five cells and needs its failure expectation and audit section.
  • FR10, the allowed-variation bullet and the verification row say "default" where List's density value is balanced (the default).

[Automated review]

Comment thread packages/core/src/List/List.spec.md Outdated
| ID | Invariant | Basis | Status |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| FR1 | List MUST own `edge` and apply one treatment to every ListItem row, interactive or not; ListItem MUST NOT expose a parallel public prop. A defined `edge` MUST win over `edgeCompensation`, and `edgeCompensation="inline"` with `edge` undefined MUST resolve to `outset`. | `component:List/DEC-1`, `component:List/DEC-3` | Accepted contract |
| FR2 | `padded` MUST preserve current header, list, and row geometry, apart from FR10's minimum row height under a finger. No treatment MAY move the header or change row block padding, gap, markers, start and end content, semantics, or hit target relative to the row box. | Owner direction | Accepted contract |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With this carve-out, a List that sets no props changes row height under a coarse pointer, but Compatibility and migration (line 66) still says "Released default preserved: yes; omission keeps current List and ListItem geometry", and the Contract at a glance row (line 48) says "omission keeps current geometry". Could both state that balanced and spacious rows grow to FR10's floor under a coarse pointer, and that a theme min-height on list-item keeps the released height?

Comment thread packages/core/src/List/List.spec.md Outdated
| FR7 | Each row's retained inset MUST come from the same private density- and theme-derived source as Item's applied inline padding. A theme `item.paddingInline` override takes precedence over every treatment's row padding, as it does over density. A consumer override of row padding through `xstyle`, `className`, or `style` is outside the alignment guarantee. | Shared two-owner geometry contract | Accepted contract |
| FR8 | `outset` and `bleed` are valid only under a host state whose published inline inset matches its applied padding: a visually neutral host, such as a Layout region with explicit padding or a padded ScrollableArea, or the content area of a Card, Section (any variant), Dialog, or BottomSheet. Those hosts bound their padding with a border, background, or clip; FR6 keeps row paint inside the padding edge, so it never covers the border or changes the host's outline. Automatic LayoutContent/LayoutPanel states with a known publication mismatch are not eligible until that gap is fixed, and Drawer's content area is not admitted until it has its own evidence. | `architecture:container-padding/INV8–INV11`; real-browser evidence in each admitted host | Accepted contract |
| FR9 | The list element containing the rows MUST clip in the inline axis with `overflow-inline: clip`, never `hidden`. Under `outset` and `bleed` its inline edges MUST reach the host's padding edge, so the clip never cuts resting row paint and a swiped row paints no further than that edge. | `spec:AST-057/FR9` | Verified by `List.test.tsx` and real-browser geometry |
| FR10 | Under a coarse primary pointer (`spec:AST-094` FR13), a ListItem row at the default or spacious density MUST be at least 52 px tall, and at least 62 px when it has a description; its content keeps its alignment in the taller row. A compact row keeps its geometry, and a fine pointer keeps every row's geometry. A theme's `min-height` on the `list-item` target takes precedence. | iOS 26.2 `UITableView` row heights: 52 pt, and 62 pt with a subtitle; owner direction that dense lists keep their density | Accepted contract |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

List's density values are compact, balanced and spacious, with balanced the default (ListContext.tsx, List.tsx). Naming balanced here, and in the allowed-variation bullet and the verification row, makes it explicit that a List passing density="balanced" is covered too.

Comment thread packages/core/src/List/List.spec.md Outdated
| ------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| FR1–FR7, FR9 | List, Item, and theme-registry tests | every treatment; all densities; static and interactive rows; header; deprecated alias alone and with `edge`; ListItem outside a List | Removing context propagation, using one host edge for both inline edges, zeroing padding without the shared inset source, or dropping the list's reach or clip fails | `audit:List/layout` |
| FR2–FR6, FR8–FR9, AR1–AR2 | Real-browser List geometry evidence | each treatment in a Card at two paddings, a Section, an inline Dialog with and without Layout, a BottomSheet, a padded ScrollableArea scrolling vertically and in both axes, a LayoutContent with explicit padding, and standalone; header and dividers; LTR/RTL; hovered and selected rows; a swiped row under `outset` and `bleed` | Header movement, content off the expected line, paint past the host's padding edge, clipped resting paint, or a changed row box fails | `audit:List/visual` |
| FR10 | List tests of the touch rule and the built stylesheet; real-browser row heights | default, spacious, and compact densities; a row with a description; a fine pointer; a theme `min-height` |

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 row fills three of the map's five cells. Could it state the failure expectation and audit section, for example "A balanced or spacious row under 52 px (62 px with a description) under a coarse pointer, a changed compact or fine-pointer row, or a theme min-height that loses, fails" and audit:List/layout?

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Thanks for measuring the iOS table rows. Cindy's decision on List rows on phones: no row-height change. Under a touch pointer only, a List row guarantees a 44 px hit target through padding or a hit area, with no visual change. Taller iOS-style row heights are being collected with other phone size differences, to be handled through mobile theme adaptations later, not in the List component. Could you rework this record PR to state the 44 px touch target instead of the 52/62 px minimums?

@cixzhang

Copy link
Copy Markdown
Contributor

/approve-spec d7ab924

@cixzhang cixzhang changed the title docs(spec): List rows under a finger reach the iOS table row heights (FR2, FR10) spec(List): under a coarse pointer, rows offer a 44 px hit target (FR10) Oct 27, 2026
@github-actions github-actions Bot added spec-auto-merge Auto-merge was enabled by the spec owner gate and removed needs:spec-owner-review Current knowledge records await owner approval labels Oct 27, 2026
@cixzhang

Copy link
Copy Markdown
Contributor

/approve-spec b5d035a

@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.

The direction of FR10 and the FR2 carve-out is approved for this head, but two record fixes are needed, and the fixed head will need a fresh /approve-spec <full head SHA> comment from an engineering owner (the author may record it as one).

  • The allowed-variation bullet (lines 132–133) promises every row a 44 px target, while FR10's room condition and half-gap clamp leave a 40 px default row on a phone at 42 px; cite FR10 with its condition or drop the bullet.
  • The FR10 verification row (line 205) still needs its failure expectation and audit section.
  • The writing-rubric results in the description describe the superseded 52/62 px record; please re-run them for this text.

[Automated review]

Comment thread packages/core/src/List/List.spec.md Outdated
Comment on lines +132 to +133
- Under a coarse primary pointer, every row's hit target is at least 44 px
tall (FR10); row geometry and appearance are unchanged.

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 restates FR10 without its condition: FR10 grants 44 px only where neighbours leave room and clamps the target to half a gap, so a 40 px default row on a phone, 2 px from the next row, gets 42 px, and a compact row with dividers keeps its box. Could it cite FR10 with the condition, for example "Under a coarse primary pointer, a row's hit target grows toward 44 px where its neighbors leave room (FR10); row geometry and appearance are unchanged", or be dropped?

Comment thread packages/core/src/List/List.spec.md Outdated
| ------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| FR1–FR7, FR9 | List, Item, and theme-registry tests | every treatment; all densities; static and interactive rows; header; deprecated alias alone and with `edge`; ListItem outside a List | Removing context propagation, using one host edge for both inline edges, zeroing padding without the shared inset source, or dropping the list's reach or clip fails | `audit:List/layout` |
| FR2–FR6, FR8–FR9, AR1–AR2 | Real-browser List geometry evidence | each treatment in a Card at two paddings, a Section, an inline Dialog with and without Layout, a BottomSheet, a padded ScrollableArea scrolling vertically and in both axes, a LayoutContent with explicit padding, and standalone; header and dividers; LTR/RTL; hovered and selected rows; a swiped row under `outset` and `bleed` | Header movement, content off the expected line, paint past the host's padding edge, clipped resting paint, or a changed row box fails | `audit:List/visual` |
| FR10 | List tests of the touch hit target and real-browser hit-testing | every density; a row with a description; compact rows packed closer than 44 px; a row with an end control; a coarse and a fine pointer; a theme row padding |

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 row still fills three of the map's five cells. Could it state the failure expectation and audit section, for example "A coarse-pointer row whose target stays at its box where its neighbors leave room, a target that reaches a neighboring row's or control's target, a changed row box, or a changed fine-pointer target fails" and audit:List/layout?

…rification row states failure and audit

The allowed-variation bullet promised every row a 44 px target, while FR10
grows a row's target toward 44 px only where its neighbors leave room and
clamps it to half a gap: it now says so and cites FR10. FR10's verification
row gains its failure expectation and its audit section
(`audit:List/layout`), like the map's other rows.
@github-actions github-actions Bot added needs:spec-owner-review Current knowledge records await owner approval and removed spec-auto-merge Auto-merge was enabled by the spec owner gate labels Oct 27, 2026

@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.

This spec change needs an engineering owner's decision on this exact head before it can be approved: FR10 and the FR2 carve-out are new component:List requirements, and the earlier /approve-spec comments name previous commits, so the latest commit reset that approval. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; because a new commit resets approval again, it is worth making these two fixes first:

  • Name FR10's coarse-pointer hit target in List's ownership boundary (the Owns list, and line 188, which still says List owns only the collection-level treatment and row placement) and in the Contract at a glance, including that it is specified and not implemented yet.
  • Re-run the writing rubric for this text and update ## Writing rubric results, which still describe the 52/62 px record; FR10's "A fine pointer keeps today's hit target" is the kind of day-relative wording R1 catches.

[Automated review]

| FR7 | Each row's retained inset MUST come from the same private density- and theme-derived source as Item's applied inline padding. A theme `item.paddingInline` override takes precedence over every treatment's row padding, as it does over density. A consumer override of row padding through `xstyle`, `className`, or `style` is outside the alignment guarantee. | Shared two-owner geometry contract | Accepted contract |
| FR8 | `outset` and `bleed` are valid only under a host state whose published inline inset matches its applied padding: a visually neutral host, such as a Layout region with explicit padding or a padded ScrollableArea, or the content area of a Card, Section (any variant), Dialog, or BottomSheet. Those hosts bound their padding with a border, background, or clip; FR6 keeps row paint inside the padding edge, so it never covers the border or changes the host's outline. Automatic LayoutContent/LayoutPanel states with a known publication mismatch are not eligible until that gap is fixed, and Drawer's content area is not admitted until it has its own evidence. | `architecture:container-padding/INV8–INV11`; real-browser evidence in each admitted host | Accepted contract |
| FR9 | The list element containing the rows MUST clip in the inline axis with `overflow-inline: clip`, never `hidden`. Under `outset` and `bleed` its inline edges MUST reach the host's padding edge, so the clip never cuts resting row paint and a swiped row paints no further than that edge. | `spec:AST-057/FR9` | Verified by `List.test.tsx` and real-browser geometry |
| FR10 | Under a coarse primary pointer (`spec:AST-094` FR13), every ListItem row MUST offer a hit target at least 44 px tall, through its padding or an invisible hit area, with no visual change, where its neighbors leave room: the hit area never overlaps a neighboring row's or control's target, and between close neighbors it is clamped to half the gap; row height and geometry stay as they are at every density. A fine pointer keeps today's hit target. | Owner decision, 2026-10-26: no row-height change, a 44 px touch target | Accepted contract |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR10 adds a row hit-target rule, but the record's ownership boundary still says List owns only "the collection-level treatment and row placement" (line 188, and the Owns list), and the Contract at a glance never mentions FR10 or that it is specified and not implemented yet. Naming the target in both places keeps FR10 inside the boundary that architecture:knowledge-contracts INV14 scopes authority to. Separately, "A fine pointer keeps today's hit target" is day-relative; "A fine pointer's hit target stays the row box" says the same thing durably.

@cixzhang

Copy link
Copy Markdown
Contributor

/approve-spec 55b54e5

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 27, 2026
@vjeux

vjeux commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

FR10 landed on main with #8687 (02c6cf3), so nothing remains here.

@vjeux

vjeux commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Closing: this record's FR10 text is already on main, word for word, from #8687 (merged Oct 27), so merging main leaves this pull request with no changes.

@vjeux vjeux closed this Oct 28, 2026

This branch was successfully deployed

1 active deployment
Preview — 55b54e51 Deployed Oct 27, 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. spec-auto-merge Auto-merge was enabled by the spec owner gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants