Block Component Templates: Quick Reference & Checklists

Copy-paste TypeScript + React templates, example data, and practical checklists for building blocks, collections, and…

·Matija Žiberna·

⚛️ Advanced React Development Guides

Comprehensive React guides covering hooks, performance optimization, and React 19 features. Includes code examples and prompts to boost your workflow.

No spam. Unsubscribe anytime.

Part 6 of the Design to Code series — Following Icons, Components & Reusable Types

This is a reference page with copy-paste templates and checklists. When you're building a block or collection, come here to grab the template that matches your scenario.

Copy-Paste Templates

Template: Block Type Definition

File: src/types/blocks/[block-name].ts

import type { Media } from '@payload-types';
import type { CTA } from '@/types/blocks';

/**
 * [BlockName] Block
 * [Brief description of what this block displays]
 */
export interface [BlockName]Block {
  // Identification
  id?: string;
  blockType: '[blockName]';
  template: 'default' | 'variant1';

  // Content
  tagline?: string;
  title?: string;
  description?: string;

  // Data
  items?: [Item][];

  // Styling
  bgColor?: 'white' | 'light' | 'dark';
}

export interface [Item] {
  id: string | number;
  title: string;
  description?: string;
  icon?: string;
  image?: Media;
  cta?: CTA;
}

Replace:

  • [BlockName] with your block name (FeaturedIndustries, Testimonials, etc.)
  • [blockName] with camelCase version
  • [Item] with your item type (Industry, Testimonial, etc.)
  • Fields based on your Figma design

Template: Block Component

File: src/components/blocks/[name]/[name]-template-1.tsx

'use client';

import type { [BlockName]Block } from '@/types/blocks';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';

interface [BlockName]Template1Props {
  data: [BlockName]Block;
}

