Bridge: a ProcessWire admin without jQuery, and every old module still in place

We built a new ProcessWire admin on native web components. It loads no jQuery at all until a screen holds a module that needs it: then the old stack loads for that screen, and the module works as before. Lighthouse on everyday screens: 98, against 69 for the stock theme.

The ProcessWire admin is made of fields, and the fields come from modules written over the years against jQuery, jQuery UI and inputfields.js. So the stock theme loads that stack on every screen, including the ones where nothing needs it. Dropping it is not an option: everything you did not write yourself would break.

Bridge is an ordinary admin theme, AdminThemeBridge, that solves this the other way round. It draws the frame and the core screens itself, with web components and a small TypeScript runtime, and loads the old stack only where it is actually needed. The code is open: github.com/theoretic/bridge, and the module to install is github.com/theoretic/AdminThemeBridge. Questions and bug reports go to the ProcessWire forum topic.

A bridge, not a replacement

The name says what it is: a bridge between the jQuery-era admin and the modern web. Everything that can be native already is. Other people’s modules stay on their side of the bridge and keep working exactly as they did.

Native screens. Most core fields — text, selects, checkboxes, dates, page references, files, images with cropping, repeaters, language tabs — and the core screens: the page tree, the editor, Lister, Modules, Fields, Templates, Users, Logs. The editor saves a page without a reload; Shift+click on the button saves the classic way.

The old stack on demand. When a screen holds a field, a Process or a site script written for jQuery, Bridge loads jQuery, jQuery UI, inputfields.js, WireTabs and the rest — in the same order, with the same globals and events as core. The server learns about it while it renders the field; the browser covers the case where the field arrives later, over AJAX. A collapsed field brings the stack only when it is opened.

Core stays untouched. wire/ is an unmodified copy of upstream. DOM ids and classes, AJAX endpoints, module class names and hookable methods are the ones core defines, so modules and hooks find what they expect. Upgrading ProcessWire is still a matter of replacing wire/.

The page editor

The page editor in the light scheme. The colours are the presets of Noël Bossart’s AdminThemeBoss: Bridge owes most of its inspiration to that theme, and its extended breadcrumbs come from there too.

What it actually weighs

98
was 69
Lighthouse performance score
Average of four screens: page tree, page editor, Lister, Modules
A
38KB
was 272 KB
JavaScript per screen
Gzipped. These screens load no jQuery at all
A
55KB
was 118 KB
CSS per screen
Gzipped, light and dark schemes in one set
A
71KB
was 237 KB
Fonts
Font Awesome icons cut into small subsets instead of the ~300 KB full file
A

Being honest about the numbers

Lighthouse 12, desktop preset, logged in, cold cache, median of three runs. Every theme ran on the same copy of a real site; “was” is the stock Uikit theme. AdminThemeBoss, which this site ran before Bridge, scores 81. The difference shows most in the time to the main content (LCP): 0.9 seconds against 3.6.

Measured on a local machine. On a real server the absolute figures are higher — the point is the difference between themes. And these are screens without old fields: a screen that holds a jQuery-era field loads the stack for that field, as it should. Bridge does not make old modules lighter — it stops charging for them where they are not used.

Compatibility is tested, not promised

“Every old module keeps working” is a big claim, so a dedicated test suite checks it. It opens every admin screen twice, under Uikit and under Bridge, and compares what the modules see.

On copies of nine of our real sites that is 1,434 screens, and none differs because of the theme. The run covers 41 of the 45 third-party modules installed on those sites; the other four have admin screens that fail under Uikit as well — they do not work on the copies at all. Interface behaviour is checked separately in Chromium and Firefox, and any page or console error fails the check.

What has not been tested: the Pro modules — ProFields, FormBuilder, ListerPro. None of our sites has them.

Old editors in the dark scheme

CKEditor is a jQuery-era field, like TinyMCE. On a screen like this Bridge loads the old stack for them, and the dark scheme reaches into the editors themselves.

The first live install: this site

On 17 October 2026 Bridge went onto atis.pro, and this site’s admin has run on it ever since. The install went wrong in three separate ways — and in none of them was Bridge directly at fault.

Memory. The install died of memory exhaustion. The culprit was another module of ours, StaticPages 0.6.3: its config file called the module itself, and config files are read on every rebuild of the module cache — so the rebuild kept starting itself. Fixed in 0.6.4.

A theme without its set-up. The crash came after the module was written to the database but before its entry reached the cache. Without that entry ProcessWire loaded the theme too early, before it knew which page was open, and the admin showed up in Times New Roman with no menu. Modules › Refresh did not help: on 3.0.274 it does nothing until some module file changes.

Someone else’s setting. AdminThemeBoss had “activate for all users” on, and on every request it put everyone back on Uikit.

The next day, on testfly.me, the same Boss setting saved a user during boot, and that brought down one of the site’s own hooks. The theme now knows about all of this: since 0.8.1 it notices a missing cache entry and rebuilds it, since 0.8.2 it refuses to be selected while Boss has that setting on. The rest is in the checklist for a live install.

The first reports

On 19 October, five days after the plan was written, Bridge went out as public beta 0.8.2, announced on the ProcessWire forum. The first problem reports came within the first day, in the topic and by private message. Each was reproduced on a clean install with the module in question, fixed and covered by a test.

The most interesting one came from the author of ProcessShowcase: his module looked broken under Bridge. It uses UIkit directly — grids, cards, UIkit.notification() — because the stock theme loads UIkit on every page. Bridge did not.

The fix in 0.9.5 works like the jQuery bridge: a UIkit layer loads only where it is needed — when the markup uses UIkit, when a module script calls UIkit, or when a module adds UIkit’s files itself. It is UIkit’s stylesheet without its base rules, recoloured with the theme’s colours for both schemes (32 KB gzipped), in its own cascade layer, so it cannot restyle the admin itself. Core screens never load it. And a UIkit.modal.confirm() call made before the layer has loaded is not lost: the layer loads and runs it.

For module authors

// run your Inputfield natively, without the legacy stack:
// implement BridgeComponent, or add the class to the theme's map
$wire->addHookAfter('AdminThemeBridge::nativeMap', function(HookEvent $e) {
	$map = $e->return;
	$map['InputfieldMyMarkup'] = array('kind' => 'wrapper');
	$e->return = $map;
});

// a module built on UIkit asks for the layer itself (0.9.5)
if(method_exists($adminTheme, 'needUikit')) $adminTheme->needUikit('MyModule');

Do none of this and nothing changes: the module gets the legacy stack, as it does today. Only a class that needs neither jQuery nor inputfields.js can be declared native. The BridgeComponent interface can be declared without the theme installed. The details are in the component contract, docs/contract.md in the repository.