kibbleforms

中文文档
0.1.5发布于 2026/11/04MITTypeScriptESM + CJS

Forms for sites that coding agents build. React components, client SDK and the kibble CLI.

原始使用文档
文档目录9 节
  1. 60-second quickstart (for agents too)
  2. Conditional logic, prefill, autocomplete, scheduling
  3. Agents
  4. CSS setup
  5. Customization
  6. Components
  7. Client SDK
  8. CLI
  9. Docs

kibbleforms

Inbound forms for sites that coding agents build. The form lives in your code; Kibble handles everything after submit: validation, spam protection, storage that stays coherent as the form changes, partial submissions, and delivery to email and webhooks.

One package, three things:

  • React components that look finished out of the box (kibbleforms) on top of headless primitives (kibbleforms/headless)
  • a client SDK (kibbleforms/client) with idempotent, retried submits
  • the kibble CLI (npx kibble …) for provisioning, pushing schemas, testing and checking builds
npm install kibbleforms

React 18 or 19. Works in Next.js (app router), Vite, Astro + React, Remix. Docs: kibbleforms.com/docs · what changed in each release: changelog.

60-second quickstart (for agents too)

npx kibble init            # sandbox project; writes kibble.json, .kibble/secret, kibble/client.ts, an AGENTS.md section,
                           # adds withKibble()/kibble() to your next/vite/astro config and the CSS imports, prints a claim link
// kibble/forms/waitlist.ts
import { defineForm } from 'kibbleforms/schema';

export const waitlist = defineForm({
  slug: 'waitlist',
  name: 'Waitlist',
  pages: [
    {
      fields: [
        { key: 'email', type: 'email', label: 'Work email', required: true },
        { key: 'name', type: 'short_text', label: 'Name' },
        { key: 'role', type: 'dropdown', label: 'Role', options: ['Founder', 'Engineer', 'Other'] },
        { key: 'consent', type: 'consent', label: 'Email me when we launch', required: true },
      ],
    },
  ],
  settings: {
    submitLabel: 'Join the waitlist',
    success: { type: 'message', title: 'You are on the list', message: 'We will be in touch.' },
  },
});
// anywhere in your site
import { Form } from 'kibbleforms';
import { kibble } from '@/kibble/client';
import { waitlist } from '@/kibble/forms/waitlist';

export function Waitlist() {
  return <Form schema={waitlist} client={kibble} />;
}
npm run dev                # the plugin registers a form whenever its file is saved
npx kibble test waitlist   # send a test submission from localhost and confirm it landed

There is no push step to remember: withKibble(nextConfig) (kibbleforms/next) or kibble() (kibbleforms/vite, also Astro and React Router) registers forms from the dev server and runs kibble sync during builds. After the project is claimed, npx kibble env vercel (or netlify, print) gives deploy builds the key so they register their forms too; without it a build only fails when visitors would be rejected.

That is a working form on localhost with no human step. To go live, a human opens the claim link (npx kibble claim --open), signs in, and names the production domains. Until then the project is a sandbox: it accepts submissions from localhost only and its data expires after 72 hours.

Field types: short_text, long_text, email, phone, url, number, single_choice, multiple_choice, dropdown, yes_no, rating, date, consent, hidden. A form with more than one page is a multi-step form; progress is saved after each step.

Conditional logic, prefill, autocomplete, scheduling

  • Conditional fields and pages: showWhen: { field: 'role', op: 'eq', value: 'other' } (or { all: [...] } / { any: [...] }) on a field or a page. Hidden fields are not validated, not required, and never sent; a page with no visible fields is skipped and Progress counts visible pages only. The hook exposes visibleKeys, visiblePages, isFieldVisible(key), isPageVisible(i).
  • Prefill: prefill: { query: 'email' } fills a field from the URL query on mount (hidden fields default to their own key); initialValues on Form/useKibbleForm is programmatic prefill and wins.
  • Autocomplete: autocomplete: 'name' | 'given-name' | 'organization' | … becomes the HTML attribute (email/tel/url fields get sensible defaults).
  • Scheduling: success: { type: 'schedule', url: 'https://calendly.com/acme/demo' } redirects with name and email prefilled and utm_* passed through; redirect accepts prefill too. The hook exposes successUrl and resolveSuccessUrl() for custom UIs, and navigate lets you replace window.location.assign.

Agents

Forms can be filled by agents. settings.agents is 'accept' (default; declared agents are labeled) or 'block', and settings.agentNotes describes good answers; agents read GET /v1/projects/<projectId>/forms/<slug>/schema.json and submit with meta.actor: { type: 'agent', name }. In the SDK: createClient({ actor }) or useKibbleForm(schema, { actor }). A block policy returns agents_blocked; the styled Form shows the server's message and hint.

CSS setup

The components are authored with Tailwind v4 utilities that read --kb-* CSS variables. Pick one:

Tailwind v4 host (your CSS file; kibble init adds this):

@import "tailwindcss";
@import "kibbleforms/theme.css";                 /* --kb-* tokens + @theme mapping (bg-kb-primary, rounded-kb, …) */
@import "kibbleforms/themes/shadcn.css";         /* shadcn/ui sites: use your --primary, --radius, … (v3: shadcn-v3.css) */
@source "../node_modules/kibbleforms/dist";      /* let Tailwind see the component classes; adjust the relative path */

The form inherits your font and follows a .dark / [data-theme="dark"] ancestor. Tune it with props: theme (colors, radius, font, colorScheme), size (sm | md | lg), layout ({ columns: 2 }) and classNames (per-part classes), on Form or once on KibbleProvider.

No Tailwind (any bundler, or a <link>):

import 'kibbleforms/styles.css';   // compiled and scoped to [data-kibble]; nothing leaks into your page

Customization

  1. CSS variables — same in both distributions. Set them globally or per form:

    :root { --kb-primary: #2563eb; --kb-radius: 999px; --kb-font: var(--font-sans); }
    [data-kibble] { --kb-space: 1rem; }
    

    Tokens: --kb-primary, --kb-primary-fg, --kb-bg, --kb-fg, --kb-muted, --kb-muted-fg, --kb-border, --kb-ring, --kb-error, --kb-radius, --kb-space, --kb-font. Every component also renders data-kb="<name>" (form, field, label, input, select, choice, option, yesno, rating, date, consent, submit, error, success, multistep, progress, nav, notice, page, help) for plain CSS targeting.

  2. Compose and override classes — every component takes className, merged with tailwind-merge so your utilities win:

    <Form schema={waitlist} client={kibble} className="grid grid-cols-2 gap-4">
      <Field name="name" />
      <Field name="email" />
      <Field name="role" className="col-span-2" />
      <Field name="consent" className="col-span-2" />
      <Submit className="col-span-2 w-auto justify-self-end">Count me in</Submit>
    </Form>
    
  3. Headless — behavior and accessibility, zero markup opinions:

    import { useKibbleForm, FormProvider, useField, Honeypot } from 'kibbleforms/headless';
    
    function Email() {
      const { field, value, setValue, error, inputProps, errorId } = useField('email');
      return (
        <label>
          {field.label}
          <input {...inputProps} value={(value as string) ?? ''} onChange={(e) => setValue(e.target.value)} />
          {error && <p id={errorId}>{error}</p>}
        </label>
      );
    }
    
    export function Custom() {
      const form = useKibbleForm(waitlist, { client: kibble });
      return (
        <FormProvider form={form}>
          <form {...form.formProps}>
            <Honeypot />
            <Email />
            <button type="submit" disabled={form.status === 'submitting'}>Send</button>
          </form>
        </FormProvider>
      );
    }
    

    useKibbleForm gives you values, errors, touched, pageIndex, pageCount, status, result, submitError, setValue, setTouched, validatePage, nextPage, prevPage, submit, reset, formProps and more. useField returns inputProps with id, name, aria-invalid, aria-describedby, aria-required and onBlur (we use aria-required instead of required so our messages show, not the browser's). usePage() and useFormStatus() cover navigation and status.

Components

Form (renders the whole form from the schema when given no children; MultiStep automatically when there is more than one page), KibbleProvider, Page, MultiStep, Progress, Field (picks the control by type), Input, Textarea, Select, Choice (radio or checkbox group), YesNo, Rating, DatePicker, Consent, Submit, FieldError, Success, Label, Help, Button.

Behavior you get for free: validation from the schema on submit (format errors on blur, "required" waits for submit), focus moves to the first invalid control or to the new page's heading, Enter submits a single-page form and advances a multi-step one, a honeypot field, time-to-submit, where the visitor came from (the page that led to the form, the landing page, the entry site and campaign tags; add <KibbleAttribution /> to the root layout to cover pages before the form), optional Cloudflare Turnstile (settings.turnstileSiteKey), idempotent submits with 3 retries on network/5xx errors, partial saves per step under a session id kept in sessionStorage, and the success message or redirect from settings.success (the server's echo wins).

Use <KibbleProvider client={kibble}> once (for example in a Next.js layout through a small client component) so pages can render <Form schema={…} /> without passing the client. In server-component frameworks import schemas from kibbleforms/schema and the client from kibbleforms/client; the default entry is client code.

Client SDK

import { createClient, KibbleError } from 'kibbleforms/client';

const kibble = createClient({ projectId: 'prj_…', endpoint: 'https://api.kibbleforms.com' });
const res = await kibble.submit(waitlist, { email: '[email protected]', consent: true }, { startedAt });
// res: { id, status, success?, flags? }  — throws KibbleError { code, message, hint, fields, status }
await kibble.savePartial(waitlist, values, { sessionId, pageIndex: 0 }); // never throws, null on failure

The server always validates against the registered schema version; client validation is a convenience. Submissions carry an Idempotency-Key (one per logical submit, reused across retries) and the schema hash so Kibble knows which version they belong to.

CLI

Every command accepts --json (machine-readable output on stdout, nothing else). Exit codes: 0 ok, 1 error, 2 check failed. Errors always say what to do next.

CommandWhat it does
kibble init [--name] [--api-url] [--forms-dir kibble/forms] [--force]Creates a sandbox project. Writes kibble.json, .kibble/secret (gitignored), kibble/client.ts, the forms directory, and a ## Kibble forms section in AGENTS.md/CLAUDE.md. Prints the claim link.
kibble push [slug]Registers every schema in the forms directory (loaded with jiti, TypeScript is fine). Prints version, diff and warnings. The framework plugin makes this rarely needed.
kibble syncThe build step: with KIBBLE_SECRET_KEY (or .kibble/secret) registers every form tagged with its source (Vercel, Netlify, Cloudflare Pages, GitHub Actions, KIBBLE_ENV); without it, checks and exits 2 only when visitors would be rejected.
kibble env vercel|netlify|printPuts the secret key into the host's build environment (stdin for Vercel, never echoed), or prints it for pasting.
kibble test [slug]Sends a test submission with sample values from localhost and confirms it was stored.
kibble statusMode, claim state, origins, and a forms table with local-vs-remote schema drift.
kibble check [--allow-sandbox]Strict local check: exits 2 if the project is unclaimed or a local schema is unregistered.
kibble claim [--open] [--wait]Prints (and opens) the claim link; --wait polls until the project is live.
kibble submissions [slug] [--status all|complete|partial|spam|test] [--limit 20]Lists recent responses with their kind, date and first answers.
kibble destinations list / add slack (prints an "Add to Slack" link for a person) or add slack <url> / add postgres "<conn>" [--table t] [--schema s] / add email <member addr> / add webhook <url> (each --form slug or --all-forms) / remove <id> / test <id>Wire Slack, Postgres (incl. Supabase pooler), email to workspace members and signed webhooks without the dashboard. Connection strings are never echoed; webhook signing secrets print once.
kibble domains list|add <domain>|remove <domain>The site's production domains (allowed origins).
kibble review <slug> [--ideal "…"] [--categories "A,B"] [--off|--on]Show or change triage for a form (ideal response enables fit levels).

Config: kibble.json ({ projectId, apiUrl, formsDir }), secret in .kibble/secret (KIBBLE_SECRET_KEY overrides, useful in CI), KIBBLE_API_URL sets the default API for init.

Docs

https://api.kibbleforms.com/llms.txt and /docs/*.md are written for agents and humans alike.

MIT

版本与发布记录

kibbleforms 共发布 7 个版本,首次发布于 2026/10/27;过去 12 个月发布了 7 个版本。

版本发布日期距今
0.1.5latest2026/11/041 天前
0.1.42026/10/288 天前
0.1.32026/10/279 天前
0.1.22026/10/279 天前
0.1.12026/10/279 天前
0.1.02026/10/279 天前

关于 kibbleforms 的常见问题

kibbleforms 是什么?

Forms for sites that coding agents build. React components, client SDK and the kibble CLI.

如何安装 kibbleforms?

在项目目录运行 npm install kibbleforms,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 kibble,可以用 npx kibble 直接运行。

kibbleforms 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

kibbleforms 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

kibbleforms 可以商用吗?

kibbleforms 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

kibbleforms 还在维护吗?

最新版本 0.1.5 发布于 2026/11/04(1 天前);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/10/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。