Skip to main content

Block factories

COMET DXP offers factories for common block use cases.

BlocksBlock

The BlocksBlock factory is used to create a block that consists of multiple child blocks.

A BlocksBlock that supports rich text, image, and link list child blocks

API

page-content.block.ts
import { createBlocksBlock } from "@comet/blocks-api";

export const PageContentBlock = createBlocksBlock(
{
supportedBlocks: {
richText: RichTextBlock,
image: DamImageBlock,
linkList: LinkListBlock,
},
},
"PageContent",
);

Admin

PageContentBlock.tsx
import { createBlocksBlock } from "@comet/blocks-admin";

export const PageContentBlock = createBlocksBlock({
name: "PageContent",
supportedBlocks: {
richText: RichTextBlock,
image: DamImageBlock,
linkList: LinkListBlock,
},
});

Site

PageContentBlock.tsx
import { BlocksBlock, PropsWithData, SupportedBlocks } from "@comet/cms-site";
import { PageContentBlockData } from "@src/blocks.generated";

const supportedBlocks: SupportedBlocks = {
richText: (props) => <RichTextBlock data={props} />,
image: (props) => <DamImageBlock data={props} />,
linkList: (props) => <LinkListBlock data={props} />,
};

export function PageContentBlock({ data }: PropsWithData<PageContentBlockData>) {
return <BlocksBlock data={data} supportedBlocks={supportedBlocks} />;
}

ColumnsBlock

The ColumnsBlock factory is used to create a block that displays content in multiple columns. A ColumnsBlock may support different layouts for the same number of columns. For instance, two columns could be represented by three layouts: Same width, weighted left, weighted right.

A ColumnsBlock that supports single-column and two columns layouts

API

columns.block.ts
import { ColumnsBlockFactory, createBlocksBlock } from "@comet/blocks-api";

const ColumnsContentBlock = createBlocksBlock(
{
supportedBlocks: {
space: SpaceBlock,
richText: RichTextBlock,
headline: HeadlineBlock,
image: DamImageBlock,
},
},
"ColumnsContent",
);

export const ColumnsBlock = ColumnsBlockFactory.create(
{
contentBlock: ColumnsContentBlock,
layouts: [{ name: "one-column" }, { name: "two-columns" }],
},
"Columns",
);

Admin

ColumnsBlock.tsx
import {
ColumnsLayoutPreview,
ColumnsLayoutPreviewContent,
ColumnsLayoutPreviewSpacing,
createBlocksBlock,
createColumnsBlock,
} from "@comet/blocks-admin";

const ColumnsContentBlock = createBlocksBlock({
name: "ColumnsContent",
supportedBlocks: {
space: SpaceBlock,
richText: RichTextBlock,
headline: HeadlineBlock,
image: DamImageBlock,
},
});

export const ColumnsBlock = createColumnsBlock({
name: "Columns",
layouts: [
{
name: "one-column",
label: "One column",
columns: 1,
preview: (
<ColumnsLayoutPreview>
<ColumnsLayoutPreviewSpacing width={2} />
<ColumnsLayoutPreviewContent width={20} />
<ColumnsLayoutPreviewSpacing width={2} />
</ColumnsLayoutPreview>
),
},
{
name: "two-columns",
label: "Two columns",
columns: 2,
preview: (
<ColumnsLayoutPreview>
<ColumnsLayoutPreviewContent width={10} />
<ColumnsLayoutPreviewSpacing width={4} />
<ColumnsLayoutPreviewContent width={10} />
</ColumnsLayoutPreview>
),
},
],
contentBlock: ColumnsContentBlock,
});

Site

ColumnsBlock.tsx
import { BlocksBlock, PropsWithData, SupportedBlocks, withPreview } from "@comet/cms-site";
import { ColumnsBlockData, ColumnsContentBlockData } from "@src/blocks.generated";

const supportedBlocks: SupportedBlocks = {
space: (props) => <SpaceBlock data={props} />,
richText: (props) => <RichTextBlock data={props} />,
headline: (props) => <HeadlineBlock data={props} />,
image: (props) => <DamImageBlock data={props} />,
};

