Skip to content

Form: let a responsive page form take its width from the parent - #1187

Merged
bert-e merged 1 commit into
development/1.0from
improvement/CUI-form-fluid-page-layout
Aug 27, 2026
Merged

Form: let a responsive page form take its width from the parent#1187
bert-e merged 1 commit into
development/1.0from
improvement/CUI-form-fluid-page-layout

Conversation

@JeanMarcMilletScality

@JeanMarcMilletScality JeanMarcMilletScality commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

TL;DR: A responsive page Form placed in a flex row collapsed to a 0px-wide box, and its content columns were pinned to 45rem so they could not narrow — putting the fields, the section actions and the footer outside the page below ~1000px. The form now takes its width from its parent and treats 45rem as a cap, so it stays usable down to ~600px with no change to how a page form looks today.

Component: Form (page layout), Box

Description:

A page Form with responsive collapses to a 0px-wide box in any flex row, and its content columns are pinned to width: 45rem so they cannot narrow below that. Together those two rules make a wizard-shaped page (step sidebar + form) unusable below ~1000px: the fields, the section actions and the Save/Continue footer sit outside the page with no way to reach them.

It is the first gap a consumer hits when it puts a page form in a flex layout, and it is currently worked around downstream with a local width wrapper.

Design: No visual change to a page form as it ships today (measured below). No API change.

Breaking Changes:

  • [] Breaking Changes

🧠 Approach

Two independent rules, both in Form.component.tsx.

1. Containment removed the form's own width. container-type: inline-size implies contain: inline-size, so a responsive Form's contents stop contributing to its intrinsic width. As a content-sized flex item it therefore resolves to 0px — and its own container query, measuring that 0px box, matches permanently, which silently pinned every flex-parented responsive form to the stacked label layout at all widths. The fix is the pairing layout/v2/panels.tsx already uses for LeftPanel/RightPanel:

Before:

container-type: inline-size;
container-name: responsive;

After:

flex: 1 1 auto;   // ←
min-width: 0;     // ←
container-type: inline-size;
container-name: responsive;

2. The page columns were pinned, not capped. BasicPageLayout is extended by the header, the scroll area and the footer, so it decides how wide a page form's content is:

Before:

const BasicPageLayout = styled.div`
  margin: 0 auto;
  width: 45rem;              // ←
  padding-right: ${spacing.f16};
`;

After:

const BasicPageLayout = styled.div`
  box-sizing: border-box;                              // ←
  margin: 0 auto;
  width: 100%;                                         // ←
  max-width: calc(${PAGE_CONTENT_MAX_WIDTH} + ${spacing.f16});   // ←
  padding-right: ${spacing.f16};
`;

Two things are load-bearing there and are the part worth reviewing:

  • box-sizing: border-box is required by width: 100%, or the content-box padding-right overflows the parent by exactly that padding.
  • the cap is calc(45rem + 16px), not 45rem. Under border-box a bare 45rem cap would shrink the column from 646px to 630px — the padding used to sit outside the pinned width. Carrying it in the cap keeps exactly the same 45rem of content as before.

📐 Measured behaviour

Content box narrowed with the viewport held constant — the case a container query exists for, and the reason a media query would not have caught any of this: a host application can shrink the box it gives an app without the viewport changing at all.

content box 1420 1200 1000 900 800 768 700 600
column width 646 646 646 547 447 415 347 247
horizontal overflow 0 0 0 0 0 0 0 0

The 45rem cap still holds wherever there is room for it (≥1000), and the footer stays inside the box at every width. A page form that is not in a flex row is untouched: 646px column at x=397 in a 1440 viewport, identical to development/1.0.

📸 Screenshots

Templates/Form → ResponsivePageFormInFlexRow — a page form in a flex row beside a 324px step sidebar.

At a 780px content box — the drawer-open case. The Continue button is gone, Optional helper text is clipped to Option, the required-fields legend is missing, and the row scrolls sideways by 305px. After: the form box is 341px instead of 0, and everything is inside it with no scrollbar.

development/1.0 this PR
780px content box before: Continue button and helper text pushed outside the frame, horizontal scrollbar 780px content box after: header legend, both fields, wrapped helper text and both footer buttons all inside the frame

At a 1440px viewport — the part that is easy to miss. The form's own @container (max-width: …) query was measuring its 0px box, so it matched permanently and every direction="horizontal" field row was pinned stacked at every width. The fields only become side-by-side once the form has a real width.

development/1.0 — labels above their inputs, with 646px of unused room to the right:

1440px before: labels stacked above their inputs despite ample room

This PR — side-by-side, as direction="horizontal" asks:

1440px after: labels beside their inputs, columns capped at 45rem and centred in the row

🔧 Usage

No API change — responsive is unchanged, and this is what now works:

<div style={{ display: 'flex' }}>
  <StepSidebar />
  <Form layout={{ kind: 'page', title: 'Create provider' }} responsive>
    <FormSection title={{ name: 'Connection', icon: 'Node-backend' }} forceLabelWidth={200}>
      ...
    </FormSection>
  </Form>
</div>

(from stories/form.stories.tsx › ResponsivePageFormInFlexRow, added here)

