Forms are application contracts, not just input boxes. The Forms module owns fields, validation, context, and submit behavior.
Use Sass v2 form classes for the visual system, then attach real forms to H/B/F sections through Databoard mapping.
A normal form uses labels, helper text, controls, textarea, and a semantic submit button.
data.item.css_class = stylesdoc-form-demo
data.description = dummy form HTML using antsand-form-control
Real pages should attach a Forms module record with data.form.id
Login forms are application forms. They post to an HMVC endpoint and must not fall back to generic thank-you behavior.
form.application_mode = login
form.submit_url = /auth/session/login
form.generic_thank_you = disabled
HMVC action verifies password_hash and sets session.auth
The page provides form_context.email. The form renders it readonly and posts password plus confirmation.
field.email.view_source = form_context.email
field.email.readonly_when_context = true
field.password_confirm.validation = Matches Field
field.password_confirm.match_field = password
Use explicit valid/invalid states and ARIA attributes so styling, JS validation, and server errors agree.
<input class="antsand-form-control is-valid" aria-invalid="false">
<div class="antsand-valid-feedback">Looks good.</div>
<input class="antsand-form-control is-invalid" aria-invalid="true">
<div class="antsand-invalid-feedback">Password is required.</div>
Choice controls use the same form-group shell, then field-specific classes for select, checkbox, and radio controls.
<select class="antsand-form-select">...</select>
<label class="antsand-form-check"><input type="checkbox"><span>Email me updates</span></label>
<label class="antsand-form-check"><input type="radio" name="frequency"><span>Weekly</span></label>
Checkout forms still use a server-rendered contract. The payment amount, currency, and store id must be present before Stripe initializes.
<div class="antsand-stripe-payment-element"
data-price-cents="2500"
data-currency="cad"
data-store-id="demo-store"></div>
<!-- Missing amount should fail early before creating payment intent. -->
Use size modifiers when the form density should match a compact toolbar or a large marketing page.
<input class="antsand-form-control antsand-form-sm">
<input class="antsand-form-control">
<input class="antsand-form-control antsand-form-lg">
Floating labels keep dense forms readable while preserving accessible label text.
<div class="antsand-form-floating">
<input class="antsand-form-control" placeholder="name@example.com">
<label>Email address</label>
</div>
Switches are choice controls for settings, notification preferences, and dashboard toggles.
<label class="antsand-form-switch">
<input type="checkbox" checked>
<span>Email notifications</span>
</label>
Input groups attach prefixes, suffixes, or action buttons without custom wrapper CSS.
<div class="antsand-input-group">
<span class="antsand-input-group-text">@</span>
<input class="antsand-form-control">
</div>
Form layout classes handle common composition without turning each generated form into custom HTML.
<div class="antsand-form-inline">...</div>
<div class="antsand-form-row-2">...</div>
<div class="antsand-form-horizontal">...</div>
Upload-heavy applications need file and drag-drop affordances that still fit the same form shell.
<input class="antsand-form-range" type="range">
<input class="antsand-form-file" type="file">
<div class="antsand-form-dropzone">Drop files here</div>
Grouped forms should express meaning in the markup, not only with visual spacing.
<fieldset class="antsand-fieldset">
<legend class="antsand-legend">Publishing settings</legend>
<label class="antsand-form-label antsand-required">Post title</label>
<input class="antsand-form-control" required>
</fieldset>
Disabled and focus-within states are CSS contracts, so server output and JS validation stay consistent.
<input class="antsand-form-control" disabled>
<div class="antsand-form-group" aria-disabled="true">...</div>
<input class="antsand-form-control" aria-invalid="true">
Use a card shell when a form is the primary object on a page, such as signup, checkout, or account settings.
<form class="antsand-form-card">
<div class="antsand-form-card-header">...</div>
<div class="antsand-form-card-body">...</div>
<div class="antsand-form-card-footer">...</div>
</form>