function ColumnsContentBlock({ data }: PropsWithData<ColumnsContentBlockData>) {
return <BlocksBlock data={data} supportedBlocks={supportedBlocks} />;
}

export function ColumnsBlock({ data: { layout, columns } }: PropsWithData<ColumnsBlockData>) {
const Root = layout === "one-column" ? OneColumnRoot : TwoColumnRoot;
return (
<Root>
{columns.map((column) => (
<ColumnsContentBlock key={column.key} data={column.props} />
))}
</Root>
);
}

CompositeBlock (Admin only)

The CompositeBlock factory composes several child blocks and fields into a block. For most of your blocks, you will use this factory to create the block in the admin.

FullWidthImageBlock.tsx
import { createCompositeBlock } from "@comet/blocks-admin";

export const FullWidthImageBlock = createCompositeBlock({
name: "FullWidthImage",
displayName: (
<FormattedMessage id="cometDemo.blocks.fullWidthImage" defaultMessage="Full Width Image" />
),
category: BlockCategory.Media,
blocks: {
image: {
block: DamImageBlock,
title: <FormattedMessage id="cometDemo.generic.image" defaultMessage="Image" />,
paper: true,
},
content: {
block: FullWidthImageContentBlock,
title: <FormattedMessage id="cometDemo.generic.content" defaultMessage="Content" />,
},
},
});

FinalFormBlock (Admin only)

The FinalFormBlock factory can be used to convert a block's AdminComponent API to the Final Form Field API. See Using blocks in forms for more details.

ImageLinkBlock

The ImageLinkBlock factory can be used to combine an image and a link block. It requires a link block to be set. An image block can be set. If none is set, it will default to PixelImageBlock.

An ImageLinkBlock

API

image-link.block.ts
import { createImageLinkBlock, DamImageBlock } from "@comet/cms-api";
import { LinkBlock } from "@src/common/blocks/linkBlock/link.block";

export const ImageLinkBlock = createImageLinkBlock({ link: LinkBlock, image: DamImageBlock });

Admin

ImageLinkBlock.tsx
import { createImageLinkBlock, DamImageBlock } from "@comet/cms-admin";
import { LinkBlock } from "@src/common/blocks/LinkBlock";

export const ImageLinkBlock = createImageLinkBlock({ link: LinkBlock, image: DamImageBlock });

Site

ImageLinkBlock.tsx
import { PropsWithData } from "@comet/cms-site";
import { ImageLinkBlockData } from "@src/blocks.generated";
import { DamImageBlock } from "@src/blocks/DamImageBlock";
import { LinkBlock } from "@src/blocks/LinkBlock";

export function ImageLinkBlock({ data: { link, image } }: PropsWithData<ImageLinkBlockData>) {
return (
<LinkBlock data={link}>
<DamImageBlock data={image} aspectRatio="1x1" />
</LinkBlock>
);
}

LinkBlock

The LinkBlock factory is a specialized OneOfBlock factory that supports setting a title.

A LinkBlock with a title field

API

link.block.ts
import { createLinkBlock } from "@comet/cms-api";

export const LinkBlock = createLinkBlock({
supportedBlocks: {
internal: InternalLinkBlock,
external: ExternalLinkBlock,
news: NewsLinkBlock,
damFileDownload: DamFileDownloadLinkBlock,
phone: PhoneLinkBlock,
email: EmailLinkBlock,
},
});

Admin

LinkBlock.tsx
import { createLinkBlock } from "@comet/cms-admin";

export const LinkBlock = createLinkBlock({
supportedBlocks: {
internal: InternalLinkBlock,
external: ExternalLinkBlock,
news: NewsLinkBlock,
damFileDownload: DamFileDownloadLinkBlock,
email: EmailLinkBlock,
phone: PhoneLinkBlock,
},
});

Site

LinkBlock.tsx
"use client";
import { OneOfBlock, PropsWithData, SupportedBlocks } from "@comet/cms-site";
import { LinkBlockData } from "@src/blocks.generated";

