{"_id":"@aidanbell/schema-form-ui","_rev":"3-ee16d9377ac46be77124efd0b61f6595","name":"@aidanbell/schema-form-ui","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@aidanbell/schema-form-ui","version":"0.1.0","license":"MIT","_id":"@aidanbell/schema-form-ui@0.1.0","maintainers":[{"name":"aidanbell","email":"aidanbell0182@gmail.com"}],"dist":{"shasum":"cb57010774022c5e39d20723ba23e2eaacd2c876","tarball":"https://registry.npmjs.org/@aidanbell/schema-form-ui/-/schema-form-ui-0.1.0.tgz","fileCount":9,"integrity":"sha512-D2oAp8NB91ctOqvZiItZD3NN1rKBKjaPxp//975DPumnIP7LSoUAlGLmzVo4ZLoZ+9+XESH2193+pGx1oGkk1Q==","signatures":[{"sig":"MEYCIQCV7rMG8mWlqjZKv5gSe2NIETbZe2KNnX7LLSUFXclDQwIhAIx9o18CtjqByEdvqVl85A+yYBMl/PRJPT6+UGFYxtsF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84863},"main":"./dist/index.cjs","type":"module","_from":"file:aidanbell-schema-form-ui-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"test":"vitest run --config ../../vitest.config.ts","build":"tsup","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"aidanbell","email":"aidanbell0182@gmail.com"},"_resolved":"/private/var/folders/xh/wc6pbw7x425c8xbl8smtt5j00000gn/T/c181e47433b70fa6c7566c3d99e4d3d0/aidanbell-schema-form-ui-0.1.0.tgz","_integrity":"sha512-D2oAp8NB91ctOqvZiItZD3NN1rKBKjaPxp//975DPumnIP7LSoUAlGLmzVo4ZLoZ+9+XESH2193+pGx1oGkk1Q==","_npmVersion":"10.8.2","description":"Styled SchemaForm for @aidanbell/schema-form","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"clsx":"^2.1.1","@base-ui/react":"^1.7.0","tailwind-merge":"^3.6.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.6","react":"^19.2.8","valibot":"^1.4.2","happy-dom":"^20.11.2","react-dom":"^19.2.8","typescript":"^5.7.3","@types/react":"^19.2.18","react-hook-form":"^7.85.0","@types/react-dom":"^19.2.4","@hookform/resolvers":"^5.7.1","@aidanbell/schema-form":"0.1.0","@testing-library/react":"^16.3.2","@testing-library/user-event":"^14.6.5"},"peerDependencies":{"react":">=18.2.0","valibot":"^1.4.2","react-dom":">=18.2.0","react-hook-form":"^7.50.0","@hookform/resolvers":">=3.0.0","@aidanbell/schema-form":">=0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/schema-form-ui_0.1.0_1787147443127_0.6610774379722828","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@aidanbell/schema-form-ui","version":"0.1.1","license":"MIT","_id":"@aidanbell/schema-form-ui@0.1.1","maintainers":[{"name":"aidanbell","email":"aidanbell0182@gmail.com"}],"dist":{"shasum":"586cf67457235fefae91133b19b1397a6303dd9b","tarball":"https://registry.npmjs.org/@aidanbell/schema-form-ui/-/schema-form-ui-0.1.1.tgz","fileCount":9,"integrity":"sha512-YncjVvWdMhAzTHHnWBWHA0VBD56QYjAmm+1m3+4Ss8fiXwtn+A9XcdSF7XEhsLvgpOWjSnml0j8TYeh+0014FA==","signatures":[{"sig":"MEUCIQD8x0m4JHivj/H6CNBDvyiTUxSBz/M1aegGWi3cvNPmzgIgJXLARq4Ru0DRDm4TiqJ6YktMJzOQ8/q5HIgWL7e6YJA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":87903},"main":"./dist/index.cjs","type":"module","_from":"file:aidanbell-schema-form-ui-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"test":"vitest run --config ../../vitest.config.ts","build":"tsup","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"aidanbell","email":"aidanbell0182@gmail.com"},"_resolved":"/private/var/folders/xh/wc6pbw7x425c8xbl8smtt5j00000gn/T/b90d63c7953574ae49490758978cae8a/aidanbell-schema-form-ui-0.1.1.tgz","_integrity":"sha512-YncjVvWdMhAzTHHnWBWHA0VBD56QYjAmm+1m3+4Ss8fiXwtn+A9XcdSF7XEhsLvgpOWjSnml0j8TYeh+0014FA==","_npmVersion":"10.8.2","description":"Styled SchemaForm for @aidanbell/schema-form","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"clsx":"^2.1.1","@base-ui/react":"^1.7.0","tailwind-merge":"^3.6.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.6","react":"^19.2.8","valibot":"^1.4.2","happy-dom":"^20.11.2","react-dom":"^19.2.8","typescript":"^5.7.3","@types/react":"^19.2.18","react-hook-form":"^7.85.0","@types/react-dom":"^19.2.4","@hookform/resolvers":"^5.7.1","@aidanbell/schema-form":"0.1.1","@testing-library/react":"^16.3.2","@testing-library/user-event":"^14.6.5"},"peerDependencies":{"react":">=18.2.0","valibot":"^1.4.2","react-dom":">=18.2.0","react-hook-form":"^7.50.0","@hookform/resolvers":">=3.0.0","@aidanbell/schema-form":">=0.1.1"},"_npmOperationalInternal":{"tmp":"tmp/schema-form-ui_0.1.1_1787163189206_0.8598553343589967","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aidanbell/schema-form-ui","version":"0.2.0","description":"Styled SchemaForm for @aidanbell/schema-form","license":"MIT","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"peerDependencies":{"@aidanbell/schema-form":">=0.2.0","@hookform/resolvers":">=5.0.1","react":">=18.2.0","react-dom":">=18.2.0","react-hook-form":"^7.50.0","valibot":"^1.4.2"},"devDependencies":{"@hookform/resolvers":"^5.7.1","@testing-library/react":"^16.3.2","@testing-library/user-event":"^14.6.5","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","happy-dom":"^20.11.2","react":"^19.2.8","react-dom":"^19.2.8","react-hook-form":"^7.85.0","tsup":"^8.3.6","typescript":"^5.7.3","valibot":"^1.4.2","@aidanbell/schema-form":"0.2.0"},"publishConfig":{"access":"public"},"dependencies":{"@base-ui/react":"^1.7.0","clsx":"^2.1.1","tailwind-merge":"^3.6.0"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit -p tsconfig.json","test":"vitest run --config ../../vitest.config.ts"},"_id":"@aidanbell/schema-form-ui@0.2.0","_integrity":"sha512-JZvdkZ0utOApMv5QKoAmvs2sbMkZJ/U3C1Z/ztBauuyiqWzhmSO7csQzdu0OHZrkOJSQk1Ix7VsvXHOY5oSEcw==","_resolved":"/private/var/folders/xh/wc6pbw7x425c8xbl8smtt5j00000gn/T/602dee11a92be115925bf44e41b01399/aidanbell-schema-form-ui-0.2.0.tgz","_from":"file:aidanbell-schema-form-ui-0.2.0.tgz","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-JZvdkZ0utOApMv5QKoAmvs2sbMkZJ/U3C1Z/ztBauuyiqWzhmSO7csQzdu0OHZrkOJSQk1Ix7VsvXHOY5oSEcw==","shasum":"3e7360136244b1de78f46812ecb0a3fabf4cfbc0","tarball":"https://registry.npmjs.org/@aidanbell/schema-form-ui/-/schema-form-ui-0.2.0.tgz","fileCount":9,"unpackedSize":105922,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBVM9jpurl3+WeeJJTJbQ4eF7tAKZhIDAENS/DGMt3vzAiBK7LfooyFP1VcWP5Sw04P+34/sA9gfPk3R4KqyHX6l3Q=="}]},"_npmUser":{"name":"aidanbell","email":"aidanbell0182@gmail.com"},"directories":{},"maintainers":[{"name":"aidanbell","email":"aidanbell0182@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/schema-form-ui_0.2.0_1787685228201_0.40074984166846517"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-19T13:50:43.001Z","modified":"2026-08-25T19:13:48.540Z","0.1.0":"2026-08-19T13:50:43.294Z","0.1.1":"2026-08-19T18:13:09.354Z","0.2.0":"2026-08-25T19:13:48.393Z"},"license":"MIT","description":"Styled SchemaForm for @aidanbell/schema-form","maintainers":[{"name":"aidanbell","email":"aidanbell0182@gmail.com"}],"readme":"# `@aidanbell/schema-form-ui`\n\n> **Experimental `0.1.x`** — API may change before 1.0.\n\nStyled, accessible React forms driven by the same JSON schema as [`@aidanbell/schema-form`](https://www.npmjs.com/package/@aidanbell/schema-form). Uses Tailwind utility classes and [Base UI](https://base-ui.com) for the checkbox primitive.\n\nThis package **does not ship a CSS file**. Your app must run Tailwind and scan this package’s built files so the utility classes in `dist` are included.\n\n## Install\n\n```bash\npnpm add @aidanbell/schema-form-ui @aidanbell/schema-form valibot react react-dom react-hook-form @hookform/resolvers\n```\n\n**Peers:** `react` / `react-dom` ≥18.2, `@aidanbell/schema-form` ≥0.1.0, `valibot` ^1.4.2, `react-hook-form` ^7.50, `@hookform/resolvers` ≥5.0.1.\n\nRuntime dependencies (installed with the package): `clsx`, `tailwind-merge`, `@base-ui/react`.\n\n## Tailwind setup (required)\n\nWithout this, controls look unstyled because Tailwind never sees the class strings in `node_modules`.\n\n### Tailwind v4\n\n```css\n@import \"tailwindcss\";\n@source \"../node_modules/@aidanbell/schema-form-ui/dist/**/*.{js,mjs}\";\n```\n\n### Tailwind v3\n\n```js\n// tailwind.config.js\ncontent: [\n  \"./src/**/*.{js,ts,jsx,tsx}\",\n  \"./node_modules/@aidanbell/schema-form-ui/dist/**/*.{js,mjs}\",\n],\n```\n\nAdjust the relative path to match where your CSS / config file lives.\n\n## Quickstart\n\n```tsx\nimport { SchemaForm } from \"@aidanbell/schema-form-ui\";\n\nconst schema = {\n  schemaVersion: 1 as const,\n  title: \"Sign up\",\n  description: \"Create an account\",\n  fields: [\n    { name: \"email\", type: \"email\" as const, label: \"Email\", required: true },\n    {\n      name: \"role\",\n      type: \"select\" as const,\n      label: \"Role\",\n      options: [\n        { label: \"Admin\", value: \"admin\" },\n        { label: \"Viewer\", value: \"viewer\" },\n      ],\n    },\n  ],\n};\n\nexport function SignupForm() {\n  return (\n    <SchemaForm\n      config={{ schema }}\n      onSubmit={(values) => {\n        console.log(values);\n      }}\n      onError={(error) => {\n        console.error(error);\n      }}\n    />\n  );\n}\n```\n\nInvalid schemas render an error UI and call `onError` with the parse result — the app does not crash. If `onSubmit` throws or rejects, `onError` receives that error as well.\n\n## Theming with `classNames`\n\nPass Tailwind classes via `config.classNames` (form-wide) and/or `config.fields[name].classNames` (per field). Later utilities win when they conflict (`cn` / `tailwind-merge`).\n\n```tsx\n<SchemaForm\n  config={{\n    schema,\n    classNames: {\n      form: \"max-w-md\",\n      label: \"font-semibold\",\n      submitButton: \"bg-emerald-600 hover:bg-emerald-700\",\n    },\n    fields: {\n      email: { classNames: { control: \"border-emerald-400\" } },\n      role: { disabled: true },\n    },\n    submitLabel: \"Create account\",\n    showReset: false,\n  }}\n  onSubmit={async (values) => {\n    /* ... */\n  }}\n/>\n```\n\n**Field overrides:** `hidden`, `disabled`, `component`, and nested `classNames` keys (`field`, `label`, `description`, `control`, `error`, …).\n\n## Customizing rendering\n\nThree layers, pick the smallest one that fits. Each layer can coexist with the others; more specific wins.\n\n### 1. Replace a control type everywhere — `config.components`\n\nSwap the input for a given `FieldType` across the form. Label, description, and error chrome stay put.\n\n```tsx\nimport { SchemaForm, type FieldControlProps } from \"@aidanbell/schema-form-ui\";\n\nfunction FancySelect({\n  field,\n  form,\n  id,\n  disabled,\n  className,\n  \"aria-invalid\": ariaInvalid,\n  \"aria-describedby\": ariaDescribedBy,\n}: FieldControlProps) {\n  return (\n    <select\n      id={id}\n      disabled={disabled}\n      className={className}\n      aria-invalid={ariaInvalid}\n      aria-describedby={ariaDescribedBy}\n      {...form.register(field.name)}\n    >\n      {(field.options ?? []).map((option) => (\n        <option key={option.value} value={option.value}>\n          {option.label}\n        </option>\n      ))}\n    </select>\n  );\n}\n\n<SchemaForm\n  config={{ schema, components: { select: FancySelect } }}\n  onSubmit={...}\n/>\n```\n\n### 2. Replace one field's control — `config.fields[name].component`\n\nSame chrome, one field. Wins over a matching `components` entry.\n\n```tsx\n<SchemaForm\n  config={{\n    schema,\n    components: { string: FancyInput },\n    fields: { nickname: { component: NicknameControl } },\n  }}\n  onSubmit={...}\n/>\n```\n\n### 3. Own the whole row — `renderField`\n\nA render prop on `SchemaForm` (not `config`). Receives the same `FieldControlProps` as custom controls, plus `defaultRender` to fall back per field.\n\n```tsx\n<SchemaForm\n  config={{ schema }}\n  renderField={(props, defaultRender) =>\n    props.field.name === \"rating\" ? (\n      <StarRatingRow {...props} />\n    ) : (\n      defaultRender(props)\n    )\n  }\n  onSubmit={...}\n/>\n```\n\n`defaultRender(props)` is equivalent to not using `renderField` at all. You can tweak props before delegating:\n\n```tsx\nrenderField={(props, defaultRender) => defaultRender({ ...props, disabled: true })}\n```\n\n`hidden` still removes a field before `renderField` is consulted.\n\n### Custom control contract\n\nCustom controls receive `FieldControlProps`: `field`, `form`, `id`, `disabled`, `error`, `className`, `aria-invalid`, and `aria-describedby`. You must wire the value into React Hook Form yourself — `form.register(field.name)` for native inputs, or `Controller` when the widget is uncontrolled. The built-in implementations in [`controls.tsx`](src/fields/controls.tsx) are the reference.\n\nIf `renderField` returns custom markup *without* calling `defaultRender`, you own the label, error display, and accessibility wiring. `aria-describedby` points at `{field.name}-error` and `{field.name}-description`; those ids only exist if you render elements with them.\n\n## API\n\n| Export | Role |\n|--------|------|\n| `SchemaForm` | Parse schema → render fields + submit/reset |\n| `SchemaField` | Single field bound to a React Hook Form instance |\n| Built-in controls | `StringControl`, `NumberControl`, `BooleanControl`, `TextareaControl`, `SelectControl`, `RadioControl` |\n| `cn` / `mergeClassNames` | Tailwind-friendly class helpers |\n| UI primitives | `Input`, `Textarea`, `Select`, `Checkbox`, `Label`, `Button` |\n\n**Types:** `SchemaFormProps`, `SchemaFormConfig`, `SchemaFormClassNames`, `FieldControlProps`.\n\nFull field contract: [`SCHEMA.md`](../../SCHEMA.md) in the monorepo.\n\n## Empty-value semantics\n\nSame as core:\n\n- Optional text-like fields submit `''` when empty.\n- Optional `number` fields submit `undefined` (never `0`).\n- `boolean` defaults to `false`; required boolean must be `true`.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}