Every new React app starts with the same wiring — generating a Tailwind dashboard base instead
▲ 7 r/tailwindcss+1 crossposts

Every new React app starts with the same wiring — generating a Tailwind dashboard base instead

routing, theme, dark mode, app shell, data table, auth stubs, permissions, form validation, mock data.

It's identical across projects and none of it is the product.

The alternative is to generate that base and start from there.

npx dashforge-cli my-app --lib tw --template dashboard

What's in the generated project:

  • Vite 7, React Router 8 (framework mode), TypeScript
  • app shell with nav, workspace switcher, stat cards, data table, users CRUD
  • mock auth with three roles (admin / editor / viewer)
  • component-level RBAC - a viewer doesn't get a disabled button, they don't get the button
  • forms with validation
  • public routes pre-rendered to static HTML, everything behind auth stays CSR
  • dark mode already wired

On the Tailwind side:

The UI layer is token-first. tailwind.config.ts loads a preset, and utilities like bg-primary-600 resolve to CSS variables driven by a theme object, so rebranding is one 50->900 ramp, not a find-and-replace across components:

color: { ...defaultTWThemeLight.color, primary: brandPrimary }

Dark mode swaps the entire token set instead of toggling per-component classes, so the brand color survives the switch.

The theme allso carries component defaults, the layer Tailwind normally leaves to you:

const components = { 
  TextField: { defaults: { size: 'md', fullWidth: true } }, 
  Stack: { defaults: { gap: 4 } }, 
  Box: { defaults: { variant: 'outlined', rounded: 'lg' } }, 
};

Set once, every instance inherits its; a prop on the element still wins.

<Card variant="outlined" rounded="lg" p={0}> 
  <CardContent p={4}> <Stack gap={2}> 
    <Typography variant="body2" color="muted">{label}</Typography>         
    <Typography variant="h4">{value}</Typography> </Stack> </CardContent> </Card>

Layout stays plain Tailwind, grid grid-cols-1 gap-4 lg:grid-cols-4 in the page, props inside the components.

Where your own code starts:

The API layer ships with a mock/live switch. The app runs standalone against mock data, and you swap a single odulle once the backend exists. So after npm run dev the app is already up, and the work left is replacing mock resources with real ones and adding domain routes. Not rebuilding the shell.

--lib tw generates the Tailwind track. No arguments runs interactive mode; -- no-install if you want to read the output before installing anything

Open source: https://github.com/kensaadi/dashforge-cli

u/kensaadi — 1 day ago

Ho riscritto il motore reattivo di Dashforge tre volte — ecco la versione che è sopravvissuta alla produzione

Negli ultimi 8 mesi ho lavorato su Dashforge, un framework React con licenza MIT per moduli basati su schemi, controllo degli accessi e orchestrazione dell'interfaccia utente.

Il progetto è ora pubblico, ma prima di chiedere a chiunque di provarlo, vorrei ricevere qualche feedback tecnico su tre decisioni su cui non sono ancora completamente sicuro.

Il motore reattivo ha richiesto tre tentativi

v1 — Rivalutare l'intero modulo

Ogni cambiamento di campo causava l'esecuzione di tutte le condizioni e reazioni.

Semplice e prevedibile, ma il costo è diventato evidente man mano che i moduli superavano circa 30 campi.

v2 — Grafico delle dipendenze esplicito

I campi e le reazioni dichiaravano le loro dipendenze, in modo che solo le parti interessate del grafico fossero valutate.

Le reazioni sincrone erano veloci, ma le operazioni asincrone introducevano condizioni di contesa. Una risposta più lenta poteva sovrascrivere il risultato di una richiesta più recente.

v3 — Grafico delle dipendenze con protezione delle risposte obsolete

Ogni esecuzione asincrona riceve una protezione isLatest() prima di impegnarsi nel suo risultato.

{
  id: "load-states",
  watch: ["country"],
  run: async ({ values, setOptions, isLatest }) => {
    const states = await api.getStates(values.country);

    if (!isLatest()) return;

    setOptions("state", states);
  }
}

Questa è la versione attualmente utilizzata in produzione.

Decisioni che non ho ancora rimpianto

Accesso a livello di campo

Invece di avvolgere i componenti in <CanRead> o <CanEdit>, i requisiti di accesso fanno parte del contratto del campo.

Sottoscrizioni dettagliate

I campi si iscrivono solo ai valori utilizzati esplicitamente dalle loro condizioni e reazioni, mentre React Hook Form rimane responsabile dello stato del modulo.

Uno schema, due renderer

Lo stesso contratto può attualmente essere reso tramite u/dashforge /tw o u/dashforge /mui.