A consumer that worked around the collapse with its own width wrapper can drop it.

🔍 Review focus

  • 🟡 src/lib/components/form/Form.component.tsx › BasicPageLayout — the border-box + calc(45rem + 16px) arithmetic. Getting the + 16px wrong is a silent 16px narrowing of every page form in the fleet.
  • 🟡 src/lib/components/form/Form.component.tsx › StyledFormflex: 1 1 auto means a responsive page form now grows to fill a flex parent instead of being content-sized. Intended, and the only way the container query can measure anything, but it is the behavioural change here.
  • src/lib/components/box/Box.ts › Box — same containment trap, so container now also emits min-width: 0. No flex on this one: Box is a general primitive and stays unopinionated about where its definite width comes from.

🧪 How to test

  1. npm run storybook, open Templates/Form → ResponsivePageFormInFlexRow.
  2. Drag the dashed frame's right edge in to ~48rem. Everything stays reachable: the labels flip to stacked, the fields shrink, the footer buttons stay inside the frame, and no horizontal scrollbar appears.
  3. Flip the responsive arg off and on — with it on, the labels are side-by-side at wide widths (before this PR they were stuck stacked, because the query was reading a 0px box).
  4. Open Templates/Form → PageForm and confirm no change: 646px column, centred.

🧭 Follow-up

  • A consumer working around this with its own width wrapper can delete it and go back to plain responsive. That deletion is this PR's real acceptance test.
  • Column alignment at wide widths is left as-is (margin: 0 auto, centred). A form in a flex row now centres its capped columns in the row, where before its 0px box left them spilling from the row's left edge. Whether they should instead stay flush with a neighbouring sidebar is a design decision, so it is deliberately not part of this fix.
What changed

Form.component.tsx carries the whole fix in two rules — StyledForm's containment block and BasicPageLayout's width. Box.ts gets the min-width: 0 companion for its container prop. stories/form.stories.tsx adds the story above, and generalises the existing ResizeFrame helper with optional minWidth / height rather than adding a second near-identical frame.

Deliberately not here: the SearchInput fluid gap (CUI-41), the label/field grid squeeze order, and the table/toast gaps — separate PRs, one surface each.

@bert-e

bert-e commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request.
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

`container-type: inline-size` implies `contain: inline-size`, so a `responsive`
Form's contents stop contributing to its intrinsic width. As a content-sized flex
item it therefore resolved to 0 and every child overflowed it — and its container
query, measuring a 0px box, matched permanently, pinning every such form to the
stacked layout. Pair the containment with `flex: 1 1 auto; min-width: 0`, the way
LeftPanel/RightPanel already do.

Separately, the page layout's three columns (header, scroll area, footer) were
pinned to `width: 45rem`, so below that the fields, the section actions and the
fixed footer sat outside the page with no way to reach them. Make it a cap:
`width: 100%` with `box-sizing: border-box`, and carry the column padding in the
cap so the *content* still gets exactly 45rem.

`Box container` had the same containment trap with no width companion, so it gets
`min-width: 0` too.

A page form's columns keep centring in the room they have (`margin: 0 auto`,
unchanged): 646px at x=397 in a 1440 viewport, exactly as before. A form in a flex
row now centres too, where before its 0px box left the columns spilling out from
the row's left edge. ResponsivePageFormAlignment renders both alignments on one
width control, for whenever that is revisited.

Verified in Storybook (ResponsivePageFormInFlexRow) at content boxes
1420/1200/1000/900/800/768/700/600: zero overflow throughout, columns aligned,
footer always inside the box, and the 45rem cap held at >= 1000.
@JeanMarcMilletScality
JeanMarcMilletScality force-pushed the improvement/CUI-form-fluid-page-layout branch from f8e567d to ba01ece Compare August 26, 2026 17:27
@JeanMarcMilletScality
JeanMarcMilletScality marked this pull request as ready for review August 27, 2026 07:28

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

ran the storybook and LGTM

@bert-e

bert-e commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

@JeanMarcMilletScality

Copy link
Copy Markdown
Contributor Author

/approve

@bert-e

bert-e commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

In the queue

The changeset has received all authorizations and has been added to the
relevant queue(s). The queue(s) will be merged in the target development
branch(es) as soon as builds have passed.

The changeset will be merged in:

  • ✔️ development/1.0

There is no action required on your side. You will be notified here once
the changeset has been merged. In the unlikely event that the changeset
fails permanently on the queue, a member of the admin team will
contact you to help resolve the matter.

IMPORTANT

Please do not attempt to modify this pull request.

  • Any commit you add on the source branch will trigger a new cycle after the
    current queue is merged.
  • Any commit you add on one of the integration branches will be lost.

If you need this pull request to be removed from the queue, please contact a
member of the admin team now.

The following options are set: approve

@bert-e

bert-e commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

I have successfully merged the changeset of this pull request
into targetted development branches:

  • ✔️ development/1.0

Please check the status of the associated issue None.

Goodbye jeanmarcmilletscality.

@bert-e
bert-e merged commit a625065 into development/1.0 Aug 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants