Morphos
Inputs

Form

A lightweight form wrapper component with typed event props for submit and reset handling.

Interactive example

Installation

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Import

import { Form } from '@morphos/inputs'

Usage

@Component()
class MyComponent extends StatefulComponent {
  @State() name = ''
  @State() email = ''
  @State() nameField = new Field()
  @State() emailField = new Field()

  handleSubmit(e: SubmitEvent) {
    e.preventDefault()
    const data = new FormData(e.target as HTMLFormElement)
    console.log({
      name: data.get('name'),
      email: data.get('email'),
    })
  }

  render() {
    return (
      <Form class="morphos-form" onSubmit={(e) => this.handleSubmit(e)}>
        <Field class="morphos-field">
          <FieldLabel field={this.nameField} class="morphos-field-label">Name</FieldLabel>
          <FieldControl class="morphos-field-control">
            <Input
              id={this.nameField.fieldId}
              class="morphos-input"
              name="name"
              value={this.name}
              onInput={(value) => (this.name = value)}
            />
          </FieldControl>
        </Field>
        <Field class="morphos-field">
          <FieldLabel field={this.emailField} class="morphos-field-label">Email</FieldLabel>
          <FieldControl class="morphos-field-control">
            <Input
              id={this.emailField.fieldId}
              class="morphos-input"
              type="email"
              name="email"
              value={this.email}
              onInput={(value) => (this.email = value)}
            />
          </FieldControl>
        </Field>
        <button type="submit" class="morphos-button">Submit</button>
      </Form>
    )
  }
}

With noValidate

@Component()
class MyComponent extends StatefulComponent {
  @State() errors: Record<string, string> = {}

  validate(data: FormData): boolean {
    const errs: Record<string, string> = {}
    if (!data.get('username')) errs.username = 'Username is required'
    if ((data.get('password') as string).length < 8)
      errs.password = 'Password must be at least 8 characters'
    this.errors = errs
    return Object.keys(errs).length === 0
  }

  handleSubmit(e: SubmitEvent) {
    e.preventDefault()
    const data = new FormData(e.target as HTMLFormElement)
    if (this.validate(data)) {
      // proceed with submission
    }
  }

  render() {
    return (
      <Form noValidate onSubmit={(e) => this.handleSubmit(e)}>
        <Input name="username" placeholder="Username" aria-label="Username" />
        {this.errors.username && <span role="alert">{this.errors.username}</span>}
        <Input type="password" name="password" placeholder="Password" aria-label="Password" />
        {this.errors.password && <span role="alert">{this.errors.password}</span>}
        <button type="submit">Log in</button>
      </Form>
    )
  }
}

Server action form

@Component()
class MyComponent extends StatefulComponent {
  handleReset() {
    console.log('Form was reset')
  }

  render() {
    return (
      <Form action="/api/contact" method="post" onReset={() => this.handleReset()}>
        <Input name="subject" placeholder="Subject" aria-label="Subject" />
        <Input name="message" placeholder="Message" aria-label="Message" />
        <button type="submit">Send</button>
        <button type="reset">Clear</button>
      </Form>
    )
  }
}

Props — Form

PropTypeDefaultDescription
actionstringURL to submit the form to
method"get" | "post"HTTP method for submission
noValidatebooleanDisables native browser validation UI
onSubmit(event: SubmitEvent) => voidFires when the form is submitted
onReset(event: Event) => voidFires when the form is reset
childrenChildrenForm fields and controls
classstringAdditional CSS classes
idstringHTML id attribute
aria-labelstringAccessible label for the form landmark
aria-labelledbystringID of an element that labels the form landmark
aria-describedbystringID of an element that describes the form landmark

Accessibility

Form renders a native <form> element, which is automatically recognized as a form landmark by assistive technologies. When aria-label is provided, screen readers announce the label when users navigate to the form landmark. Use noValidate when implementing custom validation logic with Field and FieldError components to avoid conflicting browser-native error popups.

Pair Form with Field, FieldLabel, FieldDescription, and FieldError for fully accessible form fields. Field auto-generates the descriptionId/errorId/fieldId needed to connect labels, descriptions, and error messages to their respective inputs.

Styling example

.morphos-form {
  display: flex;
  flex-direction: column;
  gap: var(--morphos-space-4);
}

On this page