Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f770476
Add @formspree/ajax package
lpssformspree Jan 31, 2026
9f16451
Implement @formspree/ajax package
lpssformspree Jan 31, 2026
356b0d0
Add ajax-demo example and update @formspree/ajax API
lpssformspree Jan 31, 2026
501bd59
Refactor form initialization to use formId instead of formEndpoint
lpssformspree Jan 31, 2026
394c496
Fix esbuild security vulnerability (GHSA-67mh-4wv8-2f99)
lpssformspree Feb 2, 2026
aca9c24
Add changeset for @formspree/ajax package
lpssformspree Feb 3, 2026
6f9d4d6
Add renderErrors, fields config, and customizable functions
lpssformspree Feb 4, 2026
faa6db8
Add data attributes for declarative form behavior
lpssformspree Feb 5, 2026
9a429b4
Refactor error styling by injecting default styles dynamically
lpssformspree Feb 5, 2026
f4f32c1
Add data-fs-message for declarative form-level message display
lpssformspree Feb 7, 2026
c598308
Enhance AJAX demo with global script tag support
lpssformspree Feb 8, 2026
ed52837
Simplify global API, add data-fs-message, and update Formspree branding
lpssformspree Feb 10, 2026
ae79c2d
Refactor form error handling and remove unused configurations
lpssformspree Feb 11, 2026
3518cdb
Update AJAX demo and form handling with improved field attributes and…
lpssformspree Feb 11, 2026
d01623c
Enhance README and AJAX library with new features and improvements
lpssformspree Feb 11, 2026
ab35664
Refactor error rendering functions in AJAX library for clarity and co…
lpssformspree Feb 11, 2026
151bd91
Update AJAX library to enhance error and success message handling
lpssformspree Feb 11, 2026
53df0e8
Enhance form configuration with optional default styles
lpssformspree Feb 11, 2026
d52a635
remove spinner styles
lpssformspree Feb 11, 2026
3061d5c
Enhance form configuration to support project ID
lpssformspree Feb 11, 2026
43a7d7e
Refactor form handling and utility functions for improved clarity and…
lpssformspree Feb 11, 2026
ed4d954
Remove ARCHITECTURE.md
lpssformspree Feb 12, 2026
b7478e8
Update package dependencies and downgrade `tsup` version to 6.2.2 for…
lpssformspree Feb 12, 2026
604304f
revert tubo
lpssformspree Feb 12, 2026
291e86f
Update turbo.json to rename 'pipeline' to 'tasks' for improved clarit…
lpssformspree Feb 12, 2026
7099f25
Address PR review feedback from colevscode
lpssformspree Feb 18, 2026
edc9e6a
Fix race condition in form submission and error handling in queue flush
lpssformspree Feb 18, 2026
a835837
update license
lpssformspree Feb 19, 2026
694ef99
Shorten unpkg CDN URLs to use package-level resolution
lpssformspree Feb 20, 2026
0baa22d
Add main demo page and update vite config entry points
lpssformspree Feb 24, 2026
9c95b08
Release @formspree/ajax@1.1.0
lpssformspree Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-pans-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@formspree/ajax': major
---

add formspree-ajax
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ yarn-error.log*
# turbo
.turbo

.vscode
.vscode

.local-dev

.claude

# playwright
.playwright-mcp
60 changes: 60 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build and Development Commands

```sh
yarn # Install dependencies
yarn build # Build all packages (required before dev)
yarn dev # Run dev server with cra-demo example on localhost:3000
yarn test # Run tests across all packages
yarn typecheck # Type check all packages
yarn lint # Lint all packages
yarn format # Format code with Prettier
yarn changeset # Create a changeset for PRs
```

### Package-specific commands

Run from package directory (e.g., `packages/formspree-core`):

```sh
yarn test # Run tests for this package
yarn build # Build this package only
yarn lint # Lint this package
```

For the ajax package specifically:

```sh
yarn demo # Build and run ajax-demo example (from packages/formspree-ajax)
```

## Architecture

This is a Yarn workspaces monorepo managed by Turborepo with three packages:

