What if you could build interactive web interfaces in Dart, without writing a separate JavaScript UI layer?
What if the server could define not only the initial HTML, but also the UI behavior that should happen when the user interacts with that HTML?
A new approach is now available for Dart.
WebForms Core (WFC) is now available for Dart.
WebForms Core is a server-side UI manipulation technology created by Elanat. It allows server-side code to generate commands for manipulating the browser DOM through the WebFormsJS runtime.
And now, Dart can act as a WebForms Commander.
A Different Approach to Web UI
A typical Dart web application can use Dart to build its server-side application and return HTML to the browser. Client-side JavaScript can then take responsibility for interactive behavior.
WebForms Core takes a different approach.
The server can define UI operations such as:
- creating HTML elements
- changing styles
- changing attributes
- changing text
- responding to events
- reading values from the DOM
- executing conditional logic
- controlling UI flow
The browser does not need to understand Dart.
Instead, the server generates WebForms Core commands, and WebFormsJS interprets and executes those commands in the browser.
The architecture is:
Dart
↓
WebForms
↓
Action Controls
↓
WebFormsJS
↓
HTML DOM
This is not a JavaScript framework.
It is a Commander–Executor architecture.
Dart is the Commander.
WebFormsJS is the Executor.
A First Dart Example
Let's start with something very simple.
Imagine a page containing a <select> element. The user selects a color, and the background and text color of the page change immediately.
Here is the Dart code:
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:webforms/webforms.dart';
void main() async {
var router = Router();
router.get('/script/web-forms.js', (Request request) {
final file = File('web/script/web-forms.js');
if (!file.existsSync()) {
return Response.notFound('web-forms.js not found');
}
return Response.ok(
file.readAsStringSync(),
headers: {'Content-Type': 'application/javascript; charset=utf-8'},
);
});
router.get('/', (Request request) {
final form = WebForms();
form.addOptionTag("<select>", "White", "white");
form.addOptionTag("-", "Light Green", "lightgreen");
form.addOptionTag("-", "Light Blue", "lightblue");
form.addOptionTag("-", "Red", "red");
form.addOptionTag("-", "Blue", "blue");
form.addOptionTag("-", "Green", "green");
form.addOptionTag("-", "Yellow", "yellow");
form.addOptionTag("-", "Orange", "orange");
form.addOptionTag("-", "Purple", "purple");
form.addOptionTag("-", "Pink", "pink");
form.addOptionTag("-", "Brown", "brown");
form.addOptionTag("-", "Black", "black");
form.addOptionTag("-", "Gray", "gray");
form.setCommentEvent("-", HtmlEvent.onChange, index: "set-color");
form.startIndex("set-color");
form.setBackgroundColor("<body>", Fetch.getValue("<select>"));
form.isEqualTo("black", Fetch.getValue("<select>"));
form.setTextColor("<body>", "white");
form.else_();
form.setTextColor("<body>", "black");
return Response.ok(
_htmlForm() + form.exportToHtmlComment(),
headers: {'Content-Type': 'text/html; charset=utf-8'},
);
});
var server = await io.serve(
router,
InternetAddress.loopbackIPv4,
8080,
);
print('Server running on http://${server.address.host}:${server.port}');
}
String _htmlForm() {
return '''
<!DOCTYPE html>
<html>
<head>
<title>Using WebForms Core</title>
<script type="module" src="/script/web-forms.js"></script>
</head>
<body>
<h1>WebForms Core in Dart</h1>
Change to change color
<select></select>
</body>
</html>
''';
}
Screenshot after running the Dart application
There is no JavaScript event handler in the Dart application.
There is no JavaScript DOM manipulation code.
There is no client-side framework.
The behavior is described through the WebForms class.
What Does the Server Actually Send?
The initial response contains the normal HTML page, followed by a WebForms Core command block:
<!--[web-forms]
ao<select>=whiteWhite
ao-=lightgreenLight Green
ao-=lightblueLight Blue
ao-=redRed
ao-=blueBlue
ao-=greenGreen
ao-=yellowYellow
ao-=orangeOrange
ao-=purplePurple
ao-=pinkPink
ao-=brownBrown
ao-=blackBlack
ao-=grayGray
Eb-=onchangeset-color
#=set-color
bc<body>=@$v<select>
{et=black@$v<select>
tc<body>=white
}e
tc<body>=black
-->
This is not JSON.
It is not an HTML attribute.
It is not a JavaScript program.
It is a WebForms Core Action Control command stream.
WebFormsJS detects the [web-forms] comment and processes it.
Then Something Interesting Happens
The browser receives the page.
WebFormsJS processes the commands.
The <select> element is populated.
The change event is connected.
The conditional behavior is prepared.
And the server-generated behavior becomes normal browser behavior.
The resulting DOM contains:
<select onchange="CommentBack(event, 'set-color', '')">
<option value="white">White</option>
<option value="lightgreen">Light Green</option>
<option value="lightblue">Light Blue</option>
...
<option value="black">Black</option>
<option value="gray">Gray</option>
</select>
The important part is the execution model.
After the initial response, the interaction does not require another server request.
The browser executes the generated behavior locally.
In this example, the sequence is essentially:
Server
↓
WebForms Commands
↓
Initial HTML Response
↓
WebFormsJS
↓
DOM + Event
↓
Browser Execution
This is one of the important ideas behind WebForms Core.
The Server Defines the Behavior
Look at this part again:
form.startIndex("set-color");
form.setBackgroundColor("<body>", Fetch.getValue("<select>"));
form.isEqualTo("black", Fetch.getValue("<select>"));
form.setTextColor("<body>", "white");
form.else_();
form.setTextColor("<body>", "black");
The Dart server code describes the behavior.
It says:
- When the
set-colorevent occurs, get the value of the<select>. - Set the body background color to that value.
- Check whether the selected value is
black. - If it is black, make the text white.
- Otherwise, make the text black.
The logic is authored on the server.
The execution happens in the browser.
That distinction is fundamental.
Server-Orchestrated UI
WebForms Core can be described as Server-Orchestrated UI.
The server does not continuously mirror the browser DOM.
It does not need a virtual DOM.
It does not require a separate frontend project.
Instead, the server sends executable UI commands, while WebFormsJS provides the browser-side runtime that executes them.
This creates a clear separation:
Server
Commander
↓
Commands
↓
Browser
Executor
↓
HTML DOM
The WebForms class is responsible for constructing the commands.
WebFormsJS is responsible for parsing and executing them.
But What About Server Requests?
WebForms Core does not require every interaction to be sent back to the server.
That is an important distinction.
There are two possible patterns.
Client execution
The server can send behavior that WebFormsJS executes directly in the browser.
For example:
User changes select
↓
WebFormsJS
↓
Read DOM value
↓
Change DOM
No additional server request is necessary.
Server execution
Other interactions can send data back to the server.
For example:
User submits form
↓
Server
↓
Dart WebForms
↓
WebForms Commands
↓
Browser
The server can then generate a new response containing only the UI operations required for that interaction.
This makes WebForms Core a hybrid system.
Some behavior can execute entirely in the browser.
Other behavior can involve the server.
A More Traditional Dart Example
Now consider a form.
The user enters a name, chooses a font size, enters a background color, and submits the form.
The Dart application can process the request:
var body = await request.readAsString();
var formData = Uri.splitQueryString(body);
if (formData['btn_SetBodyValue'] != null) {
var name = formData['txt_Name'] ?? '';
var backgroundColor = formData['txt_BackgroundColor'] ?? '';
var fontSize = int.tryParse(formData['txt_FontSize'] ?? '16') ?? 16;
final form = WebForms();
form.setFontSize(InputPlace.tag('form'), '${fontSize}px');
form.setBackgroundColor(InputPlace.tag('form'), backgroundColor);
form.setDisabled(InputPlace.name('btn_SetBodyValue'), true);
form.addTag(InputPlace.tag('form'), 'h3');
form.setText(InputPlace.tag('h3'), 'Welcome $name!');
return Response.ok(
form.response(),
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
);
}
Notice what the server returns.
It does not have to return the entire HTML document.
It can return the commands required to update the existing page.
The server might effectively say:
Change the form font size.
Change the form background.
Disable the submit button.
Add an h3 element.
Set its text.
WebFormsJS executes those commands against the existing DOM.
Dart Without a Separate UI JavaScript Layer
This is where the Dart implementation becomes interesting.
The developer can write:
final form = WebForms();
form.setBackgroundColor(InputPlace.tag('form'), backgroundColor);
form.setDisabled(InputPlace.name('btn_SetBodyValue'), true);
form.addTag(InputPlace.tag('form'), 'h3');
form.setText(InputPlace.tag('h3'), 'Welcome $name!');
Instead of writing a separate JavaScript implementation for each of these operations, the Dart application constructs the WebForms Core command stream.
The browser runtime handles the execution.
WebForms Core Is More Than HTML Generation
It would be easy to look at this and think:
"This is just server-side HTML generation."
It is not.
HTML generation is only one part of the system.
The WebForms class can construct operations involving:
- DOM manipulation
- attributes
- styles
- events
- conditions
- DOM value retrieval
- UI state
- element creation
- element replacement
- workflow
- client-side execution
- server interaction
The important unit is not simply HTML.
It is the command.
The Commander–Executor Model
WebForms Core uses two major sides.
Commander
The WebForms class runs on the server.
In this case:
Dart
↓
WebForms
The Commander constructs the UI commands.
Executor
WebFormsJS runs in the browser.
It contains the runtime required to interpret and execute those commands.
WebFormsJS
↓
HTML DOM
Together:
Dart WebForms Commander
↓
Action Controls
↓
WebFormsJS
↓
HTML DOM
This architecture allows different server-side languages to use the same browser execution model.
Dart is now another Commander.
Why Dart?
Dart is already used across server-side and application development, and the Shelf ecosystem provides a lightweight foundation for HTTP applications.
WebForms Core introduces another possibility for Dart web development:
Dart server
+
WebForms Commander
+
WebFormsJS
=
Server-Orchestrated Web UI
Instead of introducing a large frontend framework for every interactive page, developers can keep more of the UI behavior in their server-side Dart code.
That does not mean every application should work this way.
It means there is another architectural option.
This Is Only the Beginning
The examples above are deliberately small.
The interesting question is what happens when the same model is applied to more complex interactions.
What if the server can construct:
- multi-step UI workflows
- conditional DOM operations
- dynamic forms
- event-driven interactions
- partial UI updates
- client-side state operations
- complex DOM workflows
- browser-side execution without additional requests
Then the WebForms class becomes more than a response builder.
It becomes a UI Commander.
And WebFormsJS becomes more than a JavaScript helper.
It becomes the Browser Executor.
WebForms Core for Dart
WebForms Core for Dart is now available as the webforms Dart package.
The package provides the WebForms Commander:
import 'package:webforms/webforms.dart';
Install it with:
dart pub add webforms
The architecture remains the same:
Dart
↓
WebForms Commander
↓
WebForms Core Commands
↓
WebFormsJS Runtime
↓
HTML DOM
The goal is simple:
Write the UI behavior in Dart. Let the browser execute it.
This brings WebForms Core's server-orchestrated UI architecture to Dart, allowing Dart applications to generate WebForms Core commands and control browser-side UI behavior through the WebFormsJS Runtime.
WebForms Core is now available for Dart.
Related links
In Elanat:
In GitHub:
In pub.dev:


Top comments (0)