const supportedBlocks: SupportedBlocks = {
internal: ({ children, title, className, ...props }) => (
<InternalLinkBlock data={props} title={title} className={className}>
{children}
</InternalLinkBlock>
),
external: ({ children, title, className, ...props }) => (
<ExternalLinkBlock data={props} title={title} className={className}>
{children}
</ExternalLinkBlock>
),
news: ({ children, title, className, ...props }) => (
<NewsLinkBlock data={props} title={title} className={className}>
{children}
</NewsLinkBlock>
),
damFileDownload: ({ children, title, className, ...props }) => (
<DamFileDownloadLinkBlock data={props} title={title} className={className}>
{children}
</DamFileDownloadLinkBlock>
),
email: ({ children, title, className, ...props }) => (
<EmailLinkBlock data={props} title={title} className={className}>
{children}
</EmailLinkBlock>
),
phone: ({ children, title, className, ...props }) => (
<PhoneLinkBlock data={props} title={title} className={className}>
{children}
</PhoneLinkBlock>
),
};

interface LinkBlockProps extends PropsWithChildren<PropsWithData<LinkBlockData>> {
className?: string;
}

export function LinkBlock({ data, children, className }: LinkBlockProps) {
return (
<OneOfBlock data={data} supportedBlocks={supportedBlocks} className={className}>
{children}
</OneOfBlock>
);
}

ListBlock

Similar to the BlocksBlock factory, the ListBlock factory is used to create a block that consists of multiple child blocks. The main difference is that the ListBlock factory only supports a single child block type.

A ListBlock based on a link block

API

link-list.block.ts
import { createListBlock } from "@comet/blocks-api";

export const LinkListBlock = createListBlock({ block: TextLinkBlock }, "LinkList");

Admin

LinkListBlock.tsx
import { createListBlock } from "@comet/blocks-admin";

export const LinkListBlock = createListBlock({
name: "LinkList",
block: TextLinkBlock,
});

Site

LinkListBlock.tsx
import { ListBlock, PropsWithData } from "@comet/cms-site";
import { LinkListBlockData } from "@src/blocks.generated";

export function LinkListBlock({ data }: PropsWithData<LinkListBlockData>) {
return <ListBlock data={data} block={(props) => <TextLinkBlock data={props} />} />;
}

OneOfBlock

The OneOfBlock factory is used to create a block that can be one of several child blocks. Think of it as a switch or select.

A link block that is based on an OneOfBlock to support both internal and external links

API

link.block.ts
import { createOneOfBlock } from "@comet/blocks-api";

export const LinkBlock = createOneOfBlock(
{
supportedBlocks: {
internal: InternalLinkBlock,
external: ExternalLinkBlock,
news: NewsLinkBlock,
},
allowEmpty: false,
},
"Link",
);

Admin

LinkBlock.tsx
import { createOneOfBlock } from "@comet/blocks-admin";

export const LinkBlock = createOneOfBlock({
supportedBlocks: {
internal: InternalLinkBlock,
external: ExternalLinkBlock,
news: NewsLinkBlock,
},
});

Site

LinkBlock.tsx
import { OneOfBlock, PropsWithData, SupportedBlocks } from "@comet/cms-site";
import { LinkBlockData } from "@src/blocks.generated";

const supportedBlocks: SupportedBlocks = {
internal: ({ children, ...props }) => (
<InternalLinkBlock data={props}>{children}</InternalLinkBlock>
),
external: ({ children, ...props }) => (
<ExternalLinkBlock data={props}>{children}</ExternalLinkBlock>
),
news: ({ children, ...props }) => <NewsLinkBlock data={props}>{children}</NewsLinkBlock>,
};

interface LinkBlockProps extends PropsWithData<LinkBlockData> {
children: React.ReactElement;
}

export function LinkBlock({ data, children }: LinkBlockProps) {
return (
<OneOfBlock data={data} supportedBlocks={supportedBlocks}>
{children}
</OneOfBlock>
);
}

OptionalBlock

The OptionalBlock factory is used to make a block optional. Use it when some content is optional.

note

Prefer reacting to empty states (e.g., hiding a block when no image is selected) over using an OptionalBlock. Using an OptionalBlock adds additional complexity for users by requiring them to toggle the visibility.

