The self-hosted, drag and drop editor for React.
- 🖱️ Drag and drop: Visual editing for your existing React component library
- 🌐 Integrations: Load your content from a 3rd party headless CMS
- ✍️ Inline editing: Author content directly via puck for convenience
- ⭐️ No vendor lock-in: Self-host or integrate with your existing application
Render the editor:
// Editor.jsx
import { Puck } from "@measured/puck";
import "@measured/puck/dist/index.css";
// Create puck component config
const config = {
components: {
HeadingBlock: {
fields: {
children: {
type: "text",
},
},
render: ({ children }) => {
return <h1>{children}</h1>;
},
},
},
};
// Describe the initial data
const initialData = {
content: [],
root: {},
};
// Save the data to your database
const save = (data) => {};
// Render Puck editor
export function Editor() {
return <Puck config={config} data={initialData} onPublish={save} />;
}
Render the page:
// Page.jsx
import { Render } from "@measured/puck";
import "@measured/puck/dist/index.css";
export function Page() {
return <Render config={config} data={data} />;
}
Install the package
npm i @measured/puck --save
Or generate a puck application using a recipe
npx create-puck-app my-app
Puck is a React component that can be easily integrated into your existing application. We also provide helpful recipes for common use cases:
- next: Next.js app example
Puck can be configured to work with plugins. Plugins can extend the functionality to support novel functionality.
heading-analyzer
: Analyze the heading outline of your page and be warned when you're not respecting WCAG 2 accessibility standards.
The plugin API follows a React paradigm. Each plugin passed to the Puck editor can provide three functions:
renderRoot
(Component
): Render the root node of the preview contentrenderRootFields
(Component
): Render the root fieldsrenderFields
(Component
): Render the fields for the currently selected component
Each render function receives three props:
- children (
ReactNode
): The normal contents of the root or field. You must render this. - state (
AppState
): The current application state, including data and UI state - dispatch (
(action: PuckAction) => void
): The Puck dispatcher, used for making data changes or updating the UI. See the action definitions for a full reference of available mutations.
Here's an example plugin that creates a button to toggle the left side-bar:
const myPlugin = {
renderRootFields: ({ children, dispatch, state }) => (
<div>
{children}
<button
onClick={() => {
dispatch({
type: "setUi",
ui: { leftSideBarVisible: !state.ui.leftSideBarVisible },
});
}}
>
Toggle side-bar
</button>
</div>
),
};
Puck supports custom fields using the custom
field type and render
method.
In this example, we optionally add the <FieldLabel>
component to add a label:
import { FieldLabel } from "@measured/puck";
export const MyComponent: ComponentConfig = {
fields: {
myField: {
type: "custom",
render: ({ field, name, onChange, value }) => {
return (
<FieldLabel label={field.label || name}>
<input
placeholder="Enter text..."
type="text"
name={name}
defaultValue={value}
onChange={(e) => onChange(e.currentTarget.value)}
></input>
</FieldLabel>
);
},
},
},
};
Puck supports creating complex layouts (like multi-column layouts) using the <DropZone>
component.
In this example, we use the <DropZone>
component to render two nested DropZones within another component:
import { DropZone } from "@measured/puck";
export const MyComponent: ComponentConfig = {
render: () => {
return (
<div>
<DropZone zone="first-drop-zone">
<DropZone zone="second-drop-zone">
</div>
)
}
};
You can also do this at the root of your component. This is useful if you have a fixed layout and only want to make certain parts of your page customisable:
import { DropZone, Config } from "@measured/puck";
export const config: Config = {
root: {
render: ({ children }) => {
return (
<div>
{/* children renders the default zone. This can be omitted if necessary. */}
{children}
<div>
<DropZone zone="other-drop-zone">
</div>
</div>
)
}
}
};
The current DropZone implementation has certain rules and limitations:
- You can drag from the component list on the LHS into any DropZone
- You can drag components between DropZones, so long as those DropZones share a parent (also known as area)
- You can't drag between DropZones that don't share a parent (or area)
- Your mouse must be directly over a DropZone for a collision to be detected
The <Puck>
component renders the Puck editor.
- config (
Config
): Puck component configuration - data (
Data
): Initial data to render - onChange (
(Data) => void
[optional]): Callback that triggers when the user makes a change - onPublish (
(Data) => void
[optional]): Callback that triggers when the user hits the "Publish" button - renderHeader (
Component
[optional]): Render function for overriding the Puck header component - renderHeaderActions (
Component
[optional]): Render function for overriding the Puck header actions. Use a fragment. - headerTitle (
string
[optional]): Set the title shown in the header title - headerPath (
string
[optional]): Set a path to show after the header title - plugins (
Plugin[]
[optional]): Array of plugins that can be used to enhance Puck
The <Render>
component renders user-facing UI using Puck data.
- config (
Config
): Puck component configuration - data (
Data
): Data to render
The <DropZone>
component allows you to create advanced layouts, like multi-columns.
- zone (
string
): Identifier for the zone of your component, unique to the parent component - style (
CSSProperties
): Custom inline styles
The Config
object describes which components Puck should render, how they should render and which inputs are available to them.
- root (
object
)- fields (
object
):- title (
Field
): Title of the content, typically used for the page title. - [fieldName] (
Field
): User defined fields, used to describe the input data stored in theroot
key.
- title (
- render (
Component
): Render a React component at the root of your component tree. Useful for defining context providers.
- fields (
- components (
object
): Definitions for each of the components you want to show in the visual editor- [componentName] (
object
)- fields (
Field
): The Field objects describing the input data stored against this component. - render (
Component
): Render function for your React component. Receives props as defined in fields. - defaultProps (
object
[optional]): Default props to pass to your component. Will show in fields.
- fields (
- [componentName] (
A Field
represents a user input field shown in the Puck interface.
- type (
text
|textarea
|number
|select
|radio
|external
|array
|custom
): The input type to render - label (
text
[optional]): A label for the input. Will use the key if not provided. - arrayFields (
object
): Object describing sub-fields for items in anarray
input- [fieldName] (
Field
): The Field objects describing the input data for each item
- [fieldName] (
- getItemSummary (
(object, number) => string
[optional]): Function to get the name of each item when using thearray
orexternal
field types - defaultItemProps (
object
[optional]): Default props to pass to each new item added, when using aarray
field type - options (
object[]
): array of items to render for select or radio inputs- label (
string
) - value (
string
|number
|boolean
)
- label (
- adaptor (
Adaptor
): Content adaptor if using theexternal
input type - adaptorParams (
object
): Paramaters passed to the adaptor - render (
Component
): Render a custom field. Receives the props:- field (
Field
): Field configuration - name (
string
): Name of the field - value (
any
): Value for the field - onChange (
(value: any) => void
): Callback to change the value - readOnly (
boolean
|undefined
): Whether or not the field should be in readOnly mode
- field (
The AppState
object stores the puck application state.
- data (
Data
): The page data currently being rendered - ui (
object
):- leftSideBarVisible (boolean): Whether or not the left side bar is visible
- itemSelector (object): An object describing which item is selected
- arrayState (object): An object describing the internal state of array items
The Data
object stores the puck page data.
- root (
object
):- title (string): Title of the content, typically used for the page title
- [prop] (string): User defined data from
root
fields
- content (
object[]
):- type (string): Component name
- props (object):
- [prop] (string): User defined data from component fields
An Adaptor
can be used to load content from an external content repository, like Strapi.js.
- name (
string
): The human-readable name of the adaptor - fetchList (
(adaptorParams: object) => object
): Fetch a list of content and return an array
NB Using an adaptor on the reserved field name
_data
will spread the resulting data over your object, and lock the overridden fields.
Plugins that can be used to enhance Puck.
- renderRoot (
Component
): Render the root node of the preview content - renderRootFields (
Component
): Render the root fields - renderFields (
Component
): Render the fields for the currently selected component
Puck is developed and maintained by Measured, a small group of industry veterans with decades of experience helping companies solve hard UI problems. We offer consultancy and development services for scale-ups, SMEs and enterprises.
If you need support integrating Puck or creating a beautiful component library, please reach out via our website.
MIT © Measured Co.