Skip to content

Utilities

BlocksWeb provides three essential utility components that integrate your components with the visual editor: RichText, Image, and BlockOutlet. These are the building blocks for creating editor-friendly components.

Overview

UtilityPurposeUse Case
RichTextEditable formatted textBlog content, descriptions, any HTML content
ImageEditable imagesHero images, thumbnails, any visual content
BlockOutletNested componentsLayouts, composition patterns, slots

Important

Never use dangerouslySetInnerHTML for rendering HTML content. Always use the RichText utility component, which provides safe rendering and editor integration.


RichText Component

The RichText component allows content editors to format text with bold, italic, links, lists, headings, and more - all within the visual editor.

Basic Usage

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

type BlogPostProps = {
  title: string;
  content: string;
};

const BlogPost: IBlockswebComponent<BlogPostProps> = ({ title, content }) => {
  return (
    <article>
      <h1>{title}</h1>
      <RichText
        propName="content"
        text={content}
        defaultText="<p>Write your blog post content here...</p>"
      />
    </article>
  );
};

BlogPost.schema = {
  displayName: 'Blog Post',
  options: [
    {
      type: 'text',
      name: 'title',
      label: 'Post Title',
      default: 'My Blog Post'
    },
    {
      type: 'richtext',           // Important: use 'richtext' type
      name: 'content',
      label: 'Post Content',
      default: '<p>Write your content here</p>'
    }
  ]
};

export default BlogPost;

RichText Props

typescript
type RichTextProps = {
  propName: string;        // Required: Must match schema option name
  text: string;            // Required: The HTML content to render
  defaultText?: string;    // Optional: Fallback if text is empty
  className?: string;      // Optional: CSS classes
  colors?: ColorOption[];  // Optional: Custom color palette
};

type ColorOption = {
  name: string;    // Display name
  value: string;   // CSS color value
};

Custom Color Palette

Provide a custom color palette for text colors:

typescript
const colors = [
  { name: 'Primary', value: '#1F3A66' },
  { name: 'Accent', value: '#F95A25' },
  { name: 'Gray', value: '#6B7280' },
  { name: 'White', value: '#FFFFFF' }
];

<RichText
  propName="content"
  text={content}
  defaultText="<p>Content here</p>"
  colors={colors}
/>

Styling RichText

typescript
<RichText
  propName="content"
  text={content}
  className="prose prose-lg max-w-none"  // Tailwind prose styling
/>

What RichText Supports

The rich text editor supports:

  • Formatting: Bold, italic, underline, strikethrough
  • Headings: H1, H2, H3, H4, H5, H6
  • Lists: Ordered and unordered lists
  • Links: Hyperlinks with URL input
  • Colors: Custom text colors (if provided)
  • Alignment: Left, center, right, justify
  • Code: Inline code and code blocks
  • Tables: Basic table support
  • Quotes: Blockquotes

Multi-Language Support

RichText automatically supports translations:

typescript
// Content is stored per locale
locales: {
  en: {
    content: '<p>English content</p>'
  },
  nl: {
    content: '<p>Nederlandse inhoud</p>'
  }
}

// RichText automatically shows the right content based on current locale
<RichText propName="content" text={content} />

Image Component

The Image component provides a seamless way for users to upload, crop, and manage images through the asset manager.

Basic Usage

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

type HeroProps = {
  heroImage: string;
  title: string;
};

const Hero: IBlockswebComponent<HeroProps> = ({ heroImage, title }) => {
  return (
    <section>
      <Image
        propName="heroImage"
        asset={heroImage}
        default="/placeholder-hero.jpg"
        alt={title}
        className="w-full h-[600px] object-cover"
      />
      <h1>{title}</h1>
    </section>
  );
};

Hero.schema = {
  displayName: 'Hero Section',
  options: [
    {
      type: 'image',           // Important: use 'image' type
      name: 'heroImage',
      label: 'Hero Image',
      default: '/placeholder-hero.jpg'
    },
    {
      type: 'text',
      name: 'title',
      label: 'Title',
      default: 'Welcome'
    }
  ]
};

export default Hero;

Image Props