A full-width image block with optional content using an OptionalBlock

API

optional-image.block.ts
import { createOptionalBlock } from "@comet/blocks-api";

export const OptionalImageBlock = createOptionalBlock(ImageBlock);

Admin

OptionalImageBlock.tsx
import { createOptionalBlock } from "@comet/blocks-admin";

export const OptionalImageBlock = createOptionalBlock(ImageBlock);

Site

LinkBlock.tsx
import { OptionalBlock, PropsWithData } from "@comet/cms-site";
import { OptionalImageBlockData } from "@src/blocks.generated";

export function OptionalImageBlock({ data }: PropsWithData<OptionalImageBlockData>) {
return <OptionalBlock block={(props) => <ImageBlock data={props} />} data={data} />;
}

RichTextBlock

The RichTextBlock factory can be used to create a block with a rich text. It requires a link block to be set.

A RichTextBlock

API

rich-text.block.ts
import { createRichTextBlock } from "@comet/blocks-api";

import { LinkBlock } from "./linkBlock/link.block";

export const RichTextBlock = createRichTextBlock({ link: LinkBlock });

Admin

RichTextBlock.tsx
import { createRichTextBlock } from "@comet/cms-admin";

import { LinkBlock } from "./LinkBlock";

export const RichTextBlock = createRichTextBlock({ link: LinkBlock });

Site

RichTextBlock.tsx
import { hasRichTextBlockContent, PreviewSkeleton, PropsWithData } from "@comet/cms-site";
import { LinkBlockData, RichTextBlockData } from "@src/blocks.generated";
import redraft, { Renderers } from "redraft";

import { LinkBlock } from "./LinkBlock";

const defaultRenderers: Renderers = {
inline: {
BOLD: (children, { key }) => <strong key={key}>{children}</strong>,
ITALIC: (children, { key }) => <em key={key}>{children}</em>,
},
blocks: {
unstyled: (children, { keys }) =>
children.map((child, idx) => <p key={keys[idx]}>{child}</p>),
"header-one": (children, { keys }) =>
children.map((child, idx) => <h1 key={keys[idx]}>{child}</h1>),
"header-two": (children, { keys }) =>
children.map((child, idx) => <h2 key={keys[idx]}>{child}</h2>),
"header-three": (children, { keys }) =>
children.map((child, idx) => <h3 key={keys[idx]}>{child}</h3>),
"header-four": (children, { keys }) =>
children.map((child, idx) => <h4 key={keys[idx]}>{child}</h4>),
"header-five": (children, { keys }) =>
children.map((child, idx) => <h5 key={keys[idx]}>{child}</h5>),
"header-six": (children, { keys }) =>
children.map((child, idx) => <h6 key={keys[idx]}>{child}</h6>),
},
entities: {
LINK: (children, data, { key }) => {
return (
<LinkBlock key={key} data={data as LinkBlockData}>
{children}
</LinkBlock>
);
},
},
};

interface RichTextBlockProps extends PropsWithData<RichTextBlockData> {
renderers?: Renderers;
}

export function RichTextBlock({ data, renderers = defaultRenderers }: RichTextBlockProps) {
const rendered = redraft(data.draftContent, renderers);

return (
<PreviewSkeleton title="RichText" type="rows" hasContent={hasRichTextBlockContent(data)}>
{rendered}
</PreviewSkeleton>
);
}

SeoBlock

The SeoBlock factory can be used to create a block for common SEO options, e.g. HTML title or meta description. An image block can be set for the Open Graph image. If none is set, it will default to PixelImageBlock.

A SeoBlock

API

seo.block.ts
import { createSeoBlock } from "@comet/cms-api";

export const SeoBlock = createSeoBlock();

Admin

SeoBlock.tsx
import { createSeoBlock } from "@comet/cms-admin";

export const SeoBlock = createSeoBlock();

Site (Pages Router only)

Page.tsx
import { SeoBlock } from "@comet/cms-site";

<SeoBlock data={document.seo} title="Custom title" />;

SpaceBlock

The SpaceBlock factory can be used to create a block where a spacing can be selected from a list of available options.

