Skip to main content

Overview

The CMS/Website Builder example demonstrates how to build a visual content management system with Yoopta Editor. It features a drag-and-drop interface, sidebar settings panel, and live preview mode.

Live Demo

Try the interactive CMS builder demo
This is an early preview. The current demo shows basic CMS functionality with a handful of content blocks. The full vision includes AI agents, plugin marketplace, and visual theme builder.

Features

Current Implementation

Visual Editor

Drag-and-drop block editor with real-time preview

Sidebar Settings

Configure page settings, SEO, and block properties

Content Blocks

Rich set of content blocks for building pages

Live Preview

See changes in real-time as you edit

Roadmap Features

An AI agent that builds and edits pages for you — like Lovable, but with inline editing and a rich plugin ecosystem. Describe what you want and watch it assemble layouts, write copy, and style blocks in real time.
Publish your own plugins or install community-built ones. A shared ecosystem where anyone can extend the CMS with custom blocks, integrations, and themes.
Design themes with a live preview — tweak fonts, colors, spacing, and layout. Export and share themes or apply community ones in a click.
Growing library of content blocks — pricing tables, testimonials, hero sections, FAQ accordions, charts, forms, and more — all drag-and-drop ready.

Implementation Approach

Layout Structure

A typical CMS builder uses a split-panel layout:
import { useState } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';

function CMSBuilder() {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [previewMode, setPreviewMode] = useState(false);

  return (
    <div className="flex h-screen">
      {/* Sidebar */}
      {sidebarOpen && (
        <aside className="w-80 border-r bg-neutral-50">
          <PageSettings />
          <BlockSettings />
        </aside>
      )}
      
      {/* Main Editor */}
      <main className="flex-1 overflow-auto">
        <EditorToolbar
          onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
          onTogglePreview={() => setPreviewMode(!previewMode)}
        />
        
        <div className="max-w-5xl mx-auto p-8">
          <YooptaEditor
            editor={editor}
            readOnly={previewMode}
          >
            {/* UI components */}
          </YooptaEditor>
        </div>
      </main>
    </div>
  );
}

Page Settings Component

import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';

function PageSettings() {
  const [settings, setSettings] = useState({
    title: '',
    slug: '',
    description: '',
    keywords: '',
  });

  return (
    <div className="p-4 space-y-4">
      <h3 className="font-semibold">Page Settings</h3>
      
      <div className="space-y-2">
        <Label>Page Title</Label>
        <Input
          value={settings.title}
          onChange={(e) => setSettings({ ...settings, title: e.target.value })}
        />
      </div>
      
      <div className="space-y-2">
        <Label>URL Slug</Label>
        <Input
          value={settings.slug}
          onChange={(e) => setSettings({ ...settings, slug: e.target.value })}
        />
      </div>
      
      <div className="space-y-2">
        <Label>Meta Description</Label>
        <Textarea
          value={settings.description}
          onChange={(e) => setSettings({ ...settings, description: e.target.value })}
        />
      </div>
    </div>
  );
}

Block Settings

Customize individual block properties:
import { useYooptaEditor, Blocks } from '@yoopta/editor';

function BlockSettings() {
  const editor = useYooptaEditor();
  const [selectedBlock, setSelectedBlock] = useState(null);

  useEffect(() => {
    editor.on('path-change', ({ path }) => {
      if (path.current) {
        const block = Blocks.getBlock(editor, { id: path.current.blockId });
        setSelectedBlock(block);
      }
    });
  }, [editor]);

  if (!selectedBlock) {
    return <div className="p-4 text-sm text-neutral-500">No block selected</div>;
  }

  return (
    <div className="p-4 space-y-4">
      <h3 className="font-semibold">Block Settings</h3>
      
      {/* Block-specific settings based on type */}
      {selectedBlock.type === 'Image' && <ImageBlockSettings block={selectedBlock} />}
      {selectedBlock.type === 'Callout' && <CalloutBlockSettings block={selectedBlock} />}
      
      {/* Common settings */}
      <div className="space-y-2">
        <Label>Block ID</Label>
        <Input value={selectedBlock.id} readOnly />
      </div>
    </div>
  );
}

