formeo: a drag and drop form builder for the browser
Drag & Drop Form Builder
At a glance
- What is it?
- Formeo is a form builder that runs entirely in the page. It ships two halves, a visual editor where fields are dragged into a form, and a renderer that turns the saved description back into a working form, and it has no runtime dependencies at all. The theming section is the most interesting part of the documentation, because every colour is a custom property with no specificity, and the readme is honest about where that decision bites.
- Who is it for?
- formeo fits a team that wants to build forms in a browser and store the result as data, rather than shipping a form designer to its users, and a project that cannot afford a dependency with a build step of its own. It does not fit a server rendered application that needs the form markup at request time, because the renderer works in the browser.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two halves of one library
Formeo is described as a highly configurable drag and drop form building module for creating dynamic, responsive forms with an intuitive visual editor, and the package description is more compact still: a zero dependency JavaScript module for drag and drop form creation.
The two descriptions together describe the shape of the project, because the library has two halves and they are used at different times.
The editor half is where somebody designs a form. Fields are dragged in, arranged, given their labels and their validation, and the result is a data structure. The renderer half takes that data structure and produces a working form that a visitor fills in.
That separation is the whole design, and it is what makes the library useful in a way that a form designer embedded in your application usually is not. Your users get the form. Somebody else, or a member of your team, gets the editor. The form description is data, and it can be stored, versioned, reviewed, and rendered by any page that loads the library.
The demo is a separate site, and the readme links it as the place to see the editor and the renderer side by side.
The feature list makes the two halves explicit as well. There is the drag and drop editing, the column and inline layout for multi-column forms and grouped fields, conditional fields that show or hide based on user input, an extensible control API for custom field types, internationalisation out of the box, a responsive editor and renderer, a preview mode for testing a form before deployment, zero configuration with sensible defaults, an extensive options and event system, and full type definitions.
The zero dependency claim is the one that makes the rest possible, and it is the one that distinguishes the project from most form builders in the browser, which arrive with a framework, a date library, and a utility belt. The project is MIT licensed, the site is formeo.io, and the release history shows the same patch line moving quickly, with three releases in the first week of October 2026.
The editor, the renderer, and the data between them
The usage section is a set of short examples, and together they show the whole lifecycle.
You import the two classes and the stylesheet.
import { FormeoEditor, FormeoRenderer } from 'formeo'
import 'formeo/dist/formeo.min.css'The editor takes a container selector, and it needs a matching element in your page.
const editorOptions = {
editorContainer: '#formeo-editor',
// Add any additional options here
}
const editor = new FormeoEditor(editorOptions)And the form description is a property on the editor, read at save time and posted to your backend as JSON.
const formData = editor.formData
fetch('/api/forms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})The renderer takes the same data and a container, and there is a render method that takes the description.
const rendererOptions = {
renderContainer: '#formeo-renderer',
// Add any additional options here
}
const renderer = new FormeoRenderer(rendererOptions)
renderer.render(formData)And the complete example wires them together with an event, so the form is rendered again as soon as it is saved.
const editor = new FormeoEditor({
editorContainer: '#formeo-editor',
events: {
onSave: ({ formData }) => {
console.log('Form saved:', formData)
renderer.render(formData)
}
}
})Two things in that last snippet are worth noticing. The save event hands you the form data in its callback, so you do not have to poll a property, and the renderer is a separate object with its own container, so the editor and the rendered form can live in different parts of the page.
The TypeScript example adds two more things that matter for a typed codebase. There is a readiness promise, so the editor's data is not read before it is initialised, and there are exported option and data types, with the older names kept working as aliases so that a rename does not break an existing integration.
The library ships its own type definitions, and the readme says which ones are typed and points at a page for the detail.
Installing it, and the CDN instructions that are actually careful
There are three installation routes, and the third one contains a warning that is worth repeating.
The package manager route is the usual one, and there is a variant for the other package manager.
npm install formeoyarn add formeoThe third is a content delivery network, for quick prototyping or a simple project, with a script tag for the code and a link for the stylesheet.
<!-- JavaScript -->
<script src="https://unpkg.com/formeo@latest/dist/formeo.umd.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="https://unpkg.com/formeo@latest/dist/formeo.min.css">The two globals that gives you are the editor and the renderer.
And then the instruction that makes this section worth reading: for production, pin a version instead of using the latest tag, and add an integrity hash with the cross origin attribute.
That is three separate pieces of advice in one sentence, and each one fixes a different problem. Pinning the version means a deployment is reproducible, because the latest tag moves under you. The integrity hash means the browser verifies the file it downloaded against a hash you recorded, so a compromised delivery cannot silently change your code. And the cross origin attribute is what lets the browser check the hash at all on a cross origin script.
The readme does the honest thing and points at the installation section of the renderer documentation for the detail, rather than pretending the snippet is the whole story.
The package manifest also explains the file layout, which matters for bundlers. The main entry is the CommonJS build, the module field is the ES module build, and the unpkg field is the universal build for the script tag. The exports map then resolves the conditions explicitly, so an import gets the ES module with the modern type definitions, a require gets the CommonJS build with the definitions for that resolution mode, and the default condition falls back to the universal build. The stylesheet has its own export entry so that the import of it resolves in every module system.
The published files are the built output and the demo, and the package is marked as an ES module project, so the build is modern and the entry points are explicit rather than inferred.
Theming, and the three places the custom properties do not reach
The theming section is the longest prose in the readme, and it is the part that tells you what the project is really made of.
Every colour the library uses is a custom property, and so is the width of a label that sits beside its control. They are all declared on the root element with zero specificity. The consequence is that you override them on the root element or on the body, and the override always wins because there is no specificity to compete with.
The example is a dark theme, mapping the library's properties onto a dark palette.
:root {
color-scheme: dark;
color: #fafafa;
--formeo-bg: #0a0a0a;
--formeo-bg-hover: #1a1a1a;
--formeo-text: #fafafa;
--formeo-border: #262626;
--formeo-icon: #e5e5e5;
}And then there is a dark preset, which is a class you add to the body or the root element. It sets the colour scheme on that subtree, so the browser's own controls inside it darken as well, and the dialogs pick up the preset's background and text colours.
Then the three caveats, and they are the reason this section is long.
The first is about scope. The library appends its dialogs to the document body, so a rule scoped to the editor's container misses them. A narrower selector works, but only if it also covers the dialog class, and the controls panel class as well if you move that panel. That is a real consequence of appending to the body, and a page that renders a dialog outside the editor's subtree is a page where the dialog will be unstyled unless you widen the selector.
The second is that outside the dark preset, the library leaves the dialog's own background and text colour, the text colour of inputs and selects, and the checkboxes to the browser. The browser only switches them to dark mode colours under a dark colour scheme, so a custom dark theme has to set that property too or half the form will still be light.
The third is the subtle one. Setting the text property alone only recolours the text the library colours explicitly, not the labels it inherits, so you have to set the colour on your container as well.
And then the derived properties. Some defaults are computed from another colour at build time, so they do not follow when you override the base, and the readme lists them: the hover background, the overlay, the subtle danger colour, and the soft column outline.
That last one is the most important thing in the section for anybody writing a theme, because it is the difference between a theme that looks right and one that is subtly wrong in four places.
Framework integration, and the shape of the documentation
There are two framework integration guides, and the readme's one line each is enough to see that they are written by somebody who has done it.
The React guide covers custom hooks, functional components, and the context API patterns, which is the right list for a library that has state and a container element to find. The Angular guide covers a standalone component, a setup that is safe for server side rendering, and reading the submitted answers, which is the right list for a framework whose default is not a global singleton.
The server side rendering point is worth a moment, because it is the constraint that shaped the library's design. A library that creates its editor by writing into a container and that appends dialogs to the document body cannot be run on a server, and the Angular guide has to say how to avoid the browser globals during rendering. The React guide does not have that problem, because the hooks approach means the caller owns the lifecycle.
So the two guides reflect two different answers to the same question about where a component's lifetime begins.
The rest of the documentation is a set of pages linked from the readme: the renderer documentation with its installation section, the React and Angular guides, a TypeScript page, and a documentation directory in the repository. There is a change log at the root and a section of the readme for it.
The repository layout is consistent with that. There is a source directory, a tests directory, a documentation directory, a tools directory, a build configuration for the application and a separate one for the library, a test configuration for the browser automation, a formatting configuration, a hook configuration, and two instruction files for coding agents.
The presence of browser automation tests is the detail that says something about the project's maturity. A drag and drop form builder is a hard thing to test with unit tests, because the whole point of it is pointer interaction, and a library that tests it with a real browser has decided that the interactions are the behaviour rather than the decoration.
The formatting configuration is for one of the newer fast tools, and the hook configuration is for the same ecosystem, with the whole thing driven from one file, so a contributor's first commit runs the same checks the pipeline does.
The package manifest, and what it says about the build
The manifest is short, and it is worth reading because it describes the build rather than the library.
The version is in the five series with a patch number, the project is an ES module, and the three entry points are the ones a bundler, a script tag, and a legacy loader each need. The published files are the built output and the demo, and the package is marked as MIT licensed.
There is a configuration section that maps logical names to the files that implement them, and the entries are the test suite, the editor implementation, the renderer implementation, and the site's stylesheet. That is a convention for a repository that builds several entry points from one source tree, and it is what lets the build produce a library and a site from the same code.
The description in the manifest is the terse version of the readme's first line, and the keywords are the same four words in every combination: drag and drop, form builder, form maker, and forms. There is a homepage, a repository, an issue tracker, an author, and a single named contributor, which is a small project with a clear lineage rather than a team effort.
And the scripts. The development script runs the bundler in development mode and the preview script serves the built result, which is the standard pair for a project with a demo site. The build configuration comes in two files, one for the application and one for the library, which is the practical consequence of the multi-entry setup: the site and the published package are built differently even though they share the source.
The last thing worth noting is the ignore list, which names what is not published: everything, with exceptions for the modules directory and the tests. That is the mechanism that keeps the repository's own tooling out of the tarball, and it is the kind of detail that is easy to get wrong when a project has a demo site that shares a source tree with the library.
All told, the project is a small, dependency-free, actively maintained library that does one thing and does it in the browser, with the majority of its documentation effort spent on the theming caveats.
Editorial conclusion
formeo fits a team that wants to build forms in a browser and store the result as data, rather than shipping a form designer to its users, and a project that cannot afford a dependency with a build step of its own. It does not fit a server rendered application that needs the form markup at request time, because the renderer works in the browser. Verify first which controls the project ships, since the extensible control API is the part most likely to need work, that the stylesheet is loaded, because the library injects its own markup and unstyled output is the usual first surprise, and that the theme overrides you write cover the dialogs as well as the editor, since the dialogs are attached to the document body and fall outside a scoped rule.
Frequently asked questions
What does the formeo library do?
It provides a visual drag and drop form editor and a renderer that turns the saved form description back into a working form, both running in the browser with no runtime dependencies. Forms can have multi-column layouts, conditional fields, custom control types, multiple languages, and a preview mode.
How do I get the form data out of the formeo editor?
It is the form data property on the editor, and the save event passes it to you in its callback. The documentation then posts it to a backend as JSON. The TypeScript example shows reading the fields from it once the editor reports that it is ready.
How does formeo theming work?
Every colour it uses is a custom property, and so is the width of a label beside its control, all declared on the root element with zero specificity, so overriding them on the root or the body always wins. A dark preset class is also provided, and some defaults are computed from other colours at build time and do not follow when you override the base.
What should I watch out for when theming formeo?
The dialogs are appended to the document body, so a rule scoped to the editor container misses them and has to cover the dialog class as well. Outside the dark preset the library leaves some colours to the browser, so a custom dark theme has to set the colour scheme property too. And setting the text property alone does not recolour labels the library inherits, so the colour has to be set on your container as well.
How do I use formeo from a content delivery network?
Include the universal build with a script tag and the stylesheet with a link element, which exposes the editor and renderer as globals. For production the readme advises pinning a version rather than using the latest tag, and adding an integrity hash with the cross origin attribute so the browser can verify what it downloaded.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/draggable-formeo)Was this useful?