figma-code-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/bin/install.js +328 -0
- package/knowledge/README.md +62 -0
- package/knowledge/css-strategy.md +973 -0
- package/knowledge/design-to-code-assets.md +855 -0
- package/knowledge/design-to-code-layout.md +929 -0
- package/knowledge/design-to-code-semantic.md +1085 -0
- package/knowledge/design-to-code-typography.md +1003 -0
- package/knowledge/design-to-code-visual.md +1145 -0
- package/knowledge/design-tokens-variables.md +1261 -0
- package/knowledge/design-tokens.md +960 -0
- package/knowledge/figma-api-devmode.md +894 -0
- package/knowledge/figma-api-plugin.md +920 -0
- package/knowledge/figma-api-rest.md +742 -0
- package/knowledge/figma-api-variables.md +848 -0
- package/knowledge/figma-api-webhooks.md +876 -0
- package/knowledge/payload-blocks.md +1184 -0
- package/knowledge/payload-figma-mapping.md +1210 -0
- package/knowledge/payload-visual-builder.md +1004 -0
- package/knowledge/plugin-architecture.md +1176 -0
- package/knowledge/plugin-best-practices.md +1206 -0
- package/knowledge/plugin-codegen.md +1313 -0
- package/package.json +31 -0
- package/skills/README.md +103 -0
- package/skills/audit-plugin/SKILL.md +244 -0
- package/skills/build-codegen-plugin/SKILL.md +279 -0
- package/skills/build-importer/SKILL.md +320 -0
- package/skills/build-plugin/SKILL.md +199 -0
- package/skills/build-token-pipeline/SKILL.md +363 -0
- package/skills/ref-html/SKILL.md +290 -0
- package/skills/ref-layout/SKILL.md +150 -0
- package/skills/ref-payload-block/SKILL.md +415 -0
- package/skills/ref-react/SKILL.md +222 -0
- package/skills/ref-tokens/SKILL.md +347 -0
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
# PayloadCMS Block System
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Authoritative reference for the PayloadCMS block system. Documents the complete block architecture, field types, reusable field factories, block catalog, container nesting model, block rendering patterns, collection integration, Lexical editor configuration, and design token consumption. Encodes production patterns from a PayloadCMS 3.x + Next.js application with a visual builder plugin and container-first block architecture.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Reference this module when you need to:
|
|
10
|
+
|
|
11
|
+
- Build new PayloadCMS blocks following established patterns
|
|
12
|
+
- Map Figma components to PayloadCMS block types (see `payload-figma-mapping.md`)
|
|
13
|
+
- Understand the tab-based field organization pattern used across all blocks
|
|
14
|
+
- Add or modify reusable field factories (imageTabs, linkGroup, layoutMeta)
|
|
15
|
+
- Configure the Container block's nesting, layout, and alignment options
|
|
16
|
+
- Extend the block rendering registry with new block types
|
|
17
|
+
- Understand how blocks consume design tokens via CSS Modules
|
|
18
|
+
- Configure Lexical rich text editors with restricted or full feature sets
|
|
19
|
+
- Integrate blocks into Pages or other collections via the `blocks` field type
|
|
20
|
+
- Debug block schema recursion or self-nesting issues
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Content
|
|
25
|
+
|
|
26
|
+
### 1. Block Architecture Overview
|
|
27
|
+
|
|
28
|
+
PayloadCMS blocks are modular, reusable content units that editors compose into page layouts. Each block is a TypeScript object conforming to the `Block` type from `payload`, with a unique slug, a label, and a field array that defines the block's data schema.
|
|
29
|
+
|
|
30
|
+
#### Block Type Definition
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { Block } from 'payload'
|
|
34
|
+
|
|
35
|
+
export const Hero: Block = {
|
|
36
|
+
slug: 'hero',
|
|
37
|
+
fields: [
|
|
38
|
+
{
|
|
39
|
+
type: 'tabs',
|
|
40
|
+
tabs: [
|
|
41
|
+
{ label: 'Content', name: 'content', fields: [...] },
|
|
42
|
+
{ label: 'Media', fields: [...] },
|
|
43
|
+
{ label: 'Settings', fields: [...] },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Every block exports a `Block` constant from a `config.ts` file within its directory.
|
|
51
|
+
|
|
52
|
+
#### Slug Naming Convention
|
|
53
|
+
|
|
54
|
+
Block slugs use camelCase and are unique across the entire Payload configuration:
|
|
55
|
+
|
|
56
|
+
| Block | Slug |
|
|
57
|
+
|-------|------|
|
|
58
|
+
| Hero | `hero` |
|
|
59
|
+
| Card | `card` |
|
|
60
|
+
| Button | `button` |
|
|
61
|
+
| RichText | `richText` |
|
|
62
|
+
| Media | `media` |
|
|
63
|
+
| Video | `video` |
|
|
64
|
+
| Accordion | `accordion` |
|
|
65
|
+
| Stats | `stats` |
|
|
66
|
+
| Testimonial | `testimonial` |
|
|
67
|
+
| CallToAction | `callToAction` |
|
|
68
|
+
| StickyCTA | `stickyCTA` |
|
|
69
|
+
| SubNavigation | `subnavigation` |
|
|
70
|
+
| Container | `container` |
|
|
71
|
+
| Carousel | `carousel` |
|
|
72
|
+
| Tabs | `tabs` |
|
|
73
|
+
| Grid | `grid` |
|
|
74
|
+
| Form | `form` |
|
|
75
|
+
| Code | `code` |
|
|
76
|
+
| SubAccordion | `inneraccordion` |
|
|
77
|
+
|
|
78
|
+
#### Tab-Based Field Organization
|
|
79
|
+
|
|
80
|
+
All blocks organize their fields using the `tabs` field type. This provides a consistent editing experience where content, media, CTA actions, and settings are separated into distinct tab panels.
|
|
81
|
+
|
|
82
|
+
The standard tab pattern is:
|
|
83
|
+
|
|
84
|
+
1. **Content tab** -- Primary content fields (richText, text, arrays)
|
|
85
|
+
2. **Media/Image tab** -- Image upload via `imageFields` factory
|
|
86
|
+
3. **CTA tab** -- Call-to-action links and buttons (when applicable)
|
|
87
|
+
4. **Settings tab** -- Block configuration (className, layoutMeta, type variants)
|
|
88
|
+
|
|
89
|
+
Not every block has all four tabs. The minimum is two: a content-focused tab and a Settings tab.
|
|
90
|
+
|
|
91
|
+
#### Directory Structure
|
|
92
|
+
|
|
93
|
+
Blocks are organized by category under `src/admin/blocks/`:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
src/admin/blocks/
|
|
97
|
+
content/
|
|
98
|
+
Accordion/config.ts, layoutTab.ts, settingTab.ts, container.ts
|
|
99
|
+
Button/config.ts, ctaTab.ts, settingTab.ts
|
|
100
|
+
CallToAction/config.ts, settingTab.ts
|
|
101
|
+
Card/config.ts, ctaTab.ts, settingTab.ts
|
|
102
|
+
Hero/config.ts, settingTabs.ts
|
|
103
|
+
Media/config.ts, settingTab.ts
|
|
104
|
+
RichText/config.ts, settingTab.ts
|
|
105
|
+
StatCard/config.ts, settingTabs.ts
|
|
106
|
+
StickyCTA/config.ts, ctaTab.ts, settingTabs.ts
|
|
107
|
+
SubNavigation/config.ts, navItemTabs.ts, settingTabs.ts
|
|
108
|
+
Testimonial/config.ts, settingTabs.ts
|
|
109
|
+
Video/config.ts, customVideoblockField.ts, settingTabs.ts
|
|
110
|
+
layout/
|
|
111
|
+
Carousel/config.ts, carouselCard.ts, settingTabs.ts
|
|
112
|
+
Container/config.ts, settingTabs.ts
|
|
113
|
+
Grid/config.ts, settingTabs.ts
|
|
114
|
+
Tabs/config.ts, tabs.ts, container.ts, settingTabs.ts
|
|
115
|
+
FormEmbed/config.ts, settingTabs.ts
|
|
116
|
+
Code/config.ts, Component.tsx, Component.client.tsx, CopyButton.tsx
|
|
117
|
+
RenderBlocks.tsx
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Each block's `config.ts` is the entry point. Supporting files (settingTab, ctaTab, layoutTab) export individual `Field` or `Tab` definitions that are composed into the config.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 2. Field Types Reference
|
|
125
|
+
|
|
126
|
+
PayloadCMS provides a rich set of field types. The following are commonly used across blocks.
|
|
127
|
+
|
|
128
|
+
#### Core Field Types
|
|
129
|
+
|
|
130
|
+
| Type | Purpose | Example Usage |
|
|
131
|
+
|------|---------|---------------|
|
|
132
|
+
| `text` | Single-line string input | Titles, labels, slugs, URLs, className |
|
|
133
|
+
| `richText` | Lexical editor for structured content | Block body content, descriptions |
|
|
134
|
+
| `upload` | File upload linked to a collection | Images via `relationTo: 'media'` |
|
|
135
|
+
| `relationship` | Reference to another document | Internal links to `pages`, modal references |
|
|
136
|
+
| `group` | Nested field container (no repetition) | Settings groups, CTA action groups |
|
|
137
|
+
| `array` | Repeatable list of field sets | Navigation items, carousel cards, tab items |
|
|
138
|
+
| `select` | Dropdown single-choice | HTML tag selection, link type, action type |
|
|
139
|
+
| `radio` | Radio button single-choice | Layout direction, impact level, CTA type |
|
|
140
|
+
| `checkbox` | Boolean toggle | Open in new tab, hidden, default state |
|
|
141
|
+
| `number` | Numeric input | Scroll offset, video dimensions, column count |
|
|
142
|
+
| `tabs` | Tabbed field organization | Every block's top-level field structure |
|
|
143
|
+
| `row` | Horizontal field layout | Side-by-side fields in admin UI |
|
|
144
|
+
| `blocks` | Nested block composition | Container children, accordion content |
|
|
145
|
+
| `code` | Code editor with syntax highlighting | Code block content |
|
|
146
|
+
| `json` | Raw JSON with custom component | Custom video field, form select |
|
|
147
|
+
|
|
148
|
+
#### The `tabs` Field Type
|
|
149
|
+
|
|
150
|
+
The `tabs` type is the primary organizational pattern. It creates a tabbed interface in the admin panel:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
{
|
|
154
|
+
type: 'tabs',
|
|
155
|
+
tabs: [
|
|
156
|
+
{
|
|
157
|
+
label: 'Content', // Tab display name
|
|
158
|
+
name: 'content', // Optional: creates named data path
|
|
159
|
+
fields: [...] // Fields within this tab
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: 'Settings', // No name = fields stored at block root
|
|
163
|
+
fields: [...]
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
When a tab has a `name` property, its fields are nested under that path in the data (e.g., `block.content.richText`). When a tab has no `name`, fields are stored at the block root level.
|
|
170
|
+
|
|
171
|
+
#### The `blocks` Field Type (Nested Composition)
|
|
172
|
+
|
|
173
|
+
The `blocks` field enables block nesting -- a block can contain other blocks:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
{
|
|
177
|
+
type: 'blocks',
|
|
178
|
+
name: 'blocks',
|
|
179
|
+
label: false,
|
|
180
|
+
blocks: [Card, RichText, Button, ...],
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This is used by Container, Accordion, and Tabs to hold child blocks. The `blocks` array defines which block types are allowed as children.
|
|
185
|
+
|
|
186
|
+
#### Conditional Field Display
|
|
187
|
+
|
|
188
|
+
Many fields use the `admin.condition` function to show/hide based on sibling field values:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
{
|
|
192
|
+
name: 'externalLink',
|
|
193
|
+
type: 'text',
|
|
194
|
+
admin: {
|
|
195
|
+
condition: (data, siblingData) => siblingData.ctaAction === 'external',
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This pattern is used extensively for link type toggles (internal vs. external), CTA type toggles (button vs. link), and layout-specific fields (grid column count).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### 3. Reusable Field Factories
|
|
205
|
+
|
|
206
|
+
The recommended approach extracts common field patterns into reusable factories stored in `src/admin/fields/`.
|
|
207
|
+
|
|
208
|
+
#### `imageFields` (imageTabs.ts)
|
|
209
|
+
|
|
210
|
+
A group field that provides a standard image upload interface.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { Field } from 'payload'
|
|
214
|
+
|
|
215
|
+
export const imageFields: Field = {
|
|
216
|
+
name: 'image',
|
|
217
|
+
type: 'group',
|
|
218
|
+
label: false,
|
|
219
|
+
fields: [
|
|
220
|
+
{
|
|
221
|
+
name: 'image',
|
|
222
|
+
label: 'Image',
|
|
223
|
+
type: 'upload',
|
|
224
|
+
relationTo: 'media',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Usage:** Used in Hero (Media tab), Card (Image tab), Media (Image tab), CallToAction (Image tab), Testimonial (Image/Video tab), Grid (Image tab).
|
|
231
|
+
|
|
232
|
+
**Data path:** `block.image.image` (the group wraps the upload field).
|
|
233
|
+
|
|
234
|
+
#### `linkGroup()` (linkGroup.ts)
|
|
235
|
+
|
|
236
|
+
A factory function that generates an array field containing link items. Each link item uses the `link()` factory.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
type LinkGroupType = (options?: {
|
|
240
|
+
appearances?: LinkAppearances[] | false
|
|
241
|
+
overrides?: Partial<ArrayField>
|
|
242
|
+
}) => Field
|
|
243
|
+
|
|
244
|
+
export const linkGroup: LinkGroupType = ({ appearances, overrides = {} } = {}) => {
|
|
245
|
+
const generatedLinkGroup: Field = {
|
|
246
|
+
name: 'links',
|
|
247
|
+
type: 'array',
|
|
248
|
+
fields: [link({ appearances })],
|
|
249
|
+
admin: { initCollapsed: true },
|
|
250
|
+
}
|
|
251
|
+
return deepMerge(generatedLinkGroup, overrides)
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Parameters:**
|
|
256
|
+
- `appearances` -- Controls link appearance options (`'default' | 'outline'`). Pass `false` to hide appearance selector.
|
|
257
|
+
- `overrides` -- Deep-merged into the generated field. Used to customize `name`, `label`, `maxRows`.
|
|
258
|
+
|
|
259
|
+
**Usage in Hero:**
|
|
260
|
+
```typescript
|
|
261
|
+
linkGroup({
|
|
262
|
+
overrides: { maxRows: 3, name: 'buttonGroup', label: 'Button' },
|
|
263
|
+
appearances: false,
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### `link()` (link.ts)
|
|
268
|
+
|
|
269
|
+
A factory function that generates a single link group field with internal/external toggle, label, new tab checkbox, and optional appearance selector.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
type LinkType = (options?: {
|
|
273
|
+
appearances?: LinkAppearances[] | false
|
|
274
|
+
disableLabel?: boolean
|
|
275
|
+
overrides?: Record<string, unknown>
|
|
276
|
+
}) => Field
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Generated fields:**
|
|
280
|
+
- `type` (radio) -- `'reference'` (internal) or `'custom'` (external URL)
|
|
281
|
+
- `newTab` (checkbox) -- Open in new tab
|
|
282
|
+
- `reference` (relationship) -- Internal page link (shown when type is reference)
|
|
283
|
+
- `url` (text) -- External URL (shown when type is custom)
|
|
284
|
+
- `label` (text) -- Link display text (unless `disableLabel: true`)
|
|
285
|
+
- `appearance` (select) -- Visual style: `'default'` or `'outline'` (unless appearances is false)
|
|
286
|
+
|
|
287
|
+
#### `ClassName` (className/index.ts)
|
|
288
|
+
|
|
289
|
+
A text field for custom CSS class names.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
export const ClassName: Field = {
|
|
293
|
+
name: 'className',
|
|
294
|
+
label: 'Custom Classname',
|
|
295
|
+
type: 'text',
|
|
296
|
+
admin: {
|
|
297
|
+
placeholder: 'e.g., custom-style',
|
|
298
|
+
description: 'Enter a class name for custom styling or interactivity.',
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Usage:** Included in nearly every block's Settings tab. Enables editors to add custom CSS classes for per-instance styling or JavaScript hooks.
|
|
304
|
+
|
|
305
|
+
#### `LayoutMeta` (layoutMeta.ts)
|
|
306
|
+
|
|
307
|
+
A group field providing spacing and visibility controls for every block.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
export const LayoutMeta: Field = {
|
|
311
|
+
name: 'layoutMeta',
|
|
312
|
+
type: 'group',
|
|
313
|
+
label: 'Layout',
|
|
314
|
+
fields: [
|
|
315
|
+
{ type: 'row', fields: [marginTop, marginBottom] },
|
|
316
|
+
{ type: 'row', fields: [paddingTop, paddingBottom] },
|
|
317
|
+
{ name: 'hidden', type: 'checkbox', label: 'Hide this block', defaultValue: false },
|
|
318
|
+
],
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Spacing options** use Tailwind utility class values:
|
|
323
|
+
|
|
324
|
+
| Label | Margin Top | Margin Bottom | Padding Top | Padding Bottom |
|
|
325
|
+
|-------|-----------|---------------|-------------|----------------|
|
|
326
|
+
| None | `mt-0` | `mb-0` | `pt-0` | `pb-0` |
|
|
327
|
+
| XS (0.5rem) | `mt-2` | `mb-2` | `pt-2` | `pb-2` |
|
|
328
|
+
| SM (1rem) | `mt-4` | `mb-4` | `pt-4` | `pb-4` |
|
|
329
|
+
| MD (1.5rem) | `mt-6` | `mb-6` | `pt-6` | `pb-6` |
|
|
330
|
+
| LG (2rem) | `mt-8` | `mb-8` | `pt-8` | `pb-8` |
|
|
331
|
+
| XL (3rem) | `mt-12` | `mb-12` | `pt-12` | `pb-12` |
|
|
332
|
+
| 2XL (4rem) | `mt-16` | `mb-16` | `pt-16` | `pb-16` |
|
|
333
|
+
|
|
334
|
+
The values are stored as Tailwind class strings and applied directly to block wrapper elements at render time.
|
|
335
|
+
|
|
336
|
+
#### `defaultLexical` (defaultLexical.ts)
|
|
337
|
+
|
|
338
|
+
The global default Lexical editor configuration used by the Payload config's `editor` property. Provides a minimal feature set: Paragraph, Underline, Bold, Italic, and Link (with internal page references enabled).
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
export const defaultLexical: Config['editor'] = lexicalEditor({
|
|
342
|
+
features: () => [
|
|
343
|
+
ParagraphFeature(),
|
|
344
|
+
UnderlineFeature(),
|
|
345
|
+
BoldFeature(),
|
|
346
|
+
ItalicFeature(),
|
|
347
|
+
LinkFeature({ enabledCollections: ['pages'] }),
|
|
348
|
+
],
|
|
349
|
+
})
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Individual blocks override this with their own `lexicalEditor()` configurations.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### 4. Block Catalog
|
|
357
|
+
|
|
358
|
+
The complete block type catalog, organized by category.
|
|
359
|
+
|
|
360
|
+
#### Content Blocks
|
|
361
|
+
|
|
362
|
+
**Hero** (`hero`)
|
|
363
|
+
|
|
364
|
+
| Property | Value |
|
|
365
|
+
|----------|-------|
|
|
366
|
+
| Slug | `hero` |
|
|
367
|
+
| Tabs | Content, Media, Settings |
|
|
368
|
+
| Content fields | `richText` (Lexical, full features), `buttonGroup` (linkGroup, max 3, no appearances) |
|
|
369
|
+
| Media fields | `imageFields` (upload to media collection) |
|
|
370
|
+
| Settings | `className`, `type` (radio: highImpact/mediumImpact/lowImpact), `layoutMeta` |
|
|
371
|
+
| CSS Module | `hero.module.css` |
|
|
372
|
+
|
|
373
|
+
The Hero block's `type` field controls visual impact level through CSS Module classes: `.heroContainerHigh` (min-height 400px), `.heroContainerMedium` (300px), `.heroContainerLow` (200px).
|
|
374
|
+
|
|
375
|
+
**Card** (`card`)
|
|
376
|
+
|
|
377
|
+
| Property | Value |
|
|
378
|
+
|----------|-------|
|
|
379
|
+
| Slug | `card` |
|
|
380
|
+
| Tabs | Content, CTA, Image, Settings |
|
|
381
|
+
| Content fields | `richText` (Lexical, restricted: no subscript, superscript, underline, strikethrough, code, link; adds FixedToolbarFeature) |
|
|
382
|
+
| CTA fields | `cta` group: `label` (text), `ctaAction` (select: internal/external), `internalLink` (relationship to pages), `externalLink` (text), `layout` (select: wrap-all/wrap-text-description/wrap-link-button) |
|
|
383
|
+
| Image fields | `imageFields` |
|
|
384
|
+
| Settings | `className`, `layoutMeta` |
|
|
385
|
+
| CSS Module | `card.module.css` |
|
|
386
|
+
|
|
387
|
+
The Card block uses a restricted Lexical editor that removes formatting features inappropriate for card content.
|
|
388
|
+
|
|
389
|
+
**Button** (`button`)
|
|
390
|
+
|
|
391
|
+
| Property | Value |
|
|
392
|
+
|----------|-------|
|
|
393
|
+
| Slug | `button` |
|
|
394
|
+
| Tabs | CTA, Settings |
|
|
395
|
+
| CTA fields | `cta` group: `label` (text), `type` (radio: button/link), `action` group (conditional: modal/scroll actions), `link` group (conditional: internal/external link) |
|
|
396
|
+
| Settings | `className`, `newTab` (checkbox), `layoutMeta` |
|
|
397
|
+
| CSS Module | `button.module.css` |
|
|
398
|
+
|
|
399
|
+
The Button block supports two behaviors: interactive button (modal trigger or scroll-to) and navigation link (internal or external).
|
|
400
|
+
|
|
401
|
+
**RichText** (`richText`)
|
|
402
|
+
|
|
403
|
+
| Property | Value |
|
|
404
|
+
|----------|-------|
|
|
405
|
+
| Slug | `richText` |
|
|
406
|
+
| Tabs | Content, Settings |
|
|
407
|
+
| Content fields | `richText` (Lexical, full default features) |
|
|
408
|
+
| Settings | `className`, `layoutMeta` |
|
|
409
|
+
| CSS Module | `richText.module.css` |
|
|
410
|
+
|
|
411
|
+
The simplest content block. Wraps a full-featured Lexical editor.
|
|
412
|
+
|
|
413
|
+
**Media** (`media`)
|
|
414
|
+
|
|
415
|
+
| Property | Value |
|
|
416
|
+
|----------|-------|
|
|
417
|
+
| Slug | `media` |
|
|
418
|
+
| Tabs | Image, Settings |
|
|
419
|
+
| Image fields | `imageFields` |
|
|
420
|
+
| Settings | `className`, `layoutMeta` |
|
|
421
|
+
| CSS Module | `media.module.css` |
|
|
422
|
+
|
|
423
|
+
A standalone image block for displaying a single media asset.
|
|
424
|
+
|
|
425
|
+
**Video** (`video`)
|
|
426
|
+
|
|
427
|
+
| Property | Value |
|
|
428
|
+
|----------|-------|
|
|
429
|
+
| Slug | `video` |
|
|
430
|
+
| Tabs | Content, Settings |
|
|
431
|
+
| Content fields | `customThumbnailField` (json with custom component: `customVideoBlockComponent`) |
|
|
432
|
+
| Settings | `className`, `videoSize` (radio: fixed/responsive), `height` (number, min 1000, conditional), `width` (number, min 1000, conditional), `layoutMeta` |
|
|
433
|
+
| CSS Module | `video.module.css` |
|
|
434
|
+
|
|
435
|
+
Uses a custom admin component for video upload/embed. Fixed size mode enables explicit height/width controls.
|
|
436
|
+
|
|
437
|
+
**Accordion** (`accordion`)
|
|
438
|
+
|
|
439
|
+
| Property | Value |
|
|
440
|
+
|----------|-------|
|
|
441
|
+
| Slug | `accordion` |
|
|
442
|
+
| Tabs | Layout, Settings |
|
|
443
|
+
| Layout fields | `title` (text, required), `htmlTag` (select: div/span/h2-h6, default h2), `content.blocks` (blocks: Button, CallToAction, Card, Form, Media, RichText, Stats, Tabs, Testimonial, Video, NestedContainer) |
|
|
444
|
+
| Settings | `customIcon` (upload to media), `defaultState` (radio: closed/open, default closed), `className`, `layoutMeta` |
|
|
445
|
+
| CSS Module | `accordion.module.css` |
|
|
446
|
+
|
|
447
|
+
The Accordion block nests other blocks within its expandable content area. It supports a custom toggle icon and can default to open or closed state.
|
|
448
|
+
|
|
449
|
+
**SubAccordion** (`inneraccordion`)
|
|
450
|
+
|
|
451
|
+
| Property | Value |
|
|
452
|
+
|----------|-------|
|
|
453
|
+
| Slug | `inneraccordion` |
|
|
454
|
+
| Tabs | Content, Settings |
|
|
455
|
+
| Purpose | A variant of Accordion used within nested containers. Provides the same accordion behavior but with a different slug to avoid schema conflicts. |
|
|
456
|
+
|
|
457
|
+
**Stats** (`stats`)
|
|
458
|
+
|
|
459
|
+
| Property | Value |
|
|
460
|
+
|----------|-------|
|
|
461
|
+
| Slug | `stats` |
|
|
462
|
+
| Tabs | Content, Settings |
|
|
463
|
+
| Content fields | `emphasis` (text -- the large number/metric), `title` (text), `richText` (Lexical, full features), `source` (text -- citation) |
|
|
464
|
+
| Settings | `className`, `layoutMeta` |
|
|
465
|
+
| CSS Module | `stats.module.css` |
|
|
466
|
+
|
|
467
|
+
Designed for displaying statistics or metrics with a prominent emphasis value.
|
|
468
|
+
|
|
469
|
+
**Testimonial** (`testimonial`)
|
|
470
|
+
|
|
471
|
+
| Property | Value |
|
|
472
|
+
|----------|-------|
|
|
473
|
+
| Slug | `testimonial` |
|
|
474
|
+
| Tabs | Content, Image/Video, Settings |
|
|
475
|
+
| Content fields | `richText` (Lexical, restricted: HeadingFeature h2-h5 + ParagraphFeature only), `author` (text), `authorDescription` (text) |
|
|
476
|
+
| Image/Video fields | `imageFields` |
|
|
477
|
+
| Settings | `className`, `layoutMeta` |
|
|
478
|
+
| CSS Module | `testimonial.module.css` |
|
|
479
|
+
|
|
480
|
+
The Testimonial block uses a heavily restricted Lexical editor with only heading and paragraph features.
|
|
481
|
+
|
|
482
|
+
**CallToAction** (`callToAction`)
|
|
483
|
+
|
|
484
|
+
| Property | Value |
|
|
485
|
+
|----------|-------|
|
|
486
|
+
| Slug | `callToAction` |
|
|
487
|
+
| Tabs | Content, Image, Settings |
|
|
488
|
+
| Content fields | `title` (text), `richText` (Lexical, full features) |
|
|
489
|
+
| Image fields | `imageFields` |
|
|
490
|
+
| Settings | `className`, `newTab` (checkbox), `layoutMeta` |
|
|
491
|
+
| CSS Module | `callToAction.module.css` |
|
|
492
|
+
|
|
493
|
+
A prominent banner-style block with title, description, and optional image.
|
|
494
|
+
|
|
495
|
+
**StickyCTA** (`stickyCTA`)
|
|
496
|
+
|
|
497
|
+
| Property | Value |
|
|
498
|
+
|----------|-------|
|
|
499
|
+
| Slug | `stickyCTA` |
|
|
500
|
+
| Tabs | Content, CTA, Settings |
|
|
501
|
+
| Content fields | `richText` (Lexical, heavily restricted: no heading, subscript, superscript, underline, strikethrough, code, link, upload, image, blockquote, relationship, lists; adds FixedToolbarFeature) |
|
|
502
|
+
| CTA fields | Same button/link CTA pattern as Button block |
|
|
503
|
+
| Settings | `className`, `layoutMeta` |
|
|
504
|
+
| CSS Module | `stickyCta.module.css` |
|
|
505
|
+
|
|
506
|
+
A fixed-position CTA bar. The Lexical editor is stripped to support only basic paragraph text.
|
|
507
|
+
|
|
508
|
+
**SubNavigation** (`subnavigation`)
|
|
509
|
+
|
|
510
|
+
| Property | Value |
|
|
511
|
+
|----------|-------|
|
|
512
|
+
| Slug | `subnavigation` |
|
|
513
|
+
| Tabs | Nav Items, Settings |
|
|
514
|
+
| Nav Items fields | `navItems` (array, maxRows 6): each item has `label` (text), `action` (select: internal/external), `internalLink` (relationship to pages, conditional), `externalLink` (text, conditional) |
|
|
515
|
+
| Settings | `className`, `layoutMeta` |
|
|
516
|
+
| CSS Module | `subNavigation.module.css` |
|
|
517
|
+
|
|
518
|
+
An in-page navigation bar with up to 6 items linking to internal pages or external URLs.
|
|
519
|
+
|
|
520
|
+
#### Layout Blocks
|
|
521
|
+
|
|
522
|
+
**Container** (`container`)
|
|
523
|
+
|
|
524
|
+
| Property | Value |
|
|
525
|
+
|----------|-------|
|
|
526
|
+
| Slug | `container` |
|
|
527
|
+
| Tabs | Layout, Settings |
|
|
528
|
+
| Layout fields | `content.blocks` (blocks: 14 block types, excludes Container itself) |
|
|
529
|
+
| Settings | `htmlTag`, `className`, `layout`, `columnsNumber`, `width`, `alignItems`, `justifyContent`, `gap`, `layoutMeta` |
|
|
530
|
+
| CSS Module | `container.module.css` |
|
|
531
|
+
|
|
532
|
+
The Container block is the primary layout mechanism. See Section 5 for a deep dive.
|
|
533
|
+
|
|
534
|
+
**Carousel** (`carousel`)
|
|
535
|
+
|
|
536
|
+
| Property | Value |
|
|
537
|
+
|----------|-------|
|
|
538
|
+
| Slug | `carousel` |
|
|
539
|
+
| Tabs | Layout, Settings |
|
|
540
|
+
| Layout fields | `richText` (Lexical, heading/intro), `carouselCard` (array of blocks: Card, Stats, Testimonial) |
|
|
541
|
+
| Settings | `className`, `layoutMeta` |
|
|
542
|
+
| CSS Module | (none specific -- uses container/card styles) |
|
|
543
|
+
|
|
544
|
+
Carousel items are stored as an array where each item contains a `blocks` field allowing Card, Stats, or Testimonial blocks.
|
|
545
|
+
|
|
546
|
+
**Tabs** (`tabs`)
|
|
547
|
+
|
|
548
|
+
| Property | Value |
|
|
549
|
+
|----------|-------|
|
|
550
|
+
| Slug | `tabs` |
|
|
551
|
+
| Tabs | Header, Tabs, Settings |
|
|
552
|
+
| Header fields | `richText` (Lexical, description/intro) |
|
|
553
|
+
| Tabs fields | `tabs` (array): each tab has `title` group (title text + auto-generated slug), `blocks` (blocks: NestedContainer, RichText, Button, CallToAction, Card, Form, Media, Stats, Testimonial, Video), and per-tab `settings` |
|
|
554
|
+
| Settings | `className`, `customIcon` (upload to media), `layoutMeta` |
|
|
555
|
+
|
|
556
|
+
The Tabs block auto-generates URL-safe slugs from tab titles via a `beforeChange` hook.
|
|
557
|
+
|
|
558
|
+
**Grid** (`grid`)
|
|
559
|
+
|
|
560
|
+
| Property | Value |
|
|
561
|
+
|----------|-------|
|
|
562
|
+
| Slug | `grid` |
|
|
563
|
+
| Tabs | Content, Image, Settings |
|
|
564
|
+
| Content fields | `title` (text), `richText` (Lexical, description) |
|
|
565
|
+
| Image fields | `imageFields` |
|
|
566
|
+
| Settings | `className` (no LayoutMeta) |
|
|
567
|
+
|
|
568
|
+
A simpler layout block for grid-based content with title, description, and image.
|
|
569
|
+
|
|
570
|
+
#### Utility Blocks
|
|
571
|
+
|
|
572
|
+
**Form** (`form`)
|
|
573
|
+
|
|
574
|
+
| Property | Value |
|
|
575
|
+
|----------|-------|
|
|
576
|
+
| Slug | `form` |
|
|
577
|
+
| Tabs | Content, Settings |
|
|
578
|
+
| Content fields | `customSelectField` (json with custom component for form selection), `submitAction` (select: displayMessage/redirectToPage), `redirectPage` (relationship to pages, conditional), `thankYouMessage` (richText, conditional) |
|
|
579
|
+
| Settings | `className`, `layoutMeta` |
|
|
580
|
+
| CSS Module | `formEmbed.module.css` |
|
|
581
|
+
|
|
582
|
+
Embeds an external form with configurable submit behavior.
|
|
583
|
+
|
|
584
|
+
**Code** (`code`)
|
|
585
|
+
|
|
586
|
+
| Property | Value |
|
|
587
|
+
|----------|-------|
|
|
588
|
+
| Slug | `code` |
|
|
589
|
+
| Fields | `language` (select: typescript/javascript/css), `code` (code field) |
|
|
590
|
+
|
|
591
|
+
The Code block does not use the tabs pattern. It has a flat field structure with a syntax-highlighted code editor. Includes custom React components for rendering and copy-to-clipboard functionality.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### 5. Container Block Deep Dive
|
|
596
|
+
|
|
597
|
+
The Container block is the most important block for Figma-to-PayloadCMS mapping because it mirrors Figma's Auto Layout concept -- a flexible box that arranges children in rows, columns, or grids.
|
|
598
|
+
|
|
599
|
+
#### Nesting Model
|
|
600
|
+
|
|
601
|
+
Container children are stored at the path `layout.content.blocks`:
|
|
602
|
+
|
|
603
|
+
```
|
|
604
|
+
Container
|
|
605
|
+
└── tabs[0] (name: 'layout')
|
|
606
|
+
└── content (group)
|
|
607
|
+
└── blocks (blocks field)
|
|
608
|
+
├── Block 1
|
|
609
|
+
├── Block 2
|
|
610
|
+
└── Block 3
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
The data path for accessing nested blocks is: `container.layout.content.blocks`.
|
|
614
|
+
|
|
615
|
+
#### Self-Nesting Prevention
|
|
616
|
+
|
|
617
|
+
The top-level Container block does NOT include itself in its allowed blocks list. This prevents infinite schema recursion that would break Payload's TypeScript type generation and database schema.
|
|
618
|
+
|
|
619
|
+
However, a `NestedContainer` variant exists that IS allowed inside:
|
|
620
|
+
- Accordion content
|
|
621
|
+
- Tabs content
|
|
622
|
+
- Other nested contexts (Tabs/container.ts)
|
|
623
|
+
|
|
624
|
+
The NestedContainer uses the same `container` slug and the same settings, but its `blocks` array excludes itself. This creates a maximum nesting depth of 2 levels: Container > NestedContainer (which cannot nest further Containers).
|
|
625
|
+
|
|
626
|
+
#### Layout Options
|
|
627
|
+
|
|
628
|
+
The Container's `layout` field controls the CSS display mode:
|
|
629
|
+
|
|
630
|
+
| Value | Label | CSS Result |
|
|
631
|
+
|-------|-------|------------|
|
|
632
|
+
| `col` | Column | `display: flex; flex-direction: column;` |
|
|
633
|
+
| `row` | Row | `display: flex; flex-direction: row;` |
|
|
634
|
+
| `grid` | Grid | `display: grid;` |
|
|
635
|
+
|
|
636
|
+
Default is `col` (vertical stacking).
|
|
637
|
+
|
|
638
|
+
When `grid` is selected, an additional `columnsNumber` field appears (max 12) that controls the CSS `grid-template-columns` value. A value of 1 or less triggers responsive auto-sizing.
|
|
639
|
+
|
|
640
|
+
#### Alignment Options
|
|
641
|
+
|
|
642
|
+
**alignItems** (cross-axis alignment):
|
|
643
|
+
|
|
644
|
+
| Label | Value (Tailwind) |
|
|
645
|
+
|-------|-----------------|
|
|
646
|
+
| Start | `items-start` |
|
|
647
|
+
| Center | `items-center` |
|
|
648
|
+
| End | `items-end` |
|
|
649
|
+
| Stretch | `items-stretch` |
|
|
650
|
+
| Baseline | `items-baseline` |
|
|
651
|
+
|
|
652
|
+
**justifyContent** (main-axis alignment):
|
|
653
|
+
|
|
654
|
+
| Label | Value (Tailwind) |
|
|
655
|
+
|-------|-----------------|
|
|
656
|
+
| Start | `justify-start` |
|
|
657
|
+
| Center | `justify-center` |
|
|
658
|
+
| End | `justify-end` |
|
|
659
|
+
| Space Between | `justify-between` |
|
|
660
|
+
| Space Around | `justify-around` |
|
|
661
|
+
| Space Evenly | `justify-evenly` |
|
|
662
|
+
| Stretch | `justify-stretch` |
|
|
663
|
+
|
|
664
|
+
These values are stored as Tailwind utility class strings and applied directly to the container element.
|
|
665
|
+
|
|
666
|
+
#### Gap Options
|
|
667
|
+
|
|
668
|
+
| Label | Value (Tailwind) | Actual Size |
|
|
669
|
+
|-------|-----------------|-------------|
|
|
670
|
+
| None | `gap-0` | 0 |
|
|
671
|
+
| Extra Small | `gap-1` | 0.25rem (4px) |
|
|
672
|
+
| Small | `gap-2` | 0.5rem (8px) |
|
|
673
|
+
| Medium | `gap-4` | 1rem (16px) |
|
|
674
|
+
| Large | `gap-6` | 1.5rem (24px) |
|
|
675
|
+
| Extra Large | `gap-8` | 2rem (32px) |
|
|
676
|
+
| XXL | `gap-12` | 3rem (48px) |
|
|
677
|
+
| XXXL | `gap-16` | 4rem (64px) |
|
|
678
|
+
|
|
679
|
+
Default gap is `gap-4` (1rem).
|
|
680
|
+
|
|
681
|
+
#### Width Constraints
|
|
682
|
+
|
|
683
|
+
| Label | Value | CSS Effect |
|
|
684
|
+
|-------|-------|------------|
|
|
685
|
+
| Full | `full` | No max-width constraint |
|
|
686
|
+
| Wide | `wide` | `max-width: 1400px; margin: 0 auto;` |
|
|
687
|
+
| Narrow | `narrow` | `max-width: 800px; margin: 0 auto;` |
|
|
688
|
+
|
|
689
|
+
Default width is `full`.
|
|
690
|
+
|
|
691
|
+
#### HTML Tag Selection
|
|
692
|
+
|
|
693
|
+
The Container supports semantic HTML element selection:
|
|
694
|
+
|
|
695
|
+
| Label | Value |
|
|
696
|
+
|-------|-------|
|
|
697
|
+
| `<div>` | `div` (default) |
|
|
698
|
+
| `<section>` | `section` |
|
|
699
|
+
| `<article>` | `article` |
|
|
700
|
+
| `<aside>` | `aside` |
|
|
701
|
+
| `<header>` | `header` |
|
|
702
|
+
| `<footer>` | `footer` |
|
|
703
|
+
|
|
704
|
+
This enables proper semantic HTML output without changing block behavior.
|
|
705
|
+
|
|
706
|
+
#### Container CSS Module
|
|
707
|
+
|
|
708
|
+
The container CSS Module (`container.module.css`) provides base styles and modifier classes:
|
|
709
|
+
|
|
710
|
+
```css
|
|
711
|
+
.container {
|
|
712
|
+
gap: var(--token-spacing-md, 16px);
|
|
713
|
+
padding: var(--token-spacing-md, 16px);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.containerFlex { display: flex; }
|
|
717
|
+
.containerGrid { display: grid; }
|
|
718
|
+
.containerRow { flex-direction: row; }
|
|
719
|
+
.containerColumn { flex-direction: column; }
|
|
720
|
+
.containerNarrow { max-width: 800px; margin: 0 auto; }
|
|
721
|
+
.containerWide { max-width: 1400px; margin: 0 auto; }
|
|
722
|
+
.containerEmpty { color: var(--token-color-text-light, #9ca3af); font-style: italic; }
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
At render time, the appropriate modifier classes are composed based on the block's settings values.
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
### 6. Block Rendering
|
|
730
|
+
|
|
731
|
+
The `RenderBlocks` component is the registry that maps block types to React components.
|
|
732
|
+
|
|
733
|
+
#### RenderBlocks Pattern
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
const blockComponents = {
|
|
737
|
+
Accordion,
|
|
738
|
+
Button,
|
|
739
|
+
Code,
|
|
740
|
+
Card,
|
|
741
|
+
Hero,
|
|
742
|
+
MediaBlock,
|
|
743
|
+
RichText,
|
|
744
|
+
Stats,
|
|
745
|
+
StickyCTA,
|
|
746
|
+
SubNavigationBlock,
|
|
747
|
+
Testimonial,
|
|
748
|
+
Video,
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export const RenderBlocks: React.FC<{
|
|
752
|
+
blocks: NonNullable<Page['layout']>
|
|
753
|
+
}> = (props) => {
|
|
754
|
+
const { blocks } = props
|
|
755
|
+
const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0
|
|
756
|
+
|
|
757
|
+
if (hasBlocks) {
|
|
758
|
+
return (
|
|
759
|
+
<Fragment>
|
|
760
|
+
{blocks.map((block, index) => {
|
|
761
|
+
const { blockType } = block
|
|
762
|
+
if (blockType && blockType in blockComponents) {
|
|
763
|
+
const Block = blockComponents[blockType]
|
|
764
|
+
if (Block) {
|
|
765
|
+
return (
|
|
766
|
+
<div key={index}>
|
|
767
|
+
<Block {...block} disableInnerContainer />
|
|
768
|
+
</div>
|
|
769
|
+
)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return null
|
|
773
|
+
})}
|
|
774
|
+
</Fragment>
|
|
775
|
+
)
|
|
776
|
+
}
|
|
777
|
+
return null
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
#### How It Works
|
|
782
|
+
|
|
783
|
+
1. Each block in the page's `layout.blocks` array has a `blockType` property matching its slug
|
|
784
|
+
2. The `blockComponents` object maps block type names to their React component imports
|
|
785
|
+
3. The renderer iterates over the blocks array, looks up the component, and renders it with the block's data spread as props
|
|
786
|
+
4. The `disableInnerContainer` prop signals that the block should not render its own container wrapper (the parent already provides one)
|
|
787
|
+
5. Each block gets a wrapper `<div>` with a key based on array index
|
|
788
|
+
|
|
789
|
+
#### CSS Module Application
|
|
790
|
+
|
|
791
|
+
Each block's React component imports its corresponding CSS Module from `src/styles/blocks/`:
|
|
792
|
+
|
|
793
|
+
```typescript
|
|
794
|
+
// In Hero component
|
|
795
|
+
import styles from '@/styles/blocks/hero.module.css'
|
|
796
|
+
|
|
797
|
+
// Usage
|
|
798
|
+
<div className={styles.heroContainer}>
|
|
799
|
+
<div className={styles.heroContentArea}>
|
|
800
|
+
...
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
The CSS Modules are re-exported from `src/styles/blocks/index.ts` for convenient imports:
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
export { default as heroStyles } from './hero.module.css'
|
|
809
|
+
export { default as cardStyles } from './card.module.css'
|
|
810
|
+
export { default as containerStyles } from './container.module.css'
|
|
811
|
+
// ... all block styles
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
#### Adding a New Block to the Registry
|
|
815
|
+
|
|
816
|
+
To add a new block type:
|
|
817
|
+
|
|
818
|
+
1. Create the block config in `src/admin/blocks/{category}/{BlockName}/config.ts`
|
|
819
|
+
2. Create the renderer component
|
|
820
|
+
3. Create the CSS Module in `src/styles/blocks/{blockName}.module.css`
|
|
821
|
+
4. Add the component to `blockComponents` in `RenderBlocks.tsx`
|
|
822
|
+
5. Add the block to the Pages collection's `layout.blocks` array
|
|
823
|
+
6. Add the CSS Module export to `src/styles/blocks/index.ts`
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
### 7. Collections Integration
|
|
828
|
+
|
|
829
|
+
#### Pages Collection
|
|
830
|
+
|
|
831
|
+
The Pages collection (`src/collections/Pages/index.ts`) is the primary consumer of blocks. Pages have a tabbed structure:
|
|
832
|
+
|
|
833
|
+
```
|
|
834
|
+
Pages
|
|
835
|
+
├── title (text, required)
|
|
836
|
+
├── Layout tab (name: 'layout')
|
|
837
|
+
│ └── blocks (blocks field: 15 block types)
|
|
838
|
+
├── Settings tab (name: 'settings')
|
|
839
|
+
│ ├── header (relationship to headers)
|
|
840
|
+
│ ├── footer (relationship to footers)
|
|
841
|
+
│ ├── projects (relationship to projects)
|
|
842
|
+
│ └── className
|
|
843
|
+
├── SEO tab (name: 'meta')
|
|
844
|
+
│ └── MetaTitle, MetaImage, MetaDescription, Preview
|
|
845
|
+
└── slug (auto-generated from title)
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
The blocks field at `layout.blocks` accepts all 15 top-level block types:
|
|
849
|
+
|
|
850
|
+
```typescript
|
|
851
|
+
blocks: [
|
|
852
|
+
Accordion, Button, CallToAction, Carousel, Container,
|
|
853
|
+
Card, Form, Hero, MediaBlock, SubNavigationBlock,
|
|
854
|
+
RichText, Stats, StickyCTA, Tabs, Testimonial, Video,
|
|
855
|
+
]
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
Pages support:
|
|
859
|
+
- **Live preview** via `admin.livePreview.url`
|
|
860
|
+
- **Draft/publish** workflow via `versions.drafts`
|
|
861
|
+
- **Autosave** at 100ms intervals for live preview
|
|
862
|
+
- **Visual Builder** via a custom view at `/visual-builder`
|
|
863
|
+
|
|
864
|
+
#### Media Collection
|
|
865
|
+
|
|
866
|
+
The Media collection (`src/collections/Media.ts`) stores uploaded images:
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
export const Media: CollectionConfig = {
|
|
870
|
+
slug: 'media',
|
|
871
|
+
access: { read: () => true },
|
|
872
|
+
upload: {
|
|
873
|
+
imageSizes: [
|
|
874
|
+
{ name: 'og', width: 1200, height: 630, formatOptions: { format: 'webp' } },
|
|
875
|
+
],
|
|
876
|
+
adminThumbnail: 'og',
|
|
877
|
+
mimeTypes: ['image/*'],
|
|
878
|
+
},
|
|
879
|
+
fields: [
|
|
880
|
+
{ name: 'alt', type: 'text', required: true },
|
|
881
|
+
],
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Key details:
|
|
886
|
+
- Images are publicly readable (no auth required)
|
|
887
|
+
- Automatic WebP conversion for the `og` size (1200x630)
|
|
888
|
+
- Only image MIME types are accepted
|
|
889
|
+
- The `alt` field is required for accessibility
|
|
890
|
+
- Block image fields link to this collection via `relationTo: 'media'`
|
|
891
|
+
|
|
892
|
+
#### Relationship Patterns
|
|
893
|
+
|
|
894
|
+
Blocks reference other collections through three patterns:
|
|
895
|
+
|
|
896
|
+
1. **Upload** -- Direct file reference: `type: 'upload', relationTo: 'media'`
|
|
897
|
+
2. **Relationship (single)** -- Document reference: `type: 'relationship', relationTo: 'pages'`
|
|
898
|
+
3. **Relationship (array)** -- Multi-collection: `relationTo: ['pages']` (can reference multiple collection types)
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
### 8. Lexical Editor Configuration
|
|
903
|
+
|
|
904
|
+
PayloadCMS uses the Lexical rich text editor. The recommended approach configures it at three levels.
|
|
905
|
+
|
|
906
|
+
#### Global Default
|
|
907
|
+
|
|
908
|
+
The `defaultLexical` configuration provides the baseline feature set for all richText fields that do not override the editor:
|
|
909
|
+
|
|
910
|
+
```typescript
|
|
911
|
+
lexicalEditor({
|
|
912
|
+
features: () => [
|
|
913
|
+
ParagraphFeature(),
|
|
914
|
+
UnderlineFeature(),
|
|
915
|
+
BoldFeature(),
|
|
916
|
+
ItalicFeature(),
|
|
917
|
+
LinkFeature({ enabledCollections: ['pages'] }),
|
|
918
|
+
],
|
|
919
|
+
})
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
#### Full Feature Set
|
|
923
|
+
|
|
924
|
+
Most blocks use `lexicalEditor({})` which loads all default features:
|
|
925
|
+
|
|
926
|
+
```typescript
|
|
927
|
+
// Hero, RichText, Stats, CallToAction, Carousel
|
|
928
|
+
{ name: 'richText', type: 'richText', editor: lexicalEditor({}) }
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
This includes heading, bold, italic, underline, strikethrough, subscript, superscript, code, link, upload, blockquote, lists, and more.
|
|
932
|
+
|
|
933
|
+
#### Restricted Feature Sets
|
|
934
|
+
|
|
935
|
+
Some blocks restrict the Lexical editor to appropriate features:
|
|
936
|
+
|
|
937
|
+
**Card** -- Removes subscript, superscript, underline, strikethrough, code, link. Adds FixedToolbarFeature:
|
|
938
|
+
|
|
939
|
+
```typescript
|
|
940
|
+
lexicalEditor({
|
|
941
|
+
features: ({ defaultFeatures }) => [
|
|
942
|
+
...defaultFeatures.filter(
|
|
943
|
+
(feature) => !['subscript', 'superscript', 'underline',
|
|
944
|
+
'strikethrough', 'code', 'link'].includes(feature.key),
|
|
945
|
+
),
|
|
946
|
+
FixedToolbarFeature(),
|
|
947
|
+
],
|
|
948
|
+
})
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
**StickyCTA** -- Heavily restricted. Removes heading, subscript, superscript, underline, strikethrough, code, link, upload, image, blockquote, relationship, all list types. Only paragraph and basic formatting remain:
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
lexicalEditor({
|
|
955
|
+
features: ({ defaultFeatures }) => [
|
|
956
|
+
...defaultFeatures.filter(
|
|
957
|
+
(feature) => !['heading', 'subscript', 'superscript', 'underline',
|
|
958
|
+
'strikethrough', 'code', 'link', 'upload', 'image',
|
|
959
|
+
'blockquote', 'relationship', 'list', 'unorderedList',
|
|
960
|
+
'orderedList', 'checklist'].includes(feature.key),
|
|
961
|
+
),
|
|
962
|
+
FixedToolbarFeature(),
|
|
963
|
+
],
|
|
964
|
+
})
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
**Testimonial** -- Minimal: only headings (h2-h5) and paragraphs:
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
lexicalEditor({
|
|
971
|
+
features: () => [
|
|
972
|
+
HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4', 'h5'] }),
|
|
973
|
+
ParagraphFeature(),
|
|
974
|
+
],
|
|
975
|
+
})
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
The pattern of restricting features per block ensures editors cannot add inappropriate content (e.g., no headings in a sticky CTA bar, no complex formatting in card text).
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
### 9. Token Integration
|
|
983
|
+
|
|
984
|
+
Blocks consume design tokens defined in `src/styles/tokens.css` through CSS Module rules that reference `--token-*` custom properties.
|
|
985
|
+
|
|
986
|
+
#### Token Categories in tokens.css
|
|
987
|
+
|
|
988
|
+
```css
|
|
989
|
+
:root {
|
|
990
|
+
/* Typography */
|
|
991
|
+
--token-font-family: system-ui, -apple-system, ...;
|
|
992
|
+
--token-font-size-base: 18px;
|
|
993
|
+
--token-font-size-sm: 15px;
|
|
994
|
+
--token-font-size-lg: 20px;
|
|
995
|
+
--token-font-size-xl: 24px;
|
|
996
|
+
--token-font-size-2xl: 32px;
|
|
997
|
+
--token-font-size-3xl: 42px;
|
|
998
|
+
--token-font-size-4xl: 64px;
|
|
999
|
+
|
|
1000
|
+
/* Line Heights */
|
|
1001
|
+
--token-line-height-base: 32px;
|
|
1002
|
+
--token-line-height-sm: 24px;
|
|
1003
|
+
--token-line-height-tight: 1.2;
|
|
1004
|
+
--token-line-height-relaxed: 1.6;
|
|
1005
|
+
|
|
1006
|
+
/* Spacing */
|
|
1007
|
+
--token-spacing-xs: 4px;
|
|
1008
|
+
--token-spacing-sm: 8px;
|
|
1009
|
+
--token-spacing-md: 16px;
|
|
1010
|
+
--token-spacing-lg: 24px;
|
|
1011
|
+
--token-spacing-xl: 32px;
|
|
1012
|
+
--token-spacing-2xl: 48px;
|
|
1013
|
+
--token-spacing-3xl: 64px;
|
|
1014
|
+
|
|
1015
|
+
/* Colors */
|
|
1016
|
+
--token-color-text: #111827;
|
|
1017
|
+
--token-color-text-muted: #6b7280;
|
|
1018
|
+
--token-color-text-light: #9ca3af;
|
|
1019
|
+
--token-color-text-inverse: #ffffff;
|
|
1020
|
+
--token-color-bg: #ffffff;
|
|
1021
|
+
--token-color-bg-subtle: #f9fafb;
|
|
1022
|
+
--token-color-bg-muted: #f3f4f6;
|
|
1023
|
+
--token-color-bg-dark: #1e1e1e;
|
|
1024
|
+
--token-color-border: #e5e7eb;
|
|
1025
|
+
--token-color-border-strong: #d1d5db;
|
|
1026
|
+
--token-color-primary: #3b82f6;
|
|
1027
|
+
--token-color-primary-hover: #2563eb;
|
|
1028
|
+
--token-color-success: #10b981;
|
|
1029
|
+
--token-color-warning: #f59e0b;
|
|
1030
|
+
--token-color-error: #ef4444;
|
|
1031
|
+
|
|
1032
|
+
/* Border Radius */
|
|
1033
|
+
--token-radius-sm: 4px;
|
|
1034
|
+
--token-radius-md: 8px;
|
|
1035
|
+
--token-radius-lg: 12px;
|
|
1036
|
+
--token-radius-full: 9999px;
|
|
1037
|
+
|
|
1038
|
+
/* Shadows */
|
|
1039
|
+
--token-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
1040
|
+
--token-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
1041
|
+
--token-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
1042
|
+
|
|
1043
|
+
/* Transitions */
|
|
1044
|
+
--token-transition-fast: 150ms ease;
|
|
1045
|
+
--token-transition-normal: 200ms ease;
|
|
1046
|
+
--token-transition-slow: 300ms ease;
|
|
1047
|
+
}
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
#### Import Pattern
|
|
1051
|
+
|
|
1052
|
+
CSS Modules import tokens.css to ensure custom properties are available:
|
|
1053
|
+
|
|
1054
|
+
```css
|
|
1055
|
+
@import '../tokens.css';
|
|
1056
|
+
|
|
1057
|
+
.card {
|
|
1058
|
+
background-color: var(--token-color-bg, #ffffff);
|
|
1059
|
+
border-radius: var(--token-radius-lg, 12px);
|
|
1060
|
+
box-shadow: var(--token-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.1));
|
|
1061
|
+
}
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
#### Fallback Pattern
|
|
1065
|
+
|
|
1066
|
+
Every `var()` reference includes a fallback value that matches the token's default. This ensures blocks render correctly even if tokens.css is not loaded:
|
|
1067
|
+
|
|
1068
|
+
```css
|
|
1069
|
+
/* Pattern: var(--token-{category}-{name}, {fallback-value}) */
|
|
1070
|
+
padding: var(--token-spacing-lg, 24px);
|
|
1071
|
+
color: var(--token-color-text, #111827);
|
|
1072
|
+
font-size: var(--token-font-size-2xl, 32px);
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
#### Token Consumption by Block
|
|
1076
|
+
|
|
1077
|
+
| Block | Token Categories Used |
|
|
1078
|
+
|-------|----------------------|
|
|
1079
|
+
| Hero | spacing, color (bg-subtle, bg-dark, text, text-inverse, primary), radius, font-size |
|
|
1080
|
+
| Card | spacing, color (bg, text, text-light, border, primary, text-inverse), radius, shadow, font-size |
|
|
1081
|
+
| Button | spacing, color (primary, primary-hover, text-inverse), radius, transition |
|
|
1082
|
+
| Accordion | spacing, color (bg, bg-subtle, text, text-muted, text-light, border, border-strong), radius |
|
|
1083
|
+
| Stats | spacing, color (bg, text, text-muted, text-light, primary, border), radius, font-size, line-height |
|
|
1084
|
+
| Testimonial | spacing, color (bg-subtle, text, text-muted, border), radius, font-size, line-height |
|
|
1085
|
+
| CallToAction | spacing, color (primary, text-inverse, text), radius, font-size |
|
|
1086
|
+
| Container | spacing, color (text-light) |
|
|
1087
|
+
| StickyCTA | spacing, color, radius |
|
|
1088
|
+
| SubNavigation | spacing, color, radius |
|
|
1089
|
+
|
|
1090
|
+
#### Tailwind Integration
|
|
1091
|
+
|
|
1092
|
+
The Container block stores alignment, gap, and spacing values as Tailwind utility class strings (e.g., `items-center`, `gap-4`, `mt-8`). These are applied directly to the rendered HTML element alongside CSS Module classes:
|
|
1093
|
+
|
|
1094
|
+
```html
|
|
1095
|
+
<section class="flex flex-col items-center gap-4 mt-8 containerStyles.container">
|
|
1096
|
+
<!-- child blocks -->
|
|
1097
|
+
</section>
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
This demonstrates the three-layer CSS architecture in action:
|
|
1101
|
+
- **Layer 1 (Tailwind):** `flex flex-col items-center gap-4 mt-8`
|
|
1102
|
+
- **Layer 2 (Tokens):** Referenced within CSS Module via `var(--token-*)`
|
|
1103
|
+
- **Layer 3 (CSS Module):** `containerStyles.container` (scoped visual styles)
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
### 10. Select Options Reference
|
|
1108
|
+
|
|
1109
|
+
The `selectOptions.ts` file defines all shared option arrays used across blocks. This is the single source of truth for select and radio field options.
|
|
1110
|
+
|
|
1111
|
+
#### Layout Options
|
|
1112
|
+
|
|
1113
|
+
```typescript
|
|
1114
|
+
export const layoutOptions = [
|
|
1115
|
+
{ label: 'Column', value: 'col' },
|
|
1116
|
+
{ label: 'Row', value: 'row' },
|
|
1117
|
+
{ label: 'Grid', value: 'grid' },
|
|
1118
|
+
]
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
#### HTML Tag Options
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
export const htmlTagOptions = [
|
|
1125
|
+
{ label: '<div>', value: 'div' },
|
|
1126
|
+
{ label: '<section>', value: 'section' },
|
|
1127
|
+
{ label: '<article>', value: 'article' },
|
|
1128
|
+
{ label: '<aside>', value: 'aside' },
|
|
1129
|
+
{ label: '<header>', value: 'header' },
|
|
1130
|
+
{ label: '<footer>', value: 'footer' },
|
|
1131
|
+
]
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
#### HTML Header Tag Options (for Accordion)
|
|
1135
|
+
|
|
1136
|
+
```typescript
|
|
1137
|
+
export const htmlHeaderTagOptions = [
|
|
1138
|
+
{ label: '<div>', value: 'div' },
|
|
1139
|
+
{ label: '<span>', value: 'span' },
|
|
1140
|
+
{ label: '<h2>', value: 'h2' },
|
|
1141
|
+
{ label: '<h3>', value: 'h3' },
|
|
1142
|
+
{ label: '<h4>', value: 'h4' },
|
|
1143
|
+
{ label: '<h5>', value: 'h5' },
|
|
1144
|
+
{ label: '<h6>', value: 'h6' },
|
|
1145
|
+
]
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
#### Impact Options (for Hero)
|
|
1149
|
+
|
|
1150
|
+
```typescript
|
|
1151
|
+
export const impactOptions = [
|
|
1152
|
+
{ label: 'High Impact', value: 'highImpact' },
|
|
1153
|
+
{ label: 'Medium Impact', value: 'mediumImpact' },
|
|
1154
|
+
{ label: 'Low Impact', value: 'lowImpact' },
|
|
1155
|
+
]
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
#### Width Options
|
|
1159
|
+
|
|
1160
|
+
```typescript
|
|
1161
|
+
export const widthOptions = [
|
|
1162
|
+
{ label: 'Full', value: 'full' },
|
|
1163
|
+
{ label: 'Wide', value: 'wide' },
|
|
1164
|
+
{ label: 'Narrow', value: 'narrow' },
|
|
1165
|
+
]
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
---
|
|
1169
|
+
|
|
1170
|
+
## Cross-References
|
|
1171
|
+
|
|
1172
|
+
- **`css-strategy.md`** -- Three-layer CSS architecture (Tailwind + Custom Properties + CSS Modules). Defines how blocks combine Tailwind utility classes for layout, CSS custom properties for tokens, and CSS Modules for visual skin. The Container block's Tailwind class storage pattern is a direct implementation of Layer 1.
|
|
1173
|
+
|
|
1174
|
+
- **`design-tokens.md`** -- Token extraction pipeline, naming conventions, and promotion thresholds. The `--token-*` custom properties in `tokens.css` follow the naming patterns and extraction rules defined in this module.
|
|
1175
|
+
|
|
1176
|
+
- **`design-tokens-variables.md`** -- Figma Variables to CSS custom property bridge. Defines how Figma Variables resolve to the `--token-*` values consumed by block CSS Modules.
|
|
1177
|
+
|
|
1178
|
+
- **`design-to-code-semantic.md`** -- Semantic HTML element selection rules and BEM naming conventions. The Container block's `htmlTag` field and the block CSS Module BEM naming patterns (`.hero`, `.hero__title`, `.heroContainerHigh`) follow the conventions in this module.
|
|
1179
|
+
|
|
1180
|
+
- **`design-to-code-layout.md`** -- Auto Layout to Flexbox mapping. The Container block's layout, alignItems, justifyContent, and gap fields directly mirror the Figma Auto Layout properties documented in this module.
|
|
1181
|
+
|
|
1182
|
+
- **`payload-figma-mapping.md`** -- Figma component to PayloadCMS block mapping rules. Uses the block catalog and field definitions from this module to determine which Figma component becomes which block type and how properties map to fields.
|
|
1183
|
+
|
|
1184
|
+
- **`payload-visual-builder.md`** -- Visual Builder plugin architecture. Defines how blocks are edited visually within the Payload admin, including the Container's children path (`layout.content.blocks`) and the token aliasing pattern for CSS isolation.
|