Skip to content

fix: addressing misbehaving margin props for Banner#266

Open
haoruikun-cb wants to merge 4 commits intomasterfrom
harry/banner-margin-exp
Open

fix: addressing misbehaving margin props for Banner#266
haoruikun-cb wants to merge 4 commits intomasterfrom
harry/banner-margin-exp

Conversation

@haoruikun-cb
Copy link
Contributor

@haoruikun-cb haoruikun-cb commented Dec 19, 2025

What changed? Why?

Summary

In a previous PR (#209), we fixed a web Banner layout regression by moving from flexGrow-based sizing to explicit width/height. That change was necessary to resolve a production issue and also cleaned up a few edge-case layout glitches (see the original PR for details).

However, after the change, the margin* style props started behaving unexpectedly. CDS margin* props are implemented as negative margins to create a “bleed” effect. When the wrapper is sized with width="100%", a negative horizontal margin simply shifts the Banner left/right while keeping its width at 100%, which removes the intended bleed. The visual result is that the Banner appears misaligned or clipped instead of extending past the container.

To restore the expected behavior, we now compute and apply an explicit wrapper width that accounts for negative margins (e.g. calc(100% + <startBleed> + <endBleed>)). This makes the Banner span the full container and bleed past it by the negative margin amount, and it correctly tracks responsive margin overrides at each breakpoint.

Root cause (required for bugfixes)

The earlier fix switched the Banner wrapper from flexGrow sizing to explicit width, but our margin* props are implemented as negative margins. With a fixed width="100%", negative horizontal margins only shift the element instead of expanding its visual footprint, so the intended “bleed” past the container was lost. This made margin-based layouts look misaligned or clipped.

UI changes

Web Old

544022633-88751421-3d52-4fdd-9aac-393a1fc0c204.mov

Web New

Screen.Recording.2026-02-02.at.2.50.50.PM.mov
Screen.Recording.2026-02-02.at.2.57.03.PM.mov

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

@cb-heimdall
Copy link
Collaborator

cb-heimdall commented Dec 19, 2025

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

@haoruikun-cb haoruikun-cb force-pushed the harry/banner-margin-exp branch from afa03c3 to d1a161f Compare February 2, 2026 22:36
@haoruikun-cb haoruikun-cb changed the title feat: experimental approach for enabling banner margin fix: addressing misbehaving margin props for Banner Feb 2, 2026

return hasResponsiveOverrides ? widthByBreakpoint : baseWidth;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Debate question: is it worth introducing complexity like this for addressing this specific use case?

Comment on lines +62 to +66
// Prefer explicit start/end over marginX (marginX sets both sides).
const startSpaceToken =
normalizeNegativeSpace(effectiveMarginStart) ?? normalizeNegativeSpace(effectiveMarginX);
const endSpaceToken =
normalizeNegativeSpace(effectiveMarginEnd) ?? normalizeNegativeSpace(effectiveMarginX);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do other components prioritize marginStart / marginEnd styling over marginX? We should keep the styling API consistent with other components since this is a common styling prop

return str.slice(1);
};

const spaceVar = (spaceToken: string) => `var(--space-${spaceToken.replace('.', '_')})`;
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be better utilized inside the component and with the useTheme hook. We could avoid these normalization methods and just append 'px' to the theme token value

<Box
position="relative"
width="100%"
width={showDismiss ? '100%' : wrapperWidth}
Copy link
Contributor

Choose a reason for hiding this comment

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

If using width="100%" is the root cause for these styling issues, could it be removed? There could be another CSS solution which has the Banner's width grow without using flex. The PR you linked that introduced this regression was addressing an issue with the Banner's height. It would be better to address the root cause of this issue. This solution does seem a bit over-engineered.

Could something like this work?

Suggested change
width={showDismiss ? '100%' : wrapperWidth}
// Replace with margin props based on order priority
style={{ width: `calc(100% + ${marginX}px` }}

desktop?: string;
} = { base: baseWidth };

const computeForBreakpoint = (breakpoint: 'phone' | 'tablet' | 'desktop') => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use the DeviceBreakpoint type from src/styles/media.ts

Suggested change
const computeForBreakpoint = (breakpoint: 'phone' | 'tablet' | 'desktop') => {
const computeForBreakpoint = (breakpoint: Pick<DeviceBreakpoint, 'phone' | 'tablet' | 'desktop') => {

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

Development

Successfully merging this pull request may close these issues.

3 participants