A code editor that runs entirely in your browser, works offline, and ships zero dependencies.
Open a project, edit across tabs, preview a multi-file site live, and keep working with the network unplugged. No build step, no backend, no package manager, no tracking.
Live demo: tabcode.pages.dev
- Why TabCode
- Features
- Getting started
- How it works
- Repository layout
- Keyboard shortcuts
- Known limitations
- Non-goals
- Browser support
- Contributing
- License
Most browser-based editors depend on a backend, pull megabytes from a CDN, or stop working the moment you lose connectivity. TabCode takes a different stance: the editor, the file access, the live preview, the linter and the AI assistant all run locally. Your code never leaves the device unless you explicitly point the assistant at a model, including one running on your own machine.
It is built for the small, daily edits, tweaking a static site, reviewing a config file, prototyping a snippet, where a full IDE is overkill but a plain textarea is not enough.
There is no package.json, no bundler and no framework. The tokenizer, the ZIP reader and writer, the Markdown renderer, the beautifier, the diff view and the editor itself are all written for this project. What ships is what you read.
That is a deliberate constraint, not an accident: a dependency-free static site can be audited in an afternoon, cached whole by a service worker, and hosted anywhere that serves files.
- Folder explorer backed by the File System Access API (real disk) or by browser storage.
- Create, rename and delete files and folders from the tree.
- ZIP import and export — a single wrapping folder is stripped automatically;
__MACOSXartifacts and binaries are skipped on import. - Project-aware live preview that resolves
<link>,<script src>and media references against your open tabs and the project, so a multi-file site renders as if it were served. Resizable split view, with the iframe'sconsolepiped to a panel below. - Global search across the whole project or just the open tabs, with regex, case-sensitive and whole-word options.
- Command palette (
Alt + Shift + P) with fuzzy search over every command.
- A real text field under the hood. The caret, the selection, the scrolling, the word wrap and the IME are the browser's, so they behave exactly the way they do everywhere else on your system — including selection handles, the long-press menu and the magnifier on touch. See How it works.
- Syntax highlighting for 35 languages through an in-house tokenizer and grammar set, retokenised incrementally as you type.
- Live linting for JSON and XML.
- Undo that groups a typed word into one step and restores the selection you had before the edit.
- Auto-indent, bracket and quote pairing, block indent and outdent, comment toggling, line move, duplicate, join, and case tools.
- Word autocomplete from document words and grammar keywords; trigger with
Ctrl/Cmd + Space. - Optional minimap, indent guides, invisible characters and active-line highlight.
- Tabs with a dirty indicator, drag-to-reorder, inline rename, middle-click to close and a context menu.
- Autosaves to IndexedDB while you type. Reloading restores the tab order, the active tab, per-file cursor and scroll position, and reopens the project.
- Closing a tab is undoable (
Alt + Shift + T) — losing the tab does not lose the file. - Persistent storage is requested so the browser is less likely to evict a project under disk pressure.
- Tap places the caret and the system keyboard opens, because the thing you tapped is a real editable field.
- Long-press gives you the native selection handles and the copy/paste menu your OS already provides.
- A touch toolbar with the keys a phone keyboard lacks — Tab, brackets, arrows, selection mode. Every key is a 44 px target.
- The status bar lifts above the virtual keyboard so the cursor position stays visible, and the toolbar sits on top of it at whatever height the keyboard actually is.
- Full UI translation in English, Portuguese (Brazil) and Spanish; the locale is remembered across sessions.
- Every menu, modal, toast and shortcut label is translated; missing keys fall back to English.
- Zero runtime dependencies and zero network calls from the editor itself.
- A strict Content Security Policy with no
unsafe-eval. The_headersfile adds HSTS,X-Frame-Options,X-Content-Type-Options,Referrer-PolicyandPermissions-Policy. - The preview iframe is sandboxed without
allow-same-origin, so previewed code runs on an opaque origin and cannot reach your files or storage. - Markdown is rendered by an in-house parser that builds DOM nodes instead of assigning
innerHTML— the XSS class is designed out, not sanitised away. - Highlighted code is inserted as text nodes, never as markup, so a file's contents can never become HTML.
- Paths are validated against traversal, uploads are size-capped (5 MB) and binaries are rejected before they reach the editor.
- Configure any OpenAI-compatible endpoint — OpenAI, Groq, OpenRouter, Together, or Ollama running locally (no key, no cloud).
- Ask about the open file or the current selection; responses stream in as they arrive.
- The model never writes directly. Every code response goes through a diff you approve before it is applied — and applying it is a normal undo step.
No build step. Serve the folder with any static server:
python3 -m http.server 8080
# or
npx serve .Then open http://localhost:8080.
For production, deploy the repository root to Cloudflare Pages or any static host. The _headers file is picked up automatically by Cloudflare Pages; on other hosts, translate it to your platform's header configuration.
Browser storage and the File System Access API require a secure context — HTTPS or
localhost.
The one design decision worth knowing before you read the code:
The editor is a real <textarea> with transparent glyphs, sitting on top of a <pre> that paints the same text in colour.
The browser owns the caret, the selection, the scrolling, the wrapping, the IME and the virtual keyboard. TabCode owns the paint. Nothing in the codebase measures text.
That matters because the alternative — computing glyph positions and drawing your own caret over browser-laid-out text — requires two independent measurement systems to agree forever. They don't. Ligatures, font fallback, tab stops, sub-pixel rounding, emoji and word wrap each break the agreement in their own way, and each break shows up as a caret in the wrong place. TabCode 0.5 worked that way and had exactly those bugs. Version 0.6 removed the measurement code entirely.
The two surfaces stay aligned by construction: every property that affects text layout is declared for both of them in a single CSS rule, the metric contract, at the top of assets/css/editor.css.
index.html app shell, CSP, script order
sw.js service worker: precache + offline strategies
manifest.json PWA manifest
_headers security headers (Cloudflare Pages)
assets/css/
styles.css application chrome
editor.css the editor surface and the metric contract
assets/js/
core.js namespace, event bus, settings, limits
editor/ the editor core
range.js a span between two document points
undo.js history, selection snapshots, coalescing
selection.js anchor/head pair over character offsets
session.js the document: lines, offsets, tokens
view.js the paint layer
input.js the bridge to the <textarea>
text-commands.js editing commands
editor.js facade consumed by the rest of the app
highlight/ tokenizer + one grammar per language family
fs.js, db.js, opfs-worker.js, workspace.js, files.js, zip.js
tabs.js, tree.js, menus.js, modals.js, toast.js, commands.js
preview.js, markdown.js, format.js, beautify.js, linter.js
findbar.js, search.js, buffer-search.js, autocomplete.js
ai.js, ai-ui.js
i18n.js, theme.js, languages.js, mobile-toolbar.js, shortcuts.js
main.js boot order
Alt is the primary modifier; add Shift for a key's secondary action. Where a Ctrl/Cmd binding also exists, both work. On mobile, the ALT button in the status bar arms the modifier for the next key press. Esc closes menus and modals.
File and project
| Shortcut | Action |
|---|---|
Alt + N |
New file |
Alt + O / Ctrl/Cmd + O |
Open file… |
Alt + S / Ctrl/Cmd + S |
Save |
Alt + Shift + S / Ctrl/Cmd + Shift + S |
Save as… |
Alt + Shift + W |
Close tab |
Alt + Shift + T |
Reopen closed tab |
Alt + K |
Open project… |
Alt + Shift + E |
Export project as ZIP |
View and navigation
| Shortcut | Action |
|---|---|
Alt + P |
Toggle preview |
Alt + B / Ctrl/Cmd + B |
Toggle sidebar |
Alt + Z |
Toggle word wrap |
Alt + Shift + P / Ctrl/Cmd + Shift + P |
Command palette |
Alt + ; |
Go to line… |
Alt + Shift + → |
Next tab |
Alt + Shift + ← |
Previous tab |
Search
| Shortcut | Action |
|---|---|
Alt + F |
Find in file |
Alt + R |
Replace in file |
Alt + Shift + F |
Search in all tabs |
Alt + G |
Find next |
Alt + Shift + G |
Find previous |
Edit
| Shortcut | Action |
|---|---|
Alt + A |
Select all |
Alt + L |
Select current line |
Alt + Shift + L |
Duplicate line |
Alt + Shift + K |
Delete line |
Alt + Shift + ↑ |
Move line up |
Alt + Shift + ↓ |
Move line down |
Alt + J |
Join lines |
Alt + U |
Uppercase selection |
Alt + Shift + U |
Lowercase selection |
Alt + / |
Toggle line comment |
Tab / Shift + Tab |
Indent / outdent |
Ctrl/Cmd + Z / Ctrl/Cmd + Shift + Z |
Undo / redo |
Caret movement, word jumps, Home/End, page keys, shift-selection and the clipboard are your browser's and your OS's — they are not rebound.
AI and settings
| Shortcut | Action |
|---|---|
Alt + I / Ctrl/Cmd + I |
Ask the AI assistant |
Alt + , |
Settings |
Alt + Shift + / |
About and shortcuts |
- Browser storage is not your disk. A project imported from a ZIP lives inside the browser's storage quota. Clearing site data deletes it. TabCode requests persistent storage, but the browser has the final say — export to ZIP for anything you cannot afford to lose.
- Firefox, Safari and mobile: no folder-from-disk. Use ZIP import. This is not a temporary gap; the File System Access API's disk pickers are Chromium-desktop only.
- No virtualisation. The whole document is in the DOM, which is what lets the browser own wrapping and hit-testing. Files above 20,000 lines drop syntax colour automatically and stay editable.
- iOS zooms when you focus a field below 16 px. The editor text is the input field now, so the old workaround (an invisible 16 px textarea) no longer applies. Keep the editor font at 16 px or larger on phones.
- The iOS magnifier shows transparent text. The visible glyphs live in the paint layer, which the magnifier does not capture. Cosmetic, and the price of native selection.
- Renaming a folder is not supported (only files). Renaming copies and deletes; doing that across a subtree half-way would leave the project broken, so it is refused instead.
- Replace-all only touches open tabs — rewriting closed files would be a mass edit with no undo.
- Formatting uses the built-in beautifier (indentation-based). Adequate for daily use, not a replacement for Prettier.
- ZIP compression requires
CompressionStream(Chrome 80+, Firefox 113+, Safari 16.4+). Without it, ZIPs are still written — just uncompressed.
Listed so nobody spends a weekend on a pull request that will be declined:
- A build step, a bundler or npm dependencies. The constraint is the point.
- Language servers, type checking or refactoring tools. They need a runtime TabCode does not have.
- Being VS Code. TabCode is for quick edits in a browser tab, including on a phone.
- Telemetry or analytics of any kind.
TabCode targets modern evergreen browsers. The full experience — disk folder access, persistent storage, the OPFS worker — is available on Chromium-based desktop browsers. Firefox, Safari and mobile browsers work with the reduced feature set described above.
Contributions are welcome. Read CONTRIBUTING.md first: it covers the code conventions, how to run and test locally, what a good pull request looks like, and how to report a security issue.
Short version: plain HTML, CSS and JavaScript; no build step; no runtime dependencies; English for code, comments and user-facing strings; open an issue before a large change.
MIT © Gabriel Masson