forms component
Drop-in semantic component. See examples, sizes, variants, and source.
These are end-to-end form recipes — login, signup, contact, settings, search, newsletter — built from the canonical input components. They show how the pieces compose and how to wire submission + validation through the bluestack framework. For per-input options (sizes, types, color intents, states), open the dedicated component pages in the sidebar.
Login Form
Email + password with auto-mounted password toggle (added by
text-input.js when an input has class password),
inline email validation on blur, and a remember-me checkbox.
Login
Signup Form with Inline Validation
Required fields receive a red * automatically (added by
text-input.js). The email/url types are validated by
text-input.js on blur and again by form_handler
on submit. Errors surface via bluestack.say().
Create account
Contact Form
A native <select> wrapped in .select-wrapper,
a textarea inside .textarea-field with a character hint, and a
"send me a copy" checkbox.
Contact us
Settings Form
Real-world settings panel: a profile section with text inputs, a
notifications section using switch for each preference, and a
danger-zone radio group for the visibility setting. Each
<fieldset> groups a logical block.
Account settings
Search / Filter Bar
Inline horizontal form: a search input with a leading magnifier icon
(class="input search"), a category select, and a submit
button. Use method="GET" so filters live in the URL.
Search bar
Payment Form
Demonstrates the credit-card and amount input types from
text-input.js: card number is auto-grouped and the brand
icon appears as you type; the amount field formats to two decimals on
blur. The expiry uses the date mask.
Card payment
Class API Reference
| Type | Class | Usage | Notes |
|---|---|---|---|
| Field shell | field |
Container around label + control + hint/error |
Add the error modifier to colour everything inside |
label |
Label text above the input | Wrap a required child for the asterisk |
|
required |
The red * marker |
Inside label |
|
| Input core | input |
Required base class for any text input | See the Text Input page for full options |
input-wrapper |
Hosts an input plus leading/trailing icons |
Use with has-left-icon / has-right-icon |
|
input-group |
Glues an input flush against a button | Newsletter signups, search bars | |
| Addons | input-addon |
Container for input + prefix/suffix add-ons | Holds addon left / addon right children |
addon |
A prefix/suffix piece (e.g. $, https://) |
Combine with left or right |
|
| Messaging | hint |
Helper text below the input | Inside field |
error-message |
Inline error message — auto-revealed when field.error |
Inside field |
|
| Form-level | data-submit="ajax" |
Opt the <form> into the bluestack AJAX submit strategy |
Without it, the form submits natively. See the wiring notes below. |
How validation & submission are wired
Validation and submission are two independent layers. The CSS is
framework-agnostic — nothing in field /
input / error-message /
hint requires bluestack JS to look right.
You can use these classes with native HTML5 forms, htmx, Alpine,
React, or anything else.
Layer 1 — Validator (always on)
app/bluestack.core/form.js is a pure
observer. It listens for submit on every
form and runs the standard HTML5 attribute checks:
required, pattern,
minlength, maxlength,
min, max,
type=email|url|number. It does
not mutate the form, its inputs, or its action.
-
Invalid →
e.preventDefault()+e.stopImmediatePropagation(), then dispatchesbluestack:form:invalid(cancelable) on the form withdetail.messagesanddetail.fields. If no listener prevents the event, falls back tobluestack.say(messages, $form)for the bundled error toast.stopImmediatePropagationensures no submission strategy fires for an invalid form. -
Valid → dispatches
bluestack:form:valid(cancelable). Listeners can callpreventDefault()to abort. Otherwise control falls through to whatever submit handlers are bound after the validator (see Layer 2) and then to native submit. -
Field labels for error messages resolve in this order:
data-message="…"→ wrapping<label>text →label[for=…]text →nameattribute.
Layer 2 — Submission (opt in)
app/bluestack.core/form_submit.js is the
bluestack AJAX strategy. It activates only on forms tagged
data-submit="ajax". For matching forms it
resolves the action, builds a FormData,
copies form attributes onto the AJAX settings (so
target, custom data-*,
etc. flow through), disables the submit button + adds
loading, and calls
bluestack.ajax(). Forms without
data-submit="ajax" are submitted natively
(or by whatever framework you've wired up).
Use any other framework
Skip data-submit="ajax" and listen for
bluestack:form:valid from your own code,
htmx, Alpine, etc.:
document.querySelector('#my-form').addEventListener('bluestack:form:valid', (e) => {
// Validation already passed. Take it from here.
fetch('/api/save', { method: 'POST', body: new FormData(e.target) });
e.preventDefault(); // Stop the native submit.
});
Or override the default error display by listening for
bluestack:form:invalid:
document.querySelector('#my-form').addEventListener('bluestack:form:invalid', (e) => {
e.preventDefault(); // Skip bluestack.say
renderMyOwnInlineErrors(e.detail.fields);
});