Activate visual editing

Uniform Canvas offers a visual editing workspace, which facilitates making structural changes directly from within the preview panel. This is available for both compositions and patterns. The Uniform SDK works with Next.js and Nuxt 3.


Visual editing requires preview mode be activated first. The instructions have changed for newer package versions (v18.0.0+), make sure you're using the latest implementation.

Visual editing is enabled by default when using <UniformComposition /> to render your compositions. See the Next.js with enhancers tab for requirements for that use case.

import { UniformComposition } from "@uniformdev/canvas-react"; export default function PageComposition({ data }) { return ( <UniformComposition data={data}> {/* Render additional slots here */} </UniformComposition> ); }

Unlike compositions, patterns don't have a project map node assigned. This means that the application doesn't know which route in your front-end app can preview a pattern.

To make this possible, you need to build a page dedicated to previewing patterns. You'll create a new route in your front-end application (for example, /playground), add <UniformPlayground /> to that page, and then let the SDK know where to redirect the pattern preview requests, using the playgroundPath attribute. See the framework-specific instructions below to learn how to achieve that.

  1. Create a new route for the preview playground, for example: pages/playground.tsx or app/playground/page.tsx.

  2. Add <UniformPlayground /> to the page.

    import { UniformPlayground } from '@uniformdev/canvas-react'; import '../components'; export default function Page() { return <UniformPlayground /> }


    ../components is used as an example here, but you need to import a file that registers all the possible components.

  3. In /api/preview.tsx add the playgroundPath attribute to createPreviewHandler.

    import { createPreviewHandler } from '@uniformdev/canvas-next'; const handler = createPreviewHandler({ playgroundPath: '/playground', // ...otherAttributes }); export default handler;


This is an experimental feature. Subject to breaking changes in future releases.

Patterns are designed for use inside other compositions, but an isolated preview of the pattern might not give an accurate representation of how the pattern will look inside a composition. To improve this, Uniform has introduced a concept of "decorators," which wrap patterns with additional rendering functionality.
Decorators are typically used to stand in for the composition where the pattern will be used, or to add some rendering controls (re-sizer, background color changer, etc.).

To create a decorator, you can use the type UniformPlaygroundDecorator for guidance. The decorator component receives 2 props:

  • children (ReactNode) the rendered pattern, with any prior decorators applied to it.
  • data (RootComponentInstance) the data of the pattern. For instance, this can be used to render a specific mock per component type.

Decorator definition:

import type { UniformPlaygroundDecorator } from '@uniformdev/canvas-react'; export const MyDecorator: UniformPlaygroundDecorator = ({ children, data }) => { return ( <div> <h2>My Decorator 🎉</h2> {children} </div> ); };

Decorator usage:

import '../components'; import { MyDecorator } from '../playgroundDecorators/MyDecorator'; import { UniformPlayground } from '@uniformdev/canvas-react'; const PlaygroundPage = () => { return <UniformPlayground decorators={[MyDecorator]} />; }; export { PlaygroundPage as default };


Resize the container
Demo of the container resizer decorator.


To allow interacting with dynamic decorators even when Page Interactions are disabled, you need to add IS_RENDERED_BY_UNIFORM_ATTRIBUTE to the clickable elements.

import { IS_RENDERED_BY_UNIFORM_ATTRIBUTE } from '@uniformdev/canvas'; import type { UniformPlaygroundDecorator } from '@uniformdev/canvas-react'; import { useState } from 'react'; const widthOptions = [720, 512, 420, 360]; export const ContainerResizer: UniformPlaygroundDecorator = ({ children }) => { const [selectedWidth, setSelectedWidth] = useState<number>(); return ( <div> <div style={{ height: 24, position: 'relative', justifyContent: 'flex-end' }}> <ContainerResizerBar width={undefined} onSelect={setSelectedWidth} /> { => ( <ContainerResizerBar key={width} width={width} onSelect={setSelectedWidth} /> ))} </div> <div style={{ width: selectedWidth, margin: 'auto' }}>{children}</div> </div> ); }; const ContainerResizerBar = ({ width, onSelect, }: { width: number | undefined; onSelect: (width: number | undefined) => void; }) => { return ( <button key={width} style={{ width: width ?? '100%', height: '100%', position: 'absolute', top: 0, left: '50%', transform: 'translateX(-50%)', background: '#ddd', border: '2px solid #999', }} onClick={() => onSelect(width)} {...{ [IS_RENDERED_BY_UNIFORM_ATTRIBUTE]: true }} /> ); };