Multi-column Layouts
Puck supports nested and multi-column layouts across any CSS layout using the slot
field.
<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>,
},
},
};
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>,
},
},
};
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>,
},
},
};
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>
),
},
},
};
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>,
},
},
};