Skip to main content
Add text comments in a spreadsheet The SpreadJS integration renders each Velt comment as a view-only overlay positioned over the commented cell or range. It does not modify your workbook. Comment anchors are stored as durable { sheetName, row, col, rowCount, colCount } data and re-resolved to pixels with SpreadJS cell geometry whenever comments render.

Setup

Step 1: Add Comment components

  • Add the Velt Comments component to the root of your app. This component is required to create and render comments in your app.
  • Authenticate the user with authProvider and set the Velt document before users add comments.
  • Set the textMode prop to false to hide the default Velt text comment tool. SpreadJS selections are handled by @veltdev/spreadjs-velt-comments.
  • Add VeltCommentsSidebar if you want a Google Docs-style comment sidebar.
import { VeltComments, VeltCommentsSidebar, VeltProvider, useSetDocument } from '@veltdev/react';

const user = {
  userId: 'user-1',
  organizationId: 'org-1',
  name: 'User One',
  email: 'user@example.com',
};

function VeltSetup() {
  useSetDocument('spreadsheet-id', { documentName: 'Spreadsheet name' });
  return null;
}

function App() {
  return (
    <VeltProvider apiKey="API_KEY" authProvider={{ user }}>
      <VeltSetup />
      <VeltComments textMode={false} />
      <VeltCommentsSidebar />
      {/* Your app content */}
    </VeltProvider>
  );
}

Step 2: Install the Velt SpreadJS extension

npm i @veltdev/spreadjs-velt-comments @mescius/spread-sheets @mescius/spread-sheets-io
@mescius/spread-sheets is a peer dependency of the Velt SpreadJS package. @mescius/spread-sheets-io is required when you import XLSX files into a workbook.
import '@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css';

Step 3: Configure the SpreadJS workbook with the Velt Comments extension

Create the workbook, import your spreadsheet, then attach SpreadJSVeltComments after the import completes. The same workbook instance is used by addComment and renderComments.
import { useEffect, useRef, useState } from 'react';
import { useCommentAnnotations } from '@veltdev/react';
import {
  SpreadJSVeltComments,
  renderComments,
  type AttachedExtension,
  type SpreadInstance,
} from '@veltdev/spreadjs-velt-comments';

const EDITOR_ID = 'my-workbook';

function SpreadsheetEditor() {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const workbookRef = useRef<SpreadInstance | null>(null);
  const extensionRef = useRef<AttachedExtension | null>(null);
  const [instance, setInstance] = useState<SpreadInstance | null>(null);
  const annotations = useCommentAnnotations();

  useEffect(() => {
    if (!hostRef.current || workbookRef.current) return;

    let cancelled = false;
    const host = hostRef.current;

    import('@mescius/spread-sheets').then(async (spreadModule) => {
      const GC = 'default' in spreadModule ? spreadModule.default : spreadModule;
      await import('@mescius/spread-sheets-io');
      if (cancelled || workbookRef.current) return;

      const workbook = new GC.Spread.Sheets.Workbook(host);
      workbookRef.current = workbook;

      const response = await fetch('/your-workbook.xlsx');
      const file = new File([await response.blob()], 'your-workbook.xlsx');

      workbook.import(
        file,
        () => {
          if (cancelled) return;

          extensionRef.current = SpreadJSVeltComments
            .configure({ editorId: EDITOR_ID })
            .attach(workbook);
          setInstance(workbook);
        },
        (error: unknown) => console.error('XLSX import failed', error),
        { fileType: GC.Spread.Sheets.FileType.excel },
      );
    });

    return () => {
      cancelled = true;
      extensionRef.current?.detach();
      workbookRef.current?.destroy();
      workbookRef.current = null;
      setInstance(null);
    };
  }, []);

  useEffect(() => {
    if (!instance) return;

    renderComments({
      instance,
      commentAnnotations: annotations ?? [],
    });
  }, [instance, annotations]);

  return <div ref={hostRef} style={{ width: '100%', height: '100vh' }} />;
}

Step 4: Add a comment button to your SpreadJS workbook

