Docs

Components

@trymirio/mirio has two layers. Headless primitives (@trymirio/mirio/headless) carry behavior and accessibility with no styling: form state, validation, conditional logic, page navigation, partial saves, focus management, labels and aria. Styled components (@trymirio/mirio) sit on top and look finished out of the box, authored in Tailwind and themed with --mirio-* CSS variables. Both are replaceable: restyle with the theme, size, layout and classNames props, variables or classes, or start from the primitives.

Zero-markup form

import { Form } from '@trymirio/mirio';
import { mirio } from '@/mirio/client';
import { waitlist } from '@/mirio/forms/waitlist';

<Form schema={waitlist} client={mirio} />

With no children, Form renders every page from the schema: one page gives you the fields and a submit button; several pages give you MultiStep with a progress line, Back and Next. After submit it shows the settings.success message, or redirects. Wrap the app once in MirioProvider to drop the client prop.

Composing

Children replace the automatic layout. Field renders any field by key (label, help, the right control, error); the controls, Label, Help, FieldError, Submit, Page, Progress and MultiStep can be arranged freely, mixed with your own JSX.

import { Form, Field, Submit } from '@trymirio/mirio';

<Form schema={contact} client={mirio} className="grid gap-6">
  <div className="grid grid-cols-2 gap-4">
    <Field name="first_name" />
    <Field name="last_name" />
  </div>
  <Field name="email" />
  <Field name="message" />
  <Submit className="w-auto justify-self-end" />
</Form>

Every styled component accepts className (merged with tailwind-merge, so your utilities win) and renders data-mirio="<name>" for CSS. Hidden fields and pages (showWhen) render nothing. See Styling for the three depths of customization.

All components

Component One line
Form The whole form from a schema; auto-renders or takes children.
Field Label + help + the control for the field type + error, from one name.
Input Single-line control for short_text, email, phone, url, number.
Textarea Multi-line control for long_text.
Select Styled native select for dropdown.
Choice Radio group (single_choice) or checkbox group (multiple_choice).
YesNo Two segmented buttons with radio semantics for yes_no.
Rating 1..max buttons with radio semantics and arrow keys for rating.
DatePicker Styled native date input for date.
Consent Checkbox with inline label for consent.
Submit Submit button with the label from settings.submitLabel and a loading state.
Success The thank-you card from settings.success.
MultiStep One page at a time with Progress, Back/Next and Submit; also covers Page and Progress.
Label, Help, FieldError The text pieces around a control, and the requiredIndicator option.
MirioProvider Provide the client and display options once.
Headless useMirioForm, FormProvider, useForm, useField, usePage, useFormStatus, Honeypot, Turnstile, with a reference table of everything the hook returns.

Behavior you get for free

  • Client validation from the schema, with the same validator the server runs; format errors on blur, "required" on submit; focus moves to the first invalid control.
  • Submit with an idempotency key and retries on network errors and 5xx; one logical submit never lands twice.
  • Page URL, referrer and UTM parameters captured; a honeypot and time-to-submit as spam signals; Turnstile when a site key is set (Mirio checks it once it has the widget's secret key); agents that declare themselves (actor) are labeled.
  • Partial saves when a visitor moves to the next step, never on keystrokes.
  • Prefill from initialValues, prefill: { query } and defaultValue; autocomplete attributes from the schema.
  • Conditional fields and pages (Conditional logic), and schedule successes that land on a booking page with name and email filled in.

Component previews