Skip to content

Options System

Understanding how the options system works is crucial to building effective BlocksWeb components. This guide explains the complete flow from schema definition to the visual editor to your component props.

Overview

The options system is the bridge between your React components and the visual editor. It defines what properties content editors can modify and how those properties are presented in the editor UI.

How Options Work

The Complete Flow

1. Developer defines schema →
2. Component registered in config →
3. User adds component to page →
4. Editor loads schema →
5. Options panel renders UI →
6. User changes value →
7. updateBlockProps() called →
8. Component re-renders with new props →
9. Auto-save to API

Schema Definition

When you define a component schema, you're creating a contract:

typescript
import { IBlockswebComponent } from '@blocksweb/core';

type HeroProps = {
  title: string;
  subtitle: string;
  buttonText: string;
};

const Hero: IBlockswebComponent<HeroProps> = ({ title, subtitle, buttonText }) => {
  return (
    <section>
      <h1>{title}</h1>
      <p>{subtitle}</p>
      <button>{buttonText}</button>
    </section>
  );
};

Hero.schema = {
  displayName: 'Hero Section',
  options: [
    {
      type: 'text',
      name: 'title',           // Must match prop name
      label: 'Hero Title',
      default: 'Welcome'
    },
    {
      type: 'text',
      name: 'subtitle',
      label: 'Subtitle',
      default: 'Get started today'
    },
    {
      type: 'text',
      name: 'buttonText',
      label: 'Button Text',
      default: 'Learn More'
    }
  ]
};

export default Hero;

Behind the Scenes

When a user selects your component in the editor:

1. Block Selection

typescript
// User clicks component in canvas
// EditorContext.setSelectedBlockId('hero-123')
const selectedBlock = findRecursiveBlockById(blocks, 'hero-123');

2. Schema Lookup

typescript
// Editor finds registered component
const component = editorComponents.find(c => c.schema.displayName === selectedBlock.type);
const schema = component.schema;

3. Options Panel Rendering

typescript
// For each option in schema
schema.options.map(option => {
  switch(option.type) {
    case 'text':
      return <TextInput {...option} />;
    case 'richtext':
      return <RichTextEditor {...option} />;
    case 'image':
      return <ImagePicker {...option} />;
    // ... etc
  }
});

4. Value Updates

typescript
// User types in text field
const handleChange = (optionName, newValue) => {
  updateBlockProps(selectedBlockId, {
    [optionName]: newValue
  });
};

5. Component Re-render

typescript
// Block props updated in EditorContext
// Canvas re-renders component with new props
<Hero
  title="New Title"        // Updated value
  subtitle="..."
  buttonText="..."
/>

Data Structure

Block Storage

Each component instance is stored as a block:

typescript
type BlockType = {
  id: string;              // Unique identifier
  type: string;            // Component displayName
  props: Record<string, any>; // All option values
};

// Example in storage:
{
  id: "hero-abc123",
  type: "Hero Section",
  props: {
    title: "Welcome to Our Site",
    subtitle: "We help you succeed",
    buttonText: "Get Started",
    backgroundColor: "#f0f0f0"
  }
}

Page Content Structure

typescript
{
  blocks: [
    {
      id: "hero-1",
      type: "Hero Section",
      props: { title: "Welcome", ... }
    },
    {
      id: "features-1",
      type: "Feature Grid",
      props: { title: "Features", items: [...] }
    }
  ],
  locales: {
    en: { /* translations */ },
    nl: { /* translations */ }
  }
}

Option Processing

Type Coercion

BlocksWeb automatically handles type conversions:

typescript
// Number option
{
  type: 'number',
  name: 'columns',
  default: 3
}

// Input: "4" (string from input)
// Stored: 4 (number)
// Received in component: 4 (number)

Default Values

Defaults are applied when:

  • Component is first added
  • Option value is undefined
  • Reset to defaults is triggered
typescript
// User adds Hero component
// Initial props automatically set:
{
  title: "Welcome",           // From default
  subtitle: "Get started today", // From default
  buttonText: "Learn More"    // From default
}

Auto-Save System

BlocksWeb automatically saves changes:

typescript
// User changes value
updateBlockProps('hero-123', { title: 'New Title' });

// After 3 seconds of inactivity
// Auto-save hook triggers
await saveToAPI({
  pageId: currentPage.id,
  blocks: updatedBlocks
});

// UI shows "Saving..." then "Saved"

Best Practices

1. Match Names Exactly

typescript
// ✅ Good - names match
type Props = { title: string };
options: [{ name: 'title', ... }]

// ❌ Bad - names don't match
type Props = { pageTitle: string };
options: [{ name: 'title', ... }]  // Won't work!

2. Always Provide Defaults

typescript
// ✅ Good - component looks complete immediately
{
  type: 'text',
  name: 'title',
  default: 'Welcome to Our Site'
}

// ❌ Bad - empty component after adding
{
  type: 'text',
  name: 'title'
  // No default
}

3. Use Appropriate Types

typescript
// ✅ Good - right tool for the job
{ type: 'richtext', name: 'content' }    // For formatted text
{ type: 'image', name: 'hero' }          // For images
{ type: 'color', name: 'bg' }            // For colors

// ❌ Bad - forcing wrong types
{ type: 'text', name: 'content' }        // Can't format text
{ type: 'text', name: 'hero' }           // Can't upload images

4. Add Help Text

typescript
{
  type: 'text',
  name: 'metaDescription',
  label: 'Meta Description',
  help: 'Appears in search results. Keep it between 150-160 characters.',
  maxLength: 160
}

Next Steps