We're going to build a small web app with Flask. It'll show four colored squares, and when you click one, you can pick a new color for it.
Before we start: Flask is a Python library that lets you build websites. You write Python functions, and Flask turns them into web pages that anyone can visit in a browser.
Create a virtual environment. This is important. Do not skip this step or I will be upset.
python -m venv .venvActivate it:
Windows:
./.venv/Scripts/activateMac/Linux:
. ./.venv/bin/activateInstall Flask.
If you're cool and using uv:
uv syncIf not:
pip install -r requirements.txtHTML is what web pages are made of. It's a bunch of tags that describe content. Tags look like this:
<tagname>content goes here</tagname>Tags have a start (<tagname>) and an end (</tagname>). Some common ones:
<h1>, a big heading<div>, a generic box/container<a>, a link<form>, a form with inputs and a submit button<input>, an input field (text, color picker, hidden field, etc.)<button>, a button
Tags can also have attributes, extra info inside the opening tag:
<a href="/somewhere">Click me</a>
<div style="background-color: red;">I'm a red box</div>Every HTML file has this skeleton:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<!-- styles and metadata go here, not visible on the page -->
</head>
<body>
<!-- your actual content goes here -->
</body>
</html><!DOCTYPE html>, tells the browser this is an HTML file<head>, stuff that isn't shown on the page (title, CSS styles, etc.)<body>, everything the user actually sees
Flask uses a system called Jinja2 to let you put Python variables into your HTML. Any time you write {{ something }}, Flask will replace it with the actual value of that variable.
For example, if we pass colors={0: "#ff00ff"} to our template, we can write:
<div style="background-color: {{ colors.get(0) }};"></div>And the browser will see:
<div style="background-color: #ff00ff;"></div>You write the template once, and Flask fills in the values each time someone loads the page.
We need two things: a Flask route that serves the page, and an HTML template that defines what it looks like.
Open app.py. Find line 13, it should be right after the colors dictionary. Add this:
@app.route("/")
def main():
return render_template("index.html", colors=colors)Line by line:
@app.route("/"), this tells Flask "when someone visits the homepage (/), run the function below it"def main():, the function Flask will runreturn render_template("index.html", colors=colors), load the template fileindex.htmland pass ourcolorsdictionary to it so the HTML can use it
Create a new folder called templates in the project root. Inside it, create a file called index.html.
Start with the skeleton and styles. Copy this in exactly, we'll add the important stuff after:
<!DOCTYPE html>
<html>
<head>
<title>Color Picker</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
display: flex;
gap: 20px;
}
.square {
width: 150px;
height: 150px;
border-radius: 8px;
cursor: pointer;
}
</style>
</head>
<body>
</body>
</html>The <style> block is CSS that makes things look nice. The part that matters: .square sets each colored box to 150x150 pixels, and .container uses display: flex to lay them out side by side.
Now add the container. Inside <body>, add:
<div class="container">
</div>This is just a box to hold our squares. The class="container" hooks it up to the CSS above.
Now add the first square. Inside the container div, add:
<a href="/picker?square=0"><div class="square" style="background-color: {{ colors.get(0) }};"></div></a>Breaking this down:
<a href="/picker?square=0">, a link to the picker page.?square=0is a URL parameter, it tells the picker which square was clicked<div class="square" ...>, the colored square itselfstyle="background-color: {{ colors.get(0) }};", this is Jinja2 in action.{{ colors.get(0) }}pulls the color for square 0 out of our Python dictionary and drops it right into the CSS
Add the other three squares the same way, just change the number each time:
<a href="/picker?square=1"><div class="square" style="background-color: {{ colors.get(1) }};"></div></a>
<a href="/picker?square=2"><div class="square" style="background-color: {{ colors.get(2) }};"></div></a>
<a href="/picker?square=3"><div class="square" style="background-color: {{ colors.get(3) }};"></div></a>flask runOpen localhost:5000. You should see four colored squares. Clicking them won't work yet, that's next.
This is the page where the user actually chooses a new color.
Add this to app.py, right after the main function:
@app.route("/picker")
def picker():
square = int(request.args.get("square", 0))
return render_template("picker.html", square=square, colors=colors)Line by line:
@app.route("/picker"), handles requests to/pickersquare = int(request.args.get("square", 0)), reads the?square=0part from the URL.request.argsis a dictionary of everything after the?in the URL. We wrap it inint()because URL parameters are always strings, and we want a number. The0is the default if nothing is passedreturn render_template("picker.html", square=square, colors=colors), loadspicker.html, passing both which square we're editing and the full colors dictionary
Create templates/picker.html.
Start with the skeleton and styles:
<!DOCTYPE html>
<html>
<head>
<title>Pick a Color</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 30px;
border-radius: 8px;
text-align: center;
}
input[type="color"] {
width: 100px;
height: 100px;
border: none;
cursor: pointer;
}
button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
</style>
</head>
<body>
</body>
</html>Add the container and heading. Inside <body>:
<div class="container">
<h1>Pick a color for Square {{ square }}</h1>
</div>{{ square }} is Jinja2 again. It'll say "Pick a color for Square 0", "Square 1", etc. depending on which one was clicked.
Add the form. Inside the container div, after the <h1>:
<form method="POST" action="/update_color">
</form>method="POST", this means the form data is sent in the request body, not the URL. We'll handle it in Step 5action="/update_color", this is where the form gets sent when the user clicks Save
Add the color picker input. Inside the form:
<input type="color" name="color" value="{{ colors.get(square) }}">type="color", tells the browser to show a color picker wheelname="color", this is the key we'll use to read the value on the servervalue="{{ colors.get(square) }}", sets the starting color to whatever the square is currently set to
Add a hidden input to send the square number along with the form. Without this, the server won't know which square to update:
<input type="hidden" name="square" value="{{ square }}">It's invisible to the user but gets submitted with the form. name="square" is how we'll read it on the server.
Add the submit button:
<button type="submit">Save</button>Restart flask run and click a square. You should see the color picker with the current color already selected. Clicking Save will crash, that's fine, we haven't built that part yet.
Last piece. Add this to app.py, after the picker function:
@app.route("/update_color", methods=["POST"])
def update():
square = int(request.form.get("square"))
color = request.form.get("color")
colors[square] = color
return redirect("/")Line by line:
@app.route("/update_color", methods=["POST"]), this route only accepts POST requests, which is what forms send. If someone tries to visit this URL directly in a browser, Flask will reject it. Themethods=["POST"]part is what makes that happensquare = int(request.form.get("square")), reads the square number from the form data.request.formis likerequest.argsbut for POST data, it contains everything the form sentcolor = request.form.get("color"), reads the chosen color (a hex string like#ff0000)colors[square] = color, updates our dictionary with the new colorreturn redirect("/"), sends the user back to the homepage so they can see the updated square
flask runOpen localhost:5000. Click a square. Pick a color. Click Save. It should update. Click another one. Marvel at your creation.
If something went wrong, here's what the complete app.py should look like:
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
colors = {
0: "#ff00ff",
1: "#ffff00",
2: "#00ff00",
3: "#00ffff",
}
@app.route("/")
def main():
return render_template("index.html", colors=colors)
@app.route("/picker")
def picker():
square = int(request.args.get("square", 0))
return render_template("picker.html", square=square, colors=colors)
@app.route("/update_color", methods=["POST"])
def update():
square = int(request.form.get("square"))
color = request.form.get("color")
colors[square] = color
return redirect("/")
if __name__ == "__main__":
app.run()