Your First Component
In this tutorial, you'll build a complete BlocksWeb component from scratch. We'll create a feature card component that can be used on marketing pages.
What You'll Build
By the end of this tutorial, you'll have created a reusable "Feature Card" component that includes:
- An icon or image
- A title
- A description
- An optional call-to-action button
Prerequisites
Before starting, make sure you have:
- A BlocksWeb project set up (Installation Guide)
- Basic knowledge of React and TypeScript
- Your development server running (
npm run dev)
Step 1: Create the Component File
Create a new file components/FeatureCard.tsx:
mkdir -p components
touch components/FeatureCard.tsxStep 2: Define the TypeScript Interface
Start by defining the props your component will receive:
// components/FeatureCard.tsx
import { IBlockswebComponent } from '@blocksweb/core';
type FeatureCardProps = {
icon: string;
title: string;
description: string;
buttonText?: string;
buttonUrl?: string;
showButton: boolean;
};TIP
Adding type definitions helps with autocomplete and catches errors early.
Step 3: Create the Component
Now create the React component:
const FeatureCard: IBlockswebComponent<FeatureCardProps> = ({
icon,
title,
description,
buttonText,
buttonUrl,
showButton
}) => {
return (
<div className="feature-card bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
{/* Icon */}
<div className="icon-wrapper mb-4">
<img
src={icon}
alt={title}
className="w-16 h-16 object-contain"
/>
</div>
{/* Title */}
<h3 className="text-2xl font-bold mb-3 text-gray-800">
{title}
</h3>
{/* Description */}
<p className="text-gray-600 mb-4 leading-relaxed">
{description}
</p>
{/* Button (conditional) */}
{showButton && buttonText && buttonUrl && (
<a
href={buttonUrl}
className="inline-block bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition-colors"
>
{buttonText}
</a>
)}
</div>
);
};Step 4: Add the Schema
This is where the BlocksWeb magic happens. The schema defines what properties can be edited in the visual editor:
FeatureCard.schema = {
displayName: 'Feature Card',
category: 'Marketing',
options: [
{
type: 'image',
name: 'icon',
label: 'Icon/Image',
default: 'https://placehold.co/64x64'
},
{
type: 'text',
name: 'title',
label: 'Title',
default: 'Amazing Feature'
},
{
type: 'text',
name: 'description',
label: 'Description',
default: 'This feature will revolutionize your workflow and make your life easier.'
},
{
type: 'checkbox',
name: 'showButton',
label: 'Show Button',
default: true
},
{
type: 'text',
name: 'buttonText',
label: 'Button Text',
default: 'Learn More'
},
{
type: 'text',
name: 'buttonUrl',
label: 'Button URL',
default: '#'
}
]
};Schema Breakdown
Let's understand each part:
- displayName: How the component appears in the component list
- category: Groups components together (e.g., "Marketing", "Content", "Layout")
- options: Array of editable properties
Each option has:
- type: The input type (text, image, checkbox, etc.)
- name: The prop name passed to your component
- label: What users see in the editor
- default: Initial value
Step 5: Export the Component
Add this at the end of your file:
export default FeatureCard;Complete File
Here's the complete FeatureCard.tsx:
import { IBlockswebComponent } from '@blocksweb/core';
type FeatureCardProps = {
icon: string;
title: string;
description: string;
buttonText?: string;
buttonUrl?: string;
showButton: boolean;
};
const FeatureCard: IBlockswebComponent<FeatureCardProps> = ({
icon,
title,
description,
buttonText,
buttonUrl,
showButton
}) => {
return (
<div className="feature-card bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
<div className="icon-wrapper mb-4">
<img src={icon} alt={title} className="w-16 h-16 object-contain" />
</div>
<h3 className="text-2xl font-bold mb-3 text-gray-800">{title}</h3>
<p className="text-gray-600 mb-4 leading-relaxed">{description}</p>
{showButton && buttonText && buttonUrl && (
<a
href={buttonUrl}
className="inline-block bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition-colors"
>
{buttonText}
</a>
)}
</div>
);
};
FeatureCard.schema = {
displayName: 'Feature Card',
category: 'Marketing',
options: [
{
type: 'image',
name: 'icon',
label: 'Icon/Image',
default: 'https://placehold.co/64x64'
},
{
type: 'text',
name: 'title',
label: 'Title',
default: 'Amazing Feature'
},
{
type: 'text',
name: 'description',
label: 'Description',
default: 'This feature will revolutionize your workflow.'
},
{
type: 'checkbox',
name: 'showButton',
label: 'Show Button',
default: true
},
{
type: 'text',
name: 'buttonText',
label: 'Button Text',
default: 'Learn More'
},
{
type: 'text',
name: 'buttonUrl',
label: 'Button URL',
default: '#'
}
]
};
export default FeatureCard;Step 6: Register the Component
Open blocksweb.config.ts and import your component:
import { IBlockswebComponent } from '@blocksweb/core';
import FeatureCard from './components/FeatureCard';
export const editorComponents: IBlockswebComponent[] = [
FeatureCard,
// Add more components here
];
export const settings = {
editorComponents,
scripts: ['https://cdn.tailwindcss.com'],
};Complete Configuration Guide
See the BlocksWeb Configuration guide for detailed information about registering components, collections, and configuring your application.
Step 7: Test in the Editor
- Make sure your dev server is running (
npm run dev) - Open http://localhost:3000
- Sign in to the editor
- Click "+ Add Block"
- Find "Feature Card" under the "Marketing" category
- Click to add it to the page
You should see your Feature Card component appear!
Step 8: Customize Properties
With the component selected:
- Click on the Icon/Image field
- Upload an icon or enter an image URL
- Change the Title to something like "Lightning Fast"
- Update the Description
- Toggle Show Button on/off
- Modify the Button Text and Button URL
Watch as your changes appear in real-time!
Enhancements
Let's make our component even better.
Add Hover Effects
<div className="feature-card bg-white rounded-lg shadow-md p-6 hover:shadow-lg hover:scale-105 transition-all duration-300">
{/* ... */}
</div>Add Icon Styles
Allow users to choose icon styles:
type FeatureCardProps = {
// ... existing props
iconStyle: 'circle' | 'square' | 'none';
};
// In the component:
const iconClasses = `w-16 h-16 object-contain ${
iconStyle === 'circle' ? 'rounded-full bg-blue-100 p-3' :
iconStyle === 'square' ? 'rounded-lg bg-gray-100 p-3' :
''
}`;
// In the schema:
{
type: 'select',
name: 'iconStyle',
label: 'Icon Style',
options: [
{ label: 'Circle', value: 'circle' },
{ label: 'Square', value: 'square' },
{ label: 'None', value: 'none' }
],
default: 'circle'
}Add Rich Text Description
Replace the plain text description with rich text:
import { RichText } from '@blocksweb/core/client';
// In the component:
<RichText
propName="description"
text={description}
defaultText="This feature will revolutionize your workflow."
/>
// In the schema (change type):
{
type: 'richtext',
name: 'description',
label: 'Description',
default: 'This feature will revolutionize your workflow.'
}Best Practices
1. Use Semantic HTML
// Good
<article className="feature-card">
<figure><img src={icon} alt={title} /></figure>
<h3>{title}</h3>
<p>{description}</p>
</article>
// Avoid
<div className="feature-card">
<div><img src={icon} alt={title} /></div>
<div>{title}</div>
<div>{description}</div>
</div>2. Provide Good Defaults
Always set sensible defaults so the component looks good immediately after being added.
3. Add Alt Text
Always include alt text for images for accessibility:
<img src={icon} alt={`${title} icon`} />4. Make It Responsive
Use responsive Tailwind classes:
<div className="feature-card p-4 md:p-6 lg:p-8">
<h3 className="text-xl md:text-2xl lg:text-3xl">
{title}
</h3>
</div>5. Group Related Options
Use clear labels and logical ordering in your schema:
options: [
// Content
{ type: 'image', name: 'icon', label: 'Icon' },
{ type: 'text', name: 'title', label: 'Title' },
{ type: 'richtext', name: 'description', label: 'Description' },
// Button
{ type: 'checkbox', name: 'showButton', label: 'Show Button' },
{ type: 'text', name: 'buttonText', label: 'Button Text' },
{ type: 'text', name: 'buttonUrl', label: 'Button URL' },
// Styling
{ type: 'select', name: 'iconStyle', label: 'Icon Style' },
{ type: 'color', name: 'backgroundColor', label: 'Background Color' },
]Common Patterns
Conditional Rendering
{showButton && (
<button>{buttonText}</button>
)}Dynamic Styling
<div
className="feature-card"
style={{ backgroundColor: bgColor }}
>Mapping Arrays
{features.map((feature, index) => (
<li key={index}>{feature}</li>
))}Next Steps
Congratulations! You've created your first BlocksWeb component. Now explore:
- Options Reference - Learn about all 12 option types
- Creating Components - Advanced component patterns
- Utilities - Use RichText, Image, and BlockOutlet
- Data Flow - Understand how props flow from editor
Troubleshooting
Component Not Showing Up
- Check it's exported:
export default FeatureCard - Check it's registered in
blocksweb.config.ts - Check the schema is attached:
FeatureCard.schema = { ... } - Refresh the browser
TypeScript Errors
Make sure your types match:
// Props type should match schema options
type FeatureCardProps = {
icon: string; // matches { type: 'image', name: 'icon' }
title: string; // matches { type: 'text', name: 'title' }
showButton: boolean; // matches { type: 'checkbox', name: 'showButton' }
};Styling Not Working
If using Tailwind:
- Check Tailwind is in
blocksweb.config.tsscripts - Or import Tailwind CSS in your root layout
- Make sure classes are not conflicting
You're now ready to build more complex components!