Tailwind, reinvented.Components, not utilities.
You're already shipping with Tailwind. Stop hand-rolling the same buttons, inputs, and modals every project. Dashforge gives you 24 typed components and 8 layout primitives that emit Tailwind utility classes you can still override. Same Tailwind you write today — half the markup, RHF-wired, RBAC-aware.
The composition layer Tailwind never shipped.
Atomic Design isn't a metaphor here — it's the package boundary. Tokens are the atoms (one source of truth), Foundation primitives are the molecules, composed forms and shells are the organisms. Build pages by composing layers, not by re-sprinkling utility classes.
Foundation primitives (the molecules)
All eight shipped in 0.2.0-beta. Browse the catalogue from the sidebar.
Style-first, or props-first?
Same WorkspaceSettings card. Same pixels in light and dark. Tailwind composes the visual; Dashforge composes the API — including the state. Pick what you'd rather write at 3 AM, and what you'd rather grep at noon.
51
LoC, utilities
19
LoC, props
−63%
Markup removed
- Toggle state lives inside the component
- Hover · focus · disabled · dark mode pre-wired
- A11y (role · aria-checked · keyboard) baked in
- Variants via props, not utility lookup
import { Switch, Checkbox, Button } from '@dashforge/tw';
export function WorkspaceSettings() {
return (
<div className="rounded-xl border border-neutral-200 bg-white p-6 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
<h3 className="text-lg font-semibold">Workspace settings</h3>
<p className="mb-5 text-sm text-neutral-600">Control access and notifications.</p>
<Switch label="Public access" helper="Anyone with the link can view." defaultChecked />
<Switch label="Email notifications" helper="Weekly activity digest." />
<Checkbox label="Require 2-factor authentication" />
<div className="mt-4 flex justify-end gap-2">
<Button variant="text">Cancel</Button>
<Button color="primary">Save changes</Button>
</div>
</div>
);
}Thirty-six lines of useState. Or thirteen.
Validation, touched state, error gating, submit wiring — handled. Pass the rules as a prop and move on. Same imports, same submit, same fields — only the glue is gone.
36
LoC, before
13
LoC, after
−64%
Code removed
- React Hook Form wired through DashForm
- Validation rules as a prop, not a callback
- Touched + dirty + error gating, automatic
- A11y attributes (aria-invalid, aria-describedby) for free
import { DashForm } from '@dashforge/forms';
import { TextField, Button } from '@dashforge/tw';
import { signIn } from './api';
export function SignIn() {
return (
<DashForm onSubmit={signIn}>
<TextField name="email" type="email" required rules={{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }} />
<TextField name="password" type="password" required rules={{ minLength: 8 }} />
<Button type="submit">Sign in</Button>
</DashForm>
);
}Thirty-six lines of useEffect. Or twenty-two.
Built on Dashforge's reactive engine — visibleWhen and access props handle conditional rendering and RBAC at the prop level. No scattered effects, no prop drilling, no manual permission gating in every component.
import { DashForm } from '@dashforge/forms';
import { RadioGroup, TextField, Button } from '@dashforge/tw';
export function SupportForm() {
return (
<DashForm>
<RadioGroup name="category" options={CATEGORIES} required />
<TextField
name="bugDetails"
label="Steps to reproduce"
visibleWhen={(engine) => engine.getNode('category')?.value === 'bug'}
required
/>
<Button
color="danger"
access={{ resource: 'invoice', action: 'delete', onUnauthorized: 'hide' }}
>
Delete invoice
</Button>
</DashForm>
);
}visibleWhen
Reactive predicate — the field unmounts/remounts when the dependency changes. Zero useEffect.
access
One prop wires the field to your RBAC layer. Hide, disable, or read-only by role.
rules
React Hook Form validation, declarative. Pass min/max/pattern/custom validators inline.
Ship in your existing Tailwind app today.
MIT-licensed, tree-shakeable, zero lock-in. Read the installation guide and have your first form running in under five minutes.