Skip to content

fix(apollo-wind): stop FormField overflowing on truncating controls - #1112

Open
BenGSchulz wants to merge 2 commits into
mainfrom
fix/form-field-grid-min-content
Open

fix(apollo-wind): stop FormField overflowing on truncating controls#1112
BenGSchulz wants to merge 2 commits into
mainfrom
fix/form-field-grid-min-content

Conversation

@BenGSchulz

Copy link
Copy Markdown
Contributor

Two related layout fixes in apollo-wind, reported against 2.43.1.

1. FormField's grid floors at min-content

FormField rendered grid gap-1.5. Its implicit column track is auto, whose minimum is the item's automatic minimum size, and grid items get min-width: auto. So the track could never be narrower than its widest child's min-content.

A control containing a truncate span is white-space: nowrap, which makes its min-content the entire string. Any such control forced the track wider than the form and overflowed its container instead of ellipsizing.

This was not limited to custom fields: field-renderer wraps every standard-typed field in FormField, so MetadataForm carried it too. It showed up wherever a form lives in a width-constrained surface, such as a properties panel, sidebar, or narrow dialog.

Fix: pin the single column to minmax(0, 1fr).

Measured, against the compiled Tailwind build

Same markup, same text, in a 320px host (304px content box):

host grid (before) grid-cols-[minmax(0,1fr)] (after)
definite 320px control 712.8px, no ellipsis 304px, ellipsizes
inline-block (shrink-to-fit) 712.8px 712.8px
flex item, flex: 0 1 auto 712.8px 712.8px
block, 1000px 1000px 1000px

An fr track resolves against the max-content constraint when the container's width is indefinite, so shrink-to-fit hosts are byte-identical. The two behaviours diverge only when the width is definite, which is exactly the broken case, so no consumer depends on today's behaviour who isn't already looking at an overflowing control.

The escape hatch survives, since className is merged last:

cn('grid grid-cols-[minmax(0,1fr)] gap-1.5', 'grid-cols-2')
  -> 'grid gap-1.5 grid-cols-2'

flex flex-col would also have fixed this, and arguably more honestly, since the automatic-minimum-size rule applies only to the flex main axis. It was rejected because it silently breaks any consumer passing grid-cols-2 / grid-cols-[auto_1fr], as those utilities do not restore display: grid. One extra class is the cheaper trade.

2. Label had no display, so space-y-* silently did nothing

Label rendered a bare Radix <label> with typography classes and no display utility, so it was display: inline. Tailwind v4 changed space-y-* to emit margin-block-end on every child but the last (v3 emitted margin-top on every child but the first). Vertical margins have no effect on non-replaced inline boxes, so the margin landed on the Label and was discarded.

Confirmed in the browser: the space-y-1.5 rule does emit margin-block-end, the Label computed to display: inline, margin-block-end computed to 6px, and the measured gap was 3.5px (pure line-box leading).

Fix: inline-block. It preserves inline-flow use, such as checkbox rows and inline hints, in a way block would not.

⚠️ Expected visual change

Anywhere a consumer wrapped a bare Label in space-y-*, the previously swallowed gap now appears. One instance exists in our own stories (Basic Contact Form, the "Urgent Request" toggle under a space-y-0.5 wrapper).

Inside FormField nothing changes: grid items are blockified, so the label already computed to block. Verified in the DOM. The datetime-picker label that passes flex items-center gap-2 stays flex via tailwind-merge. No consumer puts truncate on a Label.

Verification

  • Full apollo-wind suite: 1437 tests / 93 files passing. Four new regression tests covering the pinned column, the consumer column override, the label display, and the label display override.
  • biome check clean on all four files; tsc --noEmit clean.
  • Rendered in Storybook at desktop and 375px, no breakage.
  • test:visual is declared in turbo.json but no package implements it, so there is no visual suite to run. The pixel diff noted above is worth a manual look if snapshots exist elsewhere.

Notes for the reporter

The local @layer base workaround can be deleted once this lands:

