← Template Browser

Rich Form

Covers nine field types with server-side validation and a submission summary. Includes enctype="multipart/form-data" for file uploads.

Live Demo

We'll never share your email.


PDF or Word document, max 5 MB.

Template Code

Copy templates/rich_form.stpl into your app.stpl. Add your save logic inside the if not errors block. Move the <style> block into your stylesheet.

{#
================================================================
 Rich Form  (ScribeFramework template prefab)

 Demonstrates: text, email, number, textarea, select/dropdown,
               radio buttons, checkboxes, date, and file upload.

 How to use:
   1. Copy this route into your app.stpl
   2. Move the <style> block into your stylesheet (or keep it inline)
   3. Rename the route path and form fields to suit your needs
   4. Add real save logic inside the `if request.method == 'POST'` block

 Requires `enctype="multipart/form-data"` on the <form> when
 using file uploads. Remove it (and the file field) if not needed.
================================================================
#}

@route('/example-form', methods=['GET', 'POST'])
@no_layout
{$
page_title = "Application Form"
submitted  = False
form_data  = {}
errors     = {}

if request.method == 'POST':
    # Collect submitted values
    resume_file = request.files.get('resume')
    form_data = {
        'full_name':  request.form.get('full_name',  '').strip(),
        'email':      request.form.get('email',      '').strip(),
        'age':        request.form.get('age',        '').strip(),
        'department': request.form.get('department', ''),
        'priority':   request.form.get('priority',   ''),
        'interests':  request.form.getlist('interests'),
        'start_date': request.form.get('start_date', ''),
        'notes':      request.form.get('notes',      '').strip(),
        'resume':     resume_file.filename if resume_file and resume_file.filename else '',
    }

    # Basic server-side validation
    if not form_data['full_name']:
        errors['full_name'] = 'Full name is required.'
    if not form_data['email']:
        errors['email'] = 'Email address is required.'

    if not errors:
        # TODO: save form_data to database, send email, etc.
        submitted = True
$}

<style>
/* ── Rich Form additions ── move this block to your stylesheet ── */
.form-hint {
    font-size: 0.875rem;
    color: var(--text-muted, #64748b);
    margin-top: 0.25rem;
}
.form-error {
    font-size: 0.875rem;
    color: var(--error-color, #ef4444);
    margin-top: 0.25rem;
}
.form-group input.is-invalid,
.form-group textarea.is-invalid,
.form-group select.is-invalid {
    border-color: var(--error-color, #ef4444);
}
.form-group input[type="date"],
.form-group input[type="file"] {
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1px solid var(--border-color, #e2e8f0);
    border-radius: var(--radius-md, 0.5rem);
    font-size: 1rem;
    font-family: inherit;
}
.form-group input[type="file"] {
    padding: 0.5rem;
    border-style: dashed;
    cursor: pointer;
}
.form-group input[type="file"]:hover {
    border-color: var(--primary-color, #2563eb);
}
.radio-group,
.checkbox-group {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.25rem;
}
.radio-option,
.checkbox-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    font-weight: normal;
}
.radio-option input,
.checkbox-option input {
    width: auto;
    cursor: pointer;
    accent-color: var(--primary-color, #2563eb);
}
.form-divider {
    border: none;
    border-top: 1px solid var(--border-color, #e2e8f0);
    margin: 1.5rem 0;
}
/* ──────────────────────────────────────────────────────────── */
</style>

<div class="container-narrow">

    {% if submitted %}

    <div class="card">
        <h2>Submission Received</h2>
        <p>Thank you, <strong>{{ form_data.full_name }}</strong>! Here's what we got:</p>

        <dl style="margin-top: 1rem;">
            {% set labels = [
                ('full_name',  'Full Name'),
                ('email',      'Email'),
                ('age',        'Age'),
                ('department', 'Department'),
                ('priority',   'Priority'),
                ('start_date', 'Available From'),
                ('resume',     'Resume'),
                ('notes',      'Notes'),
            ] %}
            {% for key, label in labels %}
            <div style="display:flex; gap:1rem; padding:0.6rem 0; border-bottom:1px solid var(--border-color)">
                <dt style="font-weight:600; min-width:140px; color:var(--text-muted)">{{ label }}</dt>
                <dd>{{ form_data[key] if form_data[key] else '—' }}</dd>
            </div>
            {% endfor %}
            <div style="display:flex; gap:1rem; padding:0.6rem 0">
                <dt style="font-weight:600; min-width:140px; color:var(--text-muted)">Interests</dt>
                <dd>{{ form_data.interests | join(', ') if form_data.interests else '—' }}</dd>
            </div>
        </dl>

        <a href="{{ request.path }}" class="btn btn-secondary" style="margin-top:1.5rem">← Submit another</a>
    </div>

    {% else %}

    <div class="card">
        <h2>Application Form</h2>

        {% if errors %}
        <div class="alert alert-error">Please correct the errors below.</div>
        {% endif %}

        <form method="POST" enctype="multipart/form-data">
            {{ csrf() }}

            {# ── Text ──────────────────────────────────────────── #}
            <div class="form-group">
                <label for="full_name">
                    Full Name <span style="color:var(--error-color)">*</span>
                </label>
                <input type="text"
                       id="full_name"
                       name="full_name"
                       value="{{ request.form.get('full_name', '') }}"
                       class="{{ 'is-invalid' if errors.get('full_name') }}"
                       required
                       autofocus>
                {% if errors.get('full_name') %}
                <div class="form-error">{{ errors.full_name }}</div>
                {% endif %}
            </div>

            {# ── Email ─────────────────────────────────────────── #}
            <div class="form-group">
                <label for="email">
                    Email Address <span style="color:var(--error-color)">*</span>
                </label>
                <input type="email"
                       id="email"
                       name="email"
                       value="{{ request.form.get('email', '') }}"
                       class="{{ 'is-invalid' if errors.get('email') }}"
                       required>
                {% if errors.get('email') %}
                <div class="form-error">{{ errors.email }}</div>
                {% else %}
                <div class="form-hint">We'll never share your email.</div>
                {% endif %}
            </div>

            {# ── Number ────────────────────────────────────────── #}
            <div class="form-group">
                <label for="age">Age</label>
                <input type="number"
                       id="age"
                       name="age"
                       min="18"
                       max="120"
                       value="{{ request.form.get('age', '') }}"
                       style="max-width: 120px;">
            </div>

            <hr class="form-divider">

            {# ── Select / Dropdown ─────────────────────────────── #}
            <div class="form-group">
                <label for="department">Department</label>
                <select id="department" name="department">
                    <option value="">— Select a department —</option>
                    {% for val, label in [
                        ('engineering', 'Engineering'),
                        ('design',      'Design'),
                        ('marketing',   'Marketing'),
                        ('operations',  'Operations'),
                    ] %}
                    <option value="{{ val }}"
                            {{ 'selected' if request.form.get('department') == val }}>
                        {{ label }}
                    </option>
                    {% endfor %}
                </select>
            </div>

            {# ── Radio Buttons ─────────────────────────────────── #}
            <div class="form-group">
                <label>Priority</label>
                <div class="radio-group">
                    {% for val, label in [('low','Low'), ('medium','Medium'), ('high','High')] %}
                    <label class="radio-option">
                        <input type="radio"
                               name="priority"
                               value="{{ val }}"
                               {{ 'checked' if request.form.get('priority') == val }}>
                        {{ label }}
                    </label>
                    {% endfor %}
                </div>
            </div>

            {# ── Checkboxes ────────────────────────────────────── #}
            <div class="form-group">
                <label>Interests</label>
                <div class="checkbox-group">
                    {% for val, label in [
                        ('frontend', 'Frontend'),
                        ('backend',  'Backend'),
                        ('devops',   'DevOps'),
                        ('data',     'Data / Analytics'),
                    ] %}
                    <label class="checkbox-option">
                        <input type="checkbox"
                               name="interests"
                               value="{{ val }}"
                               {{ 'checked' if val in request.form.getlist('interests') }}>
                        {{ label }}
                    </label>
                    {% endfor %}
                </div>
            </div>

            <hr class="form-divider">

            {# ── Date ──────────────────────────────────────────── #}
            <div class="form-group">
                <label for="start_date">Available From</label>
                <input type="date"
                       id="start_date"
                       name="start_date"
                       value="{{ request.form.get('start_date', '') }}"
                       style="max-width: 200px;">
            </div>

            {# ── File Upload ───────────────────────────────────── #}
            <div class="form-group">
                <label for="resume">Resume</label>
                <input type="file"
                       id="resume"
                       name="resume"
                       accept=".pdf,.doc,.docx">
                <div class="form-hint">PDF or Word document, max 5 MB.</div>
            </div>

            {# ── Textarea ──────────────────────────────────────── #}
            <div class="form-group">
                <label for="notes">Additional Notes</label>
                <textarea id="notes"
                          name="notes"
                          rows="4"
                          placeholder="Anything else you'd like us to know?">{{ request.form.get('notes', '') }}</textarea>
            </div>

            <button type="submit" class="btn btn-primary btn-block">Submit Application</button>
        </form>
    </div>

    {% endif %}

</div>