Developers
Start building
Themes/Store forms

Store forms

Render published store forms from the theme SDK, including custom layouts, steps, files, and the contact wrapper.

On this page

Store forms are published definitions that belong to the store. A theme renders them through @poltura/theme-sdk/forms. The definition decides the field types, steps, conditions and success message. The theme decides only how the form looks.

Render a form

import { StoreForm } from '@poltura/theme-sdk/forms';

export function Contact() {
  return <StoreForm handle='contact' />;
}

<StoreForm handle='contact' /> loads the published form and renders its visible fields, steps, captcha, send button and success message. Its props shape the presentation without changing the definition:

PropWhat it does
layoutstacked, or compact for two fields per row on wide screens
appearancedefault, muted, outline, inverse, in the theme's colours and fonts
chromefull draws the title, description and card; fields draws only the fields
copyThe theme's own wording for the title, button, success message and each field's label and placeholder
fieldVisibilityFields to hide or show by field id, over the merchant's settings
emailHintsDeliverability hints under email fields; a domain that takes no mail blocks send
onAcceptedCalled with the submission id once the store accepts it, and preview (true in the Customizer)

Pass children for a custom layout. FormField draws one field with its label, control, help and error (nothing while a condition hides it), and SubmitButton is the send button, which moves to the next step on multi-step forms:

import { FormField, StoreForm, SubmitButton } from '@poltura/theme-sdk/forms';

export function Quote() {
  return (
    <StoreForm handle='quote' layout='compact' appearance='muted'>
      <FormField id='name' />
      <FormField id='email' />
      <FormField id='attachments' />
      <SubmitButton>Request a quote</SubmitButton>
    </StoreForm>
  );
}

FormField also takes a render function for custom markup. It keeps validation, errors and the aria-invalid and aria-describedby wiring. FieldControl is the platform control to place inside it:

import { FieldControl, FormField } from '@poltura/theme-sdk/forms';

export function FramedMessage() {
  return (
    <FormField id='message'>
      {(state) => (
        <div className='rounded border p-3'>
          <FieldControl
            field={state.field}
            value={state.value}
            error={state.error}
            onChange={state.setValue}
          />
        </div>
      )}
    </FormField>
  );
}

For markup that is entirely your own, two hooks read the state inside a StoreForm. useStoreForm() returns the whole form (status, values, errors, current step, submit, next, back), and useStoreFormField('email') returns one field with its value, error, visibility and setter.

Newsletter

NewsletterForm and NewsletterTrigger from @poltura/theme-sdk/forms render the newsletter sign-up. useNewsletter() reads whether the store takes sign-ups, whether they must confirm by email, and the wording to ask with. In a footer, the newsletter block (or <NewsletterForm layout='footer' /> in a section of your own) draws the footer column. Every newsletter form uses the store's published newsletter form and follows the store-wide newsletter setting, so the consent wording and validation are the same everywhere.

Contact wrapper

ContactForm from @poltura/theme-sdk/forms renders <StoreForm handle='contact' />, the store's published contact form, with wording props (formTitle, nameLabel, showName, showSubject, submitLabel and more) for the theme's own copy. The platform contact-form block passes its settings through to it.

The store must have a published form with the handle contact. The storefront never creates one.

Customizer

Merchants add a Form section (store-form) on any template and choose a published form by its name. The form owns its fields, translations and delivery. The section owns its presentation. One form can be used in several places.

SettingWhat it does
handleThe published form, chosen by name; its stable id in code
layoutstacked or compact
appearancedefault, muted, outline, inverse, in the theme's colours
widthcompact, standard, wide
placementinline, popup, or right-side drawer
triggerclick by default; automatic opens it after a delay
button_labelText on the button that opens the popup or panel
popup_delay_secondsDelay before it opens automatically; at least five seconds
popup_frequencyonce, session, or every 30 days

Automatic opening is off in the Customizer and on checkout pages. Use the preview button to test the popup there. Buttons that open the form still work. Popups and panels scroll inside the viewport, keep keyboard focus inside, close with Escape or the close button, and return focus to the button that opened them. Closing and reopening the same form keeps what the shopper typed. Navigating away or opening a different form clears it. Nothing typed is saved in browser storage.

Attach a form to a button

In the Customizer, click a link or button on the canvas to select the section or block it belongs to, or select it in the section tree. Its link setting offers Open link and Open form. Choose a published form, a popup or right side panel, and a width. Preview form tries it without publishing the theme, and publishing the theme puts it live. Hold Alt (Option on a Mac) while you click the canvas to follow the link instead of selecting it. Form controls on the canvas stay usable.

Every url and link setting and every link list entry offers the same choice. A button written in code is only editable in the Customizer if it reads a link setting. Do not attach form actions to checkout or add-to-cart buttons.

import { FormTrigger, useFormActions } from '@poltura/theme-sdk/forms';
import { Link } from '@poltura/theme-sdk/routing';
import type { LinkValue } from '@poltura/theme-sdk/elements';

export function AskButton() {
  return (
    <FormTrigger handle='contact' display='drawer' width='standard'>
      Ask a question
    </FormTrigger>
  );
}

export function SettingLink({ link }: { link: LinkValue }) {
  return <Link href={link} className='underline' />;
}

export function QuoteButton() {
  const { openForm } = useFormActions();
  return (
    <button
      type='button'
      onClick={(event) => openForm({ handle: 'quote', display: 'drawer' }, event.currentTarget)}
    >
      Request a quote
    </button>
  );
}

Link from @poltura/theme-sdk/routing renders a link setting's value as it is. When the merchant chose Open form, the stored href is a validated #poltura-form?… action, and following it opens the form over the page without navigating. Ordinary links navigate as usual. The platform supplies the form provider, so a theme does not mount one.

Initial rendering

The server loads the contact and newsletter forms ahead of time, and every form that a visible store-form section or a link setting anywhere in the theme names (up to 32 different forms). The first markup then contains the form instead of "Loading form". FormTrigger also starts loading its form when it gets focus or the pointer moves over it. A handle that only code names loads on demand. Call useFormActions().preloadForm(handle) to start loading it early, or expose it through a link setting so the server loads it. Only published forms load this way, never a draft.

Files

FormField and FieldControl handle file fields for you. A theme that draws its own file control calls useStoreForm().uploadFiles(fieldId, files) and reads the picked files from useStoreForm().files. Uploads go through the storefront, and the submitted value holds upload ids, never file URLs.

In the Customizer preview, picked files stay in the browser and are not uploaded. A preview submission is sent as a preview, carries no files and is not stored.

Where definitions come from

Theme code never defines a form's fields. Merchants create and edit forms on the admin's Forms page, and the storefront reads only what they publish.

Captcha

Each published form has its own captcha setting, on unless the merchant turned it off under Forms > Appearance in the admin and published the form. The server enforces the published setting whatever the browser sends. StoreForm draws the captcha itself.

Next

Last updated