SpreadJS renders the grid to a canvas, so there is no DOM text selection for a default text bubble menu. Add a toolbar button or contextual selection bubble that uses the workbook’s selected cell/range state, then call addComment({ instance }).
import { addComment } from '@veltdev/spreadjs-velt-comments';

const handleAddComment = async () => {
  if (!instance) return;

  const result = await addComment({ instance });
  if (!result) {
    console.warn('Select a cell or range before adding a comment.');
  }
};

<button onClick={handleAddComment}>Add Comment</button>

Step 5: Call addComment to add a comment

  • Call this method to add a comment to the currently selected cell or range.
  • Params: AddCommentArgs. It has the following properties:
    • instance: SpreadJS workbook instance created with new GC.Spread.Sheets.Workbook(host).
  • Returns: Promise<AddCommentResult | null>. It returns null if no cell/range is selected or Velt is not loaded.
import { addComment } from '@veltdev/spreadjs-velt-comments';

const result = await addComment({ instance });
To scope comments to a specific workbook on pages with multiple SpreadJS instances, pass an editor id when attaching the extension:
SpreadJSVeltComments.configure({ editorId: 'EDITOR_ID' }).attach(workbook);

Step 6: Render comments in SpreadJS workbook

  • Get comment data from the Velt SDK and render it in the SpreadJS workbook.
  • Params: RenderCommentsArgs. It has the following properties:
    • instance: SpreadJS workbook instance.
    • commentAnnotations: Array of Comment Annotation objects.
import { useEffect } from 'react';
import { useCommentAnnotations } from '@veltdev/react';
import { renderComments } from '@veltdev/spreadjs-velt-comments';

const commentAnnotations = useCommentAnnotations();

useEffect(() => {
  if (!instance) return;

  renderComments({
    instance,
    commentAnnotations: commentAnnotations ?? [],
  });
}, [instance, commentAnnotations]);
Highlights are view-only. The library re-derives each highlight from its TextEditorConfig anchor and SpreadJS repositions the overlays across scroll, zoom, row/column resize, and sheet changes. Re-run renderComments when Velt’s annotation list changes or when you import a new workbook.

Step 7: Clean up the Velt SpreadJS extension

SpreadJSVeltComments.configure(...).attach(instance) returns an AttachedExtension. Call detach() in your effect cleanup to remove listeners, clear per-instance state, and remove overlay elements before destroying the workbook.
return () => {
  extensionRef.current?.detach();
  extensionRef.current = null;
  workbookRef.current?.destroy();
};

Step 8: Style the commented text

  • Each comment is rendered through a .velt-spreadjs-overlay layer with a .velt-spreadjs-highlight range fill and a velt-comment-text marker.
  • Override the default inline highlight styles with !important.
.velt-spreadjs-highlight {
  background-color: rgba(255, 212, 0, 0.36) !important;
  border: 1.5px solid rgba(255, 170, 0, 0.95) !important;
}

velt-comment-text:hover .velt-spreadjs-highlight,
velt-comment-text[comment-selected="true"] .velt-spreadjs-highlight,
velt-comment-text.velt-comment-selected .velt-spreadjs-highlight {
  background-color: rgba(255, 212, 0, 0.62) !important;
}

Complete Example

APIs

SpreadJSVeltComments

Creates the Velt Comments extension for a SpreadJS workbook. Use SpreadJSVeltComments.configure(...).attach(instance) to attach it to a workbook instance.
import { SpreadJSVeltComments } from '@veltdev/spreadjs-velt-comments';

const attached = SpreadJSVeltComments
  .configure({ editorId: 'my-workbook' })
  .attach(instance);

attached.detach();

addComment()

Creates a comment annotation for the currently selected SpreadJS cell or range.
  • Signature: async (args: AddCommentArgs) => Promise<AddCommentResult | null>
  • Params: args: AddCommentArgs
  • Returns: Promise<AddCommentResult | null>; see AddCommentResult.
import { addComment } from '@veltdev/spreadjs-velt-comments';

const result = await addComment({ instance });

renderComments()

Renders and updates Velt comment highlights in the SpreadJS workbook.
import { renderComments } from '@veltdev/spreadjs-velt-comments';

renderComments({
  instance,
  commentAnnotations: annotations ?? [],
});