> ## Documentation Index
> Fetch the complete documentation index at: https://docs.velt.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup

> Enable Single Editor Mode to allow only one user to edit at a time while others remain in read-only mode.

## Prerequisites

* Node.js (v14 or higher)
* React (v16.8 or higher for hooks)
* A Velt account with an API key ([sign up](https://console.velt.dev/))
* Optional: TypeScript for type safety

## Setup

### Step 1: Setup Velt

Setup and initialize the Velt client by following the [Velt Setup Docs](/get-started/quickstart). This is required for Single Editor Mode to work.

### Step 2: Initialize Single Editor Mode

* Get the Live State Sync element via hook or client API.
* Enable Single Editor Mode. Optionally pass config:
  * `customMode`: When true, SDK won’t automatically make HTML elements read-only for viewers. You must manage UI state manually or using the APIs listed [here](/realtime-collaboration/single-editor-mode/customize-behavior). Default: `false`
  * `singleTabEditor`: Restrict editing to a single browser tab for the editor. Default: `true`
* Optionally enable/disable the default UI panel. Default: `enabled`
* Optionally scope the feature to specific containers.

<Note>
  - We recommend using the default UI for simplicity and velocity. You can customize it to change the look, feel and position of the UI. You can customize the UI by following [this guide](/ui-customization/features/realtime/single-editor-mode).
  - If you do want to create your UI from scratch then you can use the following APIs to create a similar UX: [Editor](/realtime-collaboration/single-editor-mode/customize-behavior#editor) and [Viewer](/realtime-collaboration/single-editor-mode/customize-behavior#viewer).
</Note>

<Tabs>
  <Tab title="React / Next.js">
    ```jsx theme={null}
      const liveStateSyncElement = useLiveStateSyncUtils();

      useEffect(() => {
        if (liveStateSyncElement) {
          // Enable Single Editor Mode
          liveStateSyncElement.enableSingleEditorMode({
            customMode: false,
            singleTabEditor: true
          });

          // Optional: Show default UI panel
          liveStateSyncElement.enableDefaultSingleEditorUI();

          // Optional: Restrict to specific containers
          liveStateSyncElement.singleEditorModeContainerIds(['editor', 'rightPanel']);
        }

        // Cleanup on unmount
        return () => liveStateSyncElement.disableSingleEditorMode();
      }, [liveStateSyncElement]);

      return (
        <div id="editor">
          {/* Single Editor Mode Panel UI */}
          <VeltSingleEditorModePanel shadowDom={false} />
        </div>
      );
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <!-- After initializing Velt client -->
    <script type="module">
      import { initVelt } from '@veltdev/client';

      let client;
      (async () => {
        client = await initVelt('YOUR_API_KEY');
        const liveStateSyncElement = client.getLiveStateSyncElement();

        liveStateSyncElement.enableSingleEditorMode({
          customMode: false,
          singleTabEditor: true
        });

        // Optional default UI
        liveStateSyncElement.enableDefaultSingleEditorUI();

        // Optional: Scope to containers
        liveStateSyncElement.singleEditorModeContainerIds(['editor', 'rightPanel']);
      })();
    </script>

    <!-- Optional built-in UI panel -->
    <velt-single-editor-mode-panel shadow-dom="false"></velt-single-editor-mode-panel>
    ```
  </Tab>
</Tabs>

### Step 3: Set the editor

* Declare the editor for the current document/session so editing is enabled for one user while others remain read-only.
* You can set the current user as the editor programmatically on an explicit action.

<Tabs>
  <Tab title="React / Next.js">
    ```jsx theme={null}
    const liveStateSyncElement = useLiveStateSyncUtils();

    // Programmatically set the current user as the editor when they perform an explicit action. eg: start typing or interact with the UI.
    function setEditor() {
      liveStateSyncElement?.setUserAsEditor();
    }
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <script type="module">
      // Assume Velt client is already initialized as shown above
      const liveStateSyncElement = Velt.getLiveStateSyncElement();

      // Programmatically set the current user as the editor when they perform an explicit action. eg: start typing or interact with the UI.
      function setEditor() {
        liveStateSyncElement.setUserAsEditor();
      }
    </script>
    ```
  </Tab>
</Tabs>

<Note>
  When the editor is set, editing tools should become enabled for that user while other users are placed in read-only mode.
  You can fine tune which elements are enabled/disabled by following [this guide](/realtime-collaboration/single-editor-mode/customize-behavior#define-single-editor-mode-elements).
</Note>

Learn more: [Set User as Editor](/realtime-collaboration/single-editor-mode/customize-behavior#setuseraseditor)

### Step 4: Update UI to reflect editor and viewer states

* Non-editor users are automatically viewers. Reflect that state in your UI.
* If you are using the default UI, it will automatically reflect the editor and viewer states in the UI. You can customize it to change the look, feel and position of the UI.

<Tabs>
  <Tab title="React / Next.js">
    ```jsx theme={null}
    // Reflect viewer vs editor state in UI
    const { isEditor, isEditorOnCurrentTab } = useUserEditorState();

    return (
      <div>
        <div>Role: {isEditor ? 'Editor' : 'Viewer'}</div>
        {isEditor && <div>Editing on this tab: {isEditorOnCurrentTab ? 'Yes' : 'No'}</div>}
      </div>
    );
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <script type="module">
      const liveStateSyncElement = Velt.getLiveStateSyncElement();

      // Reflect viewer vs editor state in UI
      const subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
        const role = userEditorAccess?.isEditor ? 'Editor' : 'Viewer';
        console.log('Role:', role, 'Editor on current tab:', userEditorAccess?.isEditorOnCurrentTab);
      });
    </script>
    ```
  </Tab>
</Tabs>

See: [Is User Editor](/realtime-collaboration/single-editor-mode/customize-behavior#isusereditor)

### Step 5: Passing access (requests + accept/reject UX)

Provide an access handoff flow so viewers can request edit access and the editor can accept or reject.

* Default UI (recommended): Use the built-in banner/controls.
* Custom UI: Use the APIs to request and respond to access.

<Tabs>
  <Tab title="React / Next.js">
    ```jsx theme={null}
    const liveStateSyncElement = useLiveStateSyncUtils();

    // Default UI (recommended): ensure default UI is enabled and/or panel is rendered
    liveStateSyncElement.enableDefaultSingleEditorUI();
    // <VeltSingleEditorModePanel shadowDom={false} />

    // Custom UI: Editor-side — show banner and accept/reject
    const editorAccessRequested = useEditorAccessRequestHandler();

    return (
      <div>
        {editorAccessRequested && (
          <div>
            {`User ${editorAccessRequested.requestedBy?.name || editorAccessRequested.requestedBy?.email} requests access`} →
            <button onClick={() => liveStateSyncElement.acceptEditorAccessRequest()}>Accept</button>
            <button onClick={() => liveStateSyncElement.rejectEditorAccessRequest()}>Reject</button>
          </div>
        )}

        {/* Custom UI: Viewer-side — request/cancel access */}
        <button
          onClick={() =>
            liveStateSyncElement.requestEditorAccess().subscribe((status) => {
              console.log('Request status:', status);
            })
          }
        >
          Request edit access
        </button>
        <button onClick={() => liveStateSyncElement.cancelEditorAccessRequest()}>Cancel request</button>
      </div>
    );
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <script type="module">
      const liveStateSyncElement = Velt.getLiveStateSyncElement();

      // Default UI (recommended)
      liveStateSyncElement.enableDefaultSingleEditorUI();

      // Custom UI: Editor-side — subscribe to requests and accept/reject
      liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
        if (data) {
          const nameOrEmail = data.requestedBy?.name || data.requestedBy?.email;
          console.log(`Access requested by: ${nameOrEmail}`);
          // Example: show banner with Accept/Reject buttons that call:
          // liveStateSyncElement.acceptEditorAccessRequest();
          // liveStateSyncElement.rejectEditorAccessRequest();
        }
      });

      // Custom UI: Viewer-side — request/cancel access
      function requestAccess() {
        liveStateSyncElement.requestEditorAccess().subscribe((status) => {
          console.log('Request status:', status);
        });
      }

      function cancelAccessRequest() {
        liveStateSyncElement.cancelEditorAccessRequest();
      }
    </script>

    <!-- Example buttons:
    <button onclick="requestAccess()">Request edit access</button>
    <button onclick="cancelAccessRequest()">Cancel request</button>
    -->
    ```
  </Tab>
</Tabs>

Learn more: [Is Editor Access Requested](/realtime-collaboration/single-editor-mode/customize-behavior#iseditoraccessrequested), [Accept Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#accepteditoraccessrequest), [Reject Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#rejecteditoraccessrequest), [Request Editor Access](/realtime-collaboration/single-editor-mode/customize-behavior#requesteditoraccess), [Cancel Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#canceleditoraccessrequest)

### Step 6: Prevent concurrent editing by the same user (tab locking)

If the same user opens another tab and tries to edit, lock the editor to a single tab and guide them back to the active tab when needed.

<Tabs>
  <Tab title="React / Next.js">
    ```jsx theme={null}
    const liveStateSyncElement = useLiveStateSyncUtils();
    const { isEditor, isEditorOnCurrentTab } = useUserEditorState();

    // If the user is the editor but on the wrong tab, prompt and switch
    if (isEditor && isEditorOnCurrentTab === false) {
      // Show UX prompt like: "You are already editing this page in another tab. Continue editing there."
      // Provide an action to move editing to this tab:
      const takeOver = () => liveStateSyncElement.editCurrentTab();
      // Example: <button onClick={takeOver}>Edit on this tab</button>
    }
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <script type="module">
      const liveStateSyncElement = Velt.getLiveStateSyncElement();

      liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
        if (userEditorAccess?.isEditor && userEditorAccess?.isEditorOnCurrentTab === false) {
          // Show UX prompt like: "You are already editing this page in another tab. Continue editing there."
          // Provide an action to move editing to this tab:
          // liveStateSyncElement.editCurrentTab();
        }
      });
    </script>
    ```
  </Tab>
</Tabs>

<Note>
  Recommended UX copy: "You are already editing this page in another tab. Continue editing there."
</Note>

Learn more: [Edit Current Tab](/realtime-collaboration/single-editor-mode/customize-behavior#editcurrenttab) and [Is User Editor](/realtime-collaboration/single-editor-mode/customize-behavior#isusereditor)

## Notes

* **Initialize Velt first**: Ensure the Velt client is initialized and the user/document are set before enabling Single Editor Mode.
* **Default UI**: The built-in UI can be shown with `enableDefaultSingleEditorUI()` and/or by rendering `<VeltSingleEditorModePanel />` (React) or `<velt-single-editor-mode-panel>` (HTML).
* **Restrict scope**: Use `singleEditorModeContainerIds([...])` to limit Single Editor Mode to specific containers instead of the entire DOM.
* **Native elements only**: When `customMode: true`, you must mark native HTML elements with attributes to control them:
  * `data-velt-sync-access="true"` to enable control
  * `data-velt-sync-access-disabled="true"` to exclude elements
  * These attributes work on native elements only, not directly on React components.

## Testing and Debugging

**To test Single Editor Mode:**

1. Open the same document as two different users in separate browser profiles (or two windows with different auth states).
2. Try editing simultaneously. Only one user should be able to edit; the other stays read-only.
3. Use the default UI panel to request/transfer access and verify the flow.

**Common issues:**

* UI not visible: Ensure `enableDefaultSingleEditorUI()` is called or the panel component/element is rendered.
* Elements not disabled: If using `customMode: true`, ensure you set `data-velt-sync-access` attributes on native elements. Attributes do not attach to React components.
* Editor can edit in multiple tabs: Confirm `singleTabEditor` is `true`. Use `editCurrentTab()` to move the editor role to the current tab.

## Complete Example

<Tabs>
  <Tab title="Complete Code">
    ```jsx Complete Implementation expandable lines theme={null}
    import React, { useEffect } from 'react';
    import {
      VeltProvider,
      useLiveStateSyncUtils,
      VeltSingleEditorModePanel,
      useUserEditorState,
      useEditorAccessRequestHandler
    } from '@veltdev/react';

    function SingleEditorExample() {
      const liveStateSyncElement = useLiveStateSyncUtils();
      const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
      const editorAccessRequested = useEditorAccessRequestHandler();

      useEffect(() => {
        liveStateSyncElement.enableSingleEditorMode({ singleTabEditor: true });
        liveStateSyncElement.enableDefaultSingleEditorUI();
        return () => liveStateSyncElement.disableSingleEditorMode();
      }, []);

      return (
        <div id="editor">
          <div>Active editor: {isEditor ? 'Yes' : 'No'}</div>
          <div>Editing on this tab: {isEditorOnCurrentTab ? 'Yes' : 'No'}</div>
          {editorAccessRequested && <div>Access requested by: {editorAccessRequested.requestedBy?.email}</div>}
          <VeltSingleEditorModePanel shadowDom={false} />
        </div>
      );
    }

    export default function App() {
      return (
        <VeltProvider apiKey="YOUR_API_KEY">
          <SingleEditorExample />
        </VeltProvider>
      );
    }
    ```
  </Tab>

  <Tab title="Live Demo">
    [Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=single-editor-mode\&layout=vertical)

    <Frame>
      <iframe src="https://landing-page-demo-velt.vercel.app/?feature=single-editor-mode&layout=vertical" className="w-full" height="500px" />
    </Frame>
  </Tab>
</Tabs>

## Next: Customize Behavior

Learn how to fine-tune Single Editor Mode behavior, timeouts, events, and UI.

[Go to Customize Behavior](/realtime-collaboration/single-editor-mode/customize-behavior)
