Open-source React 19 UI system

Copy less UI.
Ship more product.

Forms, grids, calendars, and trees as config-driven components. One schema in, a finished screen out.

create-user.tsx form
<SmartForm
  schema={userSchema}
  fields={[
    { name: "name",  type: "text" },
    { name: "email", type: "email" },
    { name: "role",  type: "select" },
  ]}
  onSubmit={createUser}
/>
7 feature engines: form, grid, search, tree, transfer, calendar, editor
27 action-button presets from one config map
0 build steps. The library ships as readable TypeScript source
React 19 with Tailwind v4, Zod v4, and shadcn/ui on Base UI

The same screens, rebuilt every sprint.

Every product needs a user table, a create form, a search bar, and a date picker. Most codebases rebuild them per feature, with slightly different validation, slightly different spacing, and entirely different bugs. Smart Component turns those recurring patterns into documented, tested building blocks instead of one-off glue.

It is not a component dump. It is a small set of engines with one shared convention, so the tenth screen costs less than the first.

Config over composition

Flat props replace six nested compound tags. The native primitives stay re-exported for the layouts config can't express.

The schema is the source of truth

One Zod schema drives validation, required marks, and TypeScript types. Nothing is declared twice.

Source you can read

No compiled bundle between you and the library. Trace any behavior straight to its file.

One schema replaces the boilerplate.

The same create-user screen, written twice. On the left, the hand-wired version every codebase accumulates. On the right, the form engine.

~500 lines, hand-wired
user-form.tsx before
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [role, setRole] = useState<Role | null>(null);
const [errors, setErrors] = useState<Errors>({});
const [submitting, setSubmitting] = useState(false);

function validate() {
  const next: Errors = {};
  if (!name.trim()) next.name = "Name is required";
  if (!EMAIL_RE.test(email)) next.email = "Invalid email";
  // ...then the markup, the focus management,
  // the reset logic, the error rendering.
  // Repeated for every form in the app.

Validation rules written twice: once in state, once in markup

Error, loading, and reset state managed by hand

Every form drifts a little further from the last one

~45 lines, schema-driven
user-form.tsx after
const userSchema = z.object({
  name:  z.string().min(1),
  email: z.string().email(),
  role:  z.enum(roles),
});

<SmartForm
  schema={userSchema}
  fields={fields}
  onSubmit={createUser}
/>

One Zod schema drives rules, types, and required marks

Field states, errors, and submit flow handled by the engine

Every form in the app looks and behaves the same

Three moves. That's the whole workflow.

No form context to wire, no column plumbing to memoize. Describe what the screen is, and the engine builds it.

Define the schema

Plain Zod. It already validates your API payloads; now it also declares your form.

schema.ts
const schema = z.object({
  title:    z.string().min(3),
  due:      z.date(),
  assignee: z.string().email(),
});

Describe the fields

UI-only concerns: labels, control types, layout. Twenty-plus field types, from currency to rich text.

fields.ts
const fields: FieldDefinition[] = [
  { name: "title",    label: "Title",    type: "text" },
  { name: "due",      label: "Due date", type: "date" },
  { name: "assignee", label: "Assignee", type: "email" },
];

Render one component

Validation, required asterisks, error placement, and the submit lifecycle all derive from the schema.

page.tsx
<SmartForm schema={schema} fields={fields} onSubmit={createTask} />

Change the schema and the form follows: new rule, new error copy, new required mark, new inferred type. There is no second place to update.

An API you can guess correctly.

The engines share one convention set, so learning the first component teaches you the other forty.

One controlled pair: data / setData

Every input-like component uses the same value contract, which is exactly why the form engine can drive any field type without adapters.

Escape hatches included

Each wrapper re-exports its native shadcn/ui primitives. When the flat API can't express a layout, drop down without changing imports.

Server contracts are validated

Paged responses are parsed with Zod before they reach the grid, so a drifting API fails loudly at the boundary instead of quietly in a cell.

users-grid.tsx data-grid
const fetchRows = createPageFetcher({
  url: "/api/users",
  itemSchema: userSchema,  // Zod-checked rows
});

<SmartServerGrid
  columnDefs={columns}
  fetchRows={fetchRows}
  persistStateKey="users-grid"
/>
toolbar.tsx buttons
<AddButton onClick={openCreate} />
<ExportButton loading={exporting} />
<DeleteButton permission="users:delete" />

Layered so you always know where code lives.

A strict one-way dependency flow. Apps consume engines, engines compose primitives, and nothing reaches around a layer.

How it's layered

Your application Routes, pages, data fetching apps/web
Smart components Flat, config-driven wrappers and button presets smart-components/
Feature engines Form, grid, search, tree, transfer, calendar, editor *-engine/
Design system shadcn/ui primitives on Base UI, Tailwind v4 tokens components/
React 19 Source-only TypeScript, no build step in between

How it's laid out

smart-component/
├─ apps/
│  └─ web/                playground and demo routes
├─ packages/
│  └─ ui/                 the library, exported as source
│     ├─ components/      shadcn/ui primitives
│     ├─ smart-components/
│     ├─ form/
│     ├─ data-grid/
│     ├─ search/
│     ├─ tree/
│     ├─ calendar/
│     └─ 
├─ docs/                  guides, plus this page
└─ scripts/               repo checks

Where it sits.

Not a component dump, not a black-box framework. A middle layer that owns the repetitive 80% and hands you the keys for the rest.

Concern Hand-rolled UI Copy-paste kits Smart Component
New CRUD screen Days of glue code Copy, then rewire by hand A schema and one component
Validation Rewritten per form Left as an exercise One Zod schema, one source of truth
Consistency Drifts per developer Drifts per paste One config map, shared conventions
Escape hatch Everything is the escape hatch Edit the copied source Native primitives re-exported
Reading the source Yours to document Snapshots go stale Source-first package, tested and documented

Read the source.
It's the whole point.

Every engine is typed, tested, and readable in an afternoon. If it saves your team a sprint, star the repo so the next team finds it.

git clone https://github.com/imsaroj/automatic-octo-fiesta && pnpm install && pnpm dev