Skip to content

feat(Modal,Backdrop): Add opt-in animation support#12552

Open
GAUNSD wants to merge 4 commits into
patternfly:mainfrom
GAUNSD:gmurcia/modal-backdrop-animations
Open

feat(Modal,Backdrop): Add opt-in animation support#12552
GAUNSD wants to merge 4 commits into
patternfly:mainfrom
GAUNSD:gmurcia/modal-backdrop-animations

Conversation

@GAUNSD

@GAUNSD GAUNSD commented Jul 9, 2026

Copy link
Copy Markdown

What:

Additional issues:

Summary

This PR adds opt-in animations to the <Modal>, and <Backdrop> components.

Evidence

Video: Modal animations in doc page as new example

modal-animations-standard.mp4

Video: Slowed down animations to 25% speed

modal-animations-slow.mp4

Detail: ModalBox's return null

Notably; the ModalBox was previously returning null which made the animation less desirable since the entire modal content was not rendered.

// Before
if (!isOpen) {
  return null
}

Now the ModalBox will skip this and only return null when animations are not set.

// After
if (!isOpen && !hasAnimations) {
  return null;
}

Detail: modal-animations.css & backdrop-animations.css

Just to get this change easily-tested and off the ground I've added the styles directly into the react-styles package.
Ideally these styles would live upstream in patternfly/modal-box.scss and other relevant areas.

Detail: Build tweaks to generateTokens.mjs & build-single-packages.mjs

I'm running locally on Windows where path separators are \ instead of the /.
While running locally, some build scripts fail because of this and tweaks were made in order to get a successful build.

Summary by CodeRabbit

  • New Features

    • Added animated modal transitions with coordinated backdrop visibility states.
    • Added support for enabling animations through modal properties or a shared animations provider.
    • Added reduced-motion support to minimize transitions for users who prefer less motion.
  • Documentation

    • Added examples demonstrating animated modals and provider-based animation configuration.
  • Bug Fixes

    • Improved cross-platform handling of package and token paths during builds.

GAUNSD and others added 2 commits July 9, 2026 17:10
[WIP] This is an initial and in-progress implementation of Modal and
Backdrop animation support.

These changes were cherry-picked (file-level) from branch
gmurcia/tearsheet, originating from the following commits:
  - c027e56 chore: Initial modal/backdrop animation impl
  - 1927470 chore: Animation tweaks.
  - a7f4102 chore: Implement TearsheetGroup for unbounded tearsheet levels

Commit-generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…AnimationsProvider + hooks

Replace custom `animated` prop with `hasAnimations` across Modal,
ModalContent, ModalBox, and Backdrop. Wire up `useHasAnimations` hook
in ModalContent and Backdrop so both participate in AnimationsProvider
context. Add second example demonstrating the AnimationsProvider path.

Generated-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Modal and Backdrop animation

