Skip to content

fix(Button): treat a string label like a Text child - #3053

Merged
mfal merged 8 commits into
mainfrom
claude/button-text-icon-link-spacing-b2b459
Sep 4, 2026
Merged

fix(Button): treat a string label like a Text child#3053
mfal merged 8 commits into
mainfrom
claude/button-text-icon-link-spacing-b2b459

Conversation

@Lisa18289

@Lisa18289 Lisa18289 commented Sep 1, 2026

Copy link
Copy Markdown
Member

A Button with an icon and a label lost the spacing between them inside a Link. Digging into that surfaced the same root cause one level down, so this fixes both.

Link wrapped all Button children in a single Text, collapsing icon and label into one flex item — the button's column-gap had nothing to apply to.

Button only wrapped children in Text when they were a lone string, and :has(.text) was the only signal that a button contains text. So a raw string next to an icon matched nothing: <Button><Icon />Label</Button> got the icon-only padding (8px instead of 16px), and a plain pending button hid its label. Which branch a button took depended on how the label happened to be written — Label and {x} Label behaved differently.

The button now carries the marker itself, so a string is detected wherever it sits, including through the fragment Link passes down. That drops the Text wrapper from both components; .content keeps the two properties it contributed (text-align, text-wrap-style).

Also removes a dead selector: &:where(:has(.text) .icon) compiles to .flow--button:where(:has(.flow--button--text) .flow--button--icon) — a compound selector that can never match.

Baselines: three scenarios change on purpose — the icon+string padding, the plain pending label, and sub-pixel text rendering where a label is no longer inside a span. The darwin baselines are updated; the linux ones come from the update-screenshots label.

🤖 Generated with Claude Code

@Lisa18289 Lisa18289 self-assigned this Sep 1, 2026
@Lisa18289 Lisa18289 added update-screenshots Label a PR to update the screenshots used for visual regression testing run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch labels Sep 1, 2026
@github-actions github-actions Bot removed run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch update-screenshots Label a PR to update the screenshots used for visual regression testing labels Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for ./packages/components/

Status Category Percentage Covered / Total
🔵 Lines 78.69% 746 / 948
🔵 Statements 78.57% 763 / 971
🔵 Functions 80.09% 165 / 206
🔵 Branches 70.33% 377 / 536
File CoverageNo changed files found.
Generated in workflow #6664 for commit f6eab9b by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Preview environments are ready:

Type URL
docs pr-3053.docs.review.flow-components.de
storybook pr-3053.storybook.review.flow-components.de

Images:

  • docs: ghcr.io/mittwald/flow/docs:pr-3053
  • storybook: ghcr.io/mittwald/flow/storybook:pr-3053

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

❌ Visual Regression Tests Failed

At least one shard did not pass. If snapshots differ from the committed baselines, download the visual-diffs-* artifacts from this run to inspect the actual/diff images (a shard that failed before comparison, e.g. during install or browser setup, leaves none).

If the differences are intentional, update the baselines by adding the update-screenshots label to the PR.

Run details

@Lisa18289 Lisa18289 changed the title fix(Link): keep the gap between icon and text in a nested Button fix(Button): treat a string label like a Text child Sep 2, 2026
@Lisa18289 Lisa18289 added update-screenshots Label a PR to update the screenshots used for visual regression testing run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch labels Sep 2, 2026
@github-actions github-actions Bot removed run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch update-screenshots Label a PR to update the screenshots used for visual regression testing labels Sep 2, 2026
@Lisa18289
Lisa18289 force-pushed the claude/button-text-icon-link-spacing-b2b459 branch from e300f2e to 656806b Compare September 3, 2026 11:12
Lisa18289 and others added 5 commits September 3, 2026 13:15
Link wrapped all Button children in a single `Text`, collapsing icon and
label into one flex item. The button's `column-gap` then had nothing to
apply to, and the icon lost its `flow--button--icon` class, so the
`:has(.text) .icon` margin didn't apply either — icon and label ended up
glued together.

Wrap only plain string children, the same condition `Button` itself uses.
This also restores the icon-only padding for an icon-only Button in a
Link, which the always-present `.text` had suppressed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
Drop the WithButtonAndIcon story — the visual test already covers it — and
trim the comment on the Text wrapper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A raw string child rendered no element, so `:has(.text)` never matched it:
`<Button><Icon />Label</Button>` got the icon-only padding (8px instead of
16px), and a plain pending button hid its label. Which branch a button took
depended on how the label was written — `Label` and `{x} Label` behaved
differently.

The button now carries the marker itself, so a string is detected wherever
it sits, including through the fragment `Link` passes down. That drops the
`Text` wrapper from both `Button` and `Link`; `.content` keeps the two
properties it contributed.

Also removes a dead selector: `&:where(:has(.text) .icon)` compiles to a
compound selector that can never match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
@Lisa18289
Lisa18289 force-pushed the claude/button-text-icon-link-spacing-b2b459 branch from 656806b to a22e769 Compare September 3, 2026 11:15
The plain variant kept its content visible while pending, succeeded or
failed, so the state icon was drawn on top of the label. The exception only
ever fired for a single string child, so no scenario showed it until string
labels started matching. Plain now behaves like every other variant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Lisa18289 Lisa18289 added update-screenshots Label a PR to update the screenshots used for visual regression testing run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch labels Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
@Lisa18289
Lisa18289 marked this pull request as ready for review September 4, 2026 04:28
@Lisa18289
Lisa18289 requested a review from a team September 4, 2026 04:28
Comment thread packages/components/src/lib/react/remote.ts Outdated
`<Button><Icon />{42}</Button>` matched no text marker and got the
icon-only padding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mfal
mfal enabled auto-merge (squash) September 4, 2026 06:17
@mfal
mfal merged commit 13cc756 into main Sep 4, 2026
22 checks passed
@mfal
mfal deleted the claude/button-text-icon-link-spacing-b2b459 branch September 4, 2026 06:33
mfal added a commit that referenced this pull request Sep 4, 2026
#3053 landed `containsTextChild` with its own `isTextNode` predicate and its
own fragment recursion, which is the same classification `joinTextChildren`
does here. Two traversals, one piece of knowledge.

`containsTextChild` now asks `extractTextFromChildren` and compares against
`undefined`, so `isTextNode` goes away. One behavior change follows from the
shared reading: `extractTextFromChildren` trims, so children that are only
whitespace no longer count as text. That is the right answer for the question
`Button` and `Link` ask — a blank string is not a label, and the control should
keep its icon-only layout. Pinned by a test.

The boolean answer no longer short-circuits at the first text child. Irrelevant
at the child counts these components see.

Also drops `Button` from `extractTextFromFirstChild`'s doc comment: #3053
replaced its call with `containsTextChild`, leaving `Markdown`, `Initials` and
`Truncate` as the callers that depend on the strict reading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lisa18289 added a commit that referenced this pull request Sep 4, 2026
#3053 landed the same predicate in `remote.ts` for the button's text marker.
Export it and drop the local copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lisa18289 added a commit that referenced this pull request Sep 4, 2026
#3053 landed the same predicate in `remote.ts`. Applied per child it answers
exactly this question, and it looks through fragments, so `<>{"Text"}</>` is
wrapped and truncated too — the local helper returned false for that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants