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

# IDEs

> Open a Devbox in VS Code, Cursor, JetBrains, or Zed over SSH.

export const KeepTabPosition = () => {
  useEffect(() => {
    const stateKey = "__namespaceKeepTabPosition";
    const releaseState = state => {
      state.instances -= 1;
      if (state.instances > 0) return;
      state.removeListeners();
      if (window[stateKey] === state) delete window[stateKey];
    };
    const existingState = window[stateKey];
    if (existingState) {
      existingState.instances += 1;
      return () => releaseState(existingState);
    }
    const maxSettleTime = 250;
    const requiredStableFrames = 3;
    const positionTolerance = 0.5;
    let animationFrame;
    let observer;
    let settleDeadline;
    let stableFrames = 0;
    let tabListToKeep;
    let tabListTop;
    const findTab = event => {
      if (!(event.target instanceof Element)) return null;
      return event.target.closest(".tab-container [role='tab']");
    };
    const findScrollContainer = element => {
      for (let parent = element.parentElement; parent; parent = parent.parentElement) {
        const {overflowY} = getComputedStyle(parent);
        if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
          return parent;
        }
      }
      return null;
    };
    const stopKeepingPosition = () => {
      if (animationFrame) cancelAnimationFrame(animationFrame);
      animationFrame = undefined;
      observer?.disconnect();
      tabListToKeep = undefined;
    };
    const restoreTabListPosition = () => {
      if (!tabListToKeep?.isConnected) return 0;
      const offset = tabListToKeep.getBoundingClientRect().top - tabListTop;
      if (Math.abs(offset) < positionTolerance) return offset;
      const scrollOptions = {
        top: offset,
        behavior: "instant"
      };
      const scrollContainer = findScrollContainer(tabListToKeep);
      if (scrollContainer) {
        scrollContainer.scrollBy(scrollOptions);
      } else {
        window.scrollBy(scrollOptions);
      }
      return offset;
    };
    const settlePosition = () => {
      const offset = restoreTabListPosition();
      stableFrames = Math.abs(offset) < positionTolerance ? stableFrames + 1 : 0;
      if (stableFrames >= requiredStableFrames || performance.now() >= settleDeadline) {
        stopKeepingPosition();
        return;
      }
      animationFrame = requestAnimationFrame(settlePosition);
    };
    observer = new MutationObserver(() => {
      stableFrames = 0;
      restoreTabListPosition();
    });
    const keepTabListInPlace = tab => {
      stopKeepingPosition();
      tabListToKeep = tab.closest("[role='tablist']");
      if (!tabListToKeep) return;
      tabListTop = tabListToKeep.getBoundingClientRect().top;
      settleDeadline = performance.now() + maxSettleTime;
      stableFrames = 0;
      observer.observe(document.getElementById("content") ?? document.documentElement, {
        attributes: true,
        attributeFilter: ["aria-selected", "class"],
        childList: true,
        subtree: true
      });
      animationFrame = requestAnimationFrame(settlePosition);
    };
    const selectWithoutNavigation = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      if (!tab) return;
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tab.click();
      restoreTabListPosition();
    };
    const selectWithKeyboard = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      const tabList = tab?.closest("[role='tablist']");
      if (!tab || !tabList) return;
      const tabs = Array.from(tabList.querySelectorAll(":scope > [role='tab']"));
      const currentIndex = tabs.indexOf(tab);
      let nextIndex;
      switch (event.key) {
        case "ArrowLeft":
          nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
          break;
        case "ArrowRight":
          nextIndex = (currentIndex + 1) % tabs.length;
          break;
        case "Home":
          nextIndex = 0;
          break;
        case "End":
          nextIndex = tabs.length - 1;
          break;
        case "Enter":
        case " ":
          nextIndex = currentIndex;
          break;
        default:
          return;
      }
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tabs[nextIndex]?.click();
      tabs[nextIndex]?.focus({
        preventScroll: true
      });
      restoreTabListPosition();
    };
    document.addEventListener("click", selectWithoutNavigation, true);
    document.addEventListener("keydown", selectWithKeyboard, true);
    const state = {
      instances: 1,
      removeListeners: () => {
        stopKeepingPosition();
        document.removeEventListener("click", selectWithoutNavigation, true);
        document.removeEventListener("keydown", selectWithKeyboard, true);
      }
    };
    window[stateKey] = state;
    return () => releaseState(state);
  }, []);
  return null;
};