<Field
  name="taxId"
  visibleWhen={{ field: "country", equals: "IT" }}
  access={{
    resource: "customer.taxId",
    action: "read"
  }}
  validation={{
    required: true,
    pattern: /^IT\d{11}$/
  }}
/>

Decisioni su cui ho ancora dei dubbi

1. Condizioni serializzabili vs funzioni

Dashforge utilizza condizioni dichiarative:

visibleWhen: {
  field: "country",
  equals: "IT"
}

anziché:

visibleWhen: values => values.country === "IT"

La forma dell'oggetto è più restrittiva, ma rimane serializzabile, ispezionabile e utilizzabile dagli strumenti visivi.

Accetteresti una riduzione dell'espressività per questo, o dovrebbero le funzioni rimanere una via d'uscita?

2. Due renderer UI

MUI e Tailwind condividono lo stesso schema e livello di orchestrazione.

Per un'unica applicazione potrebbe essere un'astrazione non necessaria. Per le organizzazioni che mantengono più prodotti o interfacce, potrebbe essere veramente utile.

Non sono ancora sicuro di dove si trovi quel confine.

3. Valutazione degli accessi a runtime

I permessi vengono valutati durante il rendering perché politiche e soggetti possono cambiare dinamicamente.

La valutazione a tempo di compilazione ridurrebbe il lavoro a runtime, ma renderebbe anche le politiche dinamiche considerevolmente più difficili.

Vuoi mantenere questo a runtime, compilare ciò che può essere compilato, o usare un approccio ibrido?

Provalo

Il CLI genera un'applicazione completa React 19 + TypeScript invece di un vuoto avviamento:

Sono disponibili due varianti UI.

Tailwind CSS

npx dashforge-cli my-app --lib tw

La variante Tailwind include:

  • u/dashforge /tw
  • Tema Tailwind e token di design
  • tw-theme
  • tw-tokens
  • dashforgePreset()
  • DashforgeTailwindProvider
  • Controllo della modalità scura tramite toggleMode()

Mui

npx dashforge-cli my-app --lib mui

La variante Material UI include:

  • u/dashforge /ui
  • theme-mui
  • Token di design condivisi
  • Material UI
  • DashforgeThemeProvider
  • Modalità scura tramite cambio tema

Entrambe le varianti generano la stessa struttura applicativa con opinione:

  • Shell dell'app con navigazione laterale, barra superiore e selettore di spazio di lavoro
  • Quattro schede statistiche
  • Due schede di esempio con segnaposto per grafico
  • Tabella di dati fittizi
  • Modalità framework React Router
  • Rendering statico pre-rendering per / e /sign-in
  • Autenticazione fittizia
  • Percorsi protetti
  • Integrazione RBAC
  • Moduli Dashforge
  • CRUD utenti collegati a un'API in stile kit

Il CLI attualmente offre un template:

--template dashboard

L'obiettivo è ridurre l'attrito nella configurazione iniziale e permettere agli sviluppatori di valutare Dashforge all'interno di un'applicazione realistica invece di assemblare autenticazione, routing, layout, tematizzazione, permessi e moduli prima di poter provare il framework stesso.

Progetto

Repository: https://github.com/kensaadi/dashforge

Documentazione: https://dashforge-ui.com

Licenza MIT, con otto pacchetti attualmente pubblicati su npm.

La domanda che mi interessa di più: dove tracceresti il confine tra serializzabilità e normali funzioni React?

u/kensaadi — 11 days ago
▲ 1 r/tailwindcss+1 crossposts

How much time do you spend on setup before writing the first line of UI?

Every time I start a new React dashboard I lose a solid 20–30 minutes before I've

written a single line of actual UI: installing libraries, wiring the theme, setting

up auth/RBAC, getting forms to behave. It's the least fun part and it's basically

the same every project.

Curious how others deal with it:

- A personal starter/boilerplate you clone?

- A generator/CLI?

- Copy-paste from the last project?

- Or just eat the setup cost every time?

And roughly how long does it take you to go from "empty folder" to "something

running on screen"?

(For context: I got tired enough of it that I built a small CLI that scaffolds a

working admin template — RBAC + forms included — in one command. Happy to share if

it's useful, but mostly I'm curious how everyone else handles the setup tax.)

reddit.com
u/kensaadi — 12 days ago
▲ 6 r/softwarearchitecture+1 crossposts

Anyone here shipped Server-Driven UI in a React production app?

Anyone shipped Server-Driven UI in a React production app? Looking for war stories, not blog posts

I've read the usual Server-Driven UI case studies from large product teams, and I'm currently evaluating the architecture for a project.

The theory is clear. What I'm looking for now is feedback from people who actually shipped SDUI in production and discovered where the model starts to break.

You definitely don't need to answer everything. Even one painful lesson, failed approach, or unexpected trade-off would be useful.

