Browse docs
Browse docs
Released 2026-09-28
The first major. It exists because seven defects could not be fixed without changing something a consumer can observe: a ref type, the element a component renders, or which element scrolls.
Eleven packages, one version. There is no partial upgrade.
| Package | Change |
|---|---|
@dashforge/tw | 1.5.2 → 2.0.0 |
@dashforge/tw-theme · @dashforge/tw-tokens | → 2.0.0 |
@dashforge/ui · @dashforge/theme-mui · @dashforge/theme-core · @dashforge/tokens | → 2.0.0 |
@dashforge/forms · @dashforge/rbac · @dashforge/ui-core · @dashforge/calendar-core | → 2.0.0 |
Each one links to the before and after in the migration guide.
<Select>'s trigger ref is HTMLDivElement, not HTMLButtonElement.
<Select multiple> nested a <button> inside the trigger button, which
is invalid HTML and, worse, silently strips the inner control from the
accessibility tree: role="button" makes its descendants presentational,
so the delete control existed for a mouse and not for a screen reader.
The trigger is a div role="combobox" with an explicit tabIndex now.
<Slider>'s ref is HTMLDivElement, not HTMLSpanElement, and it now
actually arrives. The component was wrapped in forwardRef, named the
parameter, and never attached it, so ref.current stayed null forever
with no type error and no warning.
<Chip>'s clickable root is a div role="button" for the same reason as
<Select>: a chip that was both clickable and deletable nested a button
in a button.
<AppShell> defaults to layout="viewport"This changes which element scrolls. min-h-screen on the root made
main's scroller dead code, so the window scrolled and the header, nav
and footer scrolled away with it. Pass layout="page" for the old
behaviour.
<Divider orientation="vertical"> no longer spans the container width.
It came out w-full, so in a nowrap row it squeezed its siblings and
with wrap it pushed every group onto its own line.
<TopBar>'s start slot can shrink. It carried min-w-0 and shrink-0
together, which cancel: the first exists to let an item shrink, the
second forbids it. A long brand kept its full width and pushed center
and end out of the bar.
<Stack divider> supplies the orientation you did not choose. A
<Divider> defaults to horizontal, which is h-0 w-full: in a row that
is nothing to see plus a claim on the whole width, so the items were
squeezed rather than separated. An explicit orientation still wins, and
a divider that is not ours is passed through untouched.
<Chip variant="outline" color="neutral">'s border moved from
neutral-300 to neutral-500. At 1.42:1 light and 1.91:1 dark it failed
WCAG 1.4.11, and an outline chip IS its border. It was also the only
colour row at -300 while the other six sat at -500.
<Kbd>A keycap. It renders the semantic <kbd> element, which is the only
reason it needs to exist: a styled <span> gives you the look and not
the meaning. Chords compose rather than taking a keys array, because
baking one in would decide ⌘ + K against ⌘K for every consumer.
See Kbd.
<TopBarBrand>The mark-plus-text block for a <TopBar>'s start slot, which almost
every app was rebuilding. A sibling export rather than TopBar.Brand,
matching this catalog's convention. Layout only: a brand that navigates
home is a link wrapped around it.
See TopBar → TopBarBrand.
<Button> gains pressed and color="inverse"pressed is a toggle state, and its ARIA is opt-in: omit the prop
and nothing is emitted, not even aria-pressed="false", because
announcing "not pressed" on an ordinary action would make every plain
button in the catalog lie. The "on" look is LeftNav's active-row
treatment rather than something invented.
color="inverse" reads the theme-invariant inverse role, for surfaces
that stay dark whatever the theme does.
<Chip draggable>, with onDragStart and onDragEnd. Native
HTML5 drag-and-drop, so no dnd dependency is pulled in and the
dataTransfer payload stays yours. A drag is a pointer gesture:
native DnD is not keyboard operable, so pair it with an onClick that
does the same thing or the palette fails WCAG 2.1.1 in your app.<Box borderStyle> (solid | dashed | dotted | double),
orthogonal to variant: a soft box can be dashed.access and visibleWhen on <Stack> and <Grid>, for parity
with <Box>. A consumer passing neither gets byte-identical output.<Calendar showSiblingDays>, and slotProps.day as a function of
the day's state. The flat object form still type-checks.<Stack> warns in development on direction="column" and on
spacing, naming the value, the accepted set and the likely intent.<Calendar> failed WCAG
contrast: 2.42:1 light and 2.53:1 dark, where the floor for an active
control's text at 14px is 4.5:1.rounded ignored the radius tokens, in ten places across
eight components. The preset extends borderRadius and defines no
DEFAULT, so the bare utility keeps Tailwind's hard-coded 0.25rem
and is immune to the scale by construction.<Dialog> and <Drawer> ringed the close button on :focus
rather than :focus-visible, so it lit up on every mouse click.body and lengthened the page.<Drawer> sat on MUI's z ladder, so a <Dialog> opened from
inside it rendered behind.2xs token tier.tsBuildInfoFile sat outside
dist, so deleting dist left a valid incremental cache describing
declarations that no longer existed: tsc --build emitted nothing and
dependents failed with TS6305.Upgrade every @dashforge/* package in one step.
pnpm add @dashforge/tw@2 @dashforge/tw-theme@2 @dashforge/tw-tokens@2 \
@dashforge/forms@2 @dashforge/ui-core@2 @dashforge/rbac@2 \
@dashforge/calendar-core@2If your lockfile resists, delete node_modules and the @dashforge/*
entries from it, then install again.
Then work through the
migration guide,
which is organised by who is affected rather than by package: most
consumers touch nothing, and the ones who do are the ones holding a
ref to a <Select>, <Slider> or <Chip>, or relying on the window
scrolling under an <AppShell>.
| Peer | Required range |
|---|---|
react | ^18.0.0 || ^19.0.0 |
tailwindcss | >=3.4.1 <5 |
@dashforge/tw-theme | 2.0.0 exactly |
@dashforge/tw-tokens | 2.0.0 exactly |
The Tailwind range gained an upper bound in this release, at <5 rather
than <4: Tailwind 4 works. dashforgePreset() is a JavaScript
config object, which v4 still accepts, and the catalog is built and
tested against v4 in the monorepo.
What is not there yet is the CSS-first authoring path that v4 introduced,
where configuration moves into @theme instead of a config file. That is
#141, for a later
cycle. Until then you configure Dashforge the v3 way on either major.
No new runtime dependencies.