DocsIntegrating PuckMulti-column Layouts

Multi-column Layouts

Puck supports nested and multi-column layouts across any CSS layout using the slot field.

Slots replace the <DropZone> component component, which will soon be deprecated and removed. For migration notes, see these docs.

Nested components

Add the slot field to your component to create a zone that you can drop components into.

const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => {
        return <Content />;
      },
    },
    Card: {
      render: () => <div>Hello, world</div>,
    },
  },
};
Interactive Demo
Nested components example

Fixed layouts

Combine multiple DropZones to achieve fixed layouts. By default, components inside a DropZone are arranged along the vertical (block) axis.

const config = {
  components: {
    Example: {
      fields: {
        leftColumn: {
          type: "slot",
        },
        rightColumn: {
          type: "slot",
        },
      },
      render: ({ leftColumn: LeftColumn, rightColumn: RightColumn }) => {
        return (
          <div
            style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}
          >
            <LeftColumn />
            <RightColumn />
          </div>
        );
      },
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
Interactive Demo
Fixed layout example

Fluid layouts

Apply the CSS display property to a slot via the style or className props to arrange your components in different layouts. Puck supports drag-and-drop for all display values, including grid and flex.

const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => (
        <Content
          style={{
            // Use CSS grid in this slot
            display: "grid",
            gridTemplateColumns: "2fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
Interactive Demo
Fluid layout using CSS grid

Removing the wrapper

By default, Puck will wrap your components in a div element. For some layouts, you may need to eliminate the wrapping element and treat the child component as a direct descendant of its’ parent slot.

For example, this is required if you wish to use CSS rules like flex-grow, grid-column, or grid-row.

Use the inline component parameter to remove the wrapping element. When using this API, you must also specify which element is draggable by passing the puck.dragRef prop to your element’s ref prop.

const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => (
        <Content
          style={{
            display: "grid",
            gridTemplateColumns: "1fr 1fr 1fr 1fr",
            gridTemplateRows: "1fr 1fr 1fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    Card: {
      inline: true, // Enable inline mode, removing the Puck wrapper
      render: ({ text, spanCol, spanRow, puck }) => (
        <div
          ref={puck.dragRef} // Let Puck know this element is draggable
          style={{
            gridColumn: `span ${spanCol}`,
            gridRow: `span ${spanRow}`,
          }}
        >
          {text}
        </div>
      ),
    },
  },
};
Interactive Demo
Advanced grid example

Restricting components

Use the allow and disallow DropZone props to restrict which components can be dragged into a DropZone.

const config = {
  components: {
    fields: {
      content: {
        type: "slot",
      },
    },
    Example: {
      render: ({ content: Content }) => {
        return <Content allow={["Card"]} />;
      },
    },
  },
};

Combine this with categories to restrict behavior based on your existing groups:

const config = {
  categories: {
    typography: {
      components: ["Card"],
    },
  },
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => {
        return <Content allow={categories.typography.components} />;
      },
    },
  },
};

Setting default props

Use slots with defaultProps to pre-populate it when the component is inserted.

const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      defaultProps: {
        content: [
          {
            type: "Card",
            props: {
              id: "12345",
              text: "Pre-populated",
            },
          },
        ],
      },
      render: ({ content: Content }) => <Content />,
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
Interactive Demo
Fluid layout using CSS grid

Further reading