Introduction
Up to now everything we built lived on the server. WebFluid calls its frontend layer the surface, and it is what turns a backend into a fullstack runtime. Before we serve an actual frontend in the next chapter, let's get to know the tooling that sits underneath it — and finally cash in on all that "magic behind the scenes" we kept mentioning.
Batteries for the browser
The surface bundles two things you'd normally install by hand: a standalone
Node.js runtime and the Tailwind CLI. Neither ships
inside the package — they are downloaded on first use and cached, so the framework
can drive Vite and compile your CSS without you touching a global toolchain. If you
already have a system Node, WebFluid happily uses that instead.
You rarely call them directly, but when you need to, the CLI forwards straight through:
# Run any node/npm command against the bundled runtime
wf node node --version
wf node npm --version
# Run the Tailwind CLI directly
wf tailwind -- --help
In this release the passthrough CLI printouts are not implemented. You won't receive any outputs from those commands. They only fail loudly. This is a bug and will be fixed in the next public alpha.
The three feature switches
Just like extensions, the surface features are optioned through switches — this time
in the [features] section of your app config:
[general]
SECRET_KEY = supersecret
[features]
WF_THEMES = 1
WF_TAILWIND = 1
WF_PROCESSING = 1
-
WF_PROCESSING— the heart of the surface. It injects a shared template context, registers the default error pages, a request logger and the/server-configendpoint. -
WF_TAILWIND— compiles atailwind_raw.cssinto a minifiedtailwind.cssfor the framework and every app static folder on startup. -
WF_THEMES— enables swappable stylesheets and a light/dark client helper.
Meet fluid_base.html
With processing enabled, the framework's own base template becomes useful. It ships inside
the package as fluid_base.html, already wired for themes, Tailwind, the
injected scripts and locale-aware text. Instead of hand-writing a full document like we did
in Getting Started, our page can now just extend it:
{% extends "fluid_base.html" %}
{% block title %}{{ _('Home') }}{% endblock %}
{% block content %}
<section>
<h1>{{ _('Hello %(name)s!', name=name) }}</h1>
<p>{{ _('Welcome to my liquified application.') }}</p>
</section>
{% endblock %}
Where the variables come from
Notice we never passed theme, src or url_for to the
template — yet fluid_base.html uses all of them. That is
WF_PROCESSING at work. It registers a context processor that adds, on every
render:
src— the framework's client scripts (base, and the i18n / events clients when those extensions are on).url_for— a request-aware reverse url helper that returns paths.theme— the active theme stylesheet link (when WF_THEMES is on).LANG,YEAR,id— small conveniences for your markup.- the underscore gettext helper — even without Babel, processing installs a no-op fallback so
_()always works.
This is why the base template just works. The default error pages (errors/403, errors/404, errors/500) come from the same place, so a 404 already renders a styled page out of the box. We'll see exactly how all of these templates are found in the Template resolution chapter.
Styling with Tailwind
With WF_TAILWIND on, the framework looks for a tailwind_raw.css
in any static/css folder and compiles a minified tailwind.css
next to it on startup. So all you maintain is the raw entry point:
@import "tailwindcss" source("../../");
@theme {
/* your design tokens go here */
}
The compiled stylesheet is then linked for you through the wf_tailwind variable
that fluid_base.html already includes.
Themes
When WF_THEMES is enabled the framework registers its own theme and exposes a
tiny API on your fluid: add_theme(name, link) to register more,
set_theme(request, name) to remember a choice in the session, and
get_theme() to resolve the active one. On the client, the injected
base.js already handles the light/dark preference via
window.wf.switchTheme().
That's the surface groundwork. Processing gives our templates their context, Tailwind gives them style, and the bundled Node runtime is standing by. Now let's actually serve a frontend.
Continue reading
From here you can continue straight with Integration.