typescript
type ImageProps = {
  propName: string;        // Required: Must match schema option name
  asset: string;           // Required: Image URL from props
  default?: string;        // Optional: Fallback image URL
  alt?: string;            // Optional: Alt text for accessibility
  className?: string;      // Optional: CSS classes
  width?: number;          // Optional: Image width
  height?: number;         // Optional: Image height
  loading?: 'lazy' | 'eager'; // Optional: Loading strategy
};

Responsive Images

typescript
<Image
  propName="heroImage"
  asset={heroImage}
  default="/placeholder.jpg"
  alt="Hero"
  className="w-full h-auto md:h-[400px] lg:h-[600px] object-cover"
  loading="lazy"
/>

Multiple Images

For components with multiple images:

typescript
type GalleryProps = {
  image1: string;
  image2: string;
  image3: string;
};

const Gallery: IBlockswebComponent<GalleryProps> = ({
  image1,
  image2,
  image3
}) => {
  return (
    <div className="grid grid-cols-3 gap-4">
      <Image propName="image1" asset={image1} alt="Gallery image 1" />
      <Image propName="image2" asset={image2} alt="Gallery image 2" />
      <Image propName="image3" asset={image3} alt="Gallery image 3" />
    </div>
  );
};

Gallery.schema = {
  displayName: 'Image Gallery',
  options: [
    { type: 'image', name: 'image1', label: 'Image 1' },
    { type: 'image', name: 'image2', label: 'Image 2' },
    { type: 'image', name: 'image3', label: 'Image 3' }
  ]
};

Asset Manager Integration

When users click an Image component in the editor:

  1. Asset manager opens
  2. User can upload new images or select existing ones
  3. Images are automatically optimized
  4. Selected image URL is saved to the component prop
  5. Component re-renders with new image

Image Optimization

BlocksWeb automatically optimizes images:

  • WebP format conversion
  • Multiple sizes for responsive images
  • Lazy loading support
  • CDN delivery

BlockOutlet Component

The BlockOutlet component enables component composition by allowing one component to contain others.

Basic Usage

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

type PageLayoutProps = {
  headerComponent: any;    // Nested header component
  footerComponent: any;    // Nested footer component
};

const PageLayout: IBlockswebComponent<PageLayoutProps> = ({
  headerComponent,
  footerComponent,
  children
}) => {
  return (
    <div className="page-layout">
      {/* Render header component */}
      <BlockOutlet
        propName="headerComponent"
        blocks={headerComponent}
      />

      {/* Main content */}
      <main className="container mx-auto py-8">
        {children}
      </main>

      {/* Render footer component */}
      <BlockOutlet
        propName="footerComponent"
        blocks={footerComponent}
      />
    </div>
  );
};

PageLayout.schema = {
  displayName: 'Page Layout',
  options: [
    {
      type: 'component',           // Allows nested component
      name: 'headerComponent',
      label: 'Header',
      allowedComponents: ['Header', 'SimpleHeader']
    },
    {
      type: 'component',
      name: 'footerComponent',
      label: 'Footer',
      allowedComponents: ['Footer', 'SimpleFooter']
    }
  ]
};

export default PageLayout;

BlockOutlet Props

typescript
type BlockOutletProps = {
  propName: string;              // Required: Must match schema option name
  blocks: any | any[];           // Required: Nested component(s)
  allowedComponents?: string[];  // Optional: Restrict component types
  className?: string;            // Optional: Wrapper classes
};

Single vs Array of Components

Single Component:

typescript
type CardProps = {
  icon: any;  // Single nested component
};

<BlockOutlet propName="icon" blocks={icon} />

Array of Components:

typescript
type SectionProps = {
  features: any[];  // Array of nested components
};

<BlockOutlet propName="features" blocks={features} />

// Schema:
{
  type: 'component',
  name: 'features',
  label: 'Features',
  multiple: true,  // Allows multiple components
  allowedComponents: ['FeatureCard']
}

Restricting Allowed Components

Limit which components can be nested:

