How a block works
Blökkli is the block-based page builder behind revenexx themes. It is shipped and running live customer storefronts today.
This page is the reference for what a block is. The sibling pages cover the parts a block declares: options, fields and shells, inline editing, and registration. For the step-by-step walkthrough — writing your first block, wiring it to catalog data — follow Customize blocks.
A block is a Vue component
There is no block description language and no JSON block format you author. A block is an ordinary Vue 3 component with one extra call:
<script setup lang="ts">
const props = defineProps<{
label: string
}>()
const { options } = defineBlokkli({
bundle: 'badge',
options: {
icon: {
type: 'text',
label: 'Icon',
default: '',
description: 'Solar icon token, e.g. bolt',
},
},
editor: {
mockProps: () => ({ label: 'B2B Supply Solutions' }),
},
})
</script>
<template>
<div class="badge">
<UIcon v-if="options.icon" :name="options.icon" />
<span v-blokkli-editable:label>{{ props.label }}</span>
</div>
</template>
Two things to notice. The component's props become the editor's content fields — an editor filling in "Label" is setting props.label. And bundle is the identifier that ties this component to its entry in the theme manifest; get it out of sync and the block renders as unknown.
Props are content, options are settings
The distinction runs through everything else, so it's worth being precise about it.
| Props | Options | |
|---|---|---|
| What they hold | The block's content — the headline, the body text, the product id | The block's settings — alignment, spacing, variant, width |
| Who sets them | An editor, typing into a field or directly on the canvas | An editor, turning a knob in the sidebar |
| How you declare them | defineProps<{ … }>(), as in any Vue component | The options key on defineBlokkli |
| How you read them | props.headline | the returned options object, which is reactive |
If you find yourself putting a colour hex in a prop, it probably wants to be an option. If you find yourself putting a paragraph of copy in an option, it probably wants to be a prop.
Where blocks live
app/components/blokkli/<bundle>/index.vue
Naming is snake_case throughout — product_grid, accordion_item, account_title. A bundle with several related components keeps them in the same directory, with index.vue as the block itself.
defineBlokkli keys
bundle is the only required key. The other six are all optional, and each one is covered in depth on its own page.
| Key | Purpose |
|---|---|
bundle | The block's id. Must match its blokkli.blocks[].id in theme.json. |
options | Editor settings for this block. Returned as a reactive options object. See Block options. |
globalOptions | Names of options shared across blocks, merged into options. See Block options. |
propsFieldMapping | Declares that a prop is a nested field, an editable region, or a drop target rather than a plain value. See Shells and fields. |
renderFor | Restricts where the component is used, so one bundle can render differently by parent, field, or provider. See Inline editing. |
editor | Editor-only behaviour: icon, mockProps, previewWidth, maxInstances, editTitle, fieldLayout, and friends. None of it ships to the live page. |
chunkName | Assigns the component to a named import chunk. |
editor.mockProps is the one to reach for early: it gives the editor something to render before real content exists, so a freshly dragged block does not look broken. See Inline editing.
bundle ties three things together
The single most common failure — a block rendering as "unknown" — is always the same cause: three strings that must match don't.
- The component's
bundleindefineBlokkli. - The
idinblokkli.blocks[]intheme.json. - The key in the theme's block-id-to-component map.
Align all three and the block appears. Details in Registering blocks.
One requirement on the theme
The editor anchors to the element with id nuxt-root. Nuxt's default root id is __nuxt, so a theme must rename it or the editor cannot attach:
export default defineNuxtConfig({
app: {
rootId: 'nuxt-root',
},
})
Themes extending @revenexx/cover-theme inherit this. If you build directly on @revenexx/cover, set it yourself.
Next steps
- Block options — the eight option types and the
jsonescape hatch. - Shells and fields — blocks that contain other blocks.
- Inline editing — editing text on the canvas, and mock props.
- Registering blocks — the manifest side.
- Block catalog — check before you write a new one.
- Customize blocks — the tutorial: write a block, register it, connect it to catalog data.