Skip to main content

Overview

The README Editor example demonstrates how to build a specialized Markdown editor for creating README files. It includes section templates, live preview, and download functionality.

Live Demo

Try the interactive README editor demo

Features

Section Templates

Pre-built sections for common README parts

Live Preview

Toggle between edit and preview modes

Markdown Export

Download as README.md with proper formatting

GitHub Style

Preview with GitHub-flavored Markdown styling

Implementation

README Editor Component

import { useMemo, useState } from 'react';
import YooptaEditor, { createYooptaEditor, Blocks } from '@yoopta/editor';
import { README_PLUGINS, README_MARKS } from './config';
import { Button } from '@/components/ui/button';
import { Download, Eye, FileText, Plus } from 'lucide-react';

function ReadmeEditor() {
  const [previewMode, setPreviewMode] = useState(false);
  
  const editor = useMemo(() => {
    return createYooptaEditor({
      plugins: README_PLUGINS,
      marks: README_MARKS,
    });
  }, []);

  const handleDownload = () => {
    const markdown = editor.getMarkdown();
    const blob = new Blob([markdown], { type: 'text/markdown' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'README.md';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div className="h-screen flex flex-col">
      {/* Toolbar */}
      <div className="border-b p-4 flex justify-between items-center">
        <div className="flex gap-2">
          <SectionTemplates />
          <Button
            variant={previewMode ? 'default' : 'outline'}
            onClick={() => setPreviewMode(!previewMode)}
          >
            <Eye className="h-4 w-4 mr-2" />
            {previewMode ? 'Edit' : 'Preview'}
          </Button>
        </div>
        
        <Button onClick={handleDownload}>
          <Download className="h-4 w-4 mr-2" />
          Download README.md
        </Button>
      </div>

      {/* Editor/Preview Area */}
      <div className="flex-1 overflow-auto">
        {previewMode ? (
          <MarkdownPreview markdown={editor.getMarkdown()} />
        ) : (
          <div className="max-w-4xl mx-auto p-8">
            <YooptaEditor
              editor={editor}
              placeholder="Start writing your README..."
            />
          </div>
        )}
      </div>
    </div>
  );
}

Section Templates

Pre-built sections for common README parts:
import { useYooptaEditor, Blocks } from '@yoopta/editor';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
} from '@/components/ui/dropdown-menu';

const README_SECTIONS = [
  {
    name: 'Installation',
    icon: 'Package',
    blocks: [
      { type: 'HeadingTwo', text: 'Installation' },
      { type: 'Code', language: 'bash', text: 'npm install package-name' },
    ],
  },
  {
    name: 'Usage',
    icon: 'BookOpen',
    blocks: [
      { type: 'HeadingTwo', text: 'Usage' },
      { type: 'Paragraph', text: 'Here\'s a quick example:' },
      { type: 'Code', language: 'javascript', text: 'import Package from "package-name";' },
    ],
  },
  {
    name: 'Features',
    icon: 'Zap',
    blocks: [
      { type: 'HeadingTwo', text: 'Features' },
      { type: 'BulletedList', items: ['Feature 1', 'Feature 2', 'Feature 3'] },
    ],
  },
  {
    name: 'Contributing',
    icon: 'Users',
    blocks: [
      { type: 'HeadingTwo', text: 'Contributing' },
      { type: 'Paragraph', text: 'Contributions are welcome! Please feel free to submit a Pull Request.' },
    ],
  },
  {
    name: 'License',
    icon: 'FileText',
    blocks: [
      { type: 'HeadingTwo', text: 'License' },
      { type: 'Paragraph', text: 'This project is licensed under the MIT License.' },
    ],
  },
];

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

  const insertSection = (section: typeof README_SECTIONS[0]) => {
    const currentPath = editor.path.current;
    const lastBlock = Object.values(editor.getEditorValue()).pop();
    const insertAt = lastBlock ? lastBlock.meta.order + 1 : 0;

    section.blocks.forEach((block, index) => {
      editor.insertBlock(block.type, {
        at: insertAt + index,
        elements: editor.y(block.type.toLowerCase(), {
          children: [editor.y.text(block.text || '')],
        }),
      });
    });
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">
          <Plus className="h-4 w-4 mr-2" />
          Add Section
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        {README_SECTIONS.map((section) => (
          <DropdownMenuItem
            key={section.name}
            onClick={() => insertSection(section)}
          >
            {section.name}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Plugin Configuration

import Paragraph from '@yoopta/paragraph';
import Headings from '@yoopta/headings';
import Lists from '@yoopta/lists';
import Blockquote from '@yoopta/blockquote';
import Code from '@yoopta/code';
import Table from '@yoopta/table';
import Divider from '@yoopta/divider';
import Image from '@yoopta/image';
import Link from '@yoopta/link';
import { Bold, Italic, Strike, CodeMark } from '@yoopta/marks';

export const README_PLUGINS = [
  Paragraph,
  Headings.HeadingOne,
  Headings.HeadingTwo,
  Headings.HeadingThree,
  Lists.BulletedList,
  Lists.NumberedList,
  Lists.TodoList,
  Blockquote,
  Code.extend({
    options: {
      languages: ['javascript', 'typescript', 'bash', 'python', 'json', 'yaml'],
    },
  }),
  Table,
  Divider,
  Image,
  Link,
];

export const README_MARKS = [Bold, Italic, Strike, CodeMark];

Markdown Preview

import { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import remarkGfm from 'remark-gfm';

function MarkdownPreview({ markdown }: { markdown: string }) {
  return (
    <div className="max-w-4xl mx-auto p-8">
      <div className="prose prose-neutral max-w-none">
        <ReactMarkdown
          remarkPlugins={[remarkGfm]}
          components={{
            code({ node, inline, className, children, ...props }) {
              const match = /language-(\w+)/.exec(className || '');
              return !inline && match ? (
                <SyntaxHighlighter
                  style={vscDarkPlus}
                  language={match[1]}
                  PreTag="div"
                  {...props}
                >
                  {String(children).replace(/\n$/, '')}
                </SyntaxHighlighter>
              ) : (
                <code className={className} {...props}>
                  {children}
                </code>
              );
            },
          }}
        >
          {markdown}
        </ReactMarkdown>
      </div>
    </div>
  );
}

Initial README Template

const INITIAL_README = {
  'block-1': {
    type: 'HeadingOne',
    value: [{
      type: 'heading-one',
      children: [{ text: 'My Awesome Project' }],
    }],
    meta: { order: 0, depth: 0 },
  },
  'block-2': {
    type: 'Paragraph',
    value: [{
      type: 'paragraph',
      children: [
        { text: 'A brief description of what this project does and who it\'s for. Built with ' },
        { text: 'Yoopta Editor', bold: true },
        { text: ' - the most powerful rich-text editor.' },
      ],
    }],
    meta: { order: 1, depth: 0 },
  },
  'block-3': {
    type: 'HeadingTwo',
    value: [{
      type: 'heading-two',
      children: [{ text: 'Features' }],
    }],
    meta: { order: 2, depth: 0 },
  },
  'block-4': {
    type: 'BulletedList',
    value: [{
      type: 'bulleted-list',
      children: [
        { type: 'list-item', children: [{ text: 'Easy to use' }] },
        { type: 'list-item', children: [{ text: 'Fast and reliable' }] },
        { type: 'list-item', children: [{ text: 'Well documented' }] },
      ],
    }],
    meta: { order: 3, depth: 0 },
  },
};

Badges Component

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

  const BADGES = [
    {
      name: 'Build Status',
      markdown: '![Build Status](https://img.shields.io/badge/build-passing-brightgreen)',
    },
    {
      name: 'License',
      markdown: '![License](https://img.shields.io/badge/license-MIT-blue)',
    },
    {
      name: 'Version',
      markdown: '![Version](https://img.shields.io/badge/version-1.0.0-blue)',
    },
  ];

  const insertBadge = (badge: typeof BADGES[0]) => {
    const currentBlock = Blocks.getBlock(editor, { id: editor.path.current?.blockId });
    if (currentBlock) {
      // Insert badge image
      editor.insertBlock('Image', {
        at: currentBlock.meta.order,
        props: { src: badge.markdown, alt: badge.name },
      });
    }
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">
          Add Badge
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        {BADGES.map((badge) => (
          <DropdownMenuItem
            key={badge.name}
            onClick={() => insertBadge(badge)}
          >
            {badge.name}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

GitHub Flavored Markdown

The Markdown export supports:
  • Headings (# H1, ## H2, ### H3)
  • Lists (bulleted, numbered, task lists)
  • Code blocks with syntax highlighting
  • Tables with alignment
  • Blockquotes
  • Emphasis (bold, italic, strikethrough)
  • Links and Images
  • Horizontal rules

Source Code

View Full Source

Complete README editor implementation on GitHub

Use Cases

Project Documentation

Create README files for GitHub projects

Technical Writing

Write technical documentation in Markdown

Blog Posts

Draft blog posts in Markdown format

Knowledge Base

Build documentation sites and wikis

Next Steps

Chat Applications

See messaging interface examples

Markdown Export

Learn about Markdown serialization

Build docs developers (and LLMs) love