HTML Forms
A form is how a web page sends data to a server. The user fills in fields and clicks a button. The browser packages everything up and sends it to a URL you specify.
The <form> element
Every form starts with a <form> tag with two key attributes:
<form action="/input" method="POST">
<!-- fields go here -->
<button type="submit">Save Task</button>
</form>
action="/input"— the URL the form data gets sent to. This is a route in your Flask app.method="POST"— how the data is sent. You'll learn what POST means on the next page. For now: forms that save data usemethod="POST".
Input fields
Text input
<label for="title">Title:</label>
<input type="text" name="title" id="title">
A single line of text. The label tag links to the input via the matching for and id values — clicking the label focuses the input.
Textarea
<label for="description">Description:</label>
<textarea name="description" id="description"></textarea>
Multi-line text. Used for longer entries like descriptions or notes.
Select dropdown
<label for="status">Status:</label>
<select name="status" id="status">
<option value="In Progress">In Progress</option>
<option value="Blocked">Blocked</option>
<option value="Done">Done</option>
</select>
A dropdown menu. The value attribute on each <option> is what Flask receives — the text between the tags is just what the user sees. If you omit value, the visible text is used instead.
The rule that breaks everything if you forget it
Every field must have a name attribute. That name becomes the key Flask uses to find the data. No name — Flask receives nothing for that field, and you'll get a KeyError or just missing data with no obvious error message.
<!-- WRONG — Flask can't find this -->
<input type="text" id="title">
<!-- RIGHT — Flask reads this as request.form["title"] -->
<input type="text" name="title" id="title">
Datalist — native autocomplete
Your starter code already includes a <datalist> for project title suggestions. It provides autocomplete from existing project names without any JavaScript:
<!-- The datalist holds the options — generated by Jinja -->
<datalist id="project-suggestions">
{% for p in projects %}
<option value="{{ p }}">
{% endfor %}
</datalist>
<!-- The input connects to it via the list= attribute -->
<input type="text" name="project_title" id="project_title" list="project-suggestions">
The list="project-suggestions" on the input must match the id="project-suggestions" on the datalist exactly. The options come from the projects list passed from the GET route — Flask reads the CSV to find existing project names before rendering the form.
The complete form
Putting it together — this is what your finished input.html form should look like:
<form action="/input" method="POST">
<label for="project_title">Project:</label>
<input type="text" name="project_title" id="project_title" list="project-suggestions">
<label for="title">Title:</label>
<input type="text" name="title" id="title">
<label for="description">Description:</label>
<textarea name="description" id="description"></textarea>
<label for="status">Status:</label>
<select name="status" id="status">
<option value="In Progress">In Progress</option>
<option value="Blocked">Blocked</option>
<option value="Done">Done</option>
</select>
<label for="next_steps">Next Steps:</label>
<input type="text" name="next_steps" id="next_steps">
<button type="submit">Save Task</button>
</form>
Don't build this yet — read the next two pages first so you understand what happens when the user clicks "Save Task."