Skip to content

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:

bash
mkdir -p components
touch components/FeatureCard.tsx

Step 2: Define the TypeScript Interface

Start by defining the props your component will receive:

typescript
// 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:

typescript
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:

typescript
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:

typescript
export default FeatureCard;

Complete File

Here's the complete FeatureCard.tsx:

typescript
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:

typescript
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

  1. Make sure your dev server is running (npm run dev)
  2. Open http://localhost:3000
  3. Sign in to the editor
  4. Click "+ Add Block"
  5. Find "Feature Card" under the "Marketing" category
  6. Click to add it to the page

You should see your Feature Card component appear!

Step 8: Customize Properties

With the component selected:

  1. Click on the Icon/Image field
  2. Upload an icon or enter an image URL
  3. Change the Title to something like "Lightning Fast"
  4. Update the Description
  5. Toggle Show Button on/off
  6. 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

typescript
<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:

typescript
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:

typescript
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

typescript
// 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:

typescript
<img src={icon} alt={`${title} icon`} />

4. Make It Responsive

Use responsive Tailwind classes:

typescript
<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>

Use clear labels and logical ordering in your schema:

typescript
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

typescript
{showButton && (
  <button>{buttonText}</button>
)}

Dynamic Styling

typescript
<div
  className="feature-card"
  style={{ backgroundColor: bgColor }}
>

Mapping Arrays

typescript
{features.map((feature, index) => (
  <li key={index}>{feature}</li>
))}

Next Steps

Congratulations! You've created your first BlocksWeb component. Now explore:

Troubleshooting

Component Not Showing Up

  1. Check it's exported: export default FeatureCard
  2. Check it's registered in blocksweb.config.ts
  3. Check the schema is attached: FeatureCard.schema = { ... }
  4. Refresh the browser

TypeScript Errors

Make sure your types match:

typescript
// 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:

  1. Check Tailwind is in blocksweb.config.ts scripts
  2. Or import Tailwind CSS in your root layout
  3. Make sure classes are not conflicting

You're now ready to build more complex components!