export const CenteredImage = ({src, alt, width, caption, className}) => {
  const [basePath, setBasePath] = useState("");
  useEffect(() => {
    const path = window.location.pathname;
    setBasePath(path === "/docs" || path.startsWith("/docs/") ? "/docs" : "");
  }, []);
  return <Frame caption={caption} className={className} style={{
    maxWidth: width,
    marginInline: "auto"
  }}>
			<OptimizedImage src={`${basePath}${src}`} alt={alt} />
		</Frame>;
};

<KeepTabPosition />

Open a Devbox in your editor and work as if the code were on your own machine. You browse and edit the repository in place, run builds and tests in the editor's terminal, and use its debugger and extensions, while everything executes on the Devbox.

[`devbox open-ide`](/docs/reference/devbox-cli/open-ide) is the common path for most editors. It configures local SSH access, then launches the editor already connected to your Devbox, so there is nothing to set up by hand.

## VS Code

VS Code opens on your machine and connects to the Devbox over SSH, so your files, terminal, and extensions all run remotely.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    devbox open-ide my-devbox
    ```

    This will:

    1. Configure local SSH access to your Devbox
    2. Ensure the [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) extension is installed (see the [VS Code Remote-SSH documentation](https://code.visualstudio.com/docs/remote/ssh) for more details)
    3. Open a new window connected to your Devbox
  </Tab>

  <Tab title="Dashboard">
    In the Devbox dashboard, press **Open VSCode**. To use VS Code in the browser instead, open the dropdown beside it and select **Open on the Web**.

    <CenteredImage alt="The Open VSCode button on a Devbox with its dropdown open" src="/docs/images/devboxes/ide/devbox-ide-vscode.webp" />

    VS Code installs the Namespace Devbox extension automatically. On first use, the extension will open your browser to authenticate with Namespace. Once authenticated, it connects to your Devbox.
  </Tab>
</Tabs>

## Cursor

Cursor opens as its own desktop app, connected to your Devbox over SSH.

<Tabs>
  <Tab title="CLI">
    Pass `--flavor cursor` to launch Cursor instead of VS Code:

    ```bash theme={null}
    devbox open-ide my-devbox --flavor cursor
    ```
  </Tab>

  <Tab title="Dashboard">
    In the Devbox dashboard, open the dropdown beside **Open VSCode** and select **Open Cursor**.

    <CenteredImage alt="The Open VSCode dropdown on a Devbox with Open Cursor selected" src="/docs/images/devboxes/ide/devbox-ide-cursor.webp" />
  </Tab>
</Tabs>

## JetBrains

JetBrains IDEs support remote development through [JetBrains Gateway](https://www.jetbrains.com/remote-development/gateway/) or the built-in remote development features in IDEs like IntelliJ IDEA, GoLand, PyCharm, and WebStorm. See the [JetBrains remote development documentation](https://www.jetbrains.com/help/idea/remote-development-starting-page.html) for more details.

1. Configure SSH access. This writes the SSH key and proxy config so Gateway can connect without manual setup:

```bash theme={null}
devbox configure-ssh my-devbox
```

2. Open JetBrains Gateway and select **SSH Connection**
3. Create a new connection with the host `my-devbox.devbox.namespace` and username `devbox`
4. Select the IDE and project directory on the remote host

JetBrains Gateway installs the IDE backend on your Devbox and streams the UI to your local machine.

## Zed

Open your Devbox in Zed:

```bash theme={null}
devbox open-ide my-devbox --flavor zed
```

This opens Zed with a remote SSH connection to your Devbox. Zed connects using its [native SSH remote development](https://zed.dev/docs/remote-development) support.

## Other editors

`--flavor` also accepts `vscode-insiders`, `codium`, `positron`, and `windsurf`. Each launches that editor's own desktop app, connected over the same SSH access:

```bash theme={null}
devbox open-ide my-devbox --flavor windsurf
```

See [`devbox open-ide`](/docs/reference/devbox-cli/open-ide) for the full list of accepted values.

### Connect manually

To use an editor that `open-ide` does not support, run `devbox configure-ssh` first (see [Configure SSH](/docs/devbox/remote-development#configure-ssh)), then connect from your IDE using the SSH host. The dashboard's **Copy SSH command** option gives you the same connection details. You may need to specify a working directory. Use `/workspaces/{repo-name}` if you created your Devbox with `--checkout`.

## Next Steps

<Columns cols={3}>
  <Card title="Remote Development" icon="monitor" href="/docs/devbox/remote-development">
    SSH access and SSH config for a Devbox.
  </Card>

  <Card title="Sessions" icon="square-terminal" href="/docs/devbox/sessions">
    Persistent terminal sessions that survive disconnections.
  </Card>

  <Card title="devbox open-ide" icon="terminal" href="/docs/reference/devbox-cli/open-ide">
    Every flag accepted by `open-ide`.
  </Card>
</Columns>