typescript
PageLayout.schema = {
  displayName: 'Page Layout',
  options: [
    {
      type: 'component',
      name: 'sections',
      label: 'Page Sections',
      multiple: true,
      // Only these components can be added
      allowedComponents: [
        'Hero',
        'FeatureSection',
        'Testimonials',
        'CallToAction'
      ]
    }
  ]
};

Composition Pattern

Build flexible layouts with BlockOutlet:

typescript
type ContainerProps = {
  sections: any[];
};

const Container: IBlockswebComponent<ContainerProps> = ({ sections }) => {
  return (
    <div className="container mx-auto">
      <BlockOutlet
        propName="sections"
        blocks={sections}
        className="space-y-16"  // Spacing between sections
      />
    </div>
  );
};

Complete Example

Here's a real-world example using all three utilities:

typescript
import { IBlockswebComponent } from '@blocksweb/core';
import { RichText, Image, BlockOutlet } from '@blocksweb/core/client';

type ArticleProps = {
  featuredImage: string;
  title: string;
  content: string;
  relatedArticles: any[];
};

const Article: IBlockswebComponent<ArticleProps> = ({
  featuredImage,
  title,
  content,
  relatedArticles
}) => {
  return (
    <article className="article">
      {/* Featured Image */}
      <Image
        propName="featuredImage"
        asset={featuredImage}
        default="/placeholder-article.jpg"
        alt={title}
        className="w-full h-[400px] object-cover mb-8"
      />

      {/* Title */}
      <h1 className="text-4xl font-bold mb-4">{title}</h1>

      {/* Article Content - RichText for formatted content */}
      <div className="prose prose-lg max-w-none mb-12">
        <RichText
          propName="content"
          text={content}
          defaultText="<p>Write your article content here...</p>"
        />
      </div>

      {/* Related Articles - BlockOutlet for nested components */}
      <div className="related-articles">
        <h2 className="text-2xl font-bold mb-4">Related Articles</h2>
        <BlockOutlet
          propName="relatedArticles"
          blocks={relatedArticles}
          allowedComponents={['ArticleCard']}
          className="grid grid-cols-3 gap-6"
        />
      </div>
    </article>
  );
};

Article.schema = {
  displayName: 'Article',
  category: 'Content',
  options: [
    {
      type: 'image',
      name: 'featuredImage',
      label: 'Featured Image',
      default: '/placeholder-article.jpg'
    },
    {
      type: 'text',
      name: 'title',
      label: 'Article Title',
      default: 'My Article'
    },
    {
      type: 'richtext',
      name: 'content',
      label: 'Article Content',
      default: '<p>Write your article here...</p>'
    },
    {
      type: 'component',
      name: 'relatedArticles',
      label: 'Related Articles',
      multiple: true,
      allowedComponents: ['ArticleCard']
    }
  ]
};

export default Article;

Best Practices

1. Always Use RichText for HTML Content

typescript
// ✅ GOOD - Use RichText utility
<RichText propName="description" text={description} />

// ❌ BAD - Never use dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: description }} />

2. Provide Default Images

typescript
// ✅ GOOD - Fallback image
<Image
  propName="logo"
  asset={logo}
  default="/logo-placeholder.svg"
/>

// ❌ BAD - No fallback
<Image propName="logo" asset={logo} />
// Breaks if logo is undefined

3. Add Alt Text

typescript
// ✅ GOOD - Accessible
<Image
  propName="heroImage"
  asset={heroImage}
  alt={`${title} - Hero banner`}
/>

// ❌ BAD - Not accessible
<Image propName="heroImage" asset={heroImage} />

4. Restrict Allowed Components

typescript
// ✅ GOOD - Clear intent
{
  type: 'component',
  name: 'features',
  allowedComponents: ['FeatureCard']  // Only feature cards allowed
}

// ❌ BAD - Any component
{
  type: 'component',
  name: 'features'
  // No restrictions - confusing for users
}

5. Match propName to Schema

typescript
// ✅ GOOD - Names match
options: [{ name: 'content', type: 'richtext' }]
<RichText propName="content" text={content} />

// ❌ BAD - Names don't match
options: [{ name: 'content', type: 'richtext' }]
<RichText propName="description" text={content} />  // Won't work!

Next Steps