Skip to main content

Getting Started

1. Define a block

A block needs a label, a Zod schema, and a component to render it.

import { RenderBlocks, RichTextEditor } from '@repo/cms'
import { zaf } from '@repo/form'
import { z } from 'zod'
import ContentBlock from './ContentBlock.svelte'

const blocks = {
content: {
label: 'Content',
schema: z.object({
content: zaf(RichTextEditor.schema, { component: RichTextEditor.Root }),
}),
component: RenderBlocks.renderComponent(ContentBlock, {}),
},
}

2. Edit blocks in a form

Use BlockField.Root inside a form schema with zaf:

import { BlockField } from '@repo/cms'
import { FormField, zaf } from '@repo/form'
import { z } from 'zod'

const pageSchema = z.object({
title: z.string(),
blocks: zaf(z.array(z.any()), {
component: FormField.renderComponent(BlockField.Root, { blocks }),
}),
})

Then render with TypedForm:

<script lang="ts">
import { TypedForm } from '@repo/form'
</script>

<TypedForm.Remote form={createPageForm} schema={pageSchema} />

3. Render blocks on the frontend

<script lang="ts">
import { RenderBlocks } from '@repo/cms'

type Props = { blocks: Record<string, Block>; value: BlockValue[] }
const { blocks, value }: Props = $props()
</script>

<RenderBlocks.Root {blocks} {value} />

Each block's component receives its values as a data prop.

Next steps

  • Blocks -- block types, schemas, rendering
  • Rich Text -- editor setup and schema
  • Assets -- file uploads and asset management
  • SEO -- meta tags and Open Graph