Forms
Forms provide a comprehensive set of form controls and layouts. Includes inputs, textareas, selects, checkboxes, radios, switches, and validation styling. All components work with native HTML form elements enhanced with Domma's theme-aware styling.
Key Features
- Native HTML - Works with standard form elements
- Validation States - Success, error, and warning styling
- Input Groups - Addons, icons, and button combinations
- Layouts - Vertical, horizontal, and inline forms
Basic Usage
<div class="form-group">
<label>Email</label>
<input type="email" class="form-input" placeholder="you@example.com">
</div>
Quick Start
Simple Form
<form>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-input" placeholder="you@example.com">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-input">
</div>
<button type="submit" class="btn btn-primary">Sign In</button>
</form>
Tutorial
Build a registration form with validation.
Fill out the form and submit
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
// Clear previous validation
document.querySelectorAll('.form-group').forEach(group => {
group.classList.remove('has-error', 'has-success');
});
// Validate name
const name = document.getElementById('name');
if (name.value.length < 2) {
name.closest('.form-group').classList.add('has-error');
return;
}
name.closest('.form-group').classList.add('has-success');
// Validate email
const email = document.getElementById('email');
if (!email.value.includes('@')) {
email.closest('.form-group').classList.add('has-error');
return;
}
email.closest('.form-group').classList.add('has-success');
// Form is valid
console.log('Form submitted successfully!');
});
Examples
Input Types
Select Menus
Checkboxes and Radios
Input Sizes
Validation States
Looks good!
This field is required
Are you sure?
Input Groups
@
.00
Inline Forms
Best Practices
Accessibility
- Labels - Always provide
elements for inputs - ARIA Attributes - Use
aria-required,aria-invalidfor validation - Error Messages - Link errors to inputs using
aria-describedby - Keyboard Navigation - Ensure all form controls are keyboard accessible
Validation
- Client-Side First - Validate on submit before sending to server
- Clear Feedback - Use validation states and helper text
- Real-Time Validation - Consider validating on blur for better UX
- Server-Side Required - Always validate on backend for security
UX Guidelines
- Placeholder Text - Use for examples, not instructions
- Help Text - Provide context with
.form-text - Logical Grouping - Group related fields together
- Required Fields - Mark clearly with asterisk or label
- Button States - Disable submit while processing
CSS Classes
| Class | Description |
|---|---|
.form-group |
Form field container |
.form-input |
Text input styling |
.form-textarea |
Textarea styling |
.form-select |
Select dropdown styling |
.form-checkbox |
Checkbox wrapper |
.form-radio |
Radio button wrapper |
.form-text |
Helper text |
.form-input-sm |
Small input size |
.form-input-lg |
Large input size |
.has-success |
Success validation state |
.has-error |
Error validation state |
.has-warning |
Warning validation state |
.input-group |
Input group container |
.input-group-addon |
Input group prefix/suffix |
.form-inline |
Inline form layout |
Hybrid CSS + JavaScript
Forms work perfectly with pure CSS styling, but you can enhance them with JavaScript for validation, auto-save, dynamic fields, and more using Domma's declarative configuration.
Form Validation with $.setup()
$.setup({
'#registration-form': {
initial: {
data: { touched: {}, errors: {} }
},
events: {
submit: (e, $form) => {
e.preventDefault();
// Collect form data
const formData = {
username: $form.find('[name="username"]').val(),
email: $form.find('[name="email"]').val(),
password: $form.find('[name="password"]').val()
};
// Validate with utils
const errors = {};
if (_.isEmpty(_.trim(formData.username))) {
errors.username = 'Username required';
}
if (!formData.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
errors.email = 'Invalid email';
}
if (formData.password.length < 8) {
errors.password = 'Password must be 8+ characters';
}
$form.data('errors', errors);
if (_.isEmpty(errors)) {
// Submit
Domma.http.post('/api/register', formData)
.then(() => {
Domma.elements.toast.success('Registered successfully!');
window.location = '/dashboard';
})
.catch(err => {
Domma.elements.toast.error(err.message);
});
} else {
// Show errors
_.forEach(errors, (msg, field) => {
$form.find(`[name="${field}"]`)
.closest('.form-group')
.addClass('has-error')
.find('.error-message').text(msg);
});
}
},
'blur .form-input': _.debounce((e, $form) => {
const $input = $(e.target);
const field = $input.attr('name');
const value = $input.val();
const $group = $input.closest('.form-group');
// Mark as touched
const touched = $form.data('touched') || {};
touched[field] = true;
$form.data('touched', touched);
// Validate
let isValid = true;
if (field === 'email') {
isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
} else if (field === 'password') {
isValid = value.length >= 8;
} else {
isValid = !_.isEmpty(_.trim(value));
}
$group.toggleClass('has-error', !isValid);
$group.toggleClass('has-success', isValid);
}, 300)
}
}
});
Auto-Save Draft
$.setup({
'#article-form': {
initial: {
data: { saveTimer: null, lastSaved: null }
},
events: {
'input .form-input, change select': _.debounce((e, $form) => {
// Collect current state
const data = {
title: $form.find('[name="title"]').val(),
content: $form.find('[name="content"]').val(),
category: $form.find('[name="category"]').val()
};
// Save draft
Domma.http.post('/api/drafts/auto-save', data)
.then(() => {
$form.data('lastSaved', new Date());
$('#save-status').text('Draft saved ' + D().format('HH:mm:ss'));
});
}, 2000)
}
}
});
Dynamic Form Fields
$.setup({
'#contact-form': {
events: {
'click #add-phone': (e, $form) => {
const phoneCount = $form.find('.phone-input').length;
const html = `
<div class="form-group">
<input type="tel" name="phone_${phoneCount}"
class="form-input phone-input"
placeholder="Phone ${phoneCount + 1}">
</div>
`;
$form.find('#phone-inputs').append(html);
},
'change [name="contact-method"]': (e, $form) => {
const method = $(e.target).val();
// Show/hide relevant fields
$form.find('.email-field').toggle(method === 'email');
$form.find('.phone-field').toggle(method === 'phone');
$form.find('.address-field').toggle(method === 'mail');
}
}
}
});
Multi-Step Form
$.setup({
'#wizard-form': {
initial: {
data: { currentStep: 1, totalSteps: 3, formData: {} }
},
events: {
'click .next-step': (e, $form) => {
const data = $form.data();
if (data.currentStep < data.totalSteps) {
// Collect current step data
$form.find(`.step-${data.currentStep} .form-input`).each(function() {
const $input = $(this);
data.formData[$input.attr('name')] = $input.val();
});
// Move to next step
data.currentStep++;
$form.data(data);
// Update UI
$form.find('.step').hide();
$form.find(`.step-${data.currentStep}`).fadeIn();
$('#step-indicator').text(`Step ${data.currentStep} of ${data.totalSteps}`);
}
},
'click .prev-step': (e, $form) => {
const data = $form.data();
if (data.currentStep > 1) {
data.currentStep--;
$form.data(data);
$form.find('.step').hide();
$form.find(`.step-${data.currentStep}`).fadeIn();
$('#step-indicator').text(`Step ${data.currentStep} of ${data.totalSteps}`);
}
}
}
}
});
Benefits of Hybrid Approach
- Progressive Enhancement - Forms work without JS, enhanced with JS
- Better UX - Real-time validation, auto-save, dynamic fields
- Maintainability - Centralized logic with $.setup()
- Accessibility - CSS provides base styling, JS adds interactivity
CSS-Only vs CSS+JS Hybrid
CSS-Only (Default)
<form action="/submit" method="POST">
<div class="form-group">
<label>Email</label>
<input type="email"
class="form-input"
name="email"
required>
</div>
<button type="submit"
class="btn btn-primary">
Submit
</button>
</form>
<!-- Works with native validation -->
CSS+JS Hybrid (Enhanced)
<form id="enhanced-form">
<div class="form-group">
<label>Email</label>
<input type="email"
class="form-input"
name="email">
<span class="error-message"></span>
</div>
<button type="submit"
class="btn btn-primary">
Submit
</button>
</form>
<script>
$.setup({
'#enhanced-form': {
events: {
submit: validateAndSubmit,
'blur .form-input': validateField
}
}
});
</script>