The JustHTML Framework

This is not a framework

No package to install. No CLI. No config file. This is a methodology — a way of thinking about web development that uses the platform directly. The "framework" is HTML, CSS, and JavaScript. You already have it. It came with the browser.

The Stack

You need three things:

  • A backend server — any language, any framework. Python + Flask, C# + ASP.NET, Go, Node (even!). Its only job: serve HTML strings.
  • HTML files — your templates and content. Not JSX. Not .vue. Just HTML.
  • A browser — which you already have.

What a "template" actually is

A template is just a string with placeholders. Your server reads an HTML file, replaces the placeholders with real values, and sends the result. That's it. This is how the web worked before virtual DOMs were invented. It still works. It works great.

This very site uses a custom string-replacement engine written in about 100 lines of C#. It loads layout files, injects partials, and replaces tokens like {{ title }} with actual values. No library required.

Structure: Layout → Partials → Content

Split your HTML into three layers:

  • Layout — the full page shell: <html>, <head>, <body>, header, footer.
  • Partials — reusable fragments: nav, footer, card components.
  • Content — page-specific HTML with front matter metadata (title, description, etc.).

Your server assembles these at request time and serves a complete HTML document.

But what about components?

CSS classes are your components. A .card class applied to a <div> is a component. A partial HTML file included by your server is a component. JavaScript modules that attach behaviour to [data-component] attributes are components. You don't need React to have components — you need consistent naming conventions.

But what about state?

data- attributes live on the DOM. Read them with element.dataset.value. Write them with element.dataset.value = 'new'. For UI state (open/closed, loading, selected), this is sufficient for most applications. For complex multi-page state, session storage or a cookie is fine. For truly complex state: consider whether you need it, or whether a server round-trip would be cleaner.

But what about routing?

Your backend does routing. A GET request to /about returns the about page. A POST to /contact processes the form. This is how HTTP was designed. Client-side routing is a workaround for single-page apps that don't want to make server requests — but if you have a server, use it. URLs are free.

But what about build tools?

You don't need them. CSS custom properties replace SCSS variables. ES modules (<script type="module">) handle imports. fetch() handles AJAX. Minification is a deployment concern, not a development one, and your CDN or reverse proxy can handle it. Every "you need a bundler for this" claim deserves scrutiny.

Getting started with AI assistance

Tell your AI assistant the stack upfront. At the start of every session:

"We are building a web app with vanilla HTML, CSS, and JavaScript. No npm. No React. No frontend build tools. The backend is [language/framework] — it compiles to a binary and serves HTML from templates. Use the browser's native APIs."

AI tools are obedient. They default to what's common. You can change the defaults with one sentence.

The simplest possible example

# Python + Flask
from flask import Flask, render_template_string

app = Flask(__name__)

LAYOUT = """
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
  <h1>{{ title }}</h1>
  <p>{{ content }}</p>
</body>
</html>
"""

@app.route("/")
def home():
    return render_template_string(LAYOUT,
        title="Hello",
        content="No npm was harmed in the making of this page.")

That's a web app. Add CSS. Add JavaScript. Add a database. You now have everything you need.

Ready to build?

Walk through a real working example — a multi-page C# site with a layout file, string replacement, and dynamic routing — built exactly the way this page describes.

Build Your First Templated Site →