@wandelbots/mui
v1.7.2
Published
Wandelbots Nova MUI layer: dark-mode MUI-7 theme (createNovaMuiTheme) + presentational React components and input validators.
Keywords
Readme
@wandelbots/mui
The MUI layer of the Wandelbots Nova design system: a thin, visual-only React component library holding the Wandelbots company-CI theme and small presentational components (inputs, selects, buttons, layout primitives, input parsers/validators). Visual building blocks are defined once here and reused consistently across apps.
This is the only package that ships a MUI theme. @wandelbots/design-tokens stays
framework-agnostic (values only); the theme composes those values into MUI shape here.
Pick @wandelbots/mui or @wandelbots/hui — never both. The two are alternatives,
not companions: they cover the same primitives from the same design tokens through two
different styling systems. MUI apps take this package; headless / non-MUI apps take
@wandelbots/hui.
Non-goals: no domain logic, no data fetching, no API/WebSocket code, no robot/motion
knowledge. Those live in wandelbots-js-react-components (separate repo).
- Peers: React 19, MUI 7, emotion 11. Depends on
@wandelbots/design-tokens(workspace:*). - Fonts: the theme references the font-family tokens only. To actually load the faces,
the consuming app imports
@wandelbots/fonts/cssonce at its entry point — otherwise the stacks fall back tosystem-ui. - Dark mode only
- Built with Vite library mode (ESM + CJS), TypeScript strict, Biome, Vitest + Storybook.
See AGENTS.md for the working conventions, and the monorepo root
README.md for the design-system roadmap.
Theme
One function. createNovaMuiTheme(...overrides) is the entire theme API — Nova palette,
paletteExt, elevation ramp, typography and component overrides in a single Theme. Dark by
default, since dark is the only mode in v1.
import { ThemeProvider } from "@mui/material/styles"
import { createNovaMuiTheme } from "@wandelbots/mui"
// Build it once, at module scope — a new Theme object on every render would
// defeat emotion's style caching.
const theme = createNovaMuiTheme()
function App() {
return <ThemeProvider theme={theme}>...</ThemeProvider>
}Nothing else is exported, deliberately: no pre-built theme constant (so importing the package
never eagerly builds a theme you didn't ask for), and no darkBaseOptions() /
commonComponents() building blocks (so the internal layering stays free to change). Customise
by passing ThemeOptions, or compose with MUI's own createTheme — and light mode will land
inside this factory rather than as a second export.
Importing the package also activates the type augmentation, so theme.palette.tertiary.main,
theme.paletteExt.primary.hover, and theme.palette.backgroundPaperElevation[8] are all typed.
Package-specific theme extensions (jogging-panel colors, DataGrid styling slots) belong in the
consuming package — add your own declare module "@mui/material/styles" there.
Overriding and extending
The factory is variadic and uses MUI's canonical createTheme(base, ...overrides) for
deep-merging. Pass any number of ThemeOptions; each layer merges on top of the previous one,
and the last one wins.
const theme = createNovaMuiTheme({
palette: { primary: { main: "#ff00ff" } },
components: {
MuiDialog: { styleOverrides: { paper: { borderRadius: 16 } } },
},
})
// theme.palette.primary.main → "#ff00ff"
// theme.paletteExt.primary.hover → preserved from base
// theme.components.MuiButton.* → preserved (deep-merge, not replace)// Multi-layer: app-wide plus feature-specific
const theme = createNovaMuiTheme(appOverrides, joggingPanelOverrides)
// Or layer on afterwards with MUI's own createTheme
import { createTheme } from "@mui/material/styles"
const theme = createTheme(createNovaMuiTheme(), myOverrides)How propagation works. Nova's palette-dependent component overrides are MUI function-form callbacks — they aren't called when the theme is built, only stored as function references. MUI invokes them at component render time with the final merged theme from
<ThemeProvider>, so the order in which you build the theme doesn't matter: an override likecreateNovaMuiTheme({ palette: { text: { secondary: "#abcdef" } } })still reachesMuiTab'scolor. The one exception isMuiCssBaseline.styleOverrides, deliberately a static object so consumer global selectors deep-merge instead of replacing Nova's scrollbar rules.
Light mode
Not supported yet — createNovaMuiTheme({ palette: { mode: "light" } }) throws. When the upstream
Figma sync ships light tokens, light mode will be added inside createNovaMuiTheme (via MUI 7's
colorSchemes), so existing call sites won't change and no new export appears.
Migrating from @wandelbots/design-tokens/mui
The theme moved out of the token package. The function keeps its name, so for most apps it's a one-line import change:
- import { createNovaMuiTheme } from "@wandelbots/design-tokens/mui"
+ import { createNovaMuiTheme } from "@wandelbots/mui"It is also a visual change, though. createNovaMuiTheme now includes the NovaDeck-derived
styling the old token-package version lacked — background.default: #161825, body2 at 14/400,
and MuiAutocomplete / MuiSelect root / MuiTooltip overrides. If your app was adding those
locally to compensate, drop them.
Two other things changed:
createVisualThemeis gone — it was the layer that added exactly that styling. Everything it did is now increateNovaMuiTheme, so replace the call one-for-one.darkTheme,darkBaseOptions()andcommonComponents()are gone. Replace adarkThemeimport with a module-scopeconst theme = createNovaMuiTheme(). Anything built on the building blocks should usecreateNovaMuiTheme(...overrides), orcreateTheme(createNovaMuiTheme(), ...)to layer on afterwards.
Keep @wandelbots/design-tokens installed for raw token values and CSS variables — it just no
longer carries a /mui entry.
Scripts
Run from the monorepo root with pnpm --filter @wandelbots/mui <script>.
| Script | Purpose |
|---|---|
| pnpm build | Vite library build + .d.ts emit → dist/ |
| pnpm dev | Storybook dev server on :6006 |
| pnpm typecheck | tsc --noEmit over shippable source |
| pnpm test / pnpm test:unit | Vitest unit tests |
| pnpm verify | build + unit + Storybook interaction/smoke tests → VERIFY: PASS |
| pnpm screenshot <storyId> | Playwright screenshot of a story → .screenshots/ |
Lint/format are repo-wide from the root: pnpm lint, pnpm format (Biome).
