Skip to main content

Overview

The Word Example demonstrates how to build a Microsoft Word-style editor with a fixed toolbar at the top. All formatting buttons use the Yoopta Editor API methods directly.

Live Demo

Try the interactive Word-style editor demo

Features

Fixed Toolbar

Persistent toolbar at the top with all formatting options

Text Formatting

Bold, Italic, Underline, Strikethrough, Code, Highlight

Block Types

Headings, Lists, Tables, Code blocks, Blockquotes

Export Options

Export to HTML, Markdown, Plain Text, or JSON

Implementation

Fixed Toolbar Component

The key difference from a floating toolbar is that it’s always visible:
import { useYooptaEditor, Blocks, Marks } from '@yoopta/editor';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import {
  Bold,
  Italic,
  Underline,
  Strikethrough,
  Code,
  Heading1,
  Heading2,
  List,
  ListOrdered,
} from 'lucide-react';

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

  return (
    <div className="sticky top-0 z-10 flex items-center gap-1 p-2 border-b bg-white">
      {/* Text Formatting */}
      <div className="flex gap-0.5">
        <Button
          size="sm"
          variant={Marks.isActive(editor, { type: 'bold' }) ? 'default' : 'ghost'}
          onClick={() => Marks.toggle(editor, { type: 'bold' })}
        >
          <Bold className="h-4 w-4" />
        </Button>
        
        <Button
          size="sm"
          variant={Marks.isActive(editor, { type: 'italic' }) ? 'default' : 'ghost'}
          onClick={() => Marks.toggle(editor, { type: 'italic' })}
        >
          <Italic className="h-4 w-4" />
        </Button>
        
        <Button
          size="sm"
          variant={Marks.isActive(editor, { type: 'underline' }) ? 'default' : 'ghost'}
          onClick={() => Marks.toggle(editor, { type: 'underline' })}
        >
          <Underline className="h-4 w-4" />
        </Button>
      </div>

      <Separator orientation="vertical" className="h-6" />

      {/* Block Types */}
      <div className="flex gap-0.5">
        <Button
          size="sm"
          variant="ghost"
          onClick={() => editor.toggleBlock('HeadingOne', { focus: true })}
        >
          <Heading1 className="h-4 w-4" />
        </Button>
        
        <Button
          size="sm"
          variant="ghost"
          onClick={() => editor.toggleBlock('BulletedList', { focus: true })}
        >
          <List className="h-4 w-4" />
        </Button>
        
        <Button
          size="sm"
          variant="ghost"
          onClick={() => editor.toggleBlock('NumberedList', { focus: true })}
        >
          <ListOrdered className="h-4 w-4" />
        </Button>
      </div>

      <Separator orientation="vertical" className="h-6" />

      {/* Export */}
      <ExportDropdown />
    </div>
  );
}

Editor Setup

import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { WORD_PLUGINS, WORD_MARKS } from './config';
import { WordToolbar } from './word-toolbar';
import { applyTheme } from '@yoopta/themes-shadcn';

function WordEditor() {
  const editor = useMemo(() => {
    return createYooptaEditor({
      plugins: applyTheme(WORD_PLUGINS),
      marks: WORD_MARKS,
    });
  }, []);

  return (
    <div className="h-screen flex flex-col">
      {/* Fixed toolbar */}
      <WordToolbar />
      
      {/* Editor area */}
      <div className="flex-1 overflow-auto bg-neutral-50">
        <div className="max-w-4xl mx-auto my-8 bg-white shadow-sm min-h-[11in] p-16">
          <YooptaEditor
            editor={editor}
            placeholder="Start typing..."
            style={{ width: '100%' }}
          />
        </div>
      </div>
    </div>
  );
}

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, Underline, Strike, CodeMark, Highlight } from '@yoopta/marks';

export const WORD_PLUGINS = [
  Paragraph,
  Headings.HeadingOne,
  Headings.HeadingTwo,
  Headings.HeadingThree,
  Lists.BulletedList,
  Lists.NumberedList,
  Blockquote,
  Code,
  Table,
  Divider,
  Image,
  Link,
];

export const WORD_MARKS = [Bold, Italic, Underline, Strike, CodeMark, Highlight];

Export Functionality

Export Dropdown

