Custom Interfaces
Puck uses compositional patterns and UI overrides to enable completely custom editor interfaces.
See a custom interface example
Composition
Custom interfaces can be implementing by providing children
to the <Puck>
component:
import { Puck } from "@measured/puck";
export function Editor() {
return (
<Puck>
<div
style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gridGap: 16 }}
>
<div>
{/* Render the drag-and-drop preview */}
<Puck.Preview />
</div>
<div>
{/* Render the component list */}
<Puck.Components />
</div>
</div>
</Puck>
);
}
Compositional components
Puck exposes its core components, allowing you to compose them together to create new layouts:
<Puck.Components>
- A draggable list of components.<Puck.Fields>
- The fields for the currently selected item.<Puck.Outline>
- An interactive outline.<Puck.Preview>
- A drag-and-drop preview.
The internal UI for these components can be changed by implementing UI overrides or theming.
Helper components
Puck also exposes helper components for even deeper customization:
<Drawer>
- A reference list of items that can be dragged into a droppable area, normally<Puck.Preview>
.<Drawer.Item>
- An item that can be dragged from a<Drawer>
.<FieldLabel>
- A styled label for creating inputs.
Custom components
Access the Puck AppState
via the usePuck
hook to integrate with Puck with custom editor components:
import { Puck, usePuck } from "@measured/puck";
const JSONRenderer = () => {
const { appState } = usePuck();
return <div>{JSON.stringify(appState.data)}</div>;
};
export function Editor() {
return (
<Puck>
<JSONRenderer />
</Puck>
);
}
UI Overrides
UI overrides allow you to change how Puck renders. It can be used with or without compositional components.
Use the overrides
prop to implement an override:
import { Puck } from "@measured/puck";
export function Editor() {
return (
<Puck
// ...
overrides={{
// Render a custom element for each item in the component list
componentItem: ({ name }) => (
<div style={{ backgroundColor: "hotpink" }}>{name}</div>
),
}}
/>
);
}
There are many different overrides available. See the overrides
API reference for the full list.
Custom publish button example
A common use case is to override the Puck header. You can either use the header
override to change the entire header, or use the headerActions
override to inject new controls into the header and change the publish button.
Here’s an example that replaces the default publish button with a custom one:
import { Puck, usePuck } from "@measured/puck";
const save = () => {};
export function Editor() {
return (
<Puck
// ...
overrides={{
headerActions: ({ children }) => {
const { appState } = usePuck();
return (
<>
<button
onClick={() => {
save(appState.data);
}}
>
Save
</button>
{/* Render default header actions, such as the default Button */}
{/*{children}*/}
</>
);
},
}}
/>
);
}
Custom field type example
An advanced use case is overriding all fields of a certain type by specifying the fieldTypes
override.
import { Puck } from "@measured/puck";
export function Editor() {
return (
<Puck
// ...
overrides={{
fieldTypes: {
// Override all text fields with a custom input
text: ({ name, onChange, value }) => (
<input
defaultValue={value}
name={name}
onChange={(e) => onChange(e.currentTarget.value)}
style={{ border: "1px solid black", padding: 4 }}
/>
),
},
}}
/>
);
}