Browse docs
Browse docs
A few patterns in @dashforge/tw look like inconsistencies at first
glance, but they're intentional category-specific choices. This page
documents the most surprising ones so contributors and consumers can
read intent, not guess it.
If you read the variant axis across the catalogue you'll notice the prop values are not the same word in every component:
| Component | Variants |
|---|---|
<Button>, <IconButton> | solid · outline · ghost · link |
<Chip> | soft · solid · outline |
<Alert>, <Snackbar> | standard · filled · outlined |
<Card> | outlined · elevated · plain |
This is not drift — it's three separate visual vocabularies that each reflect the component's category:
Button/IconButton) inherit the MUI lineage
(solid / outline / text → renamed link for clarity), where
ghost is the dedicated no-bg interactive treatment.Alert / Snackbar) ship the MUI Alert
vocabulary (standard / filled / outlined) because consumers
migrating from MUI find call sites that translate 1-to-1.Chip) carry their own (soft / solid /
outline) because soft (tinted bg with primary fg) is the
natural Chip default and doesn't exist on Button/Alert.Card) have elevated because card shadow
is the discriminator visitors expect, not solid/filled.Trying to unify these into a single vocabulary would either lose
information (the soft Chip would have to become "outlined-fill" or
similar) or burden every consumer with translation. The asymmetry is
the right cost.
Rule for new components: pick the vocabulary that best fits the component's category, not the one closest to an existing component in a different category. Then document the chosen axis in the API table.
As of @dashforge/[email protected] every interactive component in the
catalog supports the same two bridge props:
| Prop | Type | Behavior |
|---|---|---|
access | AccessSpec | RBAC gating via @dashforge/rbac. Hide / disable / readonly modes. |
visibleWhen | (engine: Engine) => boolean | Engine-reactive predicate via @dashforge/forms. Returns null when false. |
The list of covered components, after the Sprint 4.4 retrofit:
TextField, Checkbox, Switch, RadioGroup,
Textarea, NumberField, OTPField, Autocomplete, DatePicker,
TimePicker, DateTimePicker, DateRangePickerButton, IconButtonBox (and Card via Box inheritance)Chip, Avatar, BadgeAlertMenuItemThe rule going forward: any new interactive component ships with these two props from day-1. Pure presentational atoms (Spinner, Divider, AvatarGroup, MenuLabel, MenuSeparator) do not — they have no behavior to gate.
The two props are byte-identical to the MUI side via the shared
@dashforge/ui-core + @dashforge/forms packages. Form schemas and
RBAC declarations are portable across flavors.
className / sx raw in our internal contractsThe @dashforge/tw components themselves all accept className (it
merges via tailwind-merge, your classes win). That escape hatch is
for consumers building app surfaces.
But internal contracts — the ones our own components and the
upcoming Blueprint runtime use — should NOT accept className or
sx raw. Styling lives in token-driven Box-style props (p, m,
bg, rounded, elevation, etc.) that compile down to the right
Tailwind classes through dashforgePreset(). The token layer is the
contract between Figma and React; bypassing it with raw CSS leaks
ad-hoc styling into the system.
The rule: every standard layout / spacing / surface concept ships as a typed prop with token values. Custom CSS belongs in a custom component, not in a prop string.
severity="danger" instead of "error"<Alert> and <Snackbar> accept severity="danger" where MUI uses
"error". This deliberately aligns the prop value with the
danger.* token palette name exposed by dashforgePreset() (and by
@dashforge/tw-tokens). The visual is identical; only the prop
value differs.
Migrating from MUI: search-and-replace severity="error" →
severity="danger". No other change needed.
libs/dashforge/tw/PARITY.md — per-component parity audit + the
"TW-lead Presentational primitives" section that lists the Sprint
4.4 additions as intentional MUI-flavor gaps.*.types.ts files — the variant unions are typed
literals; the asymmetry is captured in TypeScript and surfaces in
IDE autocomplete.If you find a component where one of these decisions is violated,
open an issue and mention @dashforge/tw design parity.