Docs
Next

Field patterns

Theme and background fields, conditional fields, link fields, reused fields and block images.

On this page

These patterns recur in Systhema's own blocks. Reuse them in your custom blocks and templates so editors meet the same controls everywhere.

Theme + background fieldsLink to this section

import { getFirstKey, getTokens, toTitleCase } from '@systhemaui/core'

const { colorSystem } = getTokens()
const colorSystemKeys = Object.keys(colorSystem || {})
const layoutBackgrounds = Object.keys(
  (colorSystem[getFirstKey(colorSystem) as string] as Record<string, Record<string, unknown>>)
    .layout?.bg || {},
)

const themeField = {
  name: 'theme',
  type: 'select',
  options: colorSystemKeys.map((k) => ({ label: toTitleCase(k), value: k })),
  defaultValue: colorSystemKeys[0],
}

const layoutBgField = {
  name: 'layoutBg',
  type: 'select',
  options: layoutBackgrounds.map((k) => ({ label: toTitleCase(k), value: k })),
  defaultValue: layoutBackgrounds[0],
}

Conditional fieldsLink to this section

{ name: 'url', type: 'text', admin: { condition: (_, s) => s?.linkType === 'custom' } }
{ name: 'reference', type: 'relationship', relationTo: 'pages',
  admin: { condition: (_, s) => s?.linkType === 'internal' } }

linkFields() (from @systhemaui/payload/fields) is the linkType / url / reference / newTab set the built-in button, chip, icon, card, image, video and column-item blocks are built from, with the same translated labels, conditions and defaults. Spread it into any block or template field list:

import { linkFields } from '@systhemaui/payload/fields'

fields: [
  { name: 'title', type: 'text' },
  ...linkFields(), // None / Custom URL / Internal link, "None" preselected
  // ...linkFields({ allowNone: false })                   — always links (a button)
  // ...linkFields({ relationTo: ['pages', 'posts'] })     — polymorphic target
  // ...linkFields({ newTab: false })                       — no "open in new tab" box
  // ...linkFields({ newTabNeedsTarget: true })             — "open in new tab" only once a URL/page is set
  // ...linkFields({ condition: (s) => s?.layout === 'card' }) — every link field gated on a sibling value
  // ...linkFields({ description: 'Makes the whole card clickable' }) — help text under the link type
]

Render it with the same helper the blocks use: linkType === 'custom' ? url : localizedInternalRefToHref(reference, locale) (internalRefToHref / localizedInternalRefToHref resolve a populated reference to its href, homepage-aware).

Reusing block fieldsLink to this section

import { ButtonBlockFields } from '@systhemaui/payload/blocks'

fields: [{ name: 'title', type: 'text' }, ...ButtonBlockFields]

Block images (editor preview)Link to this section

A block carries two images and they are different sizes. Payload draws admin.images.icon at 20x20 in the Lexical slash menu and toolbar, and admin.images.thumbnail in a 3:2 object-fit: cover box in the block picker and the Blocks-field selector. Draw them separately. A 20px glyph scaled up to picker size is a blur.

Systhema's own thumbnails are 480x320 wireframes in utils/blockThumbnails.ts, one per built-in block.

import { encodedSvg } from '@systhemaui/payload/utils/svg'
import { blockThumbnail } from '@systhemaui/payload/utils/blockThumbnails'

admin: {
  images: {
    icon: encodedSvg({ name: 'lexical-block-section', hexIdentifier: 'b5b5b5' }),
    thumbnail: { url: blockThumbnail('section'), alt: 'Section block preview' },
  },
}

Either slot also takes a bare URL string instead of the { url, alt } object. Payload deprecated the older top-level imageURL and imageAltText in favour of admin.images.