Skip to content

fix(Badge): a badge grows with its text - #8236

Merged
cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c27-badge-grows
Oct 31, 2026
Merged

cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c27-badge-grows

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

User impact

A Badge holds a fixed height (--spacing-5, 20px in the neutral theme, or a theme's own, such as Butter's 30px). When its label's text grows with a larger text size, the label outgrows the pill: it spills out, and white text on a colored badge leaves the color behind and becomes unreadable.

Problem and solution fit

  • Why it occurs: the badge's height is fixed, while its label's line follows the text size.
  • Why it harms the task: at larger text sizes a badge's status or count is clipped or unreadable.
  • Why that matters: the readers who need larger text are the ones who lose the information.

A badge with a label is now never shorter than one line of that label (min-height: 1lh). The minimum doesn't bind at the default size, so nothing moves there; at larger sizes the pill grows with its label.

Expected behavior and authority

component:Badge FR3: a badge is one line, and its label never escapes its container. WCAG 1.4.4 (Resize Text): text can grow without losing content.

Smallest restoration

  • min-height: 1lh on a badge with a label, while main's height stays: a theme's height (Butter's 30px included) holds at the default size.
  • A color-only badge has no text to hold, so it keeps its size at every text size (component:Badge FR1).
  • No width floor: a badge still shrinks and stays inside a crowded row or a cell narrower than one line. Earlier versions of this change with a minimum width broke that and changed color-only badges' size, so it went.

Evidence

  • Reproduction before the change: in the Badge "Larger Text" story, where the supporting text size is raised on a wrapper, labels spill out of their pills.
  • Result after the change: each badge with a label grows to hold it, and color-only badges keep their size.
  • Representative unchanged path: at the default text size every badge's geometry is exactly main's, under the neutral theme and under Butter.
  • Regression test or other mutation-sensitive proof: BadgeLargerText.a11y.chromium.spec.ts fails 3 of its 10 cases with main's Badge.tsx (the badge growing with its label at 1.25rem and 2.5rem text, under the neutral theme and Butter) and passes 10 of 10 with this change.

iOS simulator, the Larger Text story, main's Badge above this pull request:

Neutral badges, main above this pull request Butter badges, main above this pull request

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

Local gates at a774d6f (on main 9ee7457):

@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 30, 2026 11:17pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 30, 2026, 11:32 PM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@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

Modified Components

Badge (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 664B 664B 0B
Lines of Code 217 226 -
Complexity Low Medium (7) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/Badge 3.9KB 3.9KB +40B 3.8KB 3.9KB +16B
core/ChatComposer 62.3KB 62.3KB +44B 11.0KB 11.0KB +13B
core/ChatComposerDrawer 24.3KB 24.3KB +45B 4.6KB 4.6KB +13B
core/ChatComposerInput 57.1KB 57.1KB +37B 10.2KB 10.2KB +13B
core/ChatComposerTokenElement 57.1KB 57.1KB +37B 10.2KB 10.2KB +13B
core/ChatTokenizedText 6.9KB 7.0KB +38B 3.9KB 3.9KB +12B
core/ChatToolCalls 32.5KB 32.5KB +43B 9.1KB 9.1KB +13B
core/MultiSelector 74.3KB 74.4KB +49B 14.8KB 14.9KB +14B
lab/CommentComposer N/A 95.9KB N/A N/A 1.9KB N/A
lab/CommentThread N/A 218.4KB N/A N/A 2.4KB N/A
lab/Comments N/A 223.0KB N/A N/A 2.5KB N/A

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

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
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 35.6KB 35.6KB +10B

Accessibility Audit

Status: The accessibility audit did not run on this pull request. It runs after merges to main and before each release; pull requests that touch components run it with the ci:heavy-lanes label.


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

@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 Badge fix itself works: with 20px and 40px supporting text, badges now grow to 33.3px and 66.7px and keep their label inside, and default-size badges are pixel-identical across all seven first-party themes. Approval waits on an engineering owner's spec decision: this diff also carries its dependency stack, so an owner has to approve or reject the draft AST-094 requirements (FR1–FR23, DEC-1, DEC-2) and provide or identify the spec that governs the new typography.dynamicType theme option and <html data-astryx-text-size>, recording it by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner). Independently of that decision:

  • Land the stack through its own pull request first (or retarget this one onto it), so this diff is the 168-line Badge commit.
  • Update the branch from main: pr-a11y currently stops at main's new WheelPinchZoom contract, whose spec file this branch lacks, before it reaches the new Badge contract.
  • Remove, or land, the files that packages/core/src/motion/MEASUREMENTS.md and the uikit headers point to (uikit/switch.ts, uikit/contextMenu.ts, decay.ts and others).

[Automated review]

where a switch outside any scroll view is needed.

Done (iOS 26.2 simulator, Oct 23 2026; in the switch's own table,
`uikit/switch.ts`): M-SW02, M-SW03, M-SW04, M-SW05, M-SW06, M-SW09 (UISwitch

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

uikit/switch.ts is not in the tree at this head (nor is uikit/contextMenu.ts, line 254), so the switch rows marked done point at measured values nobody can find. Drop the reference or land the file with the table.

@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 Badge fix works: with 20px and 40px supporting text, badges now grow to 33.3px and 66.7px and keep their label inside, default-size badges are unchanged in all eight Storybook themes on desktop and phone, and the new Chromium contract passes in CI. Approval waits on an engineering owner's spec decision, because this diff also carries its dependency stack: an owner has to approve or reject the draft AST-094 requirements (FR1–FR23, DEC-1, DEC-2) and provide or identify the spec that governs the new typography.dynamicType theme option and <html data-astryx-text-size>, including whether the reader's text size may take the phone base below the 16px that design:mobile-typography DR1 sets, recording it by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner). Independently of that decision:

  • Land the stack through #8053 and #8230 first, or retarget this pull request onto #8230, so this diff is the 255-line Badge change.
  • Correct or land what the stack points at but does not contain: uikit/switch.ts, uikit/contextMenu.ts, decay.ts, rubberBand.ts, projection.ts, sources.test.ts, and the spec:AST-090 that AST-094 FR7 amends.

[Automated review]

follows.
- **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the
exit its UIKit counterpart plays. **Proposed; pending owner review:** this
departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR7 departs from “the draft spec:AST-090 FR1 … and FR10”, but no AST-090 record exists on main or at this head, and the Current-state impact repeats it (line 235). Cite the record that holds those exit and reduced-motion rules, or drop the amendment.

* the rubber-band coefficient, the bounce-back spring, paging and the
* content-touch delay
* @position The scroll domain of the UIKit constants table (motion/uikit);
* read by decay.ts, rubberBand.ts, projection.ts and components

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

decay.ts, rubberBand.ts and projection.ts are not in the tree at this head, and neither is sources.test.ts (line 14; the enforcing test is uikit.test.ts). Point at what exists, or land those files with the table.

@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 Badge fix works where it matters: with 1.25rem and 2.5rem supporting text the pill grows to 33.3px and 66.7px and keeps its label inside, default-size badges are pixel-identical, and the new Chromium contract passes in CI. Approval waits on an engineering owner's spec decision, because this diff also carries its dependency stack: an owner has to approve or reject the draft AST-094 requirements (FR1–FR23, DEC-1, DEC-2) and provide or identify the spec that governs the new typography.dynamicType theme option and <html data-astryx-text-size>, including whether the reader's text size may take the phone base below the 16px that design:mobile-typography DR1 sets, recording it by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner). Independently of that decision:

  • min-width: 1lh lets a badge escape a container narrower than one line (10.7px past a 56px cell at 2.5rem text), against component:Badge FR3; min-width: min(1lh, 100%) keeps it inside.
  • component:Badge FR1 still records a fixed --spacing-5 block size, so amend it with the one-line minimum and list the larger-text contract in its verification.
  • Land the stack through #8053 and #8230 first, or retarget onto #8230, so this diff is the 278-line Badge change, and correct the stack's references to files and to an AST-090 record that do not exist.

[Automated review]

Comment thread packages/core/src/Badge/Badge.tsx Outdated
// label at least that wide.
maxWidth: '100%',
minWidth: 0,
minWidth: '1lh',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

min-width wins over max-width: 100%, so a badge can no longer shrink below one line of its own text: at 2.5rem supporting text in a 56px cell every badge is 66.7px wide and ends 10.7px past the cell, where component:Badge FR3 keeps the root capped at its container. min-width: min(1lh, 100%) keeps the badges inside the cell and leaves roomy rows unchanged (the count is still a circle); a narrow-container row in the larger-text contract would hold it.

@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 Badge fix works where it matters: at 1.25rem and 2.5rem supporting text the pill grows to 33.3px and 66.7px and keeps its label inside, labeled badges are unchanged at the default size, and the new larger-text contract passes in CI. Approval waits on an engineering owner's spec decision: component:Badge FR1 still fixes the block size at --spacing-5, which this change intentionally exceeds, so an owner has to update FR1, and has to approve or reject the draft AST-094 requirements (FR1–FR23, DEC-1, DEC-2) and provide or identify the spec that governs the new typography.dynamicType option and <html data-astryx-text-size>, recording it by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner). Independently of that decision:

  • min-width: min(1lh, 100%) still lets a badge leave a crowded flex row (44.7px past a 260px row at 2.5rem text), against component:Badge FR3; keeping min-width: 0 lets min-height: 1lh alone fix the spill.
  • The same minimum widens empty-label badges from 16px to 20px at the default size, which the changeset says does not happen.
  • Land the stack through #8053 and #8230 first, or retarget onto #8230, so this diff is the 325-line Badge change, and correct the stack's references to files and to an AST-090 record that do not exist.

[Automated review]

Comment thread packages/core/src/Badge/Badge.tsx Outdated
// one line of its text must still stay inside it and truncate.
maxWidth: '100%',
minWidth: 0,
minWidth: 'min(1lh, 100%)',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

100% here is the containing block's width, which in a flex row is the whole row, not the space the row's other items leave. At 2.5rem text beside a 230px label that cannot shrink in a 260px row, the count stays 66.7px wide and ends 44.7px past the row, where min-width: 0 keeps it inside, so component:Badge FR3 (“may shrink below its content size”, “The badge stays within its container”) no longer holds there. Keeping min-width: 0 lets min-height: 1lh alone fix the spill; a crowded-row case in the larger-text contract would hold it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed and fixed in be1de76. Measured in Chromium at 402 × 714 with neutral: at 2.5rem text beside a 230px title in a 260px row, min-width: min(1lh, 100%) kept the count 66.7px wide, 44.7px past the row; with min-width: 0 it shrinks to 22px and stays inside. Any width floor that keeps a count circular breaks FR3 the same way in a crowded row, so min-width is back to main's 0 and only min-height: 1lh remains, applied when the badge has a label. The Larger Text story now has a crowded row (a 200px title that cannot shrink beside a count in a 260px row) and a 52px cell, and the larger-text contract asserts the count stays inside both; against the previous head those cases fail (14.7px past at 2.5rem).

Comment thread .changeset/badge-grows-with-text.md Outdated
'@astryxdesign/core': patch
---

[fix] Badge: a badge is now never shorter than one line of its own text and never narrower than it is tall. With a theme that follows the reader's text size on iOS (`typography.dynamicType`), a badge's label grew at the larger and accessibility sizes while the badge kept its fixed height, so the label spilled out of the pill and white text on a colored badge became unreadable. The badge now grows with its label, and a one-character count stays at least as wide as it is tall instead of turning into a tall capsule, while a badge in a container narrower than that still stays inside it. At the default size nothing changes, and a theme that sets the badge's height keeps that height there while its badges still grow with larger text.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

“At the default size nothing changes” does not hold for an empty-label badge: under neutral min-width: min(1lh, 100%) widens it from 16 × 20 to 20 × 20. Either keep min-width: 0 or say so here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed and fixed in be1de76: with min-width back to 0, an empty-label badge is 16 × 20 again at the default size (24 × 30 under Butter). min-height: 1lh now applies only when the badge has a label, so a color-only badge also keeps that size at 1.25rem and 2.5rem instead of growing into a 16 × 67 sliver. Each row of the Larger Text story ends with a color-only badge, the larger-text contract checks its height and width at every size (20 × 20 against 16 × 20 fails it), and the changeset now says a color-only badge keeps its size.

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows to 33.3px and 66.7px and keeps its label inside, while default-size, color-only and narrow-container badges are unchanged. Approval waits on an engineering owner's spec decision, recorded by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner): component:Badge FR1 still fixes the block size at --spacing-5, so an owner has to update it for this intentional change, and has to approve or reject the draft AST-094 requirements (FR1–FR23, DEC-1, DEC-2) and provide or identify the spec for the stack's typography.dynamicType, theme motion roles and the two experimental subpaths. Independently of that decision:

  • Land the stack through #8076 and #8230 (and the pull requests they merge) first, or retarget onto #8230, so this diff is the 386-line Badge change instead of 31,379 authored lines.
  • Drop the platform adaptation condition or add it to spec:AST-012 FR2, whose vocabulary is closed.
  • Reconcile the theme motion roles with AST-094 FR4 and DEC-1, which reject themed touch physics, and fix FR7's reference to a spec:AST-090 that does not exist.

[Automated review]

* is everything else. Pair `ios` with `pointer: 'coarse'` for a touch
* profile, such as touch motion tuned per platform.
*/
platform?: 'ios' | 'other';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

platform extends the adaptation-rule vocabulary that spec:AST-012 FR2 closes (“Future environment fields require explicit additions to this closed vocabulary”), and no record adds it. Drop it from this stack, or add it to FR2 in an owner-approved record change.

rest point by projecting its velocity the way its UIKit counterpart does:
with a deceleration rate, or with a measured projection time.
- **FR4 — Physics are not themable in touch presentations.** A touch
presentation MUST read its motion from the table on every theme. Themes keep

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR4 says a touch presentation reads its motion from the table on every theme, and DEC-1 rejects tuning touch motion per theme, but this diff lets defineTheme({motion: {springs, durations, curves}}) retune those roles and the animator plays the theme's value (packages/core/src/motion/animator.ts:119–:125). One of the two has to change before the record is approved.

@vjeux
vjeux force-pushed the uikit/c27-badge-grows branch from f14b903 to 2d8bdbe Compare October 25, 2026 09:29
@github-actions github-actions Bot removed the needs:design-review Affects visuals — Design should review label Oct 25, 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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px and keeps its label inside, while default-size, colour-only and narrow-container badges are unchanged, and the new larger-text contract fails its enlarged-text cases without the fix. Approval waits on an engineering owner's spec decision, recorded by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner): component:Badge FR1 still fixes the block size at --spacing-5, so it needs updating for this intentional change, and #8230's typography.dynamicType with <html data-astryx-text-size> needs a governing spec provided or identified, including whether the reader's text size may take the phone base below the 16px that design:mobile-typography DR1 sets. Landing #8230 through its own review first would leave this pull request with only the Badge change. Independently of that decision:

  • typography.dynamicType never scales the literal --text-supporting-size: 12px that Butter, Stone and Y2K set, so under those themes supporting text and badge labels stay 12px at every reader size; scale literal --text-*-size overrides too, or move those themes onto the scale and document the limit.

[Automated review]

Comment thread packages/core/src/theme/dynamicType.ts Outdated
* never goes below the step's own size, so the theme's sizes at Large stay
* exactly its own.
*/
export function scaledFontSizeEntries(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only the twelve raw --font-size-* steps are rescaled here, so a semantic size that a theme sets as a literal never follows the reader. Butter, Stone and Y2K set '--text-supporting-size': '12px', so a theme extending them with typography: {dynamicType: true} keeps supporting text and every Badge label at 12px at every text size while body text grows about threefold at AX5, against the docs' “every font size follows the reader's choice”. Scaling a literal --text-*-size override on the curve of the step its core default reads (--text-supporting-size reads --font-size-sm, Footnote), or moving those themes onto the scale and documenting the limit, would close it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in #8230 (merged into this branch): a semantic size a theme fixes at a length now scales on the curve of the step its core default reads, so '--text-supporting-size': '12px' scales like Footnote; a size that reads a variable, em or % follows its source and is never scaled twice. The Dynamic Type CSS comes after the theme's own rules, so each adaptation rule's block re-declares the sizes it sets through a variable once an earlier block scaled them, and Butter and Stone's phone rule keeps its step on phones.

Measured on the iOS 26.2 simulator, Core/Themes/Dynamic Type › Fixed Supporting Size (Y2K with dynamicType): at AX5, supporting text is 40.6px (12 × 44/13) beside 49.9px body, where it stayed 12px before; the badges on that page go from 30px tall with 12px labels at Large to 62px tall with 40.6px labels at AX5. Three tests that failed before cover the root, each adaptation rule's block, and sizes that read a variable or their parent.

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px and keeps its label inside, default-size and color-only badges are unchanged, and #8230 now scales the 12px supporting size that Butter, Stone and Y2K fix. Approval waits on an engineering owner's spec decision, recorded by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner): component:Badge FR1 still fixes the block size at --spacing-5, so it needs updating for this intentional change, and #8230's typography.dynamicType with <html data-astryx-text-size> needs a governing spec provided or identified, including whether the reader's text size may take the phone base below the 16px that design:mobile-typography DR1 sets. Landing #8230 through its own review first would leave this pull request with only the Badge change.

[Automated review]

@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 Badge fix works and the Dynamic Type stack follows spec:AST-102, which is now current: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px with its label inside, and default-size and color-only badges are unchanged. Three changes are needed before approval:

  • Keep !important outside the scaling expression: '--text-supporting-size': '12px !important' now compiles to calc(12px !important * …), which browsers reject, so that text loses the theme's size everywhere (packages/core/src/theme/dynamicType.ts lines 221 and 286).
  • Remove typography.dynamicType from the Badge changeset, since that option does not exist.
  • Add a macOS Safari check to the description showing the theme's own sizes there, as spec:AST-102 FR3's verification asks.

[Automated review]

Comment thread .changeset/badge-grows-with-text.md Outdated
'@astryxdesign/core': patch
---

[fix] Badge: a badge with a label is now never shorter than one line of that label. With a theme that follows the reader's text size (`typography.dynamicType`), a badge's label grew at the larger and accessibility sizes while the badge kept its fixed height, so the label spilled out of the pill and white text on a colored badge became unreadable. The badge now grows with its label. At the default size nothing changes, a theme that sets the badge's height keeps that height there while its badges still grow with larger text, a color-only badge keeps its size at every text size, and a badge still shrinks and truncates in a container too narrow for it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No typography.dynamicType option exists at this head: the theme tests assert defineTheme({typography: {dynamicType: true}}) is a type error, and spec:AST-102 FR3 rules one out. Something like “When text follows the reader's text size, as every theme does on iPhone and iPad” keeps the point without sending readers to an API they cannot use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reworded in 87946a5: "When text follows the reader's text size, as every theme does on iPhone and iPad, a badge's label grew…". It names no option.

Comment thread packages/core/src/theme/dynamicType.ts Outdated
.filter(name => tokens[name] !== undefined)
.map(name => {
const value = tokens[name];
const scaled = `calc(${value} * var(${dynamicTypeScaleProperty(FONT_SIZE_STEP_STYLES[name])}, 1))`;

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 wraps the whole authored value, so a step written as '1rem !important' becomes min(calc(1rem !important * var(--_dynamic-type-body, 1)), max(var(--font-size-lg), 1rem !important)), and readerTextSizeTokens does the same to '12px !important' (line 286). !important inside calc() makes the value invalid where it is used, so the text falls back to its parent's size (20px instead of 12px in Chromium) in every browser, while the base emits these tokens as written. Splitting off a trailing !important before scaling and appending it after the expression keeps both the size and the importance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at origin in #8230 (082e89e), which this branch now merges. The scaling splits off a trailing !important, scales and order-checks the bare value, and puts the importance back after the whole expression, for raw steps and for fixed sizes alike: '12px !important' now emits calc(12px * var(--_dynamic-type-footnote, 1)) !important. A test that failed before covers both paths, plus the order check across an important step. In Chromium 149, that theme inside a 20px parent now computes supporting text at 12px and the step at 16px; the previous head gave 20px for both.

Comment thread packages/core/src/theme/dynamicType.ts Outdated
* `font: -apple-system-body`, as every browser on iPhone and iPad does. There
* is no platform, touch-callout or pointer check, since touch behavior is one
* on every touch device and this is a style the engine itself provides.
* macOS Safari knows the keyword too, but its Body is a fixed 13px that names

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 is the only evidence for macOS Safari, and the jsdom test fakes the 13px. spec:AST-102 FR3's verification lists macOS Safari as a representative state, so a measurement of the probe and the Dynamic Type story in macOS Safari would back it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not measured: no Mac was available to the test rig, so both descriptions now say "not measured on macOS Safari" instead of claiming it. The no-category path (Safari's fixed 13px Body names no category, so every scale stays 1) is covered only by the jsdom test, which fakes that size. A real macOS Safari measurement is still owed.

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px with its label inside, and default-size and colour-only badges are unchanged, but three changes are needed before approval:

  • Land #8230 first and then update this branch from main, so this pull request carries only the 371-line Badge change instead of 2,008 lines; updating also clears the pr-interaction failure.
  • In #8230, scale only fixed-length font-size steps, because a theme step written as var(--font-size-base) now forms a reference cycle and that text loses the theme's size in every browser (packages/core/src/theme/dynamicType.ts lines 218–233).
  • Add a macOS Safari measurement of the text-size probe and the Dynamic Type story, which spec:AST-102 FR3's verification lists.

[Automated review]

Comment thread packages/core/src/theme/dynamicType.ts Outdated
Comment on lines +218 to +233
.filter(name => tokens[name] !== undefined)
.map(name => {
const [value, important] = splitImportant(tokens[name]);
const scaled = `calc(${value} * var(${dynamicTypeScaleProperty(FONT_SIZE_STEP_STYLES[name])}, 1))`;
const next = steps[steps.indexOf(name) + 1];
if (next === undefined) {
return [name, scaled + important];
}
const own = lengthInPx(value);
const nextOwn = lengthInPx(splitImportant(tokens[next] ?? '')[0]);
const inOrder = own !== null && nextOwn !== null && own <= nextOwn;
return [
name,
(inOrder
? `min(${scaled}, var(${next}))`
: `min(${scaled}, max(var(${next}), ${value}))`) + important,

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 scales and caps every step a theme writes, whatever its value. A theme with a type scale that sets '--font-size-lg': 'var(--font-size-base)' now gets --font-size-base: min(…, max(var(--font-size-lg), 0.875rem)) beside --font-size-lg: min(calc(var(--font-size-base) * …), max(var(--font-size-xl), var(--font-size-base))), a reference cycle: in Chromium both compute to nothing and the text falls back to its parent's 16px instead of 14px, with no reader size set. Scaling only fixed-length steps (as readerTextSizeTokens does with isFixedSize) and capping by the next step only when its value is a fixed length would keep such themes on their own sizes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at origin in #8230 (d7d6ba5), which this branch now merges. Each step a block writes at a fixed length keeps its scaled size in a private --_scaled-font-size-* property, and the step reads it. Caps chain only between those private properties, which only the scaling writes, so no cap can read back a variable a theme wrote, in any block. That also closes a cross-block case the narrower rule would leave open: a root ramp plus an adaptation rule that writes --font-size-lg: var(--font-size-base). A step written as a variable is emitted as written and follows what it reads, so nothing scales twice. Tests that failed before cover the alias at the root, the alias in a rule over a root ramp, and an acyclic check over every step aliased at the root and in a rule. In Chromium 149, your 14/1.2 theme in a 16px parent with no reader size now computes body and large text at 14px and small text at 12px; all three were 16px before. The rule case matches.

Comment thread packages/core/src/theme/dynamicType.ts Outdated
* `font: -apple-system-body`, as every browser on iPhone and iPad does. There
* is no platform, touch-callout or pointer check, since touch behavior is one
* on every touch device and this is a style the engine itself provides.
* macOS Safari knows the keyword too, but its Body is a fixed 13px that names

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 is the only statement of what happens in macOS Safari, and the jsdom test behind it fakes the 13px Body. spec:AST-102 FR3's verification lists macOS Safari as a representative state, so a measurement there of the probe's computed size and of the Dynamic Type story would back it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as before: not measured, because no Mac was available to the test rig. Both descriptions say "not measured on macOS Safari" instead of claiming it, and the jsdom test, which fakes the 13px Body, is the only coverage of that path. The measurement is still owed.

Comment on lines +9 to +11
* the theme's own size at Large, a theme emits the scaled sizes only when it
* opts in (root and adaptation rules, behind the capability alone), and the root sync
* writes and clears `<html data-astryx-text-size>`

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 header still describes an opt-in and a root sync that writes <html data-astryx-text-size>; the tests below assert that neither exists. Rewording it to the no-option, no-attribute behaviour keeps it accurate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reworded in d7d6ba5. The header now describes the current behaviour: every theme writes its sizes that way at its root and in its adaptation rules, with no option and no reference cycle, and the root sync reads a hidden probe and adopts a stylesheet, writing nothing on <html>.

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

On landing #8230 first: keeping this branch on #8230 until it merges is what lets the Badge fix be tested at the text sizes it fixes. Once a maintainer merges #8230, this branch updates from main, which brings it down to the Badge change alone and clears the pr-interaction failure (the interaction harness isn't on this branch yet).

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px with its label inside, and default-size and colour-only badges are unchanged in every theme, but two changes are needed before approval:

  • Land #8230 first and then update this branch from main, so this pull request carries only the 371-line Badge change instead of 2,297 authored lines.
  • Measure the text-size probe and the Dynamic Type story in macOS Safari (in #8230 if it lands first), because spec:AST-102 FR3's verification and spec:AST-013 FR10 call for that evidence behind the code's macOS claim.

[Automated review]

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px with its label inside, and default-size and colour-only badges are unchanged in every theme, but three changes are needed before approval:

  • Land #8230 first and then update this branch from main, so this pull request carries only the 371-line Badge change instead of 2,310 authored lines.
  • Measure the text-size probe and the Dynamic Type story in macOS Safari (in #8230 if it lands first), because spec:AST-102 FR3's verification and spec:AST-013 FR10 call for that evidence behind the code's macOS claim.
  • Scale the font sizes a theme sets in onDark and onLight as well: generateOnMediaCSS writes them without readerTextSizeEntries, so text on a MediaTheme surface ignores the reader's text size.

[Automated review]

// gives the page its scales (see dynamicType.ts); elsewhere each scale is
// 1, so the values are the theme's own.
joinDeclarations(
readerTextSizeEntries(tokenEntries),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only tokenBlock routes through readerTextSizeEntries; generateOnMediaCSS writes a theme's onDark and onLight tokens straight through joinDeclarations (:1697–:1707), so onDark: {tokens: {'--font-size-base': '15px'}} compiles to [data-astryx-media="dark"] { … --font-size-base: 15px; } and text on that MediaTheme surface ignores the reader's text size (spec:AST-102 FR3). Passing the on-media entries through readerTextSizeEntries as well, with an onDark case in dynamicType.test.ts, would close it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at origin in #8230 (82390ce), which this branch now merges. A media surface's token entries now go through readerTextSizeEntries, as the root and adaptation blocks do: onDark: {tokens: {'--font-size-base': '15px'}} compiles to the scaled private size plus --font-size-base: var(--_scaled-font-size-base) on [data-astryx-media="dark"], and a size fixed in onLight scales on its step's curve. A test that failed before covers both.

@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 Badge fix works: at 1.25rem and 2.5rem supporting text a labeled badge grows from 20px to 33.3px and 66.7px with its label inside, while default-size and colour-only badges are unchanged under neutral and Butter. Two changes are needed before approval:

  • Land #8230 first and then update this branch from main, so this pull request carries only the 371-line Badge change instead of 2,345 authored lines.
  • Measure the text-size probe and the Dynamic Type story in macOS Safari (in #8230 if it lands first), because spec:AST-102 FR3's verification and spec:AST-013 FR10 call for that evidence behind the code's macOS claim.

[Automated review]

@vjeux

vjeux commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

We can't capture on macOS, so there are no macOS Safari screenshots. What this PR has: iOS 26.2 Safari on the simulator (the Dynamic Type story at Large, AX3 and AX5, and the Fixed Supporting Size story) and Chromium (the Larger Text story at the default size, 1.25rem and 2.5rem, under neutral and Butter). On macOS, #8230's fixed 13px body size is covered only by a jsdom test. Whether that's enough is up to you.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:34
A badge held a fixed height, the theme's --spacing-5, so when its
label's text grew (a larger text size) the label spilled out of the
pill and white text on a colored badge left the color behind. A badge
with a label keeps main's height and gains min-height: 1lh, so the
theme's height (Butter's 30px included) holds at the default size and
the badge grows past it when the text needs more room. A color-only
badge has no text and keeps its size at every text size, and there is
no width floor: a badge still shrinks and stays inside a container
narrower than one line (component:Badge FR3).

@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 Badge fix is ready from a review standpoint: a labeled badge now grows with its text (20px to 33.3px and 66.7px at 1.25rem and 2.5rem supporting text under neutral), every default-size badge is unchanged across the themes, and the new Chromium contract fails its grow cases without the fix. The change matches component:Badge FR1 and spec:AST-102 FR4 as they stand on main, so no spec decision is needed. Next step: mark the pull request ready for review so CI runs on this commit.

[Automated review]

@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Oct 31, 2026
@cixzhang
cixzhang marked this pull request as ready for review October 31, 2026 02:25
@cixzhang
cixzhang merged commit 04e0e52 into meta:main Oct 31, 2026
139 of 143 checks passed
@vjeux
vjeux deleted the uikit/c27-badge-grows branch October 31, 2026 03:51

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) 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.

2 participants