Preview Mode

Toggle between edit and preview modes:
function EditorToolbar({ onTogglePreview }) {
  const [isPreview, setIsPreview] = useState(false);

  const handlePreviewToggle = () => {
    setIsPreview(!isPreview);
    onTogglePreview();
  };

  return (
    <div className="flex items-center justify-between p-4 border-b">
      <div className="flex gap-2">
        <Button onClick={handlePreviewToggle}>
          {isPreview ? 'Edit' : 'Preview'}
        </Button>
        <Button variant="outline">Save Draft</Button>
        <Button variant="default">Publish</Button>
      </div>
    </div>
  );
}

CMS-Specific Plugins

Configure plugins optimized for landing pages:
import Paragraph from '@yoopta/paragraph';
import Headings from '@yoopta/headings';
import Image from '@yoopta/image';
import Video from '@yoopta/video';
import Callout from '@yoopta/callout';
import Accordion from '@yoopta/accordion';
import Tabs from '@yoopta/tabs';
import Steps from '@yoopta/steps';
import Divider from '@yoopta/divider';

export const CMS_PLUGINS = [
  Paragraph,
  Headings.HeadingOne,
  Headings.HeadingTwo,
  Headings.HeadingThree,
  Image.extend({
    options: {
      async onUpload(file) {
        const formData = new FormData();
        formData.append('file', file);
        
        const response = await fetch('/api/upload', {
          method: 'POST',
          body: formData,
        });
        
        const { url } = await response.json();
        return { src: url };
      },
    },
  }),
  Video,
  Callout,
  Accordion,
  Tabs,
  Steps,
  Divider,
];

Export to HTML

Generate production-ready HTML:
import { useYooptaEditor } from '@yoopta/editor';

function ExportButton() {
  const editor = useYooptaEditor();

  const handleExport = () => {
    const html = editor.getHTML();
    
    // Add page wrapper and styles
    const fullPage = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${pageSettings.title}</title>
  <meta name="description" content="${pageSettings.description}">
  <link rel="stylesheet" href="/styles/page.css">
</head>
<body>
  <main class="container">
    ${html}
  </main>
</body>
</html>
    `;
    
    // Download or save to server
    const blob = new Blob([fullPage], { type: 'text/html' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${pageSettings.slug}.html`;
    a.click();
  };

  return <Button onClick={handleExport}>Export HTML</Button>;
}

Saving and Loading Pages

import { useEffect } from 'react';
import { createYooptaEditor, YooptaContentValue } from '@yoopta/editor';

function CMSEditor({ pageId }: { pageId: string }) {
  const editor = useMemo(() => createYooptaEditor({ plugins: CMS_PLUGINS }), []);

  // Load page content
  useEffect(() => {
    async function loadPage() {
      const response = await fetch(`/api/pages/${pageId}`);
      const data = await response.json();
      
      editor.setEditorValue(data.content);
    }
    
    loadPage();
  }, [pageId]);

  // Auto-save on change
  const handleChange = async (value: YooptaContentValue) => {
    await fetch(`/api/pages/${pageId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content: value }),
    });
  };

  return (
    <YooptaEditor
      editor={editor}
      onChange={handleChange}
    >
      {/* UI components */}
    </YooptaEditor>
  );
}

Source Code

View Full Source

Complete CMS implementation on GitHub

Use Cases

Landing Pages

Build marketing pages with hero sections, features, and CTAs

Blog CMS

Create and manage blog posts with rich formatting

Documentation Sites

Build knowledge bases and documentation

Portfolio Sites

Showcase projects and work samples

Next Steps

Email Builder

Learn how to build an email template editor

Exports

Export content to HTML, Markdown, and more

Build docs developers (and LLMs) love