Skip to main content
Block options give content editors control over a block’s appearance and behaviour directly from the editor’s options panel. You define options inside defineBlokkli() and read their current values through the reactive options object it returns. Changes made in the editor are reflected instantly in the rendered block — no save or reload required.

Defining Options

Pass an options object to defineBlokkli(). Each key becomes an option name; the value is an option definition that describes the input type, display label, and default value:
const { options } = defineBlokkli({
  bundle: 'banner',
  options: {
    theme: {
      type: 'radios',
      label: 'Theme',
      default: 'primary',
      options: {
        primary: 'Primary',
        secondary: 'Secondary',
        warning: 'Warning',
      },
    },
    showCloseButton: {
      type: 'checkbox',
      label: 'Show close button',
      default: false,
    },
    maxWidth: {
      type: 'number',
      label: 'Max width (px)',
      default: 1200,
    },
  },
})
Access the values in your template via the reactive options object:
<template>
  <div
    :class="`banner banner--${options.theme}`"
    :style="{ maxWidth: `${options.maxWidth}px` }"
  >
    <button v-if="options.showCloseButton">×</button>
    <slot />
  </div>
</template>
TypeScript users get full type inference on the options object — the types of each property are inferred from the option definitions you provide, including string unions for radios and checkboxes options.

Option Types

text

A single-line text input. Use it for short freeform values like button labels, override headings, or custom identifiers.
myOption: {
  type: 'text',
  label: 'Button text',
  default: 'Learn more',
}
// Value type: string

checkbox

A boolean toggle rendered as a checkbox. The value is true when checked and false (or undefined if unset) when not.
myOption: {
  type: 'checkbox',
  label: 'Show border',
  default: false,
}
// Value type: boolean | undefined

checkboxes

A multi-select input where editors can pick any combination of the defined choices. The returned value is an array of the selected keys.
myOption: {
  type: 'checkboxes',
  label: 'Features',
  default: [],
  options: {
    search: 'Search',
    sort: 'Sort',
    filter: 'Filter',
  },
}
// Value type: string[]
Use v-for or .includes() checks in your template to act on the selected values:
<ul>
  <li v-if="options.features.includes('search')">🔍 Search</li>
  <li v-if="options.features.includes('sort')">↕ Sort</li>
  <li v-if="options.features.includes('filter')">⚙ Filter</li>
</ul>

radios

A single-select input where editors pick exactly one of the defined choices. The returned value is the key of the selected option.
myOption: {
  type: 'radios',
  label: 'Column count',
  default: '3',
  displayAs: 'grid',
  options: {
    '1': '1 col',
    '2': '2 cols',
    '3': '3 cols',
    '4': '4 cols',
  },
}
// Value type: string (key of selected option)
Use the optional displayAs property to control how the choices are rendered in the editor panel:
displayAs valueAppearance
'radios'Standard radio button list (default)
'grid'Compact grid of buttons — good for numeric or short values
'colors'Colour swatches — use when option keys are CSS colour values
'icons'Icon button grid — use when options map to icon names

color

An HTML colour picker that returns a hex string. Use it for background colours, text colours, or any CSS colour value that editors should control.
myOption: {
  type: 'color',
  label: 'Background color',
  default: '#ffffff',
}
// Value type: '#rrggbb' hex string
<section :style="{ backgroundColor: options.bgColor }">
  <!-- content -->
</section>

range

A slider input for selecting a value within a numeric range. Ideal for spacing scales, opacity, border radius, or any value where approximate adjustment is more important than precision.
myOption: {
  type: 'range',
  label: 'Opacity',
  default: 100,
  min: 0,
  max: 100,
}
// Value type: number

number

A direct numeric input field. Use it when precision matters and a slider would be cumbersome — for example, an exact pixel value or an item count.
myOption: {
  type: 'number',
  label: 'Items per row',
  default: 3,
  min: 1,
  max: 6,
}
// Value type: number
Both range and number accept min and max constraints. blökkli enforces these bounds in the editor UI — editors cannot enter values outside the declared range.

Grouping Options

Add a group string to any option definition to cluster related options under a labelled section heading in the options panel. Options without a group appear in a default ungrouped section.
const { options } = defineBlokkli({
  bundle: 'promo_card',
  options: {
    heading: {
      type: 'text',
      label: 'Heading',
      group: 'Content',
      default: '',
    },
    body: {
      type: 'text',
      label: 'Body text',
      group: 'Content',
      default: '',
    },
    bgColor: {
      type: 'color',
      label: 'Background',
      group: 'Style',
      default: '#ffffff',
    },
    padding: {
      type: 'range',
      label: 'Padding',
      group: 'Style',
      default: 4,
      min: 0,
      max: 10,
    },
  },
})
This produces two collapsible sections in the editor — Content and Style — making complex blocks much easier to navigate.

Global Options

For options that apply to many blocks (for example, a site-wide spacing scale or colour scheme), define them once in nuxt.config.ts and reference them by key using the globalOptions array:
// nuxt.config.ts
export default defineNuxtConfig({
  blokkli: {
    globalOptions: {
      spacing: {
        type: 'radios',
        label: 'Spacing',
        default: 'md',
        options: {
          sm: 'Small',
          md: 'Medium',
          lg: 'Large',
        },
      },
      colorScheme: {
        type: 'radios',
        label: 'Color scheme',
        default: 'light',
        options: {
          light: 'Light',
          dark: 'Dark',
        },
      },
    },
  },
})
Then reference them in any block:
const { options } = defineBlokkli({
  bundle: 'section',
  globalOptions: ['spacing', 'colorScheme'],
})
Global options are merged into the same options reactive object alongside any local options. You access them the same way — options.spacing, options.colorScheme — no extra steps required.

Full Example

Here is a complete block that uses a range of option types and grouping:
<!-- components/Blokkli/PricingCard.vue -->
<template>
  <div
    :class="[
      'pricing-card',
      `pricing-card--${options.style}`,
      { 'pricing-card--featured': options.featured },
    ]"
    :style="{ '--accent': options.accentColor }"
  >
    <span v-if="options.badge" class="pricing-card__badge">
      {{ options.badge }}
    </span>
    <h3>{{ title }}</h3>
    <p class="pricing-card__price">{{ price }}</p>
    <ul>
      <li v-for="feature in features" :key="feature">{{ feature }}</li>
    </ul>
  </div>
</template>

<script lang="ts" setup>
defineProps<{
  title: string
  price: string
  features: string[]
}>()

const { options } = defineBlokkli({
  bundle: 'pricing_card',
  options: {
    style: {
      type: 'radios',
      label: 'Card style',
      group: 'Appearance',
      default: 'outline',
      options: { outline: 'Outline', filled: 'Filled', minimal: 'Minimal' },
    },
    accentColor: {
      type: 'color',
      label: 'Accent color',
      group: 'Appearance',
      default: '#6366f1',
    },
    featured: {
      type: 'checkbox',
      label: 'Featured card',
      group: 'Appearance',
      default: false,
    },
    badge: {
      type: 'text',
      label: 'Badge label',
      group: 'Content',
      default: '',
    },
  },
})
</script>