Schema-Driven Forms: When Rules Become Data
Why business rules belong in data rather than scattered through components — and how schema-driven forms become the first step toward a configurable system.
Most forms start simple. A few fields, a couple of validations, some conditional visibility — nothing unusual. Then the business asks for one exception. Then another. Then a different flow for a different customer.
Six months later, a form that looked perfectly reasonable now contains dozens of conditions spread across components, validation logic, event handlers, and API transformations. At that point, the problem isn't React. The problem is where the business rules live.
The Pattern
I've seen this happen repeatedly. A product team launches a form, and the first version is completely reasonable:
{type === "transfer" && amount > 1000 && (
<Field name="reason" required />
)}
Nobody complains — why would they? It's readable, explicit, and easy to understand. The problem appears when the second rule arrives. Then the fifth. Then the twentieth. Suddenly nobody can answer a simple question:
What actually makes this form valid?
Because the answer is scattered across the entire codebase.
Why Forms Become Hard to Change
The root problem isn't conditional logic — it's coupling. Business rules become tightly coupled to presentation: visibility lives in JSX, validation lives somewhere else, dependencies live inside handlers, and API transformations live somewhere else again. Every new rule means touching multiple places, and the system becomes harder to reason about with every release.
Not because the code is bad, but because the architecture assumes the rules are stable. Business rules almost never are.
Moving Rules Into Data
The first major improvement comes from changing a single assumption: instead of describing the form through code, describe it through data. Once rules become data, they can be reviewed, tested, versioned, reused, and eventually edited without changing application code.
The implementation details matter less than the shift in responsibility. The system stops asking "how do we render this form?" and starts asking "what does this form describe?"
Validation in One Place
One of the reasons I like React Hook Form and Zod is that they let validation rules live in a single location. Instead of distributing business logic across multiple components, conditional requirements can be expressed directly inside the schema.
import { z } from "zod";
const schema = z
.object({
type: z.enum(["transfer", "payment"]),
amount: z.number().positive(),
reason: z.string().optional(),
})
.superRefine((data, ctx) => {
if (
data.type === "transfer" &&
data.amount > 1000 &&
!data.reason
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["reason"],
message:
"Reason is required for transfers over 1000.",
});
}
});
const form = useForm({
resolver: zodResolver(schema),
});
Now the answer to "what makes this form valid?" lives in one place. That's already a significant improvement.
Visibility Should Be Data Too
Validation is only half of the problem; visibility rules create the same kind of complexity. The common pattern is to embed visibility directly into rendering logic, and eventually every component becomes a maze of conditions. A better approach is to describe fields declaratively:
const fields = [
{
name: "type",
kind: "select",
options: ["transfer", "payment"],
},
{
name: "amount",
kind: "number",
},
{
name: "reason",
kind: "text",
showWhen: (values) =>
values.type === "transfer" &&
values.amount > 1000,
},
];
Rendering becomes a simple iteration over configuration. The complexity doesn't disappear, but it moves into a place where it can be understood — and that's a huge difference.
The Hidden Benefit Nobody Talks About
Most articles stop here. They focus on cleaner React code: fewer conditions, better maintainability. Those benefits are real, but they're not the most interesting part. The real benefit appears later.
Once visibility, validation, and behavior become data, you can start treating them as a system. The same mechanism can drive forms, workflows, review processes, approval paths, and product variations. That's where things get interesting — because you're no longer building forms. You're building capabilities.
The First Step Toward a Platform
People often imagine platforms as something huge. In reality, many platforms start with a simple observation:
We keep solving the same problem.
A schema-driven form system is often the first sign. Rules become configuration, behavior becomes configuration, variation becomes configuration, and the system becomes capable of adapting without requiring new frontend code for every change. That's the moment a form starts becoming a platform — not because someone planned it, but because the business needed it.
What I Learned
The biggest win isn't fewer lines of code. It's deciding where complexity should live. If business rules live inside components, complexity spreads; if business rules become structured data, complexity becomes visible — and visible complexity is much easier to manage than hidden complexity.
Most teams don't set out to build configurable platforms. They simply reach a point where hardcoded workflows stop scaling, and schema-driven forms are often the first step in that transition — the start of the path I traced in Every System Eventually Wants to Become Configurable.