puck icon indicating copy to clipboard operation
puck copied to clipboard

Viewport Menu in Puck Composition Patterns

Open muchisx opened this issue 4 months ago • 3 comments

Description

Currently, Puck provides excellent composition patterns that allow developers to create custom layouts and override components like header, iframe, and outline. However, when using these composition patterns, the native viewport menu (the dropdown that allows switching between different device sizes/viewports) becomes inaccessible or completely disappears from the UI.

This creates a significant limitation for developers who want to customize Puck's layout while maintaining the viewport switching functionality. The viewport menu is a core feature for responsive design editing, and losing access to it when using composition patterns forces developers to choose between customization and functionality.

The expected outcome is to either:

  1. Have access to the viewport menu component so it can be placed in custom compositions
  2. Have pre-built composition components that include viewport functionality

This would allow developers to maintain full viewport switching capabilities while still being able to customize Puck's layout and appearance.

Considerations

  • This affects all developers using Puck's composition patterns who need viewport switching
  • The viewport menu component likely has internal state and logic that needs to be preserved
  • The solution should maintain the existing viewport configuration and behavior
  • The API should be consistent with other Puck composition patterns
  • TypeScript support should be maintained for proper typing
  • The solution should work with custom viewport configurations passed to the main Puck component
  • Backward compatibility should be maintained for existing compositions

Proposals

Proposal 1: Expose Viewport Menu Component

Expose the internal viewport menu component so it can be used in custom compositions:

import { Puck } from '@measured/puck';

// Custom composition with viewport menu
<Puck>
  <div className="custom-layout">
    <div className="custom-header">
      <Puck.ViewportMenu />
      {/* Other custom header content */}
    </div>
    <div className="custom-preview">
      <Puck.Preview />
    </div>
  </div>
</Puck>

Pros:

  • Maximum flexibility for developers
  • Maintains existing composition patterns
  • Allows precise placement of viewport menu
  • Consistent with current API design

Cons:

  • Requires exposing internal components
  • Developers need to handle layout themselves
  • More complex implementation for simple use cases

Proposal 2: ViewportPreview Composition Component

Create a new high-level composition component that includes both preview and viewport menu:

import { Puck} from '@measured/puck';

<Puck>
  <div className="custom-layout">
    <div className="custom-header">
      {/* Custom header content */}
    </div>
    {/* This includes both the Menu and the Preview together */}
    <Puck.ViewportPreview className="custom-preview" />
  </div>
</Puck>

Pros:

  • Simple to use for common cases
  • Maintains viewport functionality out of the box
  • Less prone to implementation errors
  • Good default behavior

Cons:

  • Less flexible than exposing individual components
  • May not fit all layout requirements
  • Fixed positioning of viewport menu relative to preview

Proposal 3: ViewportFrame with Sub-components

Create a ViewportFrame component with sub-components for more granular control:

import { Puck } from '@measured/puck';

<Puck>
  <div className="custom-layout">
    <Puck.ViewportFrame>
      <Puck.ViewportFrame.Menu className="custom-viewport-menu" />
      <Puck.ViewportFrame.Preview className="custom-preview" />
    </Puck.ViewportFrame>
  </div>
</Puck>

Pros:

  • Good balance between flexibility and ease of use
  • Clear component hierarchy
  • Maintains viewport state management
  • Allows custom styling while preserving functionality

Cons:

  • Introduces new component hierarchy
  • More complex than a single component solution

muchisx avatar Aug 05 '25 17:08 muchisx

Hey @muchisx,

This is something that comes up quite a lot, so thank you for opening a feature request to track it.

The current limitation in terms of implementation is that the viewport resizing logic can’t reliably support custom interfaces, because Puck doesn’t know what your editor layout constraints are:
https://github.com/puckeditor/puck/blob/main/packages/core/components/Puck/components/Canvas/index.tsx#L171-L237

The workaround right now is to:

  • Constrain the size of <Puck.Preview> to match the target device dimensions.
  • If the preview exceeds the bounds of your custom UI layout, use transform: scale() to scale it down.

That transform: scale() is also what enables zooming. That said, I agree we should provide a less convoluted way of implementing this logic.

In terms of the options you mentioned, I think Proposal 1 seems the most flexible and consistent with the rest of the composable components API, but I'd like to hear what others have to say about this. Custom UIs are meant for developers to define their own layouts, so that "con" doesn't seem too problematic to me. Normally, if you don’t want to override the layout, you should go with overrides instead.

Speaking of which, this could be paired with an override for viewport controls, which is mentioned and tracked here: #1150.

FedericoBonel avatar Aug 06 '25 06:08 FedericoBonel

@FedericoBonel Do you have an estimate for when we might expect this?

mits87 avatar Sep 09 '25 13:09 mits87

Hey @mits87! not at the moment, but we have it in our radar

FedericoBonel avatar Sep 10 '25 03:09 FedericoBonel