1. Contracts and versioning

What did your server actually send?

  • A fixed JSON schema?
  • A typed contract with generated client types?
  • Something more flexible or ad-hoc?

How did you handle backward compatibility when the server contract or client component registry changed?

Did you validate payloads at runtime with Zod, JSON Schema, or something similar?

2. Component registry and extensibility

How did you map definitions such as:

{
  "type": "TextInput",
  "props": {
    "label": "Email"
  }
}

to actual React components?

Was the registry closed and centrally controlled, or could product teams register their own components?

What happened when a payload referenced a component or prop that an older client did not support?

3. Layout and responsive behavior

Did layout live inside the server payload, or did the client retain control over composition?

For example:

  • flex/grid definitions in the payload;
  • semantic layout primitives;
  • fixed client-side templates;
  • a hybrid approach.

Who controlled responsive behavior and breakpoints?

Did mobile and desktop receive different payloads, or did the same definition adapt entirely on the client?

4. Logic, state, and forms

This is the area I'm most interested in.

How much behavior did you allow into the contract before it started becoming a programming language of its own?

In particular, how did you model:

  • conditional visibility;
  • RBAC and component-level access;
  • cascading form fields;
  • dependent validation;
  • multi-step workflows;
  • save-and-resume;
  • role-based branching;
  • asynchronous data loading;
  • domain events and analytics?

Which logic stayed on the server, which was represented declaratively in the payload, and which remained inside the client?

Where did the state live for complex forms and long-running workflows?

5. Development experience and testing

How did developers preview and debug server-driven screens?

Did you build an internal visual editor, use fixtures and Storybook, or rely on editing payloads and refreshing the application?

What gave you the most confidence?

  • Payload snapshots
  • Contract tests between server and client
  • Runtime validation
  • Generated types
  • End-to-end tests
  • Something else

Most importantly: what architectural decision looked good initially but became painful in production?

I've read the theory. I'm interested in the pragmatism of people who shipped this, maintained it, migrated it, or eventually removed parts of it.

Success stories are useful, but failures and traps are probably even more valuable.

reddit.com
u/kensaadi — 14 days ago

I rewrote Dashforge’s reactive engine 3 times — here’s the version that survived production

I’ve spent the last 8 months building Dashforge, an MIT-licensed React framework for schema-driven forms, access control and UI orchestration.

The project is now public, but before asking anyone to try it, I’d like some technical pushback on three decisions I’m still not completely sure about.

The reactive engine took three attempts

v1 — Re-evaluate the entire form

Every field change caused all conditions and reactions to run again.

Simple and predictable, but the cost became noticeable as forms grew beyond roughly 30 fields.

v2 — Explicit dependency graph

Fields and reactions declared their dependencies, so only the affected parts of the graph were evaluated.

Synchronous reactions were fast, but async operations introduced race conditions. A slower response could overwrite the result of a newer request.

v3 — Dependency graph with stale-response protection

Each async execution receives an isLatest() guard before committing its result.

{
  id: "load-states",
  watch: ["country"],
  run: async ({ values, setOptions, isLatest }) => {
    const states = await api.getStates(values.country);

    if (!isLatest()) return;

    setOptions("state", states);
  }
}

This is the version currently surviving production use.

Decisions I haven’t regretted yet

Field-level access

Instead of wrapping components in <CanRead> or <CanEdit>, access requirements are part of the field contract.

Fine-grained subscriptions

Fields subscribe only to the values explicitly used by their conditions and reactions, while React Hook Form remains responsible for form state.

One schema, two renderers

The same contract can currently be rendered through u/dashforge/tw or u/dashforge/mui.

<Field
  name="taxId"
  visibleWhen={{ field: "country", equals: "IT" }}
  access={{
    resource: "customer.taxId",
    action: "read"
  }}
  validation={{
    required: true,
    pattern: /^IT\d{11}$/
  }}
/>

Decisions I’m still questioning

1. Serializable conditions vs functions

Dashforge uses declarative conditions:

visibleWhen: {
  field: "country",
  equals: "IT"
}

rather than:

visibleWhen: values => values.country === "IT"

The object form is more restrictive, but it remains serializable, inspectable and usable by visual tooling.

Would you accept reduced expressiveness for that, or should functions remain an escape hatch?

2. Two UI renderers

MUI and Tailwind share the same schema and orchestration layer.

For a single application this may be unnecessary abstraction. For organizations maintaining multiple products or surfaces, it may be genuinely useful.

I’m not yet sure where that line is.

3. Runtime access evaluation

Permissions are evaluated while rendering because policies and subjects can change dynamically.

Compile-time evaluation would reduce runtime work, but would also make dynamic policies considerably harder.