A SpaceBlock with a list of spacings

API

space.block.ts
import { createSpaceBlock } from "@comet/blocks-api";

export enum Spacing {
d150 = "d150",
d200 = "d200",
d250 = "d250",
d300 = "d300",
d350 = "d350",
d400 = "d400",
d450 = "d450",
d500 = "d500",
d550 = "d550",
d600 = "d600",
}

export const SpaceBlock = createSpaceBlock({ spacing: Spacing }, "DemoSpace");

Admin

SpaceBlock.tsx
import { createSpaceBlock } from "@comet/blocks-admin";

const options: { value: string; label: string }[] = [
{ value: "d150", label: "Dynamic 150" },
{ value: "d200", label: "Dynamic 200" },
{ value: "d250", label: "Dynamic 250" },
{ value: "d300", label: "Dynamic 300" },
{ value: "d350", label: "Dynamic 350" },
{ value: "d400", label: "Dynamic 400" },
{ value: "d450", label: "Dynamic 450" },
{ value: "d500", label: "Dynamic 500" },
{ value: "d550", label: "Dynamic 550" },
{ value: "d600", label: "Dynamic 600" },
];

export const SpaceBlock = createSpaceBlock<string>({ defaultValue: options[0].value, options });

Site

SpaceBlock.tsx
import { PropsWithData } from "@comet/cms-site";
import { SpaceBlockData } from "@src/blocks.generated";

const spaceMapping: Record<string, number> = {
d150: 10,
d200: 20,
d250: 40,
d300: 60,
d350: 80,
d400: 100,
d450: 150,
d500: 200,
d550: 250,
d600: 300,
};

export function SpaceBlock({ data: { spacing } }: PropsWithData<SpaceBlockData>) {
return <div style={{ height: `${spaceMapping[spacing]}px` }} />;
}

TextImageBlock

The TextImageBlock factory can be used to combine a text and an image block. It supports setting the image position and aspect ratio.

A TextImageBlock

API

text-image.block.ts
import { createTextImageBlock, DamImageBlock } from "@comet/cms-api";
import { RichTextBlock } from "@src/common/blocks/rich-text.block";

export const TextImageBlock = createTextImageBlock({ text: RichTextBlock, image: DamImageBlock });

Admin

TextImageBlock.tsx
import { createTextImageBlock, DamImageBlock } from "@comet/cms-admin";
import { RichTextBlock } from "@src/common/blocks/RichTextBlock";

export const TextImageBlock = createTextImageBlock({ text: RichTextBlock, image: DamImageBlock });

Site

TextImageBlock.tsx
import { PropsWithData } from "@comet/cms-site";
import { TextImageBlockData } from "@src/blocks.generated";

import { DamImageBlock } from "./DamImageBlock";
import { RichTextBlock } from "./RichTextBlock";

export function TextImageBlock({
data: { text, image, imageAspectRatio, imagePosition },
}: PropsWithData<TextImageBlockData>) {
return (
<>
{imagePosition === "left" && (
<DamImageBlock data={image} aspectRatio={imageAspectRatio} sizes="50vw" />
)}
<RichTextBlock data={text} />
{imagePosition === "right" && (
<DamImageBlock data={image} aspectRatio={imageAspectRatio} sizes="50vw" />
)}
</>
);
}

TextLinkBlock

The TextLinkBlock factory can be used to combine a text field and a link block.

A TextLinkBlock

API

text-link.block.ts
import { createTextLinkBlock } from "@comet/blocks-api";

import { LinkBlock } from "./linkBlock/link.block";

export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock });

Admin

TextLinkBlock.tsx
import { createTextLinkBlock } from "@comet/cms-admin";

import { LinkBlock } from "./LinkBlock";

export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock });

Site

TextLinkBlock.tsx
import { PropsWithData } from "@comet/cms-site";
import { DemoTextLinkBlockData } from "@src/blocks.generated";

import { LinkBlock } from "./LinkBlock";

export function TextLinkBlock({ data: { link, text } }: PropsWithData<DemoTextLinkBlockData>) {
return <LinkBlock data={link}>{text}</LinkBlock>;
}