Layer / File(s) Summary
Animation state plumbing
packages/react-core/src/components/Backdrop/Backdrop.tsx, packages/react-core/src/components/Modal/*.tsx
Modal and Backdrop props, animation-state resolution, focus-trap behavior, and conditional animation classes are updated.
Animation styles
packages/react-styles/src/css/components/{BackdropAnimations,ModalAnimations}/*
CSS defines modal and backdrop transition states and reduced-motion overrides.
Examples and documentation
packages/react-core/src/components/Modal/examples/*
Examples demonstrate direct animation opt-in and AnimationsProvider configuration.
Cross-platform path normalization
packages/react-tokens/scripts/generateTokens.mjs, scripts/build-single-packages.mjs
Build and token-generation paths normalize Windows separators before processing.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: mcoker, nicolethoen, thatblindgeye, jcmill

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Modal
  participant ModalContent
  participant ModalBox
  participant Backdrop
  User->>Modal: toggle isOpen
  Modal->>ModalContent: pass isOpen and animation configuration
  ModalContent->>ModalBox: pass animation state
  ModalContent->>Backdrop: pass animation visibility
  ModalBox-->>User: render modal transition
  Backdrop-->>User: render backdrop transition
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Windows path-separator fixes in the build scripts are unrelated to the linked Modal/Backdrop animation issue. Move the build-script path normalization changes into a separate PR or document them as part of the linked issue scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: opt-in animation support for Modal and Backdrop.
Linked Issues check ✅ Passed The changes add opt-in Modal/Backdrop animations, use AnimationsProvider support, and preserve default behavior as requested.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@GAUNSD GAUNSD marked this pull request as ready for review July 10, 2026 00:09

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/react-tokens/scripts/generateTokens.mjs (1)

36-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Normalization approach is correct but inconsistent with build-single-packages.mjs.

formatFilePathToName uses filePath.replace(/\\/g, '/') while build-single-packages.mjs uses component.replaceAll('\\', '/'). Both achieve the same result, but the inconsistency could confuse maintainers. Consider standardizing on one approach across both scripts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-tokens/scripts/generateTokens.mjs` around lines 36 - 43,
Standardize path-separator normalization between formatFilePathToName and
build-single-packages.mjs by using the same replacement approach in both
scripts; update formatFilePathToName’s normalizedPath logic to match the
established approach used by build-single-packages.mjs.
packages/react-core/src/components/Modal/ModalContent.tsx (1)

123-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify focusTrapActive to a single const expression.

The let + if reassignment can be expressed as a single declaration, which is more concise and avoids mutable state.

♻️ Proposed refactor
-  let focusTrapActive = !disableFocusTrap;
-  if (hasAnimations) {
-    focusTrapActive = !disableFocusTrap && isOpen;
-  }
+  const focusTrapActive = !disableFocusTrap && (!hasAnimations || isOpen);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/components/Modal/ModalContent.tsx` around lines 123 -
126, Replace the mutable focusTrapActive initialization and conditional
reassignment in the ModalContent component with a single const boolean
expression that preserves the existing disableFocusTrap, hasAnimations, and
isOpen behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-core/src/components/Modal/examples/ModalAnimated.tsx`:
- Around line 16-40: ModalAnimated’s Modal configuration is missing an onClose
handler, preventing Escape-key and header close-button dismissal. Add
onClose={handleModalToggle} to the Modal component in the ModalAnimated example
while preserving the existing footer button handlers.

In `@packages/react-core/src/components/Modal/examples/ModalAnimatedProvider.tsx`:
- Around line 25-53: Add onClose={handleModalToggle} to the Modal component in
the ModalAnimatedProvider example so the header close button and Escape key both
dismiss the modal consistently with the existing button handlers.

In `@packages/react-core/src/components/Modal/ModalContent.tsx`:
- Around line 77-84: Animated modal content remains mounted after closing
because ModalContent only skips rendering when animations are disabled. Add
close-animation completion handling around useHasAnimations and the isOpen state
so the component unmounts after the exit transition finishes, using a
transition-end callback or timeout fallback, while preserving immediate null
rendering for non-animated closes.

In
`@packages/react-styles/src/css/components/ModalAnimations/modal-animations.css`:
- Around line 14-22: Update the transition declaration in
.pf-v6-c-modal-animated-open to include opacity alongside transform and
visibility, preserving the existing timing and easing so opening the modal fades
it in as well as moving it.

---

Nitpick comments:
In `@packages/react-core/src/components/Modal/ModalContent.tsx`:
- Around line 123-126: Replace the mutable focusTrapActive initialization and
conditional reassignment in the ModalContent component with a single const
boolean expression that preserves the existing disableFocusTrap, hasAnimations,
and isOpen behavior.

In `@packages/react-tokens/scripts/generateTokens.mjs`:
- Around line 36-43: Standardize path-separator normalization between
formatFilePathToName and build-single-packages.mjs by using the same replacement
approach in both scripts; update formatFilePathToName’s normalizedPath logic to
match the established approach used by build-single-packages.mjs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff6deb22-1566-4953-ad5c-1f8f2fa05074

📥 Commits

Reviewing files that changed from the base of the PR and between f8bcd7c and df1305e.

📒 Files selected for processing (11)
  • packages/react-core/src/components/Backdrop/Backdrop.tsx
  • packages/react-core/src/components/Modal/Modal.tsx
  • packages/react-core/src/components/Modal/ModalBox.tsx
  • packages/react-core/src/components/Modal/ModalContent.tsx
  • packages/react-core/src/components/Modal/examples/Modal.md
  • packages/react-core/src/components/Modal/examples/ModalAnimated.tsx
  • packages/react-core/src/components/Modal/examples/ModalAnimatedProvider.tsx
  • packages/react-styles/src/css/components/BackdropAnimations/backdrop-animations.css
  • packages/react-styles/src/css/components/ModalAnimations/modal-animations.css
  • packages/react-tokens/scripts/generateTokens.mjs
  • scripts/build-single-packages.mjs

Comment thread packages/react-core/src/components/Modal/examples/ModalAnimated.tsx
Comment on lines +77 to 84
hasAnimations: hasAnimationsProp,
...props
}: ModalContentProps) => {
if (!isOpen) {
const hasAnimations = useHasAnimations(hasAnimationsProp);

if (!isOpen && !hasAnimations) {
return null;
}

@coderabbitai coderabbitai Bot Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'packages/react-core/src/components/Modal/*' | sed 's#^`#FILE` #' | sort
echo '--- ModalContent.tsx ---'
cat -n packages/react-core/src/components/Modal/ModalContent.tsx
echo '--- search for transition/end handlers and unmount flags ---'
rg -n "onTransitionEnd|onAnimationEnd|shouldRender|setShouldRender|hasAnimations|useHasAnimations|ModalContent" packages/react-core/src/components/Modal -S

Repository: patternfly/patternfly-react

Length of output: 14629


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Modal.tsx ---'
cat -n packages/react-core/src/components/Modal/Modal.tsx
echo '--- ModalBox.tsx ---'
cat -n packages/react-core/src/components/Modal/ModalBox.tsx
echo '--- ModalContent.test.tsx relevant assertions ---'
sed -n '1,220p' packages/react-core/src/components/Modal/__tests__/ModalContent.test.tsx | cat -n
echo '--- search docs/examples for closing animation / unmount ---'
rg -n "closing animation|remove the modal|hasAnimations|isOpen|unmount|transition end|animation end" packages/react-core/src/components/Modal -S

Repository: patternfly/patternfly-react

Length of output: 22854


Animated modals still never unmount on close packages/react-core/src/components/Modal/ModalContent.tsx:82-84

With hasAnimations, ModalContent keeps rendering after isOpen becomes false, and there’s no transition-end or timeout path to remove it after the close animation. The modal stays mounted until the parent unmounts it, so the “remove the modal after the closing animation completes” behavior is still missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/components/Modal/ModalContent.tsx` around lines 77 -
84, Animated modal content remains mounted after closing because ModalContent
only skips rendering when animations are disabled. Add close-animation
completion handling around useHasAnimations and the isOpen state so the
component unmounts after the exit transition finishes, using a transition-end
callback or timeout fallback, while preserving immediate null rendering for
non-animated closes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deferring this to discuss with PF team during office-hours

Comment on lines +14 to +22
.pf-v6-c-modal-animated-open {
--pf-v6-c-modal-animated--Transition:
transform 240ms cubic-bezier(0, 0, 0.2, 1),
visibility 0ms linear 0ms;
opacity: 1;
visibility: visible;
pointer-events: unset;
transform: translate3d(0, 0, 0);
}

@coderabbitai coderabbitai Bot Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Opening animation does not fade in — opacity missing from transition in .pf-v6-c-modal-animated-open.

The open class overrides --pf-v6-c-modal-animated--Transition with only transform and visibility, omitting opacity. When the browser applies the open class, it uses the new transition value for all property changes. Since opacity is not listed, it jumps instantly from 0 to 1 — no fade-in effect. The closing animation works correctly because the base class transition (which includes opacity) applies when the closed class is used. This asymmetry contradicts the PR objective: "Fade and shift the modal slightly downward when opening."

🐛 Proposed fix: add opacity to the open transition
 .pf-v6-c-modal-animated-open {
     --pf-v6-c-modal-animated--Transition:
+        opacity 240ms cubic-bezier(0, 0, 0.2, 1),
         transform 240ms cubic-bezier(0, 0, 0.2, 1),
         visibility 0ms linear 0ms;
     opacity: 1;
     visibility: visible;
     pointer-events: unset;
     transform: translate3d(0, 0, 0);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.pf-v6-c-modal-animated-open {
--pf-v6-c-modal-animated--Transition:
transform 240ms cubic-bezier(0, 0, 0.2, 1),
visibility 0ms linear 0ms;
opacity: 1;
visibility: visible;
pointer-events: unset;
transform: translate3d(0, 0, 0);
}
.pf-v6-c-modal-animated-open {
--pf-v6-c-modal-animated--Transition:
opacity 240ms cubic-bezier(0, 0, 0.2, 1),
transform 240ms cubic-bezier(0, 0, 0.2, 1),
visibility 0ms linear 0ms;
opacity: 1;
visibility: visible;
pointer-events: unset;
transform: translate3d(0, 0, 0);
}
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 18-18: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-styles/src/css/components/ModalAnimations/modal-animations.css`
around lines 14 - 22, Update the transition declaration in
.pf-v6-c-modal-animated-open to include opacity alongside transform and
visibility, preserving the existing timing and easing so opening the modal fades
it in as well as moving it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deferring this to discuss with PF team during office-hours

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@GAUNSD, understood — deferring this to the PF team office-hours discussion makes sense given it touches the broader animation design intent. Happy to revisit once you have their input; feel free to ping if the direction changes.

- Add missing onClose handler to ModalAnimated example
- Add missing onClose handler to ModalAnimatedProvider example

Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

Modal,Backdrop - Add animation support

1 participant