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
- Contact form plugin installed and working
- 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
- The island loader discovers the
<accent-island>element - The
contact-formcomponent hydrates immediately (loadstrategy) - Server-rendered error messages (
.contact-server-error) are hidden - The island intercepts the form’s
submitevent - Native HTML5 validation runs first (required fields, email format)
- If valid, the form is submitted via
fetch()to/contact-submit - The existing
accent-contactplugin processes the POST and returns a 302 redirect - 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
- URL contains
- 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 Code | Message |
|---|---|
csrf | Security token expired. Please reload the page and try again. |
name | Please enter your name. |
email | Please enter a valid email address. |
message | Please enter a message. |
smtp | Unable to send your message. Please try again later or email us directly. |
| Network error | Network error. Please check your connection and try again. |
Comparing the Two Approaches
| Aspect | accent-contact only | + contact-form island |
|---|---|---|
| Form submission | Full page POST + redirect | fetch() with inline response |
| Validation | Server-side only | Native HTML5 first, then server |
| Error display | Page reload with ?error= query param | Inline message, no reload |
| Success display | Redirect to /contact-sent page | Inline message, form hidden |
| JavaScript required | No | No (progressive enhancement) |
| Plugin changes needed | None | None |
/contact– plain HTML form (accent-contact only)/contact-island– island-enhanced form (with contact-form.js)
Edition Requirements
| Component | Edition |
|---|---|
accent-contact plugin | Standard+ |
contact-form.js island | All editions (theme asset, no plugin system needed) |
| Islands architecture | All editions |