AvesraAvesrabeta

Form

Wrapper component for form validation and submission handling. Apply av-form to a native <form> element.

Import

import { AvFormComponent } from '@avesra/angular';

Usage

Compose fields with av-label, av-input, av-description, and av-field-error. Handle submit with the native (submit) event and set aria-label or aria-labelledby for accessibility.

Must be at least 8 characters with 1 uppercase and 1 number

import { Component, signal } from '@angular/core';
import {
  AvButtonComponent,
  AvDescriptionComponent,
  AvFieldErrorComponent,
  AvFormComponent,
  AvInputComponent,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-form-basic-demo',
  imports: [
    AvFormComponent,
    AvLabelComponent,
    AvInputComponent,
    AvDescriptionComponent,
    AvFieldErrorComponent,
    AvButtonComponent,
  ],
  host: { class: 'w-full max-w-96' },
  template: `<form
      av-form
      class="flex w-full flex-col gap-4"
      aria-label="Sign in"
      (submit)="onSubmit($event)"
    >
      <div class="flex flex-col gap-1">
        <label av-label for="form-email" required>Email</label>
        <input
          av-input
          full-width
          id="form-email"
          name="email"
          type="email"
          placeholder="john&#64;example.com"
          required
          [invalid]="emailError()"
          (input)="onEmailInput($event)"
        />
        <p av-field-error [visible]="emailError()">Please enter a valid email address</p>
      </div>

      <div class="flex flex-col gap-1">
        <label av-label for="form-password" required>Password</label>
        <input
          av-input
          full-width
          id="form-password"
          name="password"
          type="password"
          placeholder="Enter your password"
          required
          minlength="8"
          [invalid]="passwordError()"
          (input)="onPasswordInput($event)"
        />
        <p av-description>
          Must be at least 8 characters with 1 uppercase and 1 number
        </p>
        <p av-field-error [visible]="passwordError()">
          {{ passwordMessage() }}
        </p>
      </div>

      <div class="flex gap-2">
        <button av-button type="submit">Submit</button>
        <button av-button type="reset" variant="secondary">Reset</button>
      </div>
    </form>`,
})
export class FormBasicDemo {
  readonly emailError = signal(false);
  readonly passwordError = signal(false);
  readonly passwordMessage = signal('Password must be at least 8 characters');

  onEmailInput(event: Event): void {
    const value = (event.target as HTMLInputElement).value;
    const isValid = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value);

