Essay 7
Why can’t I build a simple business screen on my own any more?
What did we trade away when rapid application development became web development?
There was a time when I could build a useful business application on my own, not just one screen of it. I am not a front-end developer and never have been. That did not stop me building the interface too.
I do not mean a toy. I mean the ordinary substance of business software: forms that load a record, show twenty fields and let somebody edit five of them; grids of related data; searches, validation and buttons that carry out useful business actions. With Visual Basic that felt like the normal job of one developer. Microsoft called VB6 a rapid application development tool, and the phrase was deserved.
I would not build a new enterprise system in VB6 today, and this is not nostalgia for desktop software. What I want back is one specific thing: a reusable vocabulary pitched at the level of the business application. Not another design system. We already know how to standardise buttons, inputs and tables. I mean money fields, data forms, business grids, reconciliation views and transaction buttons, with the awkward front-end behaviour already built in.
If that vocabulary were implemented as Web Components, a developer who spends most of their time writing server-side code and SQL could assemble a useful application in ordinary HTML without becoming a front-end specialist. The people building the library can use TypeScript, bundlers and whatever else they need. The developer building the application need not inherit that machinery.
Here is what that would make possible.
I sit with the person who does the work. On the screen is a page of ordinary HTML showing representative data they recognise, in something that looks like a finished business application rather than a wireframe. They say the limit belongs beside the expiry date, not underneath it. I move it and reload. They ask what the allocations look like, so I add a grid and reload. They say the limit should be editable only while the order is unamended, and we try it both ways and look at them side by side. Half an hour later we both understand the screen, and none of that half hour was spent waiting for a build.
Then the page is not thrown away. The components on it are the production components. What is missing is the server: real data behind the form, a real transaction behind the button, and the authority rules that decide whether the amendment is allowed. The prototype grows into the application rather than being translated into one.
That was the promise of rapid application development, and it was never mainly about typing less code. It was about putting something working in front of a user early enough that the requirements could still change.
What we traded
Visual Basic was productive because it had strong opinions about the software people were building with it. A form was a first-class thing, and so were text boxes, grids and combo boxes. The tooling had already made many decisions on the developer’s behalf. If I needed a text box, I did not first have to choose a rendering architecture.
The web then solved problems that desktop software had never solved well. A URL is one of the best deployment mechanisms software has ever had. Browsers give us a security boundary, networking, history, cross-platform reach and an enormous body of compatibility work. The modern browser also earned its generality: Miro and Figma are extraordinary applications whose rich client-side state is central to the product rather than accidental complexity.
But most internal business applications are forms, grids, searches, approvals, reconciliations and reports. They need good interaction. They rarely need an unrestricted graphical application platform.
We built them from the same general-purpose material anyway. The internal systems I worked on became single-page applications: a client holding its own state, its own model of the data, its own routing and its own build, talking to a server across a wire. There were genuine gains. Componentisation gave clearer boundaries than the tangled server-rendered pages that came before, and rich interaction no longer needed a whole-page round trip. But I was never convinced we had made the ordinary business application dramatically easier to build.
This is not really an argument about architecture. Visual Basic applications were client-server too, and they were productive. What changed was the level of the material. A VB6 developer assembled a screen from controls that already knew what a business form was. A SPA developer assembles the same screen from general-purpose primitives, and builds that vocabulary again in every application.
The apparatus around that work grew too. Before the first form appears, the environment already contains a TypeScript compiler, a framework with a compilation step of its own, a bundler, a package tree, a test harness and a pipeline to run all of it. Each part has a reason. Together they are a long way from writing a web page.
Server rendering does not escape this. A realistic template combines current values, editable and read-only states, validation messages, conditional sections, generated rows and available actions. The question is the same either way: what level of reusable component does the application developer actually get?
The assumptions have moved
When single-page applications became the default, they were answering real limitations. Dependencies had to be bundled, browsers had no standard component model, CSS isolation was awkward, and older browsers constrained which features could be used.
Several of those limitations have gone. Browsers now have native modules and import maps, custom elements, and CSS features such as custom properties, grid and container queries. The internal systems I have in mind also have a different delivery profile from a public consumer site: a small known user population, long-lived sessions, and assets cached between deployments.
Bundlers, TypeScript and frameworks still solve real problems. I am more interested in which of those problems we actually have. React’s own documentation is a reminder that the question is worth asking: it still describes adding interactive components to an existing page, and notes that this was how much of React’s use at Meta looked for years. Incremental enhancement was never an illegitimate use of the technology. It became less visible once whole-application frameworks were the normal organisational choice, and the defaults we inherited came with them, drawn from a much broader class of web application than the internal systems I am describing.
The missing middle
A business application repeatedly needs concepts that sit above primitive controls but below the business rules.
Consider a numeric input in a financial application. It may need thousands separators, decimal precision, negative-number handling, units such as millions or basis points, keyboard behaviour, parsing, formatting and validation presentation. That is not the business rule deciding whether the user may amend the order, but it is considerably more than an <input>. The same pattern appears with dates, controlled lookups, grids, dirty forms, validation summaries, before-and-after views and safe transaction submission.
This is the middle I think we have lost. Browser primitives are too low-level to be the productive unit for ordinary business development. A full application framework is much larger than the missing unit. In between we need a vocabulary of components fit for business purpose.
This is not an argument against design systems, and it is not really about branding either. Carbon, Fluent and Fiori were built for enterprise software rather than for marketing, and they carry serious accessibility and interaction work. The difficulty is one of level. A design system standardises the appearance and behaviour of controls, and then stops. Fiori will give you a table. It will not give you a settlement comparison view, a form that knows what a dirty row is, or a submit button that knows what a business transaction is.
There is a real difference in the users, too. Someone visiting a consumer site arrives occasionally and has to be guided, so generous spacing, large targets and progressive disclosure are sensible. An operations user opens the same screen at eight in the morning and is still in it at five. They know the work better than the software does. For them density is a kindness, the keyboard matters more than the pointer, and every additional click is a tax paid several hundred times a day. Consumer conventions are not merely unnecessary for that kind of work. Applied to it, they are wrong.
Some of these are deliberately high-level. A data-form is not a styled <form> containing prettier inputs. It understands a row of data, coordinates the controls bound to that row, tracks what has changed, presents server-side validation errors and manages submission. A data-grid understands columns, metadata and rows rather than requiring every application to build its own client-side grid model. These components may be hard to implement well, and that is precisely why they belong in a shared library: the organisation solves that difficulty once rather than asking every team to solve part of it again.
Web Components suit this because custom elements provide a browser-standard component boundary, and because we are extending HTML rather than replacing it. A money-input is still a DOM element and can use the browser’s existing event, form and accessibility mechanisms rather than inventing equivalents. The interesting design question becomes the vocabulary, not the machinery.
I also want the data for the screen to be representable directly in the HTML:
<data-form>
<data-row limit="1250000" expiry="2026-09-30"></data-row>
<money-input field="limit" currency="GBP"></money-input>
<business-date field="expiry"></business-date>
<transaction-submit>Amend order</transaction-submit>
</data-form>
<data-grid>
<data-column field="account" label="Account"></data-column>
<data-column field="amount" label="Amount"></data-column>
<data-row account="Fund A" amount="750000"></data-row>
<data-row account="Fund B" amount="500000"></data-row>
</data-grid>
The same data-row element appears in both places, once as the subject of a form and once repeated as the contents of a grid, and in each case it sits inside the component that consumes it. Nothing has to be wired up by identifier.
The components do not need to know whether that row was typed into a prototype or emitted by a server. In a prototype the page is self-contained: no API, no mock server, no build step between the edit and the reload. The components are ES modules, resolved by name through an import map, so the page needs nothing beyond a script tag. In production the server renders the same rows from authoritative data, and nothing about the page structure is reinvented.
That is useful beyond prototyping. A back-end developer already thinks in rows, fields, types and result sets. Carrying those almost directly into the document leaves less translation between the server’s model of the screen and the browser’s. HTML becomes the description of both the interface and the data being presented, rather than the bootloader for another application state hidden in JavaScript.
None of these components decides whether the amendment is allowed. The server-side transaction still owns the authoritative business decision. The component owns presentation and interaction; the transaction owns the state change; HTTP joins them.
Which raises a question I cannot answer well. A business form is not a proprietary asset. Neither is a business grid, a controlled lookup or a comparison view. Every institution running an operations desk needs the same handful of things, and each of them is currently building those things again from primitives. Visual Basic shipped that vocabulary to everybody, which is much of why it was productive. The industry replaced it with excellent frameworks for building vocabularies rather than with the vocabulary itself.
Part of the reason is that every organisation believes its forms are special, and in small ways each one is. Part of it is that the tooling market rewarded generality, because generality sells to everybody while opinions only sell to the people who share them. But it still seems a strange place to have arrived: an industry that has standardised its databases, its protocols and its deployment, and left every business to reinvent the form.
Constraint is the feature
John Ousterhout uses the phrase somewhat general-purpose for a useful design sweet spot: functionality driven by current needs, with an interface general enough to serve more than one immediate use. A money-input should not be specific to one credit-limit screen, but it does not need to become a universal expression editor. A data-grid should solve the repeated business-grid problem well; it does not need to become Excel.
The shift is to treat constraint as a feature. The platform should have a strong default answer for what happens when somebody needs a monetary amount, a date, a lookup, a grid, a validation error or an action button. If that covers ninety percent of screens it is valuable, because the common case then demands fewer decisions. An unusual requirement can still justify a new component, or ordinary HTML and JavaScript, but writing one should feel like extending the platform rather than the routine way to build a screen.
That constraint is also what makes the conversation with the user work. A fully general toolkit offers an enormous design space, which is exactly what you want when building something extraordinary. Sitting beside an operations user working out an order-amendment screen, most of that design space is distraction. With the decisions already made in the library, the conversation can stay on their work: which facts belong on the screen, which can be changed, what should stand out, and what action should happen next.
A practical test
Pick a straightforward screen in a system you know: twenty displayed fields, five editable, one grid.
Which of its interactions are unique to this business process, and which are repeated behaviours such as money entry, dates, lookups, dirty-state handling or validation? Then trace what it takes to change one field today. How many files, models, mappings, client states and tests move with it, and which of those exist because of the business rule rather than because the interface is assembled from primitives below the level at which you actually work?
Finally, imagine the markup you would like a developer to write, and give the repeated interactions names. Then make a static version with representative data and put it in front of a user. How much of the interface could you change with them in the room before retreating into framework code, CSS or a design handoff?
The aim is not to make every interface simple. It is to make simple business screens simple enough that developers and users can shape them together.
References
- Microsoft, Microsoft Introduces Visual Basic 6.0 (1998), describing Visual Basic 6.0 as a rapid application development tool. https://news.microsoft.com/source/1998/06/15/microsoft-introduces-visual-basic-6-0/
- John Ousterhout, A Philosophy of Software Design, second-edition extract, for the idea of a “somewhat general-purpose” module. https://web.stanford.edu/~ouster/cgi-bin/aposd2ndEdExtract.pdf
- React, Add React to an Existing Project, describing rendering interactive React components within a page built with another technology. https://react.dev/learn/add-react-to-an-existing-project
- MDN,
<script type="importmap">, for mapping module specifiers in the browser without a bundler. https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap - MDN, Web Components, for custom elements, Shadow DOM and templates. https://developer.mozilla.org/en-US/docs/Web/API/Web_components