stellify/ui

Components

Form

A form orchestrator that handles synchronous validation, focus management, and alert state.

Preview

Usage

<st-form>
  <form action="/login" method="POST">
    <st-field class="grid gap-2">
      <label for="email">Email</label>
      <input id="email" name="email" type="email" required>
    </st-field>

    <st-field class="grid gap-2">
      <label for="password">Password</label>
      <input id="password" name="password" type="password" required>
    </st-field>

    <button type="submit">Sign in</button>
  </form>
</st-form>

Handling Server Errors

For fetch-based submissions, use setServerErrors() to display Laravel's 422 validation errors:

const stForm = document.querySelector('st-form')
const form = stForm.querySelector('form')

form.addEventListener('submit', async (e) => {
  e.preventDefault()

  const res = await fetch(form.action, {
    method: 'POST',
    body: new FormData(form),
    headers: { 'Accept': 'application/json' }
  })

  if (res.status === 422) {
    const { errors } = await res.json()
    stForm.setServerErrors(errors)
  } else if (res.ok) {
    window.location = '/dashboard'
  }
})

Laravel Blade Integration

<st-field> works with Laravel's @error directive out of the box. Server-rendered errors are the source of truth on initial load; client-side validation takes over when the user interacts:

<st-field class="grid gap-2">
  <label for="email">Email address</label>
  <input
    id="email"
    name="email"
    type="email"
    required
    value="{{ old('email') }}"
    @error('email') aria-invalid="true" @enderror
  >
  @error('email')
    <p data-error class="text-sm text-destructive">{{ $message }}</p>
  @enderror
</st-field>

How It Works

1

st-field discovers existing <p data-error> elements and aria-invalid attributes on page load.

2

If server errors exist, the field is treated as "touched" — client-side validation updates errors in place.

3

If no server error exists, the error element is created dynamically when validation fails.

No-JavaScript Fallback

This pattern works without JavaScript: the form renders, errors display, the user can submit. JavaScript adds client-side validation and dynamic error updates on top.