> ## Documentation Index
> Fetch the complete documentation index at: https://superdoc-caio-pizzol-docs-ai-core-preset.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# FormatCommands extension

export const SourceCodeLink = ({extension, path}) => {
  const githubPath = path || `packages/super-editor/src/editors/v1/extensions/${extension.toLowerCase()}`;
  const githubUrl = `https://github.com/superdoc-dev/superdoc/tree/main/${githubPath}`;
  return <div>
      <p>
        <a href={githubUrl} target="_blank" rel="noopener noreferrer">
          View on GitHub →
        </a>
      </p>
    </div>;
};

export const SuperDocEditor = ({html = '<p>Start editing...</p>', height = '400px', maxHeight = '400px', onReady = null, showExport = true, customButtons = null}) => {
  const [ready, setReady] = useState(false);
  const editorRef = useRef(null);
  const containerIdRef = useRef(`editor-${Math.random().toString(36).substr(2, 9)}`);
  const DEV_DIST_URL = 'http://localhost:9094/dist';
  const UNPKG_DIST_URL = 'https://unpkg.com/superdoc@latest/dist';
  const getBaseUrl = async () => {
    const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost';
    if (isDev) {
      try {
        const res = await fetch(`${DEV_DIST_URL}/superdoc.min.js`, {
          method: 'HEAD'
        });
        if (res.ok) {
          console.info('[SuperDoc Docs] Using local build from', DEV_DIST_URL);
          return DEV_DIST_URL;
        }
        console.warn('[SuperDoc Docs] Local dev server returned', res.status, '- falling back to unpkg');
      } catch (err) {
        console.warn('[SuperDoc Docs] Local dev server not reachable: falling back to unpkg.', 'Run `pnpm dev:docs` from the repo root to use your local build.', err.message);
      }
    }
    return UNPKG_DIST_URL;
  };
  const ensureStyle = baseUrl => {
    const styleHref = `${baseUrl}/style.css`;
    if (document.querySelector(`link[href="${styleHref}"]`)) return;
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = styleHref;
    document.head.appendChild(link);
  };
  const loadSuperDoc = baseUrl => {
    if (window.SuperDoc) return Promise.resolve();
    const scriptSrc = `${baseUrl}/superdoc.min.js`;
    const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
    if (existingScript) {
      if (window.SuperDoc) return Promise.resolve();
      return new Promise((resolve, reject) => {
        existingScript.addEventListener('load', resolve, {
          once: true
        });
        existingScript.addEventListener('error', reject, {
          once: true
        });
      });
    }
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = scriptSrc;
      script.onload = resolve;
      script.onerror = reject;
      document.body.appendChild(script);
    });
  };
  const initEditor = () => {
    setTimeout(() => {
      if (!window.SuperDoc) return;
      if (!document.getElementById(containerIdRef.current)) return;
      if (editorRef.current) return;
      editorRef.current = new window.SuperDoc({
        selector: `#${containerIdRef.current}`,
        html,
        rulers: true,
        contained: true,
        onReady: () => {
          setReady(true);
          if (onReady) onReady(editorRef.current);
        }
      });
    }, 100);
  };
  useEffect(() => {
    let cancelled = false;
    const boot = async () => {
      try {
        const baseUrl = await getBaseUrl();
        ensureStyle(baseUrl);
        await loadSuperDoc(baseUrl);
        if (!cancelled) initEditor();
      } catch (error) {
        console.error('Failed to boot SuperDoc:', error);
      }
    };
    boot();
    return () => {
      cancelled = true;
      editorRef.current?.destroy?.();
      editorRef.current = null;
    };
  }, []);
  const exportDocx = () => {
    if (editorRef.current?.export) {
      editorRef.current.export();
    }
  };
  return <div className="border rounded-lg bg-white overflow-hidden">
      {ready && (showExport || customButtons) && <div className="px-3 py-2 bg-gray-50 border-b">
          {customButtons && <div className="space-y-1 mb-2">
              {customButtons.map((row, rowIndex) => <div key={rowIndex} className="flex gap-1">
                  {row.map((btn, i) => <button key={i} onClick={() => btn.onClick(editorRef.current)} className={btn.className || 'flex-1 px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded hover:bg-gray-200'}>
                      {btn.label}
                    </button>)}
                </div>)}
            </div>}
          {showExport && <div className="text-right">
              <button onClick={exportDocx} className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600">
                Export DOCX
              </button>
            </div>}
        </div>}
      <div id={containerIdRef.current} style={{
    height,
    maxHeight,
    paddingLeft: '5px'
  }} />
      <style jsx>{`
        #${containerIdRef.current} .superdoc__layers {
          max-width: 660px !important;
        }
        #${containerIdRef.current} .super-editor {
          max-width: 100% !important;
          width: 100% !important;
          color: #000;
        }
        #${containerIdRef.current} .editor-element {
          width: 100% !important;
          min-width: unset !important;
          transform: none !important;
        }
        #${containerIdRef.current} .editor-element {
          h1,
          h2,
          h3,
          h4,
          h5,
          strong {
            color: #000;
          }
        }
      `}</style>
    </div>;
};

