Skip to content

Rendering blocks

A block component receives one prop, fields, matching the block type's schema. This page covers the field types that need more than printing a value straight into the template.

Text and rich text

text is a plain string. richtext is HTML:

vue
<h2>{{ fields.title }}</h2>
<div v-html="fields.body" />

The HTML is authored in the console's editor and is not arbitrary user input, but treat it as you would any HTML you render: it is sanitized on the way in, not on the way out.

Images

An image field carries the URL and its alt text:

vue
<img
  v-if="fields.image"
  :src="fields.image.url"
  :alt="fields.image.alt ?? ''"
  :width="fields.image.width"
  :height="fields.image.height"
  loading="lazy"
/>

Always render width and height when present; they are what stops the page jumping as images load.

A link arrives resolved, with an href and a status:

json
{ "kind": "doc", "route": "/products/x", "href": "/products/x", "status": "ok" }
vue
<script setup lang="ts">
import { useBlockLink } from '@nubisco/cms-vue'

const props = defineProps<{ fields: { cta?: unknown } }>()
const cta = useBlockLink(() => props.fields.cta)
</script>

<template>
  <a v-if="cta.ok" :href="cta.href">{{ fields.ctaLabel }}</a>
  <span v-else>{{ fields.ctaLabel }}</span>
</template>

status is ok, missing (the target does not exist) or unpublished (it exists but the public cannot see it). Render the label without an anchor rather than an anchor to nowhere.

Lists

A list field is an array of objects with the sub-fields the model defines:

vue
<ul>
  <li v-for="(tile, i) in fields.tiles ?? []" :key="i">
    <h3>{{ tile.name }}</h3>
    <p>{{ tile.tagline }}</p>
  </li>
</ul>

Always default to []. An optional list is absent, not empty.

Groups

A group is a single nested object — a call to action with a title, a body and a button:

vue
<section v-if="fields.cta">
  <h2>{{ fields.cta.title }}</h2>
  <p>{{ fields.cta.body }}</p>
</section>

Lines

lines is multi-line plain text. Render it preserving line breaks:

vue
<p style="white-space: pre-line">{{ fields.address }}</p>

Select, boolean, number, colour, date

Scalars. select gives one of the options the model declares, date an ISO string:

vue
<article :data-variant="fields.variant">
  <time :datetime="fields.publishedAt">{{ formatDate(fields.publishedAt) }}</time>
</article>

Writing a block that survives editing

Editors will leave optional fields empty and add list items you did not anticipate. Two habits prevent most of the resulting bug reports:

Guard every optional field. v-if="fields.subtitle" rather than rendering an empty <p> that leaves a gap.

Never index a list by a fixed position. fields.tiles[2] breaks the moment someone reorders them.

If a field must always be present, mark it required in the model. The CMS will then refuse to publish content that leaves it empty, instead of your component failing on the live site. See Field types.

In-context editing

Mark editable text so the console's canvas knows what a click selects:

vue
<h1 :data-cms-field="'hero.title'">{{ fields.hero.title }}</h1>

The path is the field's location within the block. The attribute does nothing outside the console.

A Nubisco product. Not open source.