export function [BlockName]Template1({ data }: [BlockName]Template1Props) {
  const {
    tagline,
    title,
    description,
    items = [],
    bgColor = 'white',
  } = data;

  const bgClass = {
    white: 'bg-white',
    light: 'bg-gray-50',
    dark: 'bg-gray-900',
  }[bgColor];

  return (
    <section className={`py-24 ${bgClass}`}>
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        {/* Header */}
        {tagline && (
          <p className="text-sm font-semibold text-blue-600 uppercase tracking-widest mb-2">
            {tagline}
          </p>
        )}
        {title && (
          <h2 className="text-4xl font-bold mb-4">
            {title}
          </h2>
        )}
        {description && (
          <p className="text-xl text-gray-600 mb-12 max-w-2xl">
            {description}
          </p>
        )}

        {/* Items Grid */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
          {items.map((item) => (
            <Card key={item.id}>
              <CardHeader>
                <CardTitle>{item.title}</CardTitle>
              </CardHeader>
              <CardContent>
                {item.description && <p className="text-gray-600">{item.description}</p>}
              </CardContent>
            </Card>
          ))}
        </div>

        {/* Empty State */}
        {items.length === 0 && (
          <div className="text-center py-12 text-gray-500">
            <p>No items to display</p>
          </div>
        )}
      </div>
    </section>
  );
}

Replace:

  • [BlockName] with your component/type name
  • Grid columns: adjust md:grid-cols-3 based on design
  • Card content: add fields from your type

Template: Example Data

File: src/components/blocks/[name]/[name].example.ts

import type { [BlockName]Block, [Item] } from '@/types/blocks';

const item1: [Item] = {
  id: 1,
  title: 'Item Title',
  description: 'Item description',
  icon: 'Star',
};

const item2: [Item] = {
  id: 2,
  title: 'Another Item',
  description: 'Description',
  icon: 'Heart',
};

export const [blockName]Example: [BlockName]Block = {
  blockType: '[blockName]',
  template: 'default',
  tagline: 'Section Label',
  title: 'Main Heading',
  description: 'Subtitle or description',
  items: [item1, item2],
  bgColor: 'light',
};

Template: Collection Type

File: src/types/collections/[name].ts

import type { Media } from '@payload-types';
import type { CTA } from '@/types/blocks';

/**
 * [Collection Name] Collection
 * [Description of what this entity represents]
 */
export interface [CollectionName] {
  // Core
  id: number;
  title: string;
  slug?: string;

  // Content
  description?: string;
  longDescription?: string;

  // Media
  image?: Media;
  icon?: string;

  // Link
  link?: CTA;

  // Status
  _status?: 'draft' | 'published';
  meta?: {
    title?: string;
    description?: string;
  };

  // Timestamps
  createdAt: string;
  updatedAt: string;
}

// Example item
export const [collectionNameExample]: [CollectionName] = {
  id: 1,
  title: 'Example Item',
  slug: 'example-item',
  description: 'Description of the item',
  image: {
    url: 'https://example.com/image.jpg',
    alt: 'Image alt text',
  },
  icon: 'Star',
  link: {
    label: 'Learn More',
    href: '/example',
  },
  _status: 'published',
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
};

export const [collectionNamePluralData]: [CollectionName][] = [
  [collectionNameExample],
  // Add more examples
];

Template: BlockRenderer Update

File: src/components/block-renderer.tsx

import { [BlockName]Template1 } from '@/components/blocks/[name]';

export function BlockRenderer({ block }: BlockRendererProps) {
  // ... existing blocks ...

  if (block.blockType === '[blockName]') {
    if (block.template === 'default') {
      return <[BlockName]Template1 data={block as any} />;
    }
  }

  console.warn(`Unknown block: ${block.blockType}/${block.template}`);
  return null;
}

Template: Page Data

File: src/app/data.ts

import type { Page } from '@payload-types';
import { [blockName]Example } from '@/components/blocks/[name]/[name].example';

export const homePageData: Page = {
  id: 'home',
  slug: '/',
  title: 'Home',
  layout: [
    {
      blockType: '[blockName]',
      template: 'default',
      title: 'Your Title',
      items: [/* your items */],
    } as [BlockName]Block,
  ],
};

Checklists

Creating a New Block Type

NEW BLOCK CREATION CHECKLIST:

SETUP
□ Created src/types/blocks/[name].ts with type definition
□ Exported from src/types/blocks/index.ts
□ Created interface for items (if needed)

COMPONENT
□ Created src/components/blocks/[name]/[name]-template-1.tsx
□ Imported type from @/types/blocks
□ Used shadcn/ui components (Button, Card, etc.)
□ Customized styling with Tailwind className
□ Handled empty state gracefully

EXAMPLES & DATA
□ Created src/components/blocks/[name]/[name].example.ts
□ Example data matches type exactly
□ Created array of examples (for collections)

INTEGRATION
□ Updated src/components/block-renderer.tsx
□ Added case for blockType
□ Added all template variations
□ Tested component renders without errors

TESTING
□ Added to src/app/data.ts
□ Component renders on page
□ Styling matches Figma design
□ Icons use Lucide (not SVG imports)
□ Buttons use CTA type
□ No TypeScript errors

Creating a New Collection

COLLECTION CREATION CHECKLIST:

SETUP
□ Created src/types/collections/[name].ts
□ Defined interface with all needed fields
□ Used sensible default values for optional fields

EXAMPLES
□ Created example instances of the collection
□ Created array (Plural)Data with multiple examples
□ Example data matches interface exactly
□ Used realistic values for each field

EXPORT
□ Exported type from src/types/collections/index.ts
□ Exported example data from index
□ Verified imports work in blocks

USAGE
□ Used collection type in block type definition
□ Block component correctly displays collection items
□ Page data correctly passes collection array to block
□ No TypeScript errors

Integration Checklist

INTEGRATION CHECKLIST:

TYPES
□ Type imports from correct location (@payload-types or @/types/blocks)
□ All required fields present in example data
□ Optional fields clearly marked with `?:`
□ Fields match Figma design

COMPONENTS
□ Component receives correct type prop
□ Component destructures needed fields
□ Uses shadcn/ui for generic UI
□ Uses Lucide for icons
□ No custom component recreation
□ Proper TypeScript types (no `any` type assertions)

STYLING
□ Colors match Figma (use Tailwind or inline styles)
□ Spacing matches Figma (padding, margins, gaps)
□ Responsive breakpoints work correctly
□ Hover states and transitions feel smooth
□ Dark mode (if needed) works correctly

FUNCTIONALITY
□ Interactive elements work (buttons, links)
□ Icons display correctly
□ Empty states handled gracefully
□ No console errors
□ No warnings

Quick Lookup

I need to...

NeedTemplate
Define a new block typeBlock Type Definition
Build a block componentBlock Component
Create example block dataExample Data
Define a new collectionCollection Type
Add block to BlockRendererBlockRenderer Update
Add block to pagePage Data

Icons to Use

Common icon mappings:

const iconMap = {
  'Zap': Zap,           // Fast, energy
  'Heart': Heart,       // Love, care
  'Star': Star,         // Quality, featured
  'ShoppingBag': ShoppingBag,  // Shopping, retail
  'Briefcase': Briefcase,      // Business, work
  'Mail': Mail,         // Contact, email
  'Phone': Phone,       // Contact, phone
  'MapPin': MapPin,     // Location
  'ArrowRight': ArrowRight,    // Next, forward
  'Check': Check,       // Done, complete
};

Full list at lucide.dev

Styling Utilities

Common Tailwind patterns:

// Section padding
className="py-24"  // Vertical padding
className="px-4 sm:px-6 lg:px-8"  // Responsive horizontal

// Typography
className="text-4xl font-bold"  // Large heading
className="text-xl text-gray-600"  // Subtitle
className="text-sm font-semibold uppercase"  // Label

// Grid layouts
className="grid grid-cols-1 md:grid-cols-3 gap-8"  // 1 col mobile, 3 cols desktop
className="grid grid-cols-2 lg:grid-cols-4 gap-6"  // 2 cols tablet, 4 cols desktop

// Cards & Containers
className="max-w-7xl mx-auto"  // Centered container with max width
className="rounded-lg shadow-lg"  // Border radius + shadow
className="hover:shadow-lg transition-shadow"  // Hover effect

// Backgrounds
className="bg-white"
className="bg-gray-50"
className="bg-gray-900"

When You're Stuck

  1. Type error? Check that you're importing from the right location (@/types/blocks vs @payload-types)
  2. Component not rendering? Verify BlockRenderer has the correct case for your blockType
  3. Styling doesn't match? Use browser dev tools to see what's applied; override with more specific Tailwind classes
  4. Icon not showing? Make sure it's in iconMap and spelled correctly
  5. TypeScript errors? Hover over the error; usually it's a missing field or wrong type
0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.