Alpha: Ships on npm at 7.0.0-alpha.07.0.0-alpha.0

Contact flow and 404

A form, its sent page and a 404 for any Site, built like Ledgerhouse's intake, and what a static host does with a form post.

Every Site needs three pages no visitor asks for by name: the form that takes an inquiry, the page that thanks them, and the page an unknown address lands on. Ledgerhouse, a two-partner bookkeeping firm, takes new clients through an eight-field intake this way.

Prerequisites

Ask for what you need

cta-form takes one to eight fields. Each has a lowercase name, a type of text, email, select or textarea, a label of 16 characters and a placeholder of 32; a select lists up to 12 options. The button label runs to 24, and a note under it to 80.

pages/ledgerhouse/intake.json
{
  "$schema": "https://next.mdxui.dev/schema.json",
  "theme": "vaultline",
  "frame": "none",
  "template": "contact",
  "title": "Intake · Ledgerhouse",
  "description": "Eight questions. A partner replies within two working days with a fixed fee and a start date.",
  "sections": [
    … 1 more section
    {sections.1 · cta-form
          "kind": "section",
          "shell": { "inset": "contained", "density": "section" },
          "block": {
            "type": "cta-form",
            "head": {
              "eyebrow": "Intake",
              "title": "Eight answers, one fixed fee.",
              "lede": "A partner reads every intake. The reply names a fee and a start date within two working days."
            },
            "action": "/intake/sent",
            "fields": [
              { "name": "name", "type": "text", "label": "Name" },
              {
                "name": "email",
                "type": "email",
                "label": "Email",
                "placeholder": "you@business.example"
              },
              { "name": "business", "type": "text", "label": "Business name" },
              {
                "name": "entity",
                "type": "select",
                "label": "Entity",
                "options": ["Sole owner", "Partnership", "Corporation", "Not sure"]
              },
              {
                "name": "software",
                "type": "select",
                "label": "Software",
                "options": [
                  "A cloud package",
                  "A desktop package",
                  "Spreadsheets",
                  "None yet"
                ]
              },
              {
                "name": "volume",
                "type": "select",
                "label": "Transactions",
                "options": [
                  "Under 150 a month",
                  "150 to 400 a month",
                  "400 to 1,000 a month",
                  "Over 1,000 a month"
                ]
              },
              {
                "name": "behind",
                "type": "select",
                "label": "Books behind",
                "options": [
                  "Up to date",
                  "1 to 3 months",
                  "4 to 12 months",
                  "More than a year"
                ]
              },
              {
                "name": "notes",
                "type": "textarea",
                "label": "Anything else",
                "placeholder": "Payroll, owners, deadlines"
              }
            ],
            "submit": "Send intake",
            "note": "A partner replies within two working days."
          }
        },
    … 2 more sections
  ]
}

Send the post somewhere that answers

The form renders method="post" to its action, with no script. A static host answers only GET and HEAD, so a post to a file is never received. Route the action to a function: on Cloudflare, worker.ts from Ship a Site forwards each field and answers with a redirect to the sent page. The worked Sites on these docs are static files, so their forms do not send.

pages/ledgerhouse/intake.json
{
  "$schema": "https://next.mdxui.dev/schema.json",
  "theme": "vaultline",
  "frame": "none",
  "template": "contact",
  "title": "Intake · Ledgerhouse",
  "description": "Eight questions. A partner replies within two working days with a fixed fee and a start date.",
  "sections": [
    … 1 more section
    {sections.1 · cta-form
          "kind": "section",
          "shell": { "inset": "contained", "density": "section" },
          "block": {
            "type": "cta-form",
            "head": {
              "eyebrow": "Intake",
              "title": "Eight answers, one fixed fee.",
              "lede": "A partner reads every intake. The reply names a fee and a start date within two working days."
            },
            "action": "/intake/sent",
            "fields": [
              { "name": "name", "type": "text", "label": "Name" },
              {
                "name": "email",
                "type": "email",
                "label": "Email",
                "placeholder": "you@business.example"
              },
              { "name": "business", "type": "text", "label": "Business name" },
              {
                "name": "entity",
                "type": "select",
                "label": "Entity",
                "options": ["Sole owner", "Partnership", "Corporation", "Not sure"]
              },
              {
                "name": "software",
                "type": "select",
                "label": "Software",
                "options": [
                  "A cloud package",
                  "A desktop package",
                  "Spreadsheets",
                  "None yet"
                ]
              },
              {
                "name": "volume",
                "type": "select",
                "label": "Transactions",
                "options": [
                  "Under 150 a month",
                  "150 to 400 a month",
                  "400 to 1,000 a month",
                  "Over 1,000 a month"
                ]
              },
              {
                "name": "behind",
                "type": "select",
                "label": "Books behind",
                "options": [
                  "Up to date",
                  "1 to 3 months",
                  "4 to 12 months",
                  "More than a year"
                ]
              },
              {
                "name": "notes",
                "type": "textarea",
                "label": "Anything else",
                "placeholder": "Payroll, owners, deadlines"
              }
            ],
            "submit": "Send intake",
            "note": "A partner replies within two working days."
          }
        },
    … 2 more sections
  ]
}

Thank them

The sent page is an article page: the nav, a page-head with a title of 64 and a lede of 200 that says what happens next, and the footer. Give it meta.noindex, so no search engine lists it.

pages/ledgerhouse/intake/sent.json
{
  "$schema": "https://next.mdxui.dev/schema.json",
  "theme": "vaultline",
  "frame": "none",
  "template": "article",
  "title": "Intake received · Ledgerhouse",
  "description": "Your intake reached Ledgerhouse.",
  "meta": { "noindex": true },
  "sections": [
    {sections.0 · nav
          "kind": "nav",
          "shell": {
            "inset": "bleed",
            "density": "none",
            "surface": "glass",
            "sticky": true
          },
          "block": {
            "type": "nav",
            "menu": "none",
            "brand": { "href": "/", "text": "Ledgerhouse", "mark": "landmark" },
            "links": [
              ["/services", "Services"],
              ["/pricing", "Fees"],
              ["/about", "About"]
            ],
            "cta": { "href": "/intake", "label": "Start intake" }
          }
        },
    {sections.1 · page-head
          "kind": "section",
          "shell": { "inset": "contained", "density": "section" },
          "block": {
            "type": "page-head",
            "title": "Intake received.",
            "lede": "A partner replies within two working days with a fixed fee and a start date."
          }
        },
    {sections.2 · closing
          "kind": "closing",
          "shell": { "inset": "contained", "density": "closing" },
          "block": {
            "type": "closing",
            "layout": "stack",
            "head": { "eyebrow": "While you wait", "title": "See what a close holds." },
            "actions": [
              {
                "href": "/services/monthly-close",
                "label": "Monthly close",
                "kind": "solid"
              }
            ]
          }
        },
    {sections.3 · footer
          "kind": "footer",
          "shell": { "inset": "contained", "density": "footer" },
          "block": {
            "type": "footer",
            "layout": "columns",
            "brand": { "href": "/", "text": "Ledgerhouse", "mark": "landmark" },
            "blurb": "Bookkeeping and tax for small businesses, by two partners and no handoffs.",
            "columns": [
              {
                "title": "The firm",
                "links": [
                  ["/services", "Services"],
                  ["/services/monthly-close", "Monthly close"],
                  ["/pricing", "Fees"],
                  ["/about", "About"]
                ]
              },
              {
                "title": "Clients",
                "links": [["/intake", "Intake"], ["/terms", "Terms of engagement"]]
              },
              {
                "title": "For agents",
                "links": [
                  ["/records.zip", "Site records (.zip)"],
                  ["/services/monthly-close", "What a close report holds"]
                ]
              }
            ],
            "note": [
              "Ledgerhouse is a worked example on mdxui.dev, not a customer.",
              "Rendered by @mdxui/site. Forms here do not send."
            ]
          }
        }
  ]
}

Catch every lost address

The 404 record is a page-head and a closing with a way back, between the nav and the footer. A Site without one does not build: ship.ts stops on a missing 404.json, and the host serves the page with status 404.

