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
| Utility | Purpose | Use Case |
|---|---|---|
| RichText | Editable formatted text | Blog content, descriptions, any HTML content |
| Image | Editable images | Hero images, thumbnails, any visual content |
| BlockOutlet | Nested components | Layouts, 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
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
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:
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
<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:
// 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
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
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
<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:
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:
- Asset manager opens
- User can upload new images or select existing ones
- Images are automatically optimized
- Selected image URL is saved to the component prop
- 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
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
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:
type CardProps = {
icon: any; // Single nested component
};
<BlockOutlet propName="icon" blocks={icon} />Array of Components:
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:
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:
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:
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
// ✅ GOOD - Use RichText utility
<RichText propName="description" text={description} />
// ❌ BAD - Never use dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: description }} />2. Provide Default Images
// ✅ GOOD - Fallback image
<Image
propName="logo"
asset={logo}
default="/logo-placeholder.svg"
/>
// ❌ BAD - No fallback
<Image propName="logo" asset={logo} />
// Breaks if logo is undefined3. Add Alt Text
// ✅ GOOD - Accessible
<Image
propName="heroImage"
asset={heroImage}
alt={`${title} - Hero banner`}
/>
// ❌ BAD - Not accessible
<Image propName="heroImage" asset={heroImage} />4. Restrict Allowed Components
// ✅ 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
// ✅ 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
- Options Reference - All option types explained
- Creating Components - Build production components
- Data Flow - How props flow from editor to components