Contact Form Island

Overview

The contact form island progressively enhances the contact form with client-side validation and inline feedback. When JavaScript is enabled, the form submits via fetch() and displays success or error messages without a page reload. When JavaScript is disabled, it falls back to the standard HTML POST handled by accent-contact.

The island is a theme asset (contact-form.js) that works alongside the existing accent-contact WASM plugin without modifying it.

Prerequisites

  1. Contact form plugin installed and working
  2. Islands architecture available (included in all editions)

Installation

Copy the island component into your theme and add the template:

your-site/
  plugins/
    accent-contact/                    # Existing WASM plugin (handles form POST)
      plugin.toml
      plugin.wasm
  themes/default/
    assets/js/islands/
      contact-form.js                  # Island component
    templates/
      contact-island.html.jinja        # Island-enhanced form template

The contact form island is a theme-level component (like copy-code.js or search.js). It requires the accent-contact plugin to handle form submission on the server side.

Template Setup

Create a template that wraps the contact form inside an <accent-island> element. The server-rendered form is the no-JS fallback:

{% extends "base.html.jinja" %}

{% block content %}
<article>
  <h1>{{ page.title }}</h1>
  {{ page.content | safe }}

  {# Server-side error messages (hidden by island JS when hydrated) #}
  {% if request is defined and request.query.error %}
    <div class="alert alert-error contact-server-error">
      <p>Something went wrong. Please check your input and try again.</p>
    </div>
  {% endif %}

  <accent-island data-component="contact-form" data-hydrate="load">
    <form action="/contact-submit" method="POST" class="contact-form">
      {# CSRF token injected by accent-contact on_render hook #}
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" id="name" name="name" required
               placeholder="Your name">
      </div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required
               placeholder="your@email.com">
      </div>
      <div class="form-group">
        <label for="message">Message</label>
        <textarea id="message" name="message" required rows="6"
                  placeholder="How can we help?"></textarea>
      </div>
      <button type="submit" class="btn btn-primary">Send Message</button>
    </form>
  </accent-island>
</article>

{# Load island scripts explicitly (template island, not markdown) #}
{% if not (page.islands is defined and page.islands | length > 0) %}
<script src="/{{ 'theme/assets/js/island-loader.js' | fingerprint }}" defer></script>
{% endif %}
<script src="/{{ ('theme/assets/js/islands/contact-form.js') | fingerprint }}" defer></script>
{% endblock %}

How It Works

With JavaScript Enabled

  1. The island loader discovers the <accent-island> element
  2. The contact-form component hydrates immediately (load strategy)
  3. Server-rendered error messages (.contact-server-error) are hidden
  4. The island intercepts the form’s submit event
  5. Native HTML5 validation runs first (required fields, email format)
  6. If valid, the form is submitted via fetch() to /contact-submit
  7. The existing accent-contact plugin processes the POST and returns a 302 redirect
  8. The island follows the redirect and reads the final URL:
    • URL contains /contact-sent – shows inline success message, hides the form
    • URL contains ?error=name – shows inline error message
  9. The submit button is disabled during the request to prevent double submission

With JavaScript Disabled

The <accent-island> element is ignored. The <form> inside it submits normally via POST. The accent-contact plugin handles validation and redirects to /contact?error=X on failure or /contact-sent on success.

Error Messages

The island maps the same error codes used by accent-contact to user-facing messages:

Error CodeMessage
csrfSecurity token expired. Please reload the page and try again.
namePlease enter your name.
emailPlease enter a valid email address.
messagePlease enter a message.
smtpUnable to send your message. Please try again later or email us directly.
Network errorNetwork error. Please check your connection and try again.

Comparing the Two Approaches

Aspectaccent-contact only+ contact-form island
Form submissionFull page POST + redirectfetch() with inline response
ValidationServer-side onlyNative HTML5 first, then server
Error displayPage reload with ?error= query paramInline message, no reload
Success displayRedirect to /contact-sent pageInline message, form hidden
JavaScript requiredNoNo (progressive enhancement)
Plugin changes neededNoneNone
  • /contact – plain HTML form (accent-contact only)
  • /contact-island – island-enhanced form (with contact-form.js)

Edition Requirements

ComponentEdition
accent-contact pluginStandard+
contact-form.js islandAll editions (theme asset, no plugin system needed)
Islands architectureAll editions