pages/ledgerhouse/404.json
{
  "$schema": "https://next.mdxui.dev/schema.json",
  "theme": "vaultline",
  "frame": "none",
  "template": "article",
  "title": "Not found · Ledgerhouse",
  "description": "No page at this address.",
  "meta": { "noindex": true },
  "sections": [
    {sections.0 · nav
          "kind": "nav",
          "shell": {
            "inset": "bleed",
            "density": "none",
            "surface": "glass",
            "sticky": true
          },
          "block": {
            "type": "nav",
            "menu": "none",
            "brand": { "href": "/", "text": "Ledgerhouse", "mark": "landmark" },
            "links": [
              ["/services", "Services"],
              ["/pricing", "Fees"],
              ["/about", "About"]
            ],
            "cta": { "href": "/intake", "label": "Start intake" }
          }
        },
    {sections.1 · page-head
          "kind": "section",
          "shell": { "inset": "contained", "density": "section" },
          "block": {
            "type": "page-head",
            "title": "Not in the books.",
            "lede": "No page at this address."
          }
        },
    {sections.2 · closing
          "kind": "closing",
          "shell": { "inset": "contained", "density": "closing" },
          "block": {
            "type": "closing",
            "layout": "stack",
            "head": { "eyebrow": "Not found", "title": "Back to the firm." },
            "actions": [
              { "href": "/", "label": "Ledgerhouse", "kind": "solid" },
              { "href": "/services", "label": "Services", "kind": "ghost" }
            ]
          }
        },
    {sections.3 · footer
          "kind": "footer",
          "shell": { "inset": "contained", "density": "footer" },
          "block": {
            "type": "footer",
            "layout": "columns",
            "brand": { "href": "/", "text": "Ledgerhouse", "mark": "landmark" },
            "blurb": "Bookkeeping and tax for small businesses, by two partners and no handoffs.",
            "columns": [
              {
                "title": "The firm",
                "links": [
                  ["/services", "Services"],
                  ["/services/monthly-close", "Monthly close"],
                  ["/pricing", "Fees"],
                  ["/about", "About"]
                ]
              },
              {
                "title": "Clients",
                "links": [["/intake", "Intake"], ["/terms", "Terms of engagement"]]
              },
              {
                "title": "For agents",
                "links": [
                  ["/records.zip", "Site records (.zip)"],
                  ["/services/monthly-close", "What a close report holds"]
                ]
              }
            ],
            "note": [
              "Ledgerhouse is a worked example on mdxui.dev, not a customer.",
              "Rendered by @mdxui/site. Forms here do not send."
            ]
          }
        }
  ]
}

Result

The Result is the form under each Theme. Below, the 404 page of every worked Site, each in its own Brand.

Result
Ledgerhouse › intake · vaultlineOpen the render
{
  "$schema": "https://next.mdxui.dev/schema.json",
  "theme": "vaultline",
  "frame": "none",
  "template": "contact",
  "title": "Intake · Ledgerhouse",
  "description": "Eight questions. A partner replies within two working days with a fixed fee and a start date.",
  "sections": [
    … 1 more section
    {sections.1 · cta-form
          "kind": "section",
          "shell": { "inset": "contained", "density": "section" },
          "block": {
            "type": "cta-form",
            "head": {
              "eyebrow": "Intake",
              "title": "Eight answers, one fixed fee.",
              "lede": "A partner reads every intake. The reply names a fee and a start date within two working days."
            },
            "action": "/intake/sent",
            "fields": [
              { "name": "name", "type": "text", "label": "Name" },
              {
                "name": "email",
                "type": "email",
                "label": "Email",
                "placeholder": "you@business.example"
              },
              { "name": "business", "type": "text", "label": "Business name" },
              {
                "name": "entity",
                "type": "select",
                "label": "Entity",
                "options": ["Sole owner", "Partnership", "Corporation", "Not sure"]
              },
              {
                "name": "software",
                "type": "select",
                "label": "Software",
                "options": [
                  "A cloud package",
                  "A desktop package",
                  "Spreadsheets",
                  "None yet"
                ]
              },
              {
                "name": "volume",
                "type": "select",
                "label": "Transactions",
                "options": [
                  "Under 150 a month",
                  "150 to 400 a month",
                  "400 to 1,000 a month",
                  "Over 1,000 a month"
                ]
              },
              {
                "name": "behind",
                "type": "select",
                "label": "Books behind",
                "options": [
                  "Up to date",
                  "1 to 3 months",
                  "4 to 12 months",
                  "More than a year"
                ]
              },
              {
                "name": "notes",
                "type": "textarea",
                "label": "Anything else",
                "placeholder": "Payroll, owners, deadlines"
              }
            ],
            "submit": "Send intake",
            "note": "A partner replies within two working days."
          }
        },
    … 2 more sections
  ]
}
Vanroster · callboard
Ledgerhouse · vaultline
Diffkeel · certificate
Benchlist · directory
Sidework · vaultline
Harbor tides · quaypoint

Next steps

On this page