Skip to content

Modal: Render as a bottom sheet on mobile#77956

Open
crisbusquets wants to merge 1 commit intoWordPress:trunkfrom
crisbusquets:fix/modal-mobile-bottom-sheet
Open

Modal: Render as a bottom sheet on mobile#77956
crisbusquets wants to merge 1 commit intoWordPress:trunkfrom
crisbusquets:fix/modal-mobile-bottom-sheet

Conversation

@crisbusquets
Copy link
Copy Markdown
Contributor

What?

Below the break-small (600px) breakpoint, the Modal component now renders as a bottom sheet:

  • Anchored to the bottom of the viewport (instead of the top).
  • Edge-to-edge width with rounded top corners only.
  • Height adapts to the content, capped so the dimmed overlay stays visible above.
  • Slides up from the bottom on open / down on close (instead of the desktop scale animation).

Behavior at and above break-small is unchanged.

Why?

On the previous mobile layout the modal was anchored to the top with a 40px gap and stretched to fill the remaining viewport height regardless of content.
Two consequences:

  • Short-content modals looked oversized.
  • Primary CTAs ended up at the top of the screen, far from the user's thumb on touch devices.

A bottom-sheet pattern fixes both: the modal hugs its content and CTAs land in the comfortable thumb zone.

Screenshots

Before After
Mobile wordpress github io_gutenberg_iframe html_id=components-modal--default viewMode=story(iPhone SE) localhost_50240_iframe html_args=isFullScreen_!false id=components-modal--default viewMode=story(iPhone SE)
Desktop (unchanged) (unchanged)

How?

Pure CSS change in packages/components/src/modal/style.scss:

  • Replaced margin: $grid-unit-50 0 0 0 with margin: auto 0 0 0 so the modal is pushed to the bottom by the auto top margin (in the existing display: flex overlay).
  • Added max-height: calc(100% - $grid-unit-50) so very tall content scrolls inside the sheet rather than covering the overlay.
  • Added an &.is-full-screen mobile rule so isFullScreen modals continue to cover the entire viewport (margin/border-radius zeroed, width/height 100%).
    The desktop &.is-full-screen rule was extended to reset those overrides back to margin: auto and border-radius: $radius-large.
  • Redefined the existing components-modal__appear-animation / components-modal__disappear-animation keyframes to slide (translateY(100%) ↔ 0) by
    default, and re-defined them inside @include break-small() to keep the original scale animation on desktop. The keyframe names are unchanged so
    use-modal-exit-animation.ts's animationend listener keeps working without JS changes.

Testing instructions

  1. npm run storybook:dev
  2. Open Components → Modal → Default.
  3. Mobile (DevTools device toolbar, viewport < 600px): open the modal — it should slide up from the bottom, hug the content height, and have rounded
    top corners only.
  4. Desktop (viewport ≥ 600px): open the modal — should be centered with the scale animation, identical to before.
  5. Toggle isFullScreen on:
    • Mobile: should cover the full viewport, no rounded corners.
    • Desktop: should be centered with a 20px gap and rounded corners (40px gap above the break-medium breakpoint).

Types of changes

Bug fix — restores expected mobile UX without changing the public API.

Checklist

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by the changes. (N/A — CSS only, no native equivalent.)

@crisbusquets crisbusquets requested review from a team and ajitbohra as code owners May 5, 2026 13:18
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: .

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: crisbusquets <cbusquets1989@git.wordpress.org>
Co-authored-by: ciampo <mciampini@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions github-actions Bot added the [Package] Components /packages/components label May 5, 2026
Copy link
Copy Markdown
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

This PR seems to be based on a very old version of the repository, would you be able to rebase on top of latest trunk?

Screenshot 2026-05-05 at 17 19 26

We will also need to add a CHANGELOG entry for the @wordpress/components package

Comment on lines +25 to +28
// On smaller screens, render as a bottom sheet: edge-to-edge, anchored to
// the bottom of the viewport, with a height that adapts to the content
// (capped so it never covers the full viewport).
margin: auto 0 0 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we use align-self: flex-end here instead of auto margin?

Suggested change
// On smaller screens, render as a bottom sheet: edge-to-edge, anchored to
// the bottom of the viewport, with a height that adapts to the content
// (capped so it never covers the full viewport).
margin: auto 0 0 0;
// On smaller screens, render as a bottom sheet: edge-to-edge, anchored
// to the bottom of the viewport. `align-self: flex-end` both anchors
// the modal to the bottom of the flex overlay and breaks the default
// `align-items: stretch`, so the modal hugs its content height.
align-self: flex-end;
margin: 0;

max-height: none;
border-radius: 0;
}

// Show a centered modal on bigger screens.
@include break-small() {
border-radius: $radius-large;
margin: auto;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we go with the previous suggestion, we may have to also add align-self: auto; here

// Show a centered modal on bigger screens.
@include break-small() {
border-radius: $radius-large;
margin: auto;
width: auto;
min-width: var(--wpds-dimension-surface-width-sm);
min-width: $modal-min-width;
max-width: calc(100% - #{$grid-unit-20 * 2});
max-height: calc(100% - #{$header-height * 2});

&.is-full-screen {
@include break-small() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (and not introduced by this PR): it looks like the @include break-small() { here is superflous, since this whole CSS block is already nested in a @include break-small() { block a few lines above?

We could take this opportunity to clean this code up a little

Below the `break-small` breakpoint, the Modal now anchors to the bottom
of the viewport, adapts its height to the content (capped so the overlay
stays visible above), and slides up instead of scaling in. This keeps
primary CTAs within thumb reach on touch devices.

Behavior at and above `break-small` is unchanged. `is-full-screen` still
covers the full viewport on mobile and remains centered with rounded
corners on desktop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@crisbusquets crisbusquets force-pushed the fix/modal-mobile-bottom-sheet branch from be34b94 to 97d81b4 Compare May 5, 2026 16:51
@crisbusquets
Copy link
Copy Markdown
Contributor Author

crisbusquets commented May 5, 2026

This PR seems to be based on a very old version of the repository, would you be able to rebase on top of latest trunk?

Screenshot 2026-05-05 at 17 19 26 We will also need to add a CHANGELOG entry for the `@wordpress/components` package

@ciampo Fixed! I'll review your other suggestions shortly. Thanks for reviewing it.

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

Labels

[Package] Components /packages/components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants