@tempered/hydrogen-forms

Contact form components for Shopify Hydrogen storefronts


Keywords
shopify, hydrogen, forms, contact-form, headless, react
License
MIT
Install
npm install @tempered/hydrogen-forms@0.0.0

Documentation

@tempered/hydrogen-forms

Contact form components for Shopify Hydrogen storefronts.

Installation

npm install @tempered/hydrogen-forms
# or
pnpm add @tempered/hydrogen-forms

Quick Start

1. Add the Provider

Wrap your Hydrogen app with the FormProvider:

// app/root.tsx
import { FormProvider } from '@tempered/hydrogen-forms';

export default function App() {
  return (
    <FormProvider
      config={{
        apiUrl: 'https://formbridge.temperedtools.xyz',
        shopDomain: 'your-store.myshopify.com',
      }}
    >
      <Outlet />
    </FormProvider>
  );
}

2. Add a Contact Form

// app/routes/contact.tsx
import { ContactForm } from '@tempered/hydrogen-forms';

export default function ContactPage() {
  return (
    <div>
      <h1>Contact Us</h1>
      <ContactForm
        formId="form_abc123"
        showSubject
        onSuccess={(response) => console.log('Submitted:', response.submissionId)}
      />
    </div>
  );
}

3. Add Newsletter Signup

// app/components/Footer.tsx
import { NewsletterSignup } from '@tempered/hydrogen-forms';

export function Footer() {
  return (
    <footer>
      <NewsletterSignup
        formId="form_newsletter"
        inline
        placeholder="your@email.com"
        buttonText="Subscribe"
      />
    </footer>
  );
}

Components

FormProvider

Context provider that must wrap your app.

<FormProvider
  config={{
    apiUrl: 'https://formbridge.temperedtools.xyz',
    shopDomain: 'your-store.myshopify.com',
    apiKey: 'optional_api_key', // For authenticated requests
  }}
>
  {children}
</FormProvider>

ContactForm

Pre-built contact form with name, email, subject, and message fields.

<ContactForm
  formId="form_id"
  showSubject={true}
  onSuccess={(response) => {}}
  onError={(error) => {}}
  classNames={{
    form: 'my-form',
    field: 'form-field',
    input: 'form-input',
    button: 'submit-btn',
  }}
  labels={{
    name: 'Your Name',
    email: 'Email Address',
    submit: 'Send Message',
  }}
/>

NewsletterSignup

Minimal newsletter signup form.

<NewsletterSignup
  formId="form_id"
  inline={true}
  placeholder="Enter your email"
  buttonText="Subscribe"
  successMessage="Thanks for subscribing!"
/>

CustomForm

Dynamic form that loads fields from the FormBridge backend.

<CustomForm
  formId="form_id"
  onSuccess={(response) => {}}
  submitText="Submit"
  loadingText="Submitting..."
  renderLoading={() => <div>Loading form...</div>}
/>

FormField

Individual form field component.

<FormField
  name="email"
  label="Email Address"
  type="email"
  value={value}
  onChange={handleChange}
  onBlur={handleBlur}
  required
  error={error}
  touched={touched}
/>

Hooks

useForm

Core form state management.

const form = useForm({
  initialValues: { name: '', email: '' },
  fields: [
    { id: '1', name: 'name', label: 'Name', type: 'text', required: true, sortOrder: 0 },
    { id: '2', name: 'email', label: 'Email', type: 'email', required: true, sortOrder: 1 },
  ],
});

// Use form state
form.getValue('email');
form.setValue('email', 'test@example.com');
form.getError('email');
form.validate();
form.getFieldProps('email'); // { name, value, onChange, onBlur }

useSubmit

Form submission handler.

const { submit, isSubmitting, error, response } = useSubmit({
  formId: 'form_id',
  onSuccess: (response) => console.log('Success:', response),
  onError: (error) => console.error('Error:', error),
});

await submit({ name: 'John', email: 'john@example.com' });

useValidation

Form validation utilities.

const { validate, validateSingle, isRequired } = useValidation({
  fields: formConfig.fields,
});

const result = validate(formValues);
// { isValid: boolean, errors: { fieldName: 'error message' } }

useFetchFormConfig

Fetch form configuration from the API.

const { config, isLoading, error, refetch } = useFetchFormConfig({
  formId: 'form_id',
});

Server-Side Utilities

For SSR in Hydrogen:

// app/routes/contact.tsx
import { fetchFormConfig, submitForm, createFormBridgeConfig } from '@tempered/hydrogen-forms/server';

export async function loader({ context }) {
  const config = await fetchFormConfig({
    formId: 'form_id',
    apiUrl: 'https://formbridge.temperedtools.xyz',
  });

  return { formConfig: config };
}

export async function action({ request, context }) {
  const formData = await request.formData();

  const result = await submitForm({
    formId: 'form_id',
    data: Object.fromEntries(formData),
    config: createFormBridgeConfig({
      shopDomain: context.env.PUBLIC_STORE_DOMAIN,
    }),
  });

  return result;
}

Styling

All components accept className and classNames props for styling:

<ContactForm
  formId="form_id"
  classNames={{
    form: 'contact-form',
    field: 'form-group',
    input: 'form-control',
    label: 'form-label',
    error: 'invalid-feedback',
    button: 'btn btn-primary',
    successMessage: 'alert alert-success',
    errorMessage: 'alert alert-danger',
  }}
/>

Spam Protection

FormBridge includes built-in spam protection:

  • Honeypot fields - Hidden fields that bots fill out
  • Timing analysis - Detects submissions that are too fast
  • Turnstile verification - Optional Cloudflare Turnstile support

The npm package handles honeypot and timing automatically. No configuration required.

TypeScript

Full TypeScript support with exported types:

import type {
  FormBridgeConfig,
  FormConfig,
  FormField,
  FieldType,
  SubmissionResponse,
  ContactFormProps,
  UseFormReturn,
} from '@tempered/hydrogen-forms';

Accessibility

All components are WCAG 2.2 AA compliant:

  • Proper label associations
  • Error messages with aria-live
  • Keyboard navigation support
  • Focus management on submission
  • Required field indicators

Bundle Size

< 30kb gzipped. Tree-shakeable exports.

License

MIT