Spendly — HTML, CSS & Flask Primer

Quick reference tied directly to your project files.

What HTML actually is
HTML is just labels. You wrap content in tags and the browser knows what to do with it. Every tag has an opening <tag> and a closing </tag>.
<h1>Spendly</h1>
Big bold heading
<p>Track your money</p>
A paragraph of text
<a href="/login">Login</a>
A clickable link to /login
Tags you'll use in Spendly
Structure
<div></div>
A generic box. Wrap anything in it to group or style.
used in all pages
Headings
<h1> → <h6>
h1 is biggest, h6 smallest. Use h1 once per page.
landing.html
Form wrapper
<form method="POST" action="/login"> </form>
Wraps all login/register inputs. POST sends data to Flask.
login.html, register.html
Input fields
<input type="email" name="email"> <input type="password" name="password">
The actual text boxes. name is how Flask reads it.
login.html, register.html
Button
<button type="submit"> Login </button>
Submits the form to Flask when clicked.
login.html, register.html
Link
<a href="/register"> Sign up </a>
Navigates to another page. href is the URL path.
login.html
Jinja2 — Flask's HTML superpower
Flask adds special syntax to your HTML. Two things to know:
{{ variable }}
Print a value from Python. e.g. {{ name }} becomes "Alex"
{% block content %} {% endblock %}
A slot in base.html. Child pages fill this in.
{% extends "base.html" %}
First line in login.html, register.html. Inherits the layout.
{{ url_for('static', filename='css/style.css') }}
The right way to link your CSS file in Flask.
What CSS actually is
CSS = selector + property + value. You pick an element, then tell it how to look.
h1 { color: blue; font-size: 32px; }
Every h1 on the page becomes blue, 32px.
.card { background: white; padding: 20px; }
Any element with class="card" gets a white bg + padding.
Selectors — how to target elements
Tag
h1 { } button { } input { }
Targets all elements of that type.
Class (most common)
.login-card { } .btn { }
Targets elements with that class. In HTML: class="login-card"
ID (use sparingly)
#navbar { } #submit { }
Targets one specific element. In HTML: id="navbar"
The box model — every element is a box
content
padding (inside space)
border
margin (outside space)
Padding vs Margin
padding: 20px; /* inside */ margin: 20px; /* outside */
Padding = space inside the box. Margin = space around the box.
Border
border: 1px solid #ddd; border-radius: 8px;
border-radius makes corners round. Useful for login cards.
Flexbox — centering things in Spendly
body { display: flex; justify-content: center; align-items: center; min-height: 100vh; }
Centers any child element (like login card) both horizontally and vertically on the full page.
nav { display: flex; justify-content: space-between; }
Logo on the left, links on the right. Classic navbar layout.
Properties you'll use in style.css
Colors
color: #333; background-color: #f5f5f5; background: white;
color = text color. background = fill of the box.
Typography
font-size: 16px; font-weight: bold; font-family: Arial;
Controls how text looks.
Sizing
width: 400px; max-width: 100%; height: 48px;
max-width: 100% prevents elements from overflowing on small screens.
Cursor
cursor: pointer;
Makes the mouse cursor a hand when hovering. Always add to buttons.
What Flask actually does
Flask listens for browser requests and runs a Python function in response. That function either returns HTML or redirects to another page.
1
User visits /login
2
Flask matches the URL to a route in app.py
3
The Python function runs
4
Flask renders login.html and sends it back to the browser
The anatomy of app.py
app = Flask(__name__)
Creates the Flask app. Always line 1 (after imports).
@app.route("/login")
Decorator. Tells Flask: run the next function when someone visits /login.
def login(): return render_template( "login.html" )
The function Flask runs. render_template loads the HTML file.
methods=["GET", "POST"]
GET = user just visits the page. POST = user submitted a form.
GET vs POST
GET request
@app.route("/login") def login(): return render_template( "login.html" )
User just visits the URL. Flask shows the login page. Nothing submitted yet.
POST request
if request.method == "POST": email = request.form["email"] password = request.form["password"]
User clicked submit. Flask reads what they typed. request.form["name"] matches the input's name= in HTML.
this is how login.html sends to app.py
The 5 things you import in Spendly's app.py
from flask import ( Flask, render_template, request, redirect, session )
Flask — creates the app
render_template — loads HTML files
request — reads form data
redirect — sends user to another page
session — remembers who is logged in
redirect vs render_template
render_template
return render_template( "login.html", error="Wrong password" )
Loads and returns an HTML file. Can pass data to it.
redirect
return redirect("/dashboard")
Sends the browser to a completely different URL. Use after a successful login.
session — how Flask remembers the logged-in user
session["user_id"] = user["id"]
Save the user's ID after login.
if "user_id" not in session: return redirect("/login")
Protect a page. If not logged in, send them away.
session.clear()
Log the user out. Wipes the session.
How a login works end to end in Spendly
1
User visits /login
Flask runs login() → renders login.html
2
Browser shows login.html styled by style.css
HTML provides the form structure, CSS makes it look good
3
User types email + password, clicks submit
<form method="POST" action="/login"> sends data to Flask
4
Flask reads the form data
request.form["email"], request.form["password"]
5
db.py checks the database
get_user(email, password) → returns user or None
6
If valid → save session, redirect to dashboard
session["user_id"] = user["id"] → redirect("/dashboard")
7
If invalid → re-render login with error
render_template("login.html", error="Wrong credentials")
The three files and their job
HTML
Defines structure. What's on the page and in what order. Forms, buttons, text. No styling here.
CSS
Defines appearance. Colors, sizes, spacing, layout. No content here.
Flask
Defines behaviour. What happens when a form is submitted. Reads data, talks to database, decides what page to show.