Copy and paste text formatting like Word's format painter.

Select styled text, copy its format, then apply to other text - all in two clicks.

<SuperDocEditor
  html={`
<p><strong style="color: #dc2626; font-size: 18pt;">Styled source text</strong> with multiple formats.</p>
<p>Target text - select this to apply the format.</p>
<p>Another paragraph to test format painting.</p>
`}
  height="200px"
  customButtons={[
[
  {
    label: 'Format Painter',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.commands) return
      const isStoring = !editor.storage.formatCommands?.storedStyle
      editor.commands.copyFormat()
      if (isStoring) {
        alert('Format copied! Select other text and click again to apply.')
      }
    }
  },
  {
    label: 'Clear Format',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.commands) return
      editor.commands.clearFormat()
    }
  }
]
]}
/>

## How it works

1. Select text with formatting you want to copy
2. Click Format Painter (format is now stored)
3. Select target text
4. Click Format Painter again to apply

## Use case

* **Consistency** - Apply the same styling across multiple sections
* **Speed** - Faster than manually applying multiple formats
* **Cleanup** - Use Clear Format to remove unwanted styling

## Commands

### `clearFormat`

Clear all formatting (nodes and marks)

<Note>
  Removes all marks and resets nodes to default paragraph
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.clearFormat()
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.clearFormat()
    },
  });
  ```
</CodeGroup>

### `clearMarksFormat`

Clear only mark formatting

<Note>
  Removes bold, italic, underline, colors, etc. but preserves block structure
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.clearMarksFormat()
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.clearMarksFormat()
    },
  });
  ```
</CodeGroup>

### `clearNodesFormat`

Clear only node formatting

<Note>
  Converts headings, lists, etc. to paragraphs but preserves text marks
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.clearNodesFormat()
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.clearNodesFormat()
    },
  });
  ```
</CodeGroup>

### `copyFormat`

Copy format from selection or apply copied format

<Note>
  Works like format painter - first click copies, second click applies
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.copyFormat()
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.copyFormat()
    },
  });
  ```
</CodeGroup>

### `toggleMarkCascade`

Toggle a mark type with cascade behavior, handling nested marks and negation attributes. Used internally by formatting extensions (bold, italic, etc.) but can be called directly for custom mark toggling.

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.toggleMarkCascade('bold')
  editor.commands.toggleMarkCascade('italic', { extendEmptyMarkRange: true })
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.toggleMarkCascade('bold')
      editor.commands.toggleMarkCascade('italic', { extendEmptyMarkRange: true })
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="markName" type="string" required>
  Name of the mark type to toggle
</ParamField>

<ParamField path="options" type="Object">
  Options with `negationAttrs`, `isNegation`, `styleDetector`, and `extendEmptyMarkRange`
</ParamField>

## Keyboard shortcuts

| Command       | Shortcut       | Description          |
| ------------- | -------------- | -------------------- |
| clearFormat() | `⌘/Ctrl-Alt-c` | Clear all formatting |

## Types

### `StoredStyle`

Stored format style

<Expandable title="Properties">
  <ResponseField name="name" type="string" required>
    Mark name
  </ResponseField>

  <ResponseField name="attrs" type="Object" required>
    Mark attributes
  </ResponseField>
</Expandable>

## Source code

<SourceCodeLink path="packages/super-editor/src/editors/v1/extensions/format-commands/format-commands.js" />
