@ahmadastic/froam
v9.18.0
Published
A visual editor for a running site or static HTML folder. Save supported revisions as a versioned design, generated override CSS, and a small content runtime.
Maintainers
Readme
Froam Studio
A visual editor for the site you already have. Froam overlays controls on a rendered page. Supported revisions can be saved as a versioned Froam design, generated override CSS, and a small runtime for content changes.
Froam does not rewrite React components, templates, application logic, or source stylesheets. The one exception is deliberate and narrow: copy you change (and, in Tailwind projects, an element's class list) is written back into the source only where it appears exactly once; everything else stays a Froam edit.
Try it
One command, from any folder:
npx @ahmadastic/froam◆ Froam
Paste the website you want to edit.
Already running on this computer:
1 localhost:3000 My Portfolio
2 localhost:5173 Vite + React
No website yet? Type n to start one from scratch.
Website URL, or a number:Froam finds the dev servers already running on your machine and names them by their page title, so editing your own project is a keystroke. Paste any address instead to edit a live site:
Website URL, or a number: example.comFroam picks a free port, starts a local bridge, injects the editor, and opens your browser. It does not modify the proxied server or the remote website.
If you already know what you want, skip the prompt:
npx @ahmadastic/froam example.com # a live site
npx @ahmadastic/froam localhost:3000 # your dev server
npx @ahmadastic/froam ./public # a static HTML folderNo website yet?
npx @ahmadastic/froam newFroam asks what your site is called and what kind it is (business,
restaurant, salon, shop, school, community, portfolio, product, event, or a
blank page), writes a real index.html with your name in it, and opens it in the editor with the section Library showing.
Click any text to change it, add sections, save: text and new sections are
written straight into index.html. Typing n at the first prompt does the
same, and so does pointing Froam at an empty folder.
Pick a look with --style: monochrome, ocean, mint, terracotta,
bakery, sunset, electric, citrus, lavender, sage, midnight or
noir (the default is the kind's own). A style only changes colours, corners
and the heading font, so your text and edits carry over if you switch later.
No terminal? froam.vercel.app/try makes the
same sites in your browser and hands you the folder.
The picture frames start with simple drawings in the site's colours (a dish, a room, a product, a person) until you select one and put your own photo in. To point a button somewhere else, select it and press the link button on the bar: a website, a WhatsApp chat, a phone number, an email, or another page. Each page ends with a small "Made with Froam" line and a comment with the site's id; nothing is tracked, and the line is yours to keep or delete.
npx @ahmadastic/froam new --kind business --style terracottaThen put it online:
npx @ahmadastic/froam publishThe first time, Froam asks where (Cloudflare Pages, free and fine for a business; Netlify; Vercel; or just the folder) and logs you in through the host's own tool. It remembers, so updating the live site later is the same one command.
Where your edits are saved
Editing your own project writes a froam/ workspace into that project, next to
the code it belongs to.
You do not have to tell Froam where the project is. It traces the port back to the process serving it, and that process back to the folder it was started from, then shows you the folder before creating anything:
Froam will save your edits here:
C:\Users\Ada\Desktop\my portfolio\froam
Enter to create it · n to keep them in your Froam folder · or paste another folder
[Y/n]If a server cannot be traced — a container, or a language whose launcher hides the path — Froam asks rather than guessing.
Editing a live site you do not have the source for writes to ~/Froam/<site>/
instead, so Froam never drops files into whatever directory your terminal
happened to open — including a read-only one such as C:\Windows\System32.
Editing from your phone
npx @ahmadastic/froam dev --hostKeep the terminal running. --host exposes the development bridge to your local
network; do not expose it directly to the public internet. It prints two kinds
of address:
- network — any device on your Wi-Fi can open the site with the editor.
What it saves goes to this computer (
froam/froam.published.json), and the editor here picks it up the next time you open it; Save to Repo on this computer then writes it into your code. A device like this can't write your project itself. - your phone — a pairing link, with a QR code to scan. A phone opened from
it is paired with this computer: it gets Save to Repo and writes your
project directly. The key in the link is new every time
froam devstarts, never reaches a server or a log (it's in the#part), and leaves the address bar as soon as the editor reads it. Keep the link to yourself.
Either way, an edit reaches your visitors once it goes live (below).
Keep the editor open on both if you like. A save on one shows on the other in about a second, without a reload, and neither device's save can wipe the other's edits: each save takes only what that device changed, down to the single property, and unsaved work on the other device is kept. If both change the same property of the same element, the later save wins and says so.
Going live
Go live in the toolbar puts what you saved on your real site, from the computer or a paired phone. It saves anything not saved yet, then shows where it's going and exactly what goes before anything happens:
- A site in git (GitHub and the like): it commits only the files Froam wrote (the design, the two files visitors load, placed media, and the source files Froam put copy, classes or sections into) and pushes them, so your host deploys as it always does. Anything else you're in the middle of stays uncommitted. If unpushed commits of yours would go along, it says so; if the remote has newer commits, it asks you to pull first and pushes nothing. It never forces a push or skips a hook.
- A plain HTML site set up with
froam publish: it uploads to the same host (Cloudflare Pages or Vercel), as you. Sign in to the host once withfroam publishon your computer; after that it works from your phone too.
Then it reads your live site until it serves the change, and only then says Live. Give it your site's address once (it asks) for that check; without one it says it was sent. Every open editor sees the progress.
Screen sizes
Desktop is the base and flows down. An edit made in the editor's Desktop mode (the default, on a phone too) shows at every width. Switch to Tablet or Mobile to change something for narrower screens only: a Tablet edit applies at 1024px and below, a Mobile edit at 640px and below, each over the sizes above it. The Tablet and Mobile previews show exactly that — the desktop edits underneath, that size's own on top — and saving there keeps only what that size changes, so a later desktop edit still reaches phones.
Publishing without a developer
Invite teammates who aren't developers to change the site themselves, with you approving what goes live:
- In the editor, open Share in the toolbar and copy the Can suggest changes link. Send it to them — no account, no install. (On a site running on your computer, turn on Make it reachable from anywhere first — see below.)
- They open it, say their name, and edit anything on the page. Nothing they do is live. When they're done they press Submit and add a note. The link says who it's from and what it lets them do; the first time you share, Froam asks your name so it can.
- You get the request in Share → Requests: who, why, and exactly what changed. Preview shows it on the page; Approve & publish makes it live; Request changes sends it back with your note.
Approving under froam dev does what Save to Repo does: copy is written into
your source files where it can be placed (and, in Tailwind projects, styles
into the element's class list), the rest into the Froam design files — ready
to commit. Anything approved can be taken back with Revert; whatever
someone changed again since is left alone.
A request can span pages and screen sizes. Previewing it runs checks on the page — contrast, text running off the screen, broken images, missing alt text, links that go nowhere — before you approve.
The record: who changed what, and who approved it
Every change is written down in froam/froam.record.jsonl, next to the
design: your own saves (from the computer or a paired phone), each request a
teammate submits, who approved it or sent it back and with what note, anything
taken back, and every Go Live. Rooms end; the record stays with the site.
- Record in the toolbar shows it newest first, with what changed in each
entry. Report to print makes a page you can print or attach, and
Spreadsheet downloads it as CSV. In the terminal:
froam record. - Each entry carries the hash of the one before it, so an edited, deleted or
reordered line is caught: the panel says whether the record checks out, and
froam record --verifyexits non-zero when it doesn't. - Go Live commits the record with the site and writes its newest hash into the
commit message (
Froam-Record: …), so rewriting the record later would mean rewriting pushed history too. - It holds names as people gave them, their role, pages, and the words or
settings that changed (clipped); no emails or tokens. Your own saves go
under the name in
froam.config.json("record": { "name": "…" }, set from the panel), else git'suser.name. Anyone who can read your repo can read it: to keep it on your computer only, set"record": { "commit": false }or git-ignore it.froam publishnever uploads it.
Sharing a site that's on your computer
A link to localhost only opens on your machine. Froam makes it open
anywhere:
npx @ahmadastic/froam share 3000 # an app on a port; also localhost:5173, ./siteFroam connects out to its share service and prints links that open your
local site, with the editor and the room, on any computer or phone — while it
runs (Node 22 or newer): a short link to send (they change anything, you
approve it), one to edit together, and a code to scan with your own phone,
which opens it there already as you. froam dev --share does the same, and
under froam dev Share in the editor hands out the very same links, with
On your phone at the top.
Only people you invite can open it: without a live invite the address shows
"This site is private", not even the page. Links can stop working after 24
hours or 7 days, and New links shuts out everyone who came in on the old
ones. People on a link can look, talk and suggest changes; nothing they send
can write your files, and only your own devices can open rooms. A link opened
from the laptop works page after page on the phone, across reloads, with
other shared sites open beside it; if your computer sleeps, the page says so
and opens by itself when it's back, and froam dev shares again on its own
after a restart. Set FROAM_SHARE_URL to use your own deployment of
templates/cloudflare-share.
When you're done, End collaboration in Share stops every link.
Smart Quick Edits
Some Quick Edits read the page before they change anything. They run on your computer with no AI, and the preview says what each one worked out:
| Say | What it works out |
| --- | --- |
| Fix the contrast | The colour actually behind the text, including every colour of a gradient, then the nearest shade of the same hue that passes WCAG AA. It says the ratio before and after. Over a photo it adds a soft shadow behind the letters instead. |
| Make the size fluid | A clamp() that is exactly the phone size at 375px and the current size at 1440px, with no breakpoints. It still follows the reader's text-size setting. |
| Make it frosted glass | Glass tuned to what's behind it, light or dark, using the site's own corner radius. |
| Add a gradient in the brand colour | The site's brand colour, read from its buttons, links and logo, flowing into a neighbouring hue. On text it's clipped to the letters and deepened until it reads. |
| Make it glow in the brand colour | A glow in that colour: a text glow on words, a halo on boxes. |
| Balance the lines | text-wrap: balance for headings and pretty for paragraphs, so no word is left alone on a line. |
| Match the others like it | The styles its look-alikes on the page share (corners, padding, weight, …) wherever this one differs. |
| Use the brand colour | Text in the brand colour, darkened just enough to pass. A button filled with it, with readable text. |
Selecting something offers the ones that suit it. Try again gives a different take, such as another hue pairing, a softer glow or heavier frost. When there's nothing to fix, it says so ("Already easy to read: 12.6:1").
With nothing selected, Quick Edit (and the command palette) offers three page-wide fixes:
| Say | What it does | | --- | --- | | Fix contrast everywhere | Checks every text against what is really behind it, including each colour of a gradient, and moves each one that fails WCAG AA to the nearest passing shade of its hue. Text over photos is left alone, because it can't be measured. | | Make the buttons consistent | Gives buttons that stray the corners, font, weight, tracking and case most of the page's buttons share. Sizes are left alone. | | Tidy the spacing | When most of the page's spacing sits on a 4px or 8px grid, rounds the few values that are off it onto it. A page with no scale is left as designed. |
Each one applies everything it found as a single step. A report lists every element it changed (for example "Paragraph 'Extraordinary…': 1.5:1 → 4.6:1"), and Undo all, or one Ctrl+Z, takes the whole fix back.
AI in Quick Edit
Quick Edit handles direct edits ("make it bolder", "add more space") on your
computer, with nothing uploaded. For anything else, give froam dev an AI and
turn on AI in Quick Edit:
ANTHROPIC_API_KEY=… froam dev # Claude (claude-sonnet-5 unless FROAM_AI_MODEL says otherwise)
FROAM_AI_API_KEY=… FROAM_AI_MODEL=… FROAM_AI_BASE_URL=… froam dev # any OpenAI-compatible APIClaude is called through Anthropic's own Messages API. It has to answer with
Froam's plan format, so its replies can't come back as loose prose. If both kinds of
key are set, FROAM_AI_* wins; FROAM_AI_PROVIDER=anthropic sends
FROAM_AI_API_KEY to Claude instead.
Before opening the editor, froam ai-check sends one sample request to the AI
froam dev would use and says in plain words what happened: it worked (and
how long it took), the key was refused, the API returned an error (often a
wrong model name), it couldn't be reached, or no AI is set. It never prints
the key.
The key stays with froam dev; the browser never sees it, and people on a
share link can't use it. Froam asks before the first request, sends a
description of the part you picked (layout, styles, words — never source code,
cookies or screenshots), and shows a preview you keep or discard.
Hosted: pull requests, notifications, realtime
On a hosted site, approval opens a pull request and the owner hears about requests wherever they are:
import { createFroamNotifier, createFroamRoomApi, createGitHubPublisher } from '@ahmadastic/froam/server'
const rooms = createFroamRoomApi({
storage, // your database adapter
// Approve → one commit (design + copy + Tailwind classes) as a pull request.
// Revert → closes it, or opens a revert pull request once merged.
...createGitHubPublisher({ token: process.env.GITHUB_TOKEN, repo: 'you/site', dir: 'froam', siteUrl: 'https://your-site.com' }),
// Slack (with a Review button), Discord, any webhook, or email via Resend.
notify: createFroamNotifier({ siteUrl: 'https://your-site.com', webhooks: [process.env.SLACK_WEBHOOK_URL] }),
})Links in notifications and pull requests open the editor on that request and
never carry a token. Under froam dev, set FROAM_NOTIFY_WEBHOOK (and
FROAM_SITE_URL) or "notify" in froam.config.json.
Serverless hosts can't hold connections open, so rooms there would poll. For
instant updates and live cursors, run the room server on Cloudflare
(templates/cloudflare-rooms — rooms, storage and realtime in one Worker, free
tier), or keep your own room server and add the relay in
templates/cloudflare-realtime.
The other links: Can edit together (live co-editing, for designers and developers), Can comment (clients: notes and approvals), Can view. People who comment or view get the page as a page, following along live, with Share and Chat but nothing to edit with. In Chat, type @ to mention someone, and pin a message to the selected element.
The code button beside each link shows it as a QR code, big enough for a room to scan from the screen you present from, with Copy link, your phone's share sheet and WhatsApp underneath. Everyone in the room sees each other's cursor on what it points at, whatever their screen: a laptop's cursor is drawn on the same word on a phone.
Verified static workflow
From a static site's root:
npx @ahmadastic/froam init
npx @ahmadastic/froam dev --serve . --port 4600froam init:
- creates
froam.config.json; - creates
froam/froam.design.json,froam/froam.generated.css, andfroam/froam.runtime.js; - adds the generated stylesheet and runtime tags to
index.html; and - writes the original page to
index.html.bakbefore changing it.
Open the printed local URL, make revisions, and choose Save to Repo
(Ctrl+Shift+S). Serve the initialized folder normally and reload it without
the editor to verify the result.
What happens to an edit
| Edit stage or type | Result |
| --- | --- |
| Unsaved editor preview | Browser DOM and local browser state only; it is not a source-code rewrite |
| Color, typography, spacing, border, radius, shadow, layout, or supported state style | A rule in froam.generated.css, scoped by route and viewport |
| Text replacement | Written into your source file when the original words appear there exactly once (HTML, JSX/TSX, Vue, Svelte, Astro, translation JSON); otherwise data in froam.design.json, applied by froam.runtime.js |
| Image replacement | Data in froam.design.json, applied in production by froam.runtime.js |
| Inserted block (Library pattern) | Serialized block, styled with the site's own theme variables, applied by froam.runtime.js |
| ::before / ::after | A pseudo-element rule in froam.generated.css |
| Save to Repo | Updates the design, generated CSS, and runtime; the editor may also save froam.project.json |
Generated CSS is an override layer and uses !important. The runtime is
dependency-free and embeds the content changes; it does not call a runtime API.
froam.project.json contains editor project state, can be much larger than the
production artifacts, and is not required by a plain static production page.
What modifies source files
| Command | Source effect |
| --- | --- |
| froam dev or shorthand froam <url-or-dir> | Creates the Froam workspace; does not rewrite the host application's existing components or stylesheets |
| froam init on static HTML | Changes index.html only to add the generated CSS/runtime tags and creates index.html.bak |
| froam init on Vite | Installs @ahmadastic/froam with the project's package manager (--no-install to skip), then edits vite.config.*, with a .bak file. If the install fails, the config is left alone |
| froam init on Vite (non-React), Astro, Next.js, SvelteKit, Remix | Adds the two tags to the page head it finds (index.html, the Astro layouts, app/layout, src/app.html, app/root), with a .bak, and sets "shipDir" in froam.config.json (see below) |
| Visual editing and Save to Repo | Writes Froam-owned design/output files, and writes copy edits into the source file that holds the original words (only when they appear exactly once as a whole string; never in comments, node_modules, builds or dotfolders). Turn off with --no-write-source or "writeSource": false in froam.config.json |
Review every init diff before committing it.
Shipping from a public folder
A site that isn't built from the Froam workspace serves the two production
files from its static folder. "shipDir": "public/froam" in
froam.config.json tells every save (the CLI, the bridge, the Vite plugin, and
createGitHubCommitter({ shipDir })) to copy froam.generated.css and
froam.runtime.js there, so the copies never go stale. Commit both folders.
Only those two files are copied: the workspace also holds the design source,
room chat and project notes, and those stay private.
Checking that a design still fits the page
A Froam edit is keyed by a DOM path. When someone wraps a section in a container or inserts a sibling above it, that path can stop meaning what it meant — and it fails quietly in both directions. Either the edit disappears, or the path still resolves and the edit lands on whatever moved into that slot.
froam check answers that before a merge instead of after a deploy:
froam check --app http://localhost:3000 # against a running app
froam check --serve dist # against a build◆ froam check · static → dist/
✖ / 12 anchored · 1 moved · 1 orphaned
h2 "Pricing" · desktop
was main:1/section:2/h2:1
now main:1/section:3/h2:1 81% match
p "Send anything, anywhere" · desktop
main:1/section:1/p:1 not on the page any moreEach edit resolves to one of five states: anchored (found where it was made),
moved (found elsewhere — froam check --fix re-keys it), orphaned (gone),
unverified (saved before fingerprints existed, so there is nothing to check it
against), and
missing (an unverified edit whose path no longer resolves).
The command exits non-zero on drift and on a run where no route could be loaded, so it works as a CI gate. A route it could not fetch is reported as unchecked rather than counted as healthy.
Proxy scope is not framework support
The proxy can inject Froam into many conventional HTML responses. That proves the page can be previewed; it does not prove that every route, asset, dev-server protocol, framework lifecycle, CSP, or production build is compatible.
Current evidence:
| Workflow | Status |
| --- | --- |
| Static HTML init -> edit -> Save to Repo -> plain-server reload -> recovery | Verified end to end |
| Local HTTP proxy injection and cache handling | Covered by automated integration tests |
| React/Vite package imports | Package exports load; full application workflow not verified in this audit |
| External HTTPS sites | Editor injects into the proxied page; this is a local mock-up, not a deploy path, and is not verified as general framework support |
| Mobile browser editing | --host, pairing and Save to Repo from a phone verified end to end on an emulated iPhone (scripts/test-e2e-phone.mjs); not yet on a physical device |
React and Vite integration
froam init installs the package. To do it yourself (a dependency, because
FroamRuntime ships in your bundle):
npm install @ahmadastic/froamUse the default Vite plugin export:
import froamStudio from '@ahmadastic/froam/vite'
export default {
plugins: [froamStudio({ dir: 'src/froam' })],
}Mount the runtime and gate once near the React root:
import { FroamGate, FroamRuntime, type FroamLocalDesign } from '@ahmadastic/froam'
import '@ahmadastic/froam/css'
import '@ahmadastic/froam/gate-css'
import froamDesign from './froam'
<FroamRuntime design={froamDesign as FroamLocalDesign} routes="*" />
<FroamGate enabled initialOpen={false} localRoutes="*" />In a JavaScript project, drop type FroamLocalDesign and the as cast;
froam init prints the version for your project.
enabled and localhost open the editor in development only. A production
build shows it to owners signed in through authProvider + ownerEmails, and
to nobody else. A page whose visitors should get the editor, such as a public
demo, adds showInProduction.
froam init has emitted legacy unscoped import examples in past releases.
Verify generated Vite imports against the scoped examples above.
Package exports
| Export | Purpose | Verification |
| --- | --- | --- |
| @ahmadastic/froam | React gate, runtime, and project APIs | Imports successfully |
| @ahmadastic/froam/css | Editor stylesheet | Export target exists |
| @ahmadastic/froam/gate-css | Gate stylesheet | Export target exists |
| @ahmadastic/froam/vite | Default Vite development plugin | Default export imports successfully |
| @ahmadastic/froam/server | Publish/room/server helpers | Imports successfully; production hosting not verified here |
Implemented editor surface
The current source includes selection, inline text editing, typography and spacing controls, responsive viewport stores, state styling, Look Studio, Motion Studio, layers, page planning, reference analysis, versions, local Quick Edit, publishing primitives, rooms, and project intelligence surfaces.
Implementation or unit coverage is not the same as a completed customer workflow. The exact classification (implemented, tested, unverified, and unsupported) is maintained in docs/VERIFIED_CAPABILITIES.md.
Remote model interpretation is disabled in the shipping editor by default. Local deterministic Quick Edit does not require remote AI.
Known limitations
Phone and tablet previews answer CSS media queries, viewport units and
matchMedia()for the device, on the page itself. Scrolling inside the device reaches the page's scripts:scrolllisteners fire andwindow.scrollYandscrollTo()follow the device screen, so sticky headers and scroll effects behave. A script that readswindow.innerWidthdirectly still sees the real window, and a script that listened to a media query before the preview opened hears about the change on its next resize.Generated selectors are structural paths such as
section:nth-of-type(1) > h1:nth-of-type(1). Inserting, deleting, or reordering same-tag siblings retargets an edit. Edits saved since fingerprints were introduced carry one, sofroam checkreports this and the runtime declines to restyle an element it cannot recognise; edits saved by earlier versions have nothing to check against and are reported asunverifieduntil the route is re-saved.froam checkparses HTML structurally rather than with a browser engine. Well-formed markup indexes identically to a browser — including implied end tags, synthesized<tbody>, raw-text elements and character references — but table foster-parenting and other HTML5 recovery rules are not implemented.Previewing a production URL cannot change or deploy that website. Shipping requires a project or integration you control.
Generated CSS is an override layer, not a source-level refactor. Source write-back covers copy (text) today; styles stay in
froam.generated.css.Content inside shadow roots (web components) and inside iframes is not editable yet. Portals mounted on
<body>beside#rootare.Undo and History survive a reload, including after Save to Repo; this is verified for style edits. For copy that Save to Repo has already written into your source files, version control and
index.html.bakremain the reliable recovery path.The development proxy removes CSP headers from proxied HTML so the editor can load. It does not establish compatibility with the site's production CSP.
A page that cannot load the generated stylesheet/runtime cannot ship Froam output.
CLI
froam ask what to edit, then open it
froam <url> edit a running site or a live URL
froam <dir> edit a static HTML folder
froam init scaffold and wire a detected project
froam dev start the universal development bridge
--app <url|port> proxy a served HTML page
--serve [dir] serve a static HTML folder
--port <n> bridge port (otherwise the first free port from 4600)
--open open the browser
--host [addr] expose on a trusted local network
--allow-origin <o> let a custom dev domain (e.g. http://app.test) use the bridge
--share private links that open this local site on any computer or phone
--no-write-source keep copy edits as Froam edits instead of writing them to source
froam build rebuild CSS/runtime from the design file
froam status summarize the design and generated files
froam check report edits that no longer match the page
--app <url> check against a running app
--serve [dir] check against a built or static folder
--fix re-anchor the edits that only moved
froam record who changed what and who approved it, newest first
--verify check nothing in it was changed or removed (exit 1 if so)
--report [file] a page to print or attach (--csv [file] for a spreadsheet)
--all every entry, oldest first
froam doctor check setup health
froam ai-check send one test request to the AI froam dev would use
froam migrate migrate the design format to v3
froam version print the installed package versionAll commands accept --dir <path> for a custom Froam directory. Node 18 or
newer is required.
The bridge only answers to this machine: CORS is granted to local origins
(and --allow-origin), writes from any other website are refused, the Host
header must be local (DNS rebinding), and --serve never serves dotfiles.
License
FSL-1.1-MIT. Froam is source-available, not OSI-approved open source. Each release converts to MIT two years after the date that version is made available. Read the license itself for the authoritative terms.
Using Froam in your own business is free — your team, your sites, and client work are all Permitted Purposes under FSL, along with modification, education and research.
A commercial licence is needed only to ship Froam inside a product you sell (a builder, CMS, page editor or design tool), or to replace FSL for an organisation whose policy requires an OSI-approved licence. See Commercial licensing.
