← All posts

I gave Tailwind typed props. Then it ate React Hook Form.

How the DashForge author turned Tailwind classes into typed props and built application-aware components: forms, visibleWhen, RBAC — without Controller per field.

Contents

In brief

On Dev.to, the DashForge author explains how “React forms” were not a CSS problem but a wiring problem: Controller, watch, useEffect on every field. The answer — typed props instead of className soup, a React Hook Form bridge, and components that know their form, visibility, and access rules.

What happened

The starting pain is familiar: Tailwind fixes cascade and dead styles, but visuals live in opaque strings with no autocomplete or safe refactors. The author asked: what if variant, color, size are typed props, with utilities resolved via tailwind-variants and design tokens?

Next layer — forms. The RHF Controller + render prop pattern wears thin by field fifty. A TextField with name="email" reads value, errors, and blur/submit behavior through a context bridge — no manual wiring.

Then business UI orchestration:

  • visibleWhen — the field declares when it shows (customerType === business), no watch + useEffect per rule.
  • access on buttons — invoice:update inside the component, not permissions.includes on every screen.

Architectural twist: two UI libraries (MUI and Tailwind + Radix) share orchestration props but one headless engine (form bridge, visibility, RBAC). Swappable pixels; shared application intelligence.

Why it matters

In enterprise forms, cost is rarely pixels — it is glue: connect field to form, validation, permissions, conditional visibility. A typed visual API reduces styling mistakes; application-aware components cut boilerplate across hundreds of screens.

This is not “yet another UI kit”: DashForge is alpha, but the idea is declarative props for repeated orchestration, with Radix/React Aria handling a11y.

In practice

  1. Visual API — props-first over Tailwind; one sx escape hatch with tailwind-merge.
  2. Fields<TextField name="…" label="…" /> instead of Controller + error plumbing.
  3. ConditionsvisibleWhen={{ field, equals }} instead of watch/effect.
  4. Permissionsaccess="resource:action" on interactive nodes.
  5. Maturity — alpha; review @dashforge/tw and the repo before production adoption.
Before After
className string typed variant/color/size
Controller × N context bridge
watch + effect × rules visibleWhen
permissions && in JSX access on Button

Takeaway

The story shifts focus from “how to style” to “how a component participates in the app.” If you are tired of copy-paste form wiring, DashForge is worth watching as an experiment — not a drop-in replacement for your whole stack.