Forms

Date pickers, form alignment, and input patterns.

Single Column

We'll never share your email.
<form method="post">
    {% csrf_token %}

    <div class="form-row">
        <div class="form-group">
            <label for="full_name">Full Name</label>
            <input type="text" id="full_name" name="full_name"
                   class="vTextField" placeholder="Jane Smith">
        </div>
    </div>

    <div class="form-row">
        <div class="form-group">
            <label for="email">Email</label>
            <input type="email" id="email" name="email"
                   class="vTextField" placeholder="jane@example.com">
            <span class="helptext">Help text below input</span>
        </div>
    </div>

    <div class="form-row">
        <div class="form-group">
            <label for="bio">Bio</label>
            <textarea id="bio" name="bio" class="vLargeTextField"
                      rows="3" placeholder="..."></textarea>
        </div>
    </div>

    <div class="form-row" style="margin-top: 16px;">
        <button type="button" class="button button-secondary">Cancel</button>
        <button type="submit" class="button button-primary">Save</button>
    </div>
</form>

Two-Column Alignment

Optional — used for account recovery
<form method="post">
    {% csrf_token %}

    <div class="form-row form-row-2col">
        <div class="form-group">
            <label for="first_name">First Name</label>
            <input type="text" id="first_name" name="first_name"
                   class="vTextField" placeholder="Jane">
        </div>
        <div class="form-group">
            <label for="last_name">Last Name</label>
            <input type="text" id="last_name" name="last_name"
                   class="vTextField" placeholder="Smith">
        </div>
    </div>

    <div class="form-row form-row-2col">
        <div class="form-group">
            <label for="department">Department</label>
            <select id="department" name="department" class="vTextField">
                <option value="">Choose...</option>
                <option value="eng">Engineering</option>
            </select>
        </div>
        <div class="form-group">
            <label for="role">Role</label>
            <select id="role" name="role" class="vTextField">
                <option value="">Choose...</option>
                <option value="ic">Individual Contributor</option>
            </select>
        </div>
    </div>

    <div class="form-row" style="margin-top: 16px;">
        <button type="button" class="button button-secondary">Cancel</button>
        <button type="submit" class="button button-primary">Save</button>
    </div>
</form>

Date Pickers

Click the calendar icon to pick a date
Combined date and time picker
<!-- Include admin widgets CSS in extra_css block -->
<link rel="stylesheet" href="{% static 'admin/css/widgets.css' %}">

<div class="form-row form-row-2col">
    <div class="form-group">
        <label for="start_date">Start Date</label>
        <div class="date-input-wrapper">
            <input type="date" id="start_date" name="start_date"
                   class="vTextField vDateField">
            <button type="button" class="date-picker-btn"
                    data-target="start_date" aria-label="Open calendar">
                <svg viewBox="0 0 24 24" width="20" height="20"
                     fill="currentColor">
                    <path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99
                    .9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9
                    2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11z..."/>
                </svg>
            </button>
        </div>
        <span class="helptext">Click the calendar icon</span>
    </div>
    <div class="form-group">
        <label for="end_date">End Date</label>
        <div class="date-input-wrapper">
            <input type="date" id="end_date" name="end_date"
                   class="vTextField vDateField">
            <button type="button" class="date-picker-btn"
                    data-target="end_date">...</button>
        </div>
    </div>
</div>

<!-- datetime-local for combined date+time -->
<input type="datetime-local" id="meeting_time"
       class="vTextField vDateField">

<!-- JS for date picker buttons (add to extra_js block) -->
<script>
document.querySelectorAll('.date-picker-btn').forEach(function(btn) {
    btn.addEventListener('click', function() {
        var input = document.getElementById(this.dataset.target);
        if (input.showPicker) {
            input.showPicker();
        } else {
            input.focus();
            input.click();
        }
    });
});
</script>

File & Image Upload

Standard file picker
Drag & drop an image or click to browse
JPG, PNG, GIF, WebP
Drag & drop a document or click to browse
PDF, DOC, DOCX, TXT
<!-- Basic file input -->
<input type="file" id="attachment" name="attachment" class="vTextField">

<!-- Drag & drop upload zone -->
<div class="file-upload-wrapper">
    <div class="file-upload-dropzone" id="avatar-dropzone">
        <svg viewBox="0 0 24 24" width="32" height="32"
             fill="currentColor"
             style="color: var(--body-quiet-color);">
            <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2
            2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5
            3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
        </svg>
        <span class="dropzone-text">
            Drag & drop an image or click to browse
        </span>
        <span class="dropzone-filename" id="avatar-filename"
              style="display: none;"></span>
        <input type="file" id="avatar" name="avatar"
               accept="image/*" class="file-upload-input">
    </div>
</div>

<!-- CSS needed (add to extra_css block) -->
<style>
.file-upload-wrapper { width: 100%; }
.file-upload-dropzone {
    position: relative;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 8px; padding: 24px 16px;
    border: 2px dashed var(--card-border);
    border-radius: var(--radius-md);
    background: var(--body-bg); cursor: pointer;
}
.file-upload-dropzone:hover,
.file-upload-dropzone.dragover {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 5%, var(--body-bg));
}
.file-upload-input {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    opacity: 0; cursor: pointer;
}
</style>

<!-- JS for dropzone (add to extra_js block) -->
<script>
document.querySelectorAll('.file-upload-dropzone')
  .forEach(function(zone) {
    var input = zone.querySelector('.file-upload-input');
    var text = zone.querySelector('.dropzone-text');
    var filename = zone.querySelector('.dropzone-filename');

    input.addEventListener('change', function() {
        if (this.files.length > 0) {
            text.style.display = 'none';
            filename.textContent = this.files[0].name;
            filename.style.display = '';
        }
    });

    zone.addEventListener('dragover', function(e) {
        e.preventDefault();
        zone.classList.add('dragover');
    });
    zone.addEventListener('dragleave', function() {
        zone.classList.remove('dragover');
    });
    zone.addEventListener('drop', function(e) {
        e.preventDefault();
        zone.classList.remove('dragover');
        if (e.dataTransfer.files.length > 0) {
            input.files = e.dataTransfer.files;
            input.dispatchEvent(new Event('change'));
        }
    });
});
</script>