    this.emailError.set(value.length > 0 && !isValid);
  }

  onPasswordInput(event: Event): void {
    const value = (event.target as HTMLInputElement).value;

    if (value.length === 0) {
      this.passwordError.set(false);
      return;
    }

    if (value.length < 8) {
      this.passwordMessage.set('Password must be at least 8 characters');
      this.passwordError.set(true);
      return;
    }

    if (!/[A-Z]/.test(value)) {
      this.passwordMessage.set('Password must contain at least one uppercase letter');
      this.passwordError.set(true);
      return;
    }

    if (!/[0-9]/.test(value)) {
      this.passwordMessage.set('Password must contain at least one number');
      this.passwordError.set(true);
      return;
    }

    this.passwordError.set(false);
  }

  onSubmit(event: Event): void {
    event.preventDefault();

    const form = event.target as HTMLFormElement;
    const formData = new FormData(form);
    const data: Record<string, string> = {};

    formData.forEach((value, key) => {
      data[key] = value.toString();
    });

    alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`);
  }
}

Anatomy

Import the Form parts and compose them with attribute selectors on native elements.

<form av-form aria-label="Example form" (submit)="onSubmit($event)">
  <!-- Form fields go here -->
  <button av-button type="submit">Submit</button>
  <button av-button type="reset" variant="secondary">Reset</button>
</form>

Styling

Passing Tailwind CSS classes

Pass utility classes on the host <form> and child field elements, or override BEM classes in @layer components.

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvFieldErrorComponent,
  AvFormComponent,
  AvInputComponent,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-form-custom-styling-demo',
  imports: [
    AvFormComponent,
    AvLabelComponent,
    AvInputComponent,
    AvFieldErrorComponent,
    AvButtonComponent,
  ],
  host: { class: 'w-full max-w-md' },
  template: `<form
      av-form
      class="w-full max-w-md space-y-4 rounded-lg border border-border bg-surface p-6"
      aria-label="Newsletter signup"
      (submit)="onSubmit($event)"
    >
      <div class="flex flex-col gap-1">
        <label av-label class="text-sm font-medium" for="form-styled-email">Email</label>
        <input
          av-input
          full-width
          class="rounded-full"
          id="form-styled-email"
          name="email"
          type="email"
          placeholder="Enter your email"
          required
        />
        <p av-field-error class="text-xs" [visible]="false">Please enter a valid email address</p>
      </div>

      <button av-button full-width type="submit">Submit</button>
    </form>`,
})
export class FormCustomStylingDemo {
  onSubmit(event: Event): void {
    event.preventDefault();
  }
}

CSS Classes

Form is a semantic wrapper with no default layout. Use these selectors for hooks:

  • .av-form — Root form element
  • [data-slot="form"] — Slot attribute on the host

API

Form wraps a native <form> and provides validation behavior plus shared error context for descendant fields.

PropTypeDefaultDescription
actionstring—The URL to submit the form data to.
method'get' | 'post'—The HTTP method to use when submitting the form.
enctype'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'—The encoding type for form data submission.
target'_self' | '_blank' | '_parent' | '_top'—Where to display the response after submitting the form.
novalidatebooleanfalseDisables native browser validation.
aria-labelstring—Accessibility label for the form landmark.
aria-labelledbystring—ID of the element that labels the form. Creates a form landmark when provided.
validationBehavior'native' | 'aria''native'Whether to use native HTML validation or ARIA validation. 'native' blocks form submission; 'aria' displays errors in realtime.
validationErrorsRecord<string, string | string[]>{}Server-side validation errors mapped by field name. Displayed immediately and cleared when the user modifies the field.

Form Validation

Form integrates with HTML and Avesra field validation:

  • Use built-in HTML5 validation attributes (required, minlength, pattern, etc.)
  • Drive custom validation with signals and bind [invalid] on inputs
  • Display errors with av-field-error
  • Handle submission after validation with the native (submit) event
  • Provide server-side errors via the validationErrors input, keyed by field name

Validation Behavior

The validationBehavior input controls how validation is displayed:

  • native (default) — Uses native HTML validation and blocks form submission on errors. The first invalid field is focused automatically.
  • aria — Uses ARIA attributes for validation, displays errors in realtime as the user types, and does not block submission.

Set novalidate to disable native browser validation UI when you want full control via ARIA and av-field-error.

Form Submission

Forms can be submitted in several ways:

  • Traditional submission — Set the action and method inputs to submit to a URL
  • JavaScript handling — Use the (submit) event to process form data
  • FormData API — Access form data with FormData in your submit handler

Example with FormData:

onSubmit(event: Event): void {
  event.preventDefault();
  const form = event.target as HTMLFormElement;
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  console.log('Form data:', data);
}

Integration with Form Fields

Form works with Avesra form field components and shared primitives:

  • Input / Textarea — Text inputs with labels and validation
  • Checkbox / Checkbox Group — Boolean and multi-select controls
  • Radio / Radio Group — Single selection from multiple options
  • Switch / Switch Group — Toggle controls
  • Select — Collapsible option lists
  • Fieldset — Grouped fields with legend and actions
  • Button — Submit and reset actions

Compose with shared av-label, av-description, and av-field-error. Field components pick up Form context for validation behavior and server errors when placed inside av-form.

Accessibility

Form uses a native <form> element and provides:

  • Native <form> element semantics
  • Form landmark creation with aria-label or aria-labelledby
  • Automatic focus management on the first invalid field during native validation
  • ARIA validation attributes when using validationBehavior="aria"

Made with ❤ by SyntaxHertz. Open source and available on GitHub.