Editor: Add in-editor preview modal - #78599
Conversation
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @dhanson-wp! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| { ! isGeneratingPreview && previewUrl && ( | ||
| <iframe | ||
| className={ `editor-post-preview-modal__iframe is-${ device }-preview` } | ||
| src={ previewUrl } |
There was a problem hiding this comment.
Not really. I wanted to hide the admin bar, but kept the PR focused on just the main interaction to start.
There was a problem hiding this comment.
It's easy to hide with a query arg, so worth doing here:
const src = addQueryArgs( previewUrl, {
wp_site_preview: 1,
} );| .editor-header__post-preview-button { | ||
| @include break-mobile { | ||
| display: none; | ||
| } | ||
| } |
There was a problem hiding this comment.
The current preview buttons are specific for editing and not true previews in context. This PR is intended to replace the Preview in new tab option. I do like making the Preview more prominent, though, because having it in that icon dropdown feels quite hidden. It's not a hard requirement in my mind. We could simply replace the external link and just call it Preview, then it opens the modal.
| role="toolbar" | ||
| aria-label={ __( 'Preview size' ) } | ||
| > | ||
| { DEVICES.map( ( { name, label, icon } ) => ( |
There was a problem hiding this comment.
It might be handy to default to the editor's device preview value (if one is set).
There was a problem hiding this comment.
Like so:
const deviceType = useSelect( ( select ) => {
return select( 'core/editor' ).getDeviceType();
}, [] );| .editor-post-preview-modal { | ||
| .components-modal__content { | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| padding: 0; | ||
| } | ||
|
|
||
| .components-modal__children-container { | ||
| display: flex; | ||
| flex: 1; | ||
| flex-direction: column; | ||
| min-height: 0; | ||
| } | ||
| } |
There was a problem hiding this comment.
It would be best not to override component styles like this, and instead use e.g. Stack inside the modal contents.
| align-items: center; | ||
| align-self: stretch; | ||
| color: $gray-700; | ||
| display: flex; | ||
| flex: 1; | ||
| flex-direction: column; | ||
| gap: $grid-unit-10; | ||
| justify-content: center; | ||
| text-align: center; |
There was a problem hiding this comment.
These styles can be replaced by Stack component from @wordpress/ui.
| display: flex; | ||
| flex: 1; | ||
| justify-content: center; | ||
| min-height: 0; |
There was a problem hiding this comment.
You can use Stack component to replace these styles.
| const DEVICES = [ | ||
| { | ||
| name: 'desktop', | ||
| label: __( 'Desktop' ), | ||
| icon: desktop, | ||
| }, | ||
| { | ||
| name: 'tablet', | ||
| label: __( 'Tablet' ), | ||
| icon: tablet, | ||
| }, | ||
| { | ||
| name: 'mobile', | ||
| label: __( 'Mobile' ), | ||
| icon: mobile, | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
Would be good to share these with the preview dropdown so they're not defined twice (plus a few more times in other places).
They're already drifting here to be slightly different for no reason:
gutenberg/packages/editor/src/components/preview-dropdown/index.js
Lines 98 to 121 in 5eb51cc
|
|
||
| .editor-post-preview-modal__toolbar { | ||
| align-items: center; | ||
| display: flex; |
There was a problem hiding this comment.
You can use Stack component to replace these styles.
| <div | ||
| className="editor-post-preview-modal__error" | ||
| role="alert" | ||
| > | ||
| { __( | ||
| 'The preview could not be generated. Please try again.' | ||
| ) } | ||
| </div> |
There was a problem hiding this comment.
Could use Notice component from @wordpress/ui here.
| { isGeneratingPreview && ( | ||
| <div className="editor-post-preview-modal__loading"> | ||
| <Spinner /> | ||
| <p>{ __( 'Generating preview…' ) }</p> |
There was a problem hiding this comment.
Could use Text component from @wordpress/ui here.
| const [ isGeneratingPreview, setIsGeneratingPreview ] = useState( false ); | ||
| const [ previewUrl, setPreviewUrl ] = useState(); | ||
| const [ previewError, setPreviewError ] = useState(); | ||
| const [ device, setDevice ] = useState( 'desktop' ); |
There was a problem hiding this comment.
I'm not sure how UX would be impacted, but it would be worth testing how it would feel if the selected device is shared between the dropdown and the modal.
| height: calc(100% - #{ $grid-unit-50 * 2 }); | ||
| max-height: none; | ||
| max-width: none; | ||
| width: calc(100% - #{ $grid-unit-50 * 2 }); |
There was a problem hiding this comment.
Do we need these style overrides if you're already setting isFullScreen? Modal also has a size prop in case it helps ("small", "fill", "large", "medium"). Preferably, we don't do component overrides.
| .editor-post-preview-modal__iframe.is-desktop-preview { | ||
| width: min(100%, 1200px); | ||
| } | ||
|
|
||
| .editor-post-preview-modal__iframe.is-tablet-preview { | ||
| width: 781px; | ||
| } | ||
|
|
||
| .editor-post-preview-modal__iframe.is-mobile-preview { | ||
| width: 479px; | ||
| } |
There was a problem hiding this comment.
A bit surprised that these wouldn't come from some shared definition 🤔
If they need to be kept duplicate/separate, would be good to note where else they need to be updated (and vice versa) so that they don't drift.
| <VisuallyHidden render={ <span /> }> | ||
| { __( '(opens in a modal)' ) } | ||
| </VisuallyHidden> |
There was a problem hiding this comment.
Is this accessibility note really needed? I understand it for a new tab but surprised to see it for a modal; I don't think we do that elsewhere in codebase?
| <iframe | ||
| className={ `editor-post-preview-modal__iframe is-${ device }-preview` } | ||
| src={ previewUrl } | ||
| title={ __( 'Post preview' ) } |
There was a problem hiding this comment.
Should this copy adjust based on page/post?
| import DocumentTools from '../document-tools'; | ||
| import HeaderSkeleton from './header-skeleton'; | ||
| import MoreMenu from '../more-menu'; | ||
| import PostPreviewButton from '../post-preview-button'; |
There was a problem hiding this comment.
The button is a stable API exported (and documented here) from the package, so it would be important to continue supporting it. Modal version has to be separate component.
|
I left some comments on implementation, but for the feasibility of this functionality change, I added @mtias and @jasmussen to comment. |



What?
Adds a new in-editor Preview modal to the post and page editor.
Instead of requiring users to open a separate browser tab to preview content, this introduces a modal preview layer that opens on top of the current editor and preserves editing context.
The modal includes device preview controls for:
The existing external preview flow remains available from the View dropdown as “Preview in new tab”.
Why?
Previewing content currently requires leaving the editing flow, usually through “Preview in new tab”. Device emulation, View Post, and previewing unsaved changes are also mixed together in ways that can be confusing.
Related issues/discussions:
This PR explores a concrete implementation for improving that experience by making preview feel like a layer within the editor rather than a full context switch.
It also creates a potential foundation for future preview extensions, such as:
How?
This PR introduces a modal-based preview surface that:
Screenshots
Desktop preview
Tablet preview
Mobile preview
Testing Instructions
Repeat similar checks for:
Accessibility
Things to verify:
Notes / follow-up
This PR does not attempt to solve the entire preview extensibility problem described in #65005, but it may provide a core preview surface that future extensibility work could build on.
Potential follow-up questions:
Validation
npm run test:unit packages/editor/src/components/post-preview-modal-button/test/index.jsnpm run lint:js -- packages/editor/src/components/post-preview-modal-button packages/editor/src/components/header/index.jsnpm run lint:css -- packages/editor/src/components/header/style.scss packages/editor/src/components/post-preview-modal-button/style.scssnpm run buildwp-envfor opening, switching device sizes, internal scroll, closing with Escape, and preserving the existing external preview option.