Hi 👋
There are three of us, with about fifty years of building software between us, most of it on enterprise projects expending a lot of time building forms. So we built the library we kept wishing existed.
It's called GolemUI, it's MIT, and it's at golemui.com.
There are five parts to it.
1. A JSON engine: forms as data you can move around
Your whole form is one plain data definition. Fields, validation, conditional logic, layout, all of it is serializable JSON. When a form is data instead of code, some things become easy that normally aren't:
- Store it in a database. A form definition is a row of JSON. Version it, diff it, roll it back, query it.
- Publish it from a CMS. Ops or marketing change the form and it ships without a deploy.
- Ask a model for one. Schemas are small and regular, so an LLM can write one, review one, or repair one.
The UI renders itself from the schema. Nothing else has to know.
2. A typed authoring layer, so you're not writing JSON by hand
Writing raw JSON by hand gets old fast, so the JSON isn't the authoring surface, it's the output format. You write against a typed gui.* builder and it produces the definition:
const signupForm = [
gui.inputs.textInput('email', {
label: 'Email',
validator: { required: true, format: 'email' },
}),
gui.inputs.dropdown('accountType', {
label: 'Account type',
items: ['Free', 'Pro', 'Enterprise'],
defaultValue: 'Free',
}),
gui.inputs.textInput('companyName', {
label: 'Company name',
validator: { required: true },
include: { when: '$form.accountType !== "Free"' },
}),
gui.actions.button({ label: 'Sign up', actionType: 'submit' }),
];
This is also our answer to the fair objection that declarative form formats fall apart on real UIs, that once you need conditional fields and validation across fields, the "declarative" format quietly grows a programming language inside it.
3. A full component set, or bring your own
GolemUI ships a complete widget library, everything from text inputs and dropdowns to date and time range pickers, repeaters, tag inputs, markdown editors and inline calendars. The current catalog is in the widgets reference, and it keeps growing. They're accessible out of the box (WCAG 2.1 AA, ARIA), keyboard navigable, with i18n and validation already wired. That's the plumbing you'd otherwise rebuild on every project.
But it's an engine, not a fixed component set:
-
Restyle ours. Every widget reads one set of
--gui-*custom properties, colour, spacing, radius, type ramp. Reskin an entire form in plain CSS: no component props, no theme provider, no rebuild. - Go fully headless. Skip the stylesheet and write all the CSS yourself against the BEM classes each widget emits. Accessibility, focus management and validation keep working.
- Bring your own components. Drop in Material, Shoelace, or your own, and mix them in the same form.
- Bring your own validators. Any Standard Schema validator drops in: Zod, Valibot, others. Ours are built on Zod.
- Bring your own i18n. Map your existing library in through an adapter and keep the features it's good at.
4. One definition, five frameworks
The same schema renders in React, Angular, Vue, Lit and vanilla JS, unchanged. Not five ports with five different APIs, one engine with thin framework adapters over it.
If you've ever migrated a large app between frameworks, you know the forms are the part that hurts most, because the form logic and the framework's state model grow into each other. Keeping the definition as data that no framework owns is the point.
5. A deterministic MCP server
This is the piece we're most interested in feedback on, and it's the newest.
Letting a model generate a form definition is only useful if something guarantees the output is actually valid. So GolemUI ships an MCP server that closes the loop: it validates every definition the model produces against the real schema, generates JSON or framework code on demand, and refuses to hand your app an invalid form.
The determinism is the whole feature. The model proposes, the engine decides.
What you get for free
No plugins, no premium tier, no paid components:
| Accessibility | WCAG 2.1 AA, ARIA, keyboard, focus management |
| i18n | adapter based, bring your own library |
| Validation | schema driven, Standard Schema compatible |
| Size | 14.32 kB gzipped |
| Performance | 0.19s LCP, Lighthouse 100 |
One accessibility detail worth calling out, because it's the kind of thing that's easy to get wrong: validation timing is configurable per form, blur, change, submit or eager. Marking a field invalid while someone is still typing their email address is a genuine a11y problem, and blur is the right default for most forms.
What it costs your agent, and your reviewer
The landing page runs a live comparison: the same signup form, eight fields with three conditional ones and rules that read across fields, built idiomatically in each framework, versus the GolemUI version, with the full mountable component in both columns.
Against React Hook Form:
-
396 tokens vs 674 (
cl100k_base), about 41% fewer - 0 conditional branches written by hand vs 8, because the engine resolves the conditionals declaratively
Full methodology in the benchmark post. Fewer tokens and less branching pay off twice now: it's cheaper for an agent to write, and faster for a human to read in a pull request.
Getting started
Install the engine, the shared builder, and your framework's adapter. For React:
npm i @golemui/core @golemui/gui-shared @golemui/react @golemui/gui-react --save
Then load the stylesheet once, in your CSS:
@import '@golemui/gui-components/index.css';
Define a form and render it:
import { gui } from '@golemui/gui-shared';
import { GuiForm } from '@golemui/gui-react';
const formDef = [gui.inputs.textInput('username')];
const config = { formDef, data: { username: 'golemui' } };
export function FormPage() {
return <GuiForm config={config} />;
}
Swap @golemui/react and @golemui/gui-react for the Angular, Vue or Lit adapter and the form definition stays exactly the same. The installation guide has the per framework commands, and there are runnable demos on StackBlitz if you'd rather poke at something than read.
That's the pitch. It's MIT, it's live, and the code is on GitHub.
There are three of us and we'll be in the comments, so ask us anything: how the engine resolves conditionals, where the headless and bring your own story gets awkward, how the MCP validation actually works, or where treating forms as data breaks down. That last one is the question we most want answered, and we'd rather hear it from you now than discover it ourselves in six months.
Go have a play at golemui.com and tell us what breaks. If you find a bug, open an issue, we fix them fast.
Top comments (0)