Design system

One CSS file, one JS file, one sprite

The frontend is server-rendered Jinja plus a fixed vocabulary of tokens and components. No build step, no CDN, no framework. Pages compose .fbb-* classes; they don't restyle ad-hoc. Rebrand once at the token level and it propagates everywhere.

Customize at the system level, not the page. The whole design system is static/css/fabbro.css (tokens + @font-face + components), the runtime is static/js/fabbro.js (one global fabbro), and the icons are one re-skinnable sprite. That's the entire surface.

Rebrand by overriding tokens

Every colour, space, radius, and font is a CSS custom property under :root. Override the handful that define your brand and the change reaches every button, card, and layout at once — you don't touch component rules.

:root {
  --fbb-accent:       #6c5ce7;   /* primary action, links, focus ring */
  --fbb-accent-hover: #5a4bd4;
  --fbb-ember:        #e8603c;   /* the "forged" secondary accent */
  --fbb-bg:           #0f1115;   /* app background */
  --fbb-surface:      #171a21;   /* card / panel surface */
  --fbb-text:         #e7e9ee;
  --fbb-radius:       12px;      /* global corner rounding */
  --fbb-font-display: "Syne", system-ui, sans-serif;
  --fbb-font-body:    "DM Sans", system-ui, sans-serif;
}

There are spacing tokens (--fbb-space-1 … --fbb-space-9), type-scale tokens (--fbb-text-base … --fbb-text-4xl), and motion tokens (--fbb-dur, --fbb-ease). Compose with the tokens; don't hardcode pixels in a page.

The component vocabulary

Build pages out of the ready classes. A few you'll reach for constantly:

.fbb-btn + a variant (-primary · -ember · -ghost) and size (-sm · -lg · -block) — buttons.

.fbb-card · .fbb-card-forged — panels; the forged variant is the accented hero card.

.fbb-field · .fbb-label · .fbb-input · .fbb-select · .fbb-textarea — form controls.

.fbb-badge (+ -success · -warning · -danger), .fbb-stack · .fbb-cluster · .fbb-grid-2/-3 — status pills and layout primitives.

.fbb-icon + <use href="…#name"> — an icon from the sprite, inheriting colour and stroke.

The runtime: window.fabbro

One global object carries the interactive pieces. After you inject HTML dynamically, call fabbro.init(root) to (re)bind widgets inside it.

// A toast
fabbro.toast("Saved.", { kind: "success" });

// A CSRF-aware fetch — sends the token and Accept: application/json,
// so an expired session returns 401 JSON, not a login-page 200.
const res = await fabbro.request("/notes", {
  method: "POST",
  body: JSON.stringify({ body: "hello" }),
});

// A modal, a loading veil, the theme + sidebar controllers
fabbro.modal({ title: "Confirm", body: "…" });
fabbro.loading(true);
fabbro.theme;     fabbro.sidebar;

Use fabbro.request rather than raw fetch for anything that mutates: it attaches the CSRF token from the <meta name="csrf-token"> the layouts render, so you never wire CSRF on the client by hand.

The three layouts

A page never registers a layout — it inherits one with {% extends %} and fills the blocks. Each wires the security contract (CSRF field, CSP nonce, no-flash theme) once, so pages get it free.

layout_auth.html — full-screen centered card. Login, register, forgot, set-password.

layout_public.html — institutional site: header, drawer, footer.

layout_app.html — logged-in app: retractable sidebar with a user card. New sidebar links go at its {# <fabbro:app_nav_links> #} anchor (admin-only links at app_nav_admin).

{% extends "layout_app.html" %}
{% block title %}Notes{% endblock %}
{% block content %}
  <h1 class="fbb-h1">Notes</h1>
  {% for n in notes %}
    <div class="fbb-card">{{ n.body }}</div>
  {% endfor %}
{% endblock %}

Jinja autoescapes user text; on the JS side build rows with textContent, never innerHTML, so an email stays text on both sides.

Full vocabulary: the complete token list, every component, and the whole fabbro.* API live in docs/doc-frontend.md. Load it into your AI agent alongside the architecture skill so generated UI composes the existing vocabulary instead of inventing one-off styles.
← Back to playbooks