Nasaq

FormBuilder

Build an embeddable public form in English and Arabic. Add and order fields, write show hide or require rules, choose which sites may embed it, and copy the snippet.

PreviewOpen ↗

Code

import { FormBuilder, contactFormDefinition } from "@fadymondy/nasaq/web";import { useState } from "react";export function EditForm() {  const [form, setForm] = useState(contactFormDefinition());  return <FormBuilder value={form} onValueChange={setForm} formKey="pk_live_123" embedBaseUrl="https://forms.example.com" onSave={(f) => fetch("/api/forms/1", { method: "PUT", body: JSON.stringify(f) })} />;}

Form Builders · beta

Live examples and controls: FormBuilder in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/form-builder.json

The admin side of public forms. It edits a plain FormDefinition (stored by you) and shows a live PublicForm preview beside it.

  • Fields: add eight kinds, edit labels in English and Arabic, mark required, write options (Riyadh | الرياض), reorder with the arrows or the row menu.
  • Logic: rules built with RuleBuilder that show, hide or require a field from other answers.
  • Settings: the sites allowed to embed the form, a tester for an address, thank-you messages in both languages, the honeypot.
  • Embed: an iframe or script snippet and the public link, each with a copy button.

The allowed sites list is closed by default: with nothing in it no site can embed the form, and the builder says so.

When to use

  • A CMS or admin that lets editors create contact, inquiry or subscribe forms for public sites.

When not to use

  • Forms your developers write in code: use Field and friends.
  • Complex multi-step flows: use a wizard.

Import

import { FormBuilder } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { FormBuilder, contactFormDefinition } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function EditForm() {
  const [form, setForm] = useState(contactFormDefinition());
  return <FormBuilder value={form} onValueChange={setForm} formKey="pk_live_123" embedBaseUrl="https://forms.example.com" onSave={(f) => fetch("/api/forms/1", { method: "PUT", body: JSON.stringify(f) })} />;
}

Anatomy

FormBuilder                data-slot="form-builder"
├─ header                  name, "Accepting responses" switch, Save
└─ Tabs
   ├─ Fields               list (data-slot="form-builder-field", context menu), settings card, live preview
   ├─ Logic                one RuleBuilder card per rule
   ├─ Settings             allowed sites (TagInput), address tester, thank-you (en, ar), honeypot
   └─ Embed                iframe or script CodeBlock, public link + CopyButton

API

PropTypeDefaultDescription
value? / defaultValue?FormDefinitionblank inquiry formThe form being edited.
onValueChange?(form) => voidnoneEvery edit.
formKey?string"pk_live_demo"Public key in the snippet and link.
embedBaseUrl?string"https://forms.example.com"Where public forms are served.
onSave?(form) => void | Promise<void>noneShows a Save button.
saving?booleanfalse
locale? / labels?provider localeEvery visible string in Arabic and English.

The definition, helpers and validators are documented in PublicForm.

Examples

Save through your API

import { FormBuilder, type FormDefinition } from "@fadymondy/nasaq/web";

export const Editor = ({ form, save }: { form: FormDefinition; save: (f: FormDefinition) => Promise<void> }) => <FormBuilder defaultValue={form} onSave={save} />;

Accessibility

  • Tabs use arrow keys. The field list is a list of buttons; the current one has aria-current. Each row also opens a menu (move, duplicate, remove) on context-click, long-press, Shift+F10 or the Menu key.
  • Every reorder and delete has a labelled button as well as the menu.
  • The preview is a complementary region named "Live preview".

RTL & i18n

  • Labels for the builder itself come in Arabic and English. Arabic label and thank-you inputs are right to left. The origins input and snippets are left to right.
  • Removing a field also removes rules that pointed at it.

Styling & tokens

Cards, tabs and inputs use the standard tokens. The Fields tab is two columns from lg, one below.

Do / Don't

  • Do validate submissions and the request origin on the server with the helpers from public-form.
  • Do leave the honeypot on.
  • Don't put a wildcard on a whole public suffix (https://*.com is refused).

Lab

https://docs.nasaqui.com/?path=/docs/components-form-builders-form-builder--docs

On this page