import { useState } from 'react';
import { useYooptaEditor } from '@yoopta/editor';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Download } from 'lucide-react';

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

  const exportToHTML = () => {
    const html = editor.getHTML();
    downloadFile(html, 'document.html', 'text/html');
  };

  const exportToMarkdown = () => {
    const markdown = editor.getMarkdown();
    downloadFile(markdown, 'document.md', 'text/markdown');
  };

  const exportToText = () => {
    const text = editor.getPlainText();
    downloadFile(text, 'document.txt', 'text/plain');
  };

  const exportToJSON = () => {
    const json = JSON.stringify(editor.getEditorValue(), null, 2);
    downloadFile(json, 'document.json', 'application/json');
  };

  const downloadFile = (content: string, filename: string, type: string) => {
    const blob = new Blob([content], { type });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button size="sm" variant="ghost">
          <Download className="h-4 w-4 mr-2" />
          Export
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem onClick={exportToHTML}>
          Export as HTML
        </DropdownMenuItem>
        <DropdownMenuItem onClick={exportToMarkdown}>
          Export as Markdown
        </DropdownMenuItem>
        <DropdownMenuItem onClick={exportToText}>
          Export as Plain Text
        </DropdownMenuItem>
        <DropdownMenuItem onClick={exportToJSON}>
          Export as JSON
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Export Preview Dialog

import { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Copy, Check } from 'lucide-react';

function ExportPreview({ type }: { type: 'html' | 'markdown' | 'text' }) {
  const editor = useYooptaEditor();
  const [copied, setCopied] = useState(false);

  const content = {
    html: editor.getHTML(),
    markdown: editor.getMarkdown(),
    text: editor.getPlainText(),
  }[type];

  const handleCopy = async () => {
    await navigator.clipboard.writeText(content);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <Dialog>
      <DialogContent className="max-w-3xl">
        <DialogHeader>
          <DialogTitle className="flex items-center justify-between">
            <span>Export Preview - {type.toUpperCase()}</span>
            <Button size="sm" onClick={handleCopy}>
              {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
              {copied ? 'Copied!' : 'Copy'}
            </Button>
          </DialogTitle>
        </DialogHeader>
        
        <ScrollArea className="h-[500px] w-full rounded border">
          <pre className="p-4 text-xs">
            <code>{content}</code>
          </pre>
        </ScrollArea>
      </DialogContent>
    </Dialog>
  );
}

Page Layout Styling

Create a paper-like document appearance:
const PAPER_STYLES = {
  width: '8.5in',
  minHeight: '11in',
  padding: '1in',
  margin: '0 auto',
  backgroundColor: 'white',
  boxShadow: '0 0 10px rgba(0,0,0,0.1)',
};

function WordEditor() {
  return (
    <div className="bg-neutral-100 min-h-screen">
      <WordToolbar />
      
      <div className="py-8">
        <div style={PAPER_STYLES}>
          <YooptaEditor editor={editor} />
        </div>
      </div>
    </div>
  );
}

Keyboard Shortcuts

All standard shortcuts work automatically:
  • Cmd/Ctrl + B - Bold
  • Cmd/Ctrl + I - Italic
  • Cmd/Ctrl + U - Underline
  • Cmd/Ctrl + Shift + S - Strikethrough
  • Cmd/Ctrl + E - Code
  • Cmd/Ctrl + Z - Undo
  • Cmd/Ctrl + Shift + Z - Redo
  • Tab - Increase indent
  • Shift + Tab - Decrease indent
Add print-specific CSS:
@media print {
  /* Hide toolbar when printing */
  .word-toolbar {
    display: none;
  }

  /* Remove shadows and backgrounds */
  .paper {
    box-shadow: none;
    margin: 0;
  }

  /* Page breaks */
  .page-break {
    page-break-after: always;
  }
}

Source Code

View Full Source

Complete Word editor implementation on GitHub

Key Features Demonstrated

1

Fixed Toolbar

Persistent toolbar that stays visible while scrolling
2

Direct API Usage

All buttons call editor.toggleBlock() and Marks.toggle() directly
3

Export Methods

Use getHTML(), getMarkdown(), getPlainText(), and getEditorValue()
4

Document Layout

Paper-like styling with proper dimensions and spacing

Use Cases

Document Editing

Word processing for reports, articles, and documentation

Rich Text Editor

General-purpose text editing with formatting

Blog Editor

Writing and editing blog posts with export options

Note Taking

Structured note-taking with headings and lists

Next Steps

Email Builder

See how to build an email template editor

README Editor

Learn about the Markdown-focused editor

Build docs developers (and LLMs) love