@layer base {
  :where([data-slot='form-field']) {
    grid-template-columns: minmax(0, 1fr);
  }
}

It composes with this fix rather than fighting it, so there is no version gate on removing it.

🤖 Generated with Claude Code

BenGSchulz and others added 2 commits September 1, 2026 17:59
…ellipsize

FormField's implicit `auto` column floors at its widest child's min-content.
A control containing a `truncate` span is `white-space: nowrap`, so its
min-content is the entire string, and the field pushed past a width-constrained
host instead of ellipsizing. This reached every standard-typed field, since
field-renderer wraps each one in FormField.

Pinning the track to `minmax(0, 1fr)` bounds it. A `1fr` track still resolves
against max-content when the host width is indefinite, so shrink-to-fit hosts
measure identically; the two diverge only where the width is definite, which is
the broken case. `className` merges last, so `grid-cols-2` still overrides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A bare `<label>` is `display: inline`, and vertical margins have no effect on a
non-replaced inline box. Tailwind v4 changed `space-y-*` to emit
`margin-block-end` on every child but the last, so the margin now lands on the
Label and is discarded: a `space-y-*` wrapper produces no gap at all, with
nothing to indicate the wrapper is the problem.

`inline-block` fixes it while preserving inline-flow use, which `block` would
not. Grid and flex items are blockified anyway, so nothing changes inside
FormField, and a consumer needing another display passes one.

Consumers that wrapped a bare Label in `space-y-*` will see the previously
swallowed gap appear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 2, 2026 01:10
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Sep 01, 2026, 06:19:26 PM
apollo-docs Ready Preview · Logs Sep 01, 2026, 06:19:26 PM
apollo-landing Ready Preview · Logs Sep 01, 2026, 06:19:26 PM
apollo-vertex Ready Preview · Logs Sep 01, 2026, 06:19:26 PM

@github-actions github-actions Bot added the size:M 30-99 changed lines. label Sep 2, 2026

Copilot AI 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 recommended

The changes are small, well-scoped CSS utility adjustments with explicit regression tests verifying both the new defaults and consumer override behavior.

Pull request overview

This PR addresses two Tailwind/CSS layout edge cases in apollo-wind that caused (1) FormField to overflow instead of truncating in width-constrained hosts and (2) Label to ignore space-y-* vertical spacing under Tailwind v4 due to being display: inline.

Changes:

  • Pin FormField’s single grid column to grid-cols-[minmax(0,1fr)] to allow truncating controls to shrink/ellipsis in definite-width containers.
  • Make Label render as inline-block so Tailwind v4 space-y-* margins apply, while still allowing consumers to override display via className.
  • Add regression tests asserting the default classes and that consumer overrides win (via cn + tailwind-merge behavior).
File summaries
File Description
packages/apollo-wind/src/components/ui/label.tsx Sets default Label display to inline-block and documents the Tailwind v4 spacing rationale.
packages/apollo-wind/src/components/ui/label.test.tsx Adds tests for the new default display and consumer display override behavior.
packages/apollo-wind/src/components/ui/form-field.tsx Pins the single grid column to minmax(0,1fr) to prevent overflow from truncating controls in definite-width hosts.
packages/apollo-wind/src/components/ui/form-field.test.tsx Adds tests for the pinned grid column and for consumer grid-cols-* override behavior.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Dependency License Review

  • 1951 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1722
ISC 88
Apache-2.0 55
BSD-3-Clause 27
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.2 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core 41.59 MB 49.70 MB ±0
@uipath/apollo-react 39.8% 7.56 MB 29.11 MB ±0
@uipath/apollo-ui-icons 2.85 MB 6.91 MB ±0
@uipath/apollo-wind 65.0% 100.0% (2/2) 428.8 KB 2.75 MB +582 B
@uipath/ap-chat 85.8% 43.46 MB 56.09 MB ±0

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Storybook visual diff

⚠️ Visual changes detected: 26 changed (of 464 compared, 438 unchanged). View report

Baseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs

Updated (PT): Sep 01, 2026, 06:31:55 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:apollo-wind size:M 30-99 changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants