Skip to content

fix(SegmentedControl): segments keep their width when the selection changes - #8087

Closed
vjeux wants to merge 2 commits into
meta:mainfrom
vjeux:uikit/c05-segment-width
Closed

vjeux wants to merge 2 commits into
meta:mainfrom
vjeux:uikit/c05-segment-width

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

User impact

In a content-sized SegmentedControl (the default hug layout), each segment is as wide as its label at its current weight (semibold when selected, medium otherwise), so a selection can move the segments, by an amount that depends on the font:

  • No face at the selected weight (the browser synthesizes it): a visible shift. On Linux Chromium, selecting "Table" shrinks "Grid" from 52.78 to 50.45 px and moves "List" 2.3 px left (2.7 px at phone size).
  • Phone system fonts: a device pixel or two. iOS 26.2 (SF): ≤ 0.8 pt. Android 15, Chrome 124 (Roboto): ≤ 0.4 CSS px.

After this change no segment moves or resizes with any font.

Evidence

Before, main, Linux Chromium (no medium or semibold face, so the weight is synthesized): tapping Table, List and Grid moves the segments off the red lines that mark their edges at rest
After: the same taps; every segment stays on its lines

  • Phones (measured from recordings; nothing visibly moves): ≤ 0.8 pt (iOS 26.2) and ≤ 0.38 CSS px (Android 15, Chrome 124) on main, 0 here.
  • Reproduction: SegmentedWidth.a11y.chromium.spec.ts fails on main ("Grid" 52.78 → 50.45 px on desktop, 56.89 → 54.23 px on a phone).
  • After: every segment keeps its x and width to 0.01 px in both.
  • Unchanged: centered labels, truncation, icon-only segments, accessible names.
  • Regression test: that contract, in CI.

Problem and solution fit

  • Why it occurs: the label's box alone sizes a hug segment, and the weight changes the box.
  • Why it harms: where the font lacks the selected weight, the target moves after a tap, and a sliding selection would aim at moving boxes.
  • Why it matters: selection is state, not content; platform segmented controls keep their segments fixed.

Expected behavior and authority

  • component:SegmentedControl (draft) FR4: selected, unselected and disabled items keep the same anatomy, with selection reflected as state on segmented-control-item.
  • UISegmentedControl changes the selected title's weight, never a segment's size.

Smallest restoration

  • SegmentedControlItem: the label sits in a one-cell grid with an aria-hidden twin at the selected weight. The cell is always the selected width, and the column still shrinks, so a capped control still truncates.
  • Caller burden: each label now renders twice, so a test that finds a segment by text (getByText('Grid')) matches two nodes. The changeset says to find segments by role and name.
  • No API, token, target, color or spacing change. In hug, an unselected segment is now as wide as when selected (up to 3 px wider where the weight is synthesized); fill was already equal width.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm vitest run packages/core/src/SegmentedControl packages/core/src/RadioList/__tests__/RadioGroup.a11y.test.tsx: passes, including a check that the twin is hidden from assistive technology. The existing truncation tests now read the visible title.
  • pnpm exec playwright test packages/core/src/SegmentedControl/__tests__/SegmentedWidth.a11y.chromium.spec.ts --project chromium: desktop (1024 × 768, mouse) and phone (402 × 714 at 3×, touch). 2 passed; both fail on main.
  • Recordings on main and on this branch, each with red guide lines at the segment edges at rest:
    • Linux Chromium, Playwright at DPR 2, mouse clicks: the clips above.
    • iOS 26.2 simulator (iPhone 17 Pro), Mobile Safari, real taps.
    • Android 15 emulator, Chrome 124, OS-level taps.
  • pnpm -F @astryxdesign/core typecheck, pnpm check:changesets, eslint and prettier on touched files.

@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 10:16pm UTC

Request Review

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review 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 24, 2026, 10:48 PM UTC

@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 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 fix works as described: on this head no SegmentedControl segment moves or resizes when the selection changes, accessible names are unchanged, and the new width contract passes in CI. Approval waits on spec authority, because no current record covers segment geometry under selection (component:SegmentedControl is still a draft): an engineering owner needs to decide whether a selection should never move a segment, with every hug segment resting at its selected (semibold) width and so a few pixels wider than today. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Once it is settled, label the changeset to match ([feat] under AST-017 FR3 unless the decision names an existing claim this restores), and consider mentioning that each label now appears twice in the DOM, so text-based test queries such as getByText('List') match two nodes.

[Automated review]

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

48 of 502 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 28 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

…hanges

The selected label is semibold and an unselected one medium, so a segment
sized to its own label grew as it was selected and shrank as it was
deselected (2.3 px for "Grid" on desktop, 2.7 px on a phone), shifting its
neighbors. Each label now shares its grid cell with an aria-hidden twin at
the selected weight, so a segment is always as wide as its selected label;
the column can still shrink, so a capped control still truncates. A
real-engine contract checks every segment's box across selections.
…nders twice, and how wide the shift was per platform

@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 fix works as described: on this revision no SegmentedControl segment moves or resizes when the selection changes, accessible names are unchanged, and the new width contract passes in CI. Approval waits on spec authority, because no current record covers segment geometry under selection (component:SegmentedControl is still a draft): an engineering owner needs to decide whether a selection should never move a segment, with every hug segment resting at its selected (semibold) width and so slightly wider than today, and records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Separately, please add the base-against-head size figures that the component size rules now require for a style change (architecture:component-style-authoring INV6, spec:AST-080 FR8): the gzip size of astryx.css, SegmentedControl's gzip CSS delta, and the gzip JavaScript its import pulls in. Once the behavior is settled, tag the changeset to match ([feat] under spec:AST-017 FR3 unless the decision names an existing claim this restores).

[Automated review]

'@astryxdesign/core': patch
---

[fix] SegmentedControl segments keep their width when the selection changes. The selected label is heavier, so a segment sized to its own label grew as it was selected and shrank as it was deselected (about 2–3 px in a content-sized control), shifting its neighbors. Each label now reserves its selected width.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

spec:AST-017 FR3 tags a visual change [fix] when it restores current visual authority and [feat] when it intentionally changes visual direction. No current record covers segment geometry under selection, so this reads [feat] unless the owner decision names an existing claim this restores.

@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 change works as described: on this revision no SegmentedControl segment moves or resizes when the selection changes, and accessible names are unchanged. Approval waits on spec authority, because no current record covers segment geometry under selection (component:SegmentedControl is still a draft): an engineering owner needs to decide whether a selection should never move a segment, with every hug segment resting at its selected (semibold) width and so slightly wider than today, and records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Separately, please add the base-against-head size figures that the component size rules require for a style change (architecture:component-style-authoring INV6, spec:AST-080 FR8): the gzip size of astryx.css, SegmentedControl's gzip CSS delta, and the gzip JavaScript its import pulls in. Once the behavior is settled, tag the changeset to match ([feat] under spec:AST-017 FR3 unless the decision names an existing claim this restores).

[Automated review]

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Withdrawn: the width twin is a hidden measurement element, which #8300 rules out; without it the selected-weight shift stays ≤ 0.8 pt on iOS and ≤ 0.4 px on Android.

@vjeux vjeux closed this Oct 25, 2026
@vjeux
vjeux deleted the uikit/c05-segment-width branch October 25, 2026 00:33

This branch was successfully deployed

1 active deployment
Preview — 4efea35a 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant