Mischief

32 / Documents

Schema Builder

Build the shape you want extracted from a document. Fields carry a name, type, description, and requirement, and object and array fields nest.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/schema-builder
import { SchemaBuilder } from "mischief-ui/schema-builder"
Also installs
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/schema-builder/schema-builder.tsx
"use client" import * as React from "react"import { ChevronRight, Plus, Trash2 } from "lucide-react"import { cn } from "@/lib/utils" export type SchemaFieldType =  "string" | "number" | "boolean" | "date" | "object" | "array" export type SchemaField = {  id: string  name: string  type: SchemaFieldType  description?: string

Usage

export function Extraction() {
  return (
    <SchemaBuilder
      defaultFields={[{ id: "total", name: "total", type: "number" }]}
      onFieldsChange={saveSchema}
    />
  )
}

The shape it produces

Fields come back as a tree, in the order they were arranged. Object and array fields nest through their own fields array, and everything else is a leaf. This is deliberately not JSON Schema: it is small enough to read, and short enough to convert into whatever your extractor actually wants.

const fields = [
  { id: "1", name: "total", type: "number", required: true },
  {
    id: "2",
    name: "supplier",
    type: "object",
    fields: [
      { id: "3", name: "name", type: "string", required: true },
      { id: "4", name: "vat", type: "string" },
    ],
  },
]

Six types are offered by default -- string, number, boolean, date, object, and array. Narrow that with types when your extractor supports fewer, and cap nesting with maxDepth so nobody builds a structure the other end cannot represent.

Ids for new fields

New fields need an id, and the default generator is fine for a form whose result is read once. Pass createId when the ids are going to outlive the page -- stored, compared, or sent somewhere that expects them to be stable.

<SchemaBuilder
  defaultFields={fields}
  createId={() => crypto.randomUUID()}
  onFieldsChange={save}
/>
crypto.randomUUID is available in the browser and on modern Node.

API

fields, defaultFieldsSchemaField[]The schema, controlled or uncontrolled.
onFieldsChange(fields: SchemaField[]) => voidRuns on every edit.
typesreadonly SchemaFieldType[]The type options offered. Defaults to string, number, boolean, date, object, and array.
maxDepthnumberHow far object and array fields may nest. Defaults to 3.
createId() => stringSupplies ids for new fields when you need them stable.

SchemaField

idstringUnique across the whole tree.
namestringThe field name as it will be extracted.
typeSchemaFieldTypestring, number, boolean, date, object, or array.
descriptionstringA hint for whoever, or whatever, fills it.
requiredbooleanMarks the field as expected.
fieldsSchemaField[]Children, on an object or an array.

Accessibility

Every input has a label naming the field it belongs to, so a screen reader user knows which row they are editing rather than hearing a run of unlabelled boxes. Nesting controls say which field they open, and remove buttons name the field they delete. Only object and array fields offer nesting, and nesting stops at maxDepth.