Docs
Extending Puck
Custom Interfaces

Custom Interfaces

Puck uses compositional patterns and UI overrides to enable completely custom editor interfaces.

See a custom interface example (opens in a new tab)

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>
  );
}
Interactive Demo
HeadingBlock
HeadingBlock
Hello, world

Compositional components

Puck exposes its core components, allowing you to compose them together to create new layouts:

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 }}
            />
          ),
        },
      }}
    />
  );
}

Further reading