Frontend

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:

terminal bash
# 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:

app_configs/app.ini ini
[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-config endpoint.
  • WF_TAILWIND — compiles a tailwind_raw.css into a minified tailwind.css for 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:

fluid/templates/index.html html
{% 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:

fluid/static/css/tailwind_raw.css css
@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.