Would you keep this at runtime, compile what can be compiled, or use a hybrid approach?

Try it

The CLI generates a complete React 19 + TypeScript application rather than an empty starter:

Two UI variants are available.

Tailwind CSS

npx dashforge-cli my-app --lib tw

The Tailwind variant includes:

  • u/dashforge/tw
  • Tailwind theme and design tokens
  • tw-theme
  • tw-tokens
  • dashforgePreset()
  • DashforgeTailwindProvider
  • Dark-mode control through toggleMode()

Mui

npx dashforge-cli my-app --lib mui

The Material UI variant includes:

  • u/dashforge/ui
  • theme-mui
  • Shared design tokens
  • Material UI
  • DashforgeThemeProvider
  • Dark mode through theme swapping

Both variants generate the same opinionated application structure:

  • App shell with side navigation, top bar and workspace switcher
  • Four statistic cards
  • Two example cards with chart placeholders
  • Mock data table
  • React Router framework mode
  • Static prerendering for / and /sign-in
  • Mock authentication
  • Protected routes
  • RBAC integration
  • Dashforge forms
  • Users CRUD connected to a kit-style API

The CLI currently ships one template:

--template dashboard

The goal is to reduce initial setup friction and let developers evaluate Dashforge inside a realistic application instead of assembling authentication, routing, layout, theming, permissions and forms before they can try the framework itself.

Project

Repository: https://github.com/kensaadi/dashforge

Documentation: https://dashforge-ui.com

MIT licensed, with eight packages currently published on npm.

The question I’m most interested in: where would you draw the line between serializability and ordinary React functions?

reddit.com
u/kensaadi — 15 days ago
▲ 2 r/tailwindcss+1 crossposts

Utility classes vs props-first styling in React design systems

I’ve used Tailwind in several React projects, and I think it solved a real problem: it made styling predictable, composable, and fast.

But in larger applications and design systems, I often find myself questioning code like this:

<div className="flex items-center justify-between gap-4 p-6">

Most concepts in React are exposed through APIs:

  • components
  • state
  • events
  • data fetching

Styling, however, often remains a string of utility classes that developers need to remember or look up.

I’ve been experimenting with a props-first approach:

<Flex align="center" justify="between" gap="4" padding="6">
  ...
</Flex>

The goal is not to reduce the number of characters or claim that Tailwind is bad.

The potential advantages are:

  • type safety
  • autocomplete and discoverability
  • constrained design-system values
  • easier refactoring
  • a consistent API across applications

There are obvious trade-offs too:

  • more wrapper components
  • an abstraction layer over CSS
  • potentially larger component APIs
  • escape hatches are still required
  • developers need to understand what the props generate

I don’t see this as replacing Tailwind. Tailwind could still be the implementation layer underneath these components.

The question is whether utility classes should remain the primary API developers use directly in large React applications, or whether a design system should expose a typed styling API instead.

For people working on larger React codebases or design systems: where do you draw the line?

Do you prefer utility classes directly in components, typed style props, primitives such as Stack and Flex, or a combination of these approaches?

reddit.com
u/kensaadi — 21 days ago

React forms eventually become state management systems

I keep seeing the same pattern in large React forms.

You start with react-hook-form.

Then you add:

  • dependent fields with watch() and useEffect
  • conditional visibility
  • permission-based fields
  • async validation
  • cross-field rules
  • server-driven defaults
  • reset and synchronization logic

At some point, the form is no longer just a form.

It has its own state transitions, dependencies, permissions, validation lifecycle, side effects, and derived state.

In other words, it has quietly become a state management system.

The problem is not necessarily react-hook-form. It does its job well.

The problem is that complex forms are often represented only as component trees, while their actual behavior is scattered across hooks, wrappers, schemas, and handlers.

Some warning signs:

  1. You have more useEffects than meaningful form sections
  2. Validation rules live in multiple places
  3. Adding something like “show taxId when country === IT” requires touching several files
  4. Permissions, visibility, and validation all use different abstractions
  5. Nobody is completely sure what resets when another field changes

I’ve been experimenting with treating forms as declarative contracts instead:

<Field
  name="taxId"
  visibleWhen={{ field: "country", equals: "IT" }}
  access={{ resource: "customer.taxId" }}
  validation={{ required: true }}
/>

The idea is to keep visibility, access, validation, and dependencies in one source of truth, instead of rebuilding the same orchestration through chains of effects.

Obviously, this introduces other trade-offs: abstraction cost, debugging complexity, schema design, and reduced flexibility in edge cases.

I’m curious how others handle this in large production forms.

Do you keep the logic inside React components, move it into a state machine, use a schema-driven approach, or accept the complexity as unavoidable?

reddit.com
u/kensaadi — 21 days ago