- **@formspree/core** - Core submission client and types. Provides `createClient`, `getDefaultClient`, `SubmissionError`, and submission types.
- **@formspree/react** - React hooks (`useForm`, `useSubmit`) built on top of core. Depends on core.
- **@formspree/ajax** - Vanilla JS library for declarative form handling. Recreation of [statickit-html](https://github.com/formspree/statickit-html) using core.

All packages use `tsup` for building and output to `dist/`.

## @formspree/ajax Package Guidelines

Reference API from statickit-html:

- `onSubmit(config)` - called before submission
- `onSuccess(config, response)` - called on successful submission
- `onError(config, errors)` - called on validation errors
- `onFailure(config, exception)` - called on unexpected errors
- `onInit(config)` - called when form is initialized

The `config` object contains: form element, form identifiers, data, debug flag, fields config, and client.

## Documentation

- Add JSDoc comments to all TypeScript types, interfaces, and their properties
- Include `@template` tags for generic types
- Include `@param` tags for callback parameters
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Formspree JS

A monorepo containing libraries for seamless form integration with [Formspree](https://formspree.io/) via Javascript and/or React.
A monorepo containing libraries for seamless form integration with [Formspree](https://formspree.io/) via JavaScript and/or React.

## Installation

The core and react packages can be installed using your package manager of choice.
## Packages

### `@formspree/core`

Core submission client and types. Provides `createClient`, `getDefaultClient`, `SubmissionError`, and submission types.

```sh
npm install @formspree/core

Expand All @@ -18,9 +18,9 @@ pnpm add @formspree/core

### `@formspree/react`

**Prerequisites**
React hooks (`useForm`, `useSubmit`) and components (`ValidationError`) built on top of core.

- React 16.8 or higher.
**Prerequisites:** React 16.8 or higher.

```sh
npm install @formspree/react
Expand All @@ -30,7 +30,35 @@ yarn add @formspree/react
pnpm add @formspree/react
```

_Note: `@formspree/core` is a dependency of `@formspree/react`, so you don't need to install `@formspree/core` separately._
_Note: `@formspree/core` is a dependency of `@formspree/react`, so you don't need to install it separately._

### `@formspree/ajax`

Vanilla JavaScript library for declarative form handling with plain HTML forms. No framework required — use data attributes and a single `initForm()` call, or load via a script tag with the `window.formspree()` global API.

```sh
npm install @formspree/ajax

yarn add @formspree/ajax
```

_Note: `@formspree/core` is a dependency of `@formspree/ajax`, so you don't need to install it separately._

Or via CDN (no bundler needed):

```html
<script>
window.formspree =
window.formspree ||
function () {
(formspree.q = formspree.q || []).push(arguments);
};
formspree('initForm', { formElement: '#my-form', formId: 'YOUR_FORM_ID' });
</script>
<script src="https://unpkg.com/@formspree/ajax@1/dist/global.js" defer></script>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any way to make this cleaner? This is a pretty ugly script URL. Ideally it would be something like https://unpkg.com/@formspree/ajax@1.0.0

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might be able to.. I'll check on that

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colevscode Turns out the "unpkg" field in package.json already points to dist/global.js, so unpkg resolves it automatically. I’ve updated all references to use the shorter https://unpkg.com/@formspree/ajax@1 — so no explicit path needed.

```

See the [@formspree/ajax README](./packages/formspree-ajax/README.md) for full documentation.

## Help and Support

Expand Down
3 changes: 3 additions & 0 deletions examples/ajax-demo/.env.local.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Formspree Configuration
# Copy this file to .env.local and add your form ID
VITE_FORMSPREE_FORM_ID=YOUR_FORM_ID
5 changes: 5 additions & 0 deletions examples/ajax-demo/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root: true
extends:
- '../../.eslintrc.yml'
parserOptions:
project: './tsconfig.json'
125 changes: 125 additions & 0 deletions examples/ajax-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Formspree AJAX Demo

A pure JavaScript/TypeScript example demonstrating how to use `@formspree/ajax` for form submissions without any frontend framework.

## Features

- Pure HTML/CSS/TypeScript implementation
- No React, Vue, or other framework dependencies
- Beautiful, responsive contact form UI
- Loading states and error handling
- Form validation feedback
- Success/error messages
- Built with Vite for fast development

## Setup

1. Install dependencies:

```bash
yarn install
```

2. Get your Formspree form ID:

- Go to [formspree.io](https://formspree.io)
- Create a new form or use an existing one
- Copy your form ID (e.g., `xyzabc123`)

3. Create a `.env.local` file from the template:

```bash
cp .env.local.template .env.local
```

4. Update `VITE_FORMSPREE_FORM_ID` in `.env.local` with your form ID.

## Development

Run the development server:

```bash
yarn dev
```

The demo will be available at `http://localhost:5173`

## Build

Build for production:

```bash
yarn build
```

The output will be in the `dist` directory.

## Usage

### Basic Form Initialization

```typescript
import { initForm } from '@formspree/ajax';

initForm({
formElement: '#contact-form',
formId: 'your-form-id',
onSuccess: ({ form }) => {
console.log('Success!');
form.reset();
},
onError: (_context, error) => {
const messages = error.getFormErrors().map((e) => e.message);
console.error('Validation errors:', messages);
},
onFailure: (_context, error) => {
console.error('Unexpected error:', error);
},
});
```

### With Extra Data

```typescript
initForm({
formElement: '#contact-form',
formId: 'your-form-id',
data: {
source: 'website',
timestamp: new Date().toISOString(),
},
onSuccess: ({ form }) => {
form.reset();
},
});
```

### Custom Origin (e.g., staging)

```typescript
initForm({
formElement: '#contact-form',
formId: 'your-form-id',
origin: 'https://staging.formspree.io',
debug: true,
});
```

## File Structure

```
ajax-demo/
├── public/
│ ├── index.html # HTML form with styling
│ └── main.ts # TypeScript implementation
├── .env.local.template # Environment variables template
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
```

## Learn More

- [Formspree Documentation](https://help.formspree.io/)
- [@formspree/ajax Package](../../packages/formspree-ajax)
19 changes: 19 additions & 0 deletions examples/ajax-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@formspree/ajax-demo",
"version": "1.0.0",
"private": true,
"description": "Pure JavaScript AJAX example using Formspree",
"scripts": {
"build": "tsc && vite build",
"clean": "rm -rf dist && rm -rf node_modules",
"dev": "vite",
"preview": "vite preview"
},
"dependencies": {
"@formspree/ajax": "*"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^6.0.6"
}
}
3 changes: 3 additions & 0 deletions examples/ajax-demo/public/global-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file simulates loading dist/global.js in the Vite dev environment.
// In production, you'd use: <script src="https://unpkg.com/@formspree/ajax@1/dist/global.js" defer></script>
import '@formspree/ajax/global';
91 changes: 91 additions & 0 deletions examples/ajax-demo/public/index-cdn.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Formspree AJAX - CDN Demo</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<a href="/index-packages.html" class="back-link">&larr; Package Demo</a>

<h1>Contact Form <span class="badge">CDN</span></h1>
<p class="subtitle">Using <code>window.formspree()</code> global API &mdash; no bundler required</p>

<div data-fs-success>Thanks for reaching out! We'll get back to you soon.</div>
<div data-fs-error>Something went wrong. Please try again.</div>

<form id="contact-form">
<div class="form-group">
<label for="name">Name</label>
<input
type="text"
id="name"
name="name"
placeholder="Your name"
data-fs-field
>
<span data-fs-error="name"></span>
</div>

<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="your.email@example.com"
data-fs-field
>
<span data-fs-error="email"></span>
</div>

<div class="form-group">
<label for="message">Message</label>
<textarea
id="message-field"
name="message"
placeholder="Write your message here..."
data-fs-field
></textarea>
<span data-fs-error="message"></span>
</div>

<button type="submit" data-fs-submit-btn>
Send Message
</button>
</form>

<div class="note">
<strong>How it works:</strong> Load <code>global.js</code> via a script tag.
It sets up <code>window.formspree()</code>, then call it to initialize your form.
No bundler needed!
</div>
</div>

<!--
Production usage:

<script>
window.formspree=window.formspree||function(){(formspree.q=formspree.q||[]).push(arguments)};
formspree('initForm', { formElement: '#contact-form', formId: 'YOUR_FORM_ID' });
</script>
<script src="https://unpkg.com/@formspree/ajax@1/dist/global.js" defer></script>

In this Vite demo, global.js is loaded via a module entry that aliases to the source.
-->

<!-- Step 1: Load the library -->
<script type="module" src="/global-entry.ts"></script>

<!-- Step 2: Initialize the form -->
<script type="module">
formspree('initForm', {
formElement: '#contact-form',
formId: 'YOUR_FORM_ID',
debug: true,
});
</script>
</body>
</html>
Loading
Loading