mica
mica

Example

Email is required.That doesn't look like an email address.

Basic usage

<m-field>
  <label for="email">Email</label>
  <input id="email" type="email" required />
  <m-error match="value-missing">Email is required.</m-error>
  <m-error match="type-mismatch">Not a valid email.</m-error>
  <m-error></m-error>  <!-- catch-all: browser's own message -->
</m-field>

<script type="module" src="mica/field.js"></script>

API

m-field groups one native field with declarative m-error messages. Native constraints remain the source of validity.

Mica-specific attributes

NameTypeDefaultDescription
matchvalidity causecatch-allOn m-error, matches value-missing, type-mismatch, pattern-mismatch, too-long, too-short, range-underflow, range-overflow, step-mismatch, bad-input, or custom-error.

Content/markup contracts

ContentContractDefaultDescription
Fieldone input, textarea, or selectThe first matching descendant is validated and wired to the active error.
m-errorone or more messageshiddenThe first matching error is activated; a matchless error is the fallback. An empty fallback receives the browser's validation message.

field.js

Optional. Suppresses native validation bubbles while preserving blocked submission, focuses the first invalid field, activates the first applicable error, and wires aria-invalid and aria-describedby. Once shown, an error revalidates on input and clears when fixed.

Without the module, a matchless m-error still shows through :user-invalid and submits fall back to native bubbles. Working markup, enhanced — never rendered.

<script type="module" src="mica/field.js"></script>

Helper text and errors

<m-field>
  <label for="handle">Public handle</label>
  <input id="handle" required aria-describedby="handle-help">
  <p id="handle-help">Shown on your public profile.</p>
  <m-error id="handle-error" match="custom-error"></m-error>
  <m-error match="value-missing">Choose a handle.</m-error>
</m-field>

Keep labels connected with for and helper text connected with aria-describedby. While the custom error is active, Mica adds handle-error alongside handle-help. Clearing it removes only Mica's error reference; helper descriptions remain associated.

Custom validation

“admin” is reserved. Try it, submit, then change the value. Choose a handle.

Use native setCustomValidity(message) for application rules. A nonempty message makes the field invalid; setCustomValidity('') clears that custom rule, while native constraints such as required still apply. An empty m-error match="custom-error" displays the current validation message.

const handle = document.querySelector('#handle');
function validateHandle() {
  handle.setCustomValidity(
    handle.value.trim().toLowerCase() === 'admin'
      ? 'That handle is reserved. Choose another.'
      : ''
  );
}
handle.addEventListener('input', validateHandle);
validateHandle();

Attach rule listeners to the input so they run before the event bubbles to m-field. Compute validity when values change and once for initial values: native form validation runs before the submit event. After a submit attempt, Mica updates an already visible error as the user types.

Setting custom validity alone does not display an error. Call form.reportValidity() when you deliberately want to reveal errors and focus the first invalid field, such as after a server response. After a programmatic correction, clear or recompute validity and dispatch a bubbling input event to refresh an already displayed error:

handle.value = correctedHandle;
validateHandle();
handle.dispatchEvent(new Event('input', { bubbles: true }));

Dependent fields

Choose a start date. The end must be on or after the start. Changing either date recomputes the rule. Choose an end date.

Recompute a rule whenever any value it depends on changes. For two native date inputs, the end date's validity depends on both values:

const start = document.querySelector('#start');
const end = document.querySelector('#end');
function validateDates() {
  end.setCustomValidity(
    start.value && end.value && end.value < start.value
      ? 'End date must be on or after the start date.'
      : ''
  );
}
end.addEventListener('input', validateDates);
start.addEventListener('input', () => {
  validateDates();
  end.dispatchEvent(new Event('input', { bubbles: true }));
});
validateDates();

The forwarded input event refreshes an existing end-date error without moving focus away from the start date. A new error is revealed by the next validation attempt. Keep required and a value-missing message on each field; give the end field an empty m-error match="custom-error" for this rule.

Server-returned errors

This example simulates a server response with a short delay.

Use taken@example.com to receive a field error. Editing while a response is pending discards it. Enter your email. Enter a valid email address.

Map server field errors to their native inputs. For an email field with an empty custom-error message, set the returned text as custom validity, then ask the form to display it:

email.setCustomValidity(errors.email);
form.reportValidity();

Mica displays the message as text, sets aria-invalid, and associates the error alongside existing helper descriptions. Clear a server error when its relevant input changes, before that event reaches Mica. If the field also has local custom rules, recompute those rather than unconditionally clearing all custom validity.

let revision = 0;
email.addEventListener('input', () => {
  ++revision;
  email.setCustomValidity('');
});
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const requestRevision = ++revision;
  const submittedEmail = email.value;
  try {
    const errors = await saveEmail(submittedEmail); // Application function.
    if (requestRevision !== revision) return; // Ignore stale responses.
    if (errors.email) {
      email.setCustomValidity(errors.email);
      form.reportValidity();
    }
  } catch {
    if (requestRevision === revision) {
      saveStatus.textContent = 'Could not save. Please try again.';
    }
  }
});

Here saveEmail returns a field-error object (empty on success), and saveStatus references an authored p role="status" for request feedback. For several fields, assign all returned errors before calling form.reportValidity() once. Invalidate pending responses when any submitted value is edited. Handle request failures at form level instead of marking otherwise valid fields invalid.

Application code owns rules and requests; Mica presents their validity.

← fieldsetheader navigation →