name: nest-mui description: Use when working with MUI components, sx prop styling, theme tokens, dialogs, snackbars, loading states, or ag-Grid DataGrid wrapper in the Nest monorepo. Triggers on Material-UI, MUI v7, sx prop, theme customization, AppSnackbar, LoadingBackdrop, useDialog, DataGrid, NestMuiTheme.
MUI v7 Patterns — Nest Monorepo
Material-UI v7 (@mui/material ^7.3.11) with Emotion styling. Four theme files exist per app context (note: @mui/x-date-pickers is on its own major, ^8.x, independent of material's v7). Primary styling is sx prop inline — no CSS modules, no separate style files.
Project-Specific Architecture
Theme Files
| App | Path | Extras |
|---|---|---|
| Provider Portal | apps/frontend/provider-portal/src/components/NestMuiTheme.ts |
nestBrandColors, assistantColors, custom Chip tertiary variant |
| Patient Navigator | apps/frontend/patient-navigator/src/components/NestMuiTheme.ts |
Account-level custom primaryColor, secondaryColor, background |
| Retool | apps/retool/components/src/NestMuiTheme.ts |
Shared subset |
| Yoda | apps/frontend/yoda/src/theme/theme.ts (NOT NestMuiTheme.ts) |
Dark-mode-first (rgb(24,25,27) / light #f5f5f5), DM Sans headings, own nestBrandColors |
Provider Portal / Patient Navigator / Retool export getTheme(mode, options?), mixins, and getPreferredThemeMode(). Yoda's theme exports getTheme(mode) (no options param) and getPreferredThemeMode(), but no mixins — its file lives under src/theme/, not src/components/.
Shared Theme Defaults
fontFamily: Inter, "Helvetica Neue", Arial, sans-serif
shape.borderRadius: 6
Button: disableElevation, borderRadius 8, textTransform 'none'
InputLabel: shrink always true, position relative (labels above fields)
OutlinedInput: notched legend width 0 (no notch gap)
Mixins (sx-compatible reusable styles)
import { mixins } from '@nest/provider-portal/components/NestMuiTheme'
<Box sx={mixins.scrollVertical} /> // Hidden scrollbar, vertical overflow
<Box sx={mixins.scrollHorizontal} /> // Hidden scrollbar, horizontal overflow
<Box sx={mixins.bgBlur} /> // Frosted glass backdrop
<Box sx={mixins.textOverflowEllipsis} /> // Truncate with ellipsis
<Box sx={mixins.textOverflowEllipsisMultiline} /> // Clamp to 2 lines with ellipsis
mixinslives on the provider-portal / patient-navigator / retool themes only. Yoda has nomixinsexport — replicate the style inline insxthere.
Brand Colors (Provider Portal)
import { nestBrandColors } from "@nest/provider-portal/components/NestMuiTheme";
// Each color: base, light20, light40, light60, light80
nestBrandColors.orange.base; // '#e65732' (primary)
nestBrandColors.gold.base; // '#e78d32'
nestBrandColors.pink.base; // '#c45567'
nestBrandColors.darkRed.base; // '#a23c23'
nestBrandColors.purple.base; // '#a367c8'
Import Patterns
// Named imports from barrel (most common)
import { Box, Button, Typography, Stack } from "@mui/material";
// Icons — always individual deep imports
import SearchIcon from "@mui/icons-material/Search";
import EditRoundedIcon from "@mui/icons-material/EditRounded";
// Date pickers
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
Styling with sx Prop
The repo styles with inline sx={{...}} object literals directly on the JSX — this is overwhelmingly the convention (~1,965 uses in provider-portal, ~882 in Yoda). There are no .styles.ts files and no extracted Record<string, SxProps<Theme>> style maps anywhere in the codebase; don't introduce them. Keep styling inline so new code reads like its neighbors.
function MyComponent() {
return (
<Paper sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
<Typography sx={{ mb: 3, fontSize: '1.5rem', fontWeight: 600 }}>Title</Typography>
</Paper>
)
}
SxProps<Theme> does appear (~29 files), but as a passthrough prop type so a component can forward caller styling — not as a styles object:
import type { SxProps, Theme } from '@mui/material'
function Field({ sx }: { sx?: SxProps<Theme> }) {
return <Box sx={sx} />
}
Theme Callback in sx
<Box
sx={(theme) => ({
color: theme.palette.primary.main,
bgcolor: alpha(theme.palette.background.default, 0.95),
'&:hover': { color: theme.palette.primary.dark },
})}
/>
Responsive Values
<Box
sx={{
width: { xs: '100%', sm: '80%', md: '60%' },
display: { xs: 'none', md: 'block' },
fontSize: { xs: '1rem', md: '1.5rem' },
}}
/>
Project Hooks & Components
useDialog (shared)
import { useDialog } from '@nest/shared/hooks'
function MyComponent() {
const [dialogProps, { setOpen }] = useDialog()
return (
<>
<Button onClick={() => setOpen(true)}>Open</Button>
<Dialog {...dialogProps}>
<DialogTitle>Confirm</DialogTitle>
<DialogContent>Are you sure?</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={handleConfirm} variant="contained">Confirm</Button>
</DialogActions>
</Dialog>
</>
)
}
Snackbars are per-app — pick the right one
There is no single shared snackbar. Each app rolls its own; never import another app's:
| App | Helper | Import |
|---|---|---|
| Provider Portal | useAppSnackbar() (global provider, show({...})) |
@nest/provider-portal/components/AppSnackbar |
| Yoda | useSnackbar() (local state, showSnackbar(msg, severity)) |
@nest/yoda/hooks/useSnackbar |
| Patient Navigator | ErrorSnackbar / CarePlanSnackbarContext |
@nest/patient-navigator/components/* |
AppSnackbar (provider-portal)
import { useAppSnackbar } from "@nest/provider-portal/components/AppSnackbar";
function MyComponent() {
const { show } = useAppSnackbar();
const handleSave = async () => {
try {
await saveData();
show({ severity: "success", message: "Saved successfully" });
} catch {
show({
severity: "error",
message: "Failed to save",
action: { label: "Retry", onClick: handleSave },
});
}
};
}
Options: severity ('success' | 'error' | 'warning' | 'info'), message (ReactNode), action ({ label, onClick }), autoHideDuration (default 8000ms).
LoadingBackdrop (provider-portal)
import LoadingBackdrop from '@nest/provider-portal/components/LoadingBackdrop'
// Simple loading
<LoadingBackdrop open={isLoading} />
// With upload progress (shows percentage)
<LoadingBackdrop open={isUploading} progress={uploadPercent} />
Shared TextField (libs/shared)
import { TextField } from '@nest/shared/components'
// Standard — defaults to size="small", variant="outlined"
<TextField label="Name" value={name} onChange={handleChange} />
// As select with options
<TextField label="Status" options={[
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
]} />
// Read-only display mode
<TextField label="Email" value={email} asDisplay />
// Loading skeleton
<TextField label="Name" loading />
SubmitButton (provider-portal)
import SubmitButton from '@nest/provider-portal/components/SubmitButton'
<SubmitButton loading={isSaving}>Save Changes</SubmitButton>
// Shows CircularProgress overlay when loading, disables button
Yoda Primitives (do NOT reuse provider-portal ones)
Yoda is its own app with its own theme and its own snackbar/dialog helpers under apps/frontend/yoda/src/. useAppSnackbar, LoadingBackdrop, and SubmitButton are provider-portal-only — they are not importable from Yoda. Use Yoda's local equivalents instead.
useSnackbar (Yoda)
Local-state hook (apps/frontend/yoda/src/hooks/useSnackbar.ts), not a global provider. Severity is 'success' | 'error' only.
import { useSnackbar } from '@nest/yoda/hooks/useSnackbar'
const { showSnackbar, closeSnackbar, snackbar } = useSnackbar()
showSnackbar('Saved', 'success')
// Render <AppSnackbar {...snackbar} onClose={closeSnackbar} /> from @nest/yoda/components/ui
ConfirmDialog (Yoda)
Default export at apps/frontend/yoda/src/components/ui/ConfirmDialog.tsx. Prefer it over window.confirm() (not themeable, blocks the JS thread).
import ConfirmDialog from '@nest/yoda/components/ui/ConfirmDialog'
<ConfirmDialog open={!!target} onConfirm={handleConfirm} onCancel={() => setTarget(null)} />
DataGrid (ag-Grid Wrapper, Provider Portal)
The provider portal uses ag-Grid, NOT @mui/x-data-grid. The custom wrapper applies MUI theme tokens.
import { DataGrid, DataGridContainer } from '@nest/provider-portal/components/DataGrid'
<DataGridContainer>
<DataGrid
gridId="patients-grid"
storePreferences // Persists column widths/order to localStorage
columnDefs={columnDefs}
rowData={patients}
totalRowCount={patients.length}
noRowsOverlayComponentParams={{
title: 'No patients found',
body: 'Try adjusting your filters',
}}
/>
</DataGridContainer>
Key props: gridId + storePreferences for localStorage persistence, totalRowCount drives empty state, footerContent for custom footer.
Common Patterns
Card
<Card>
<CardContent>
<Typography variant="h5" component="div">Title</Typography>
<Typography variant="body2" color="text.secondary">Description</Typography>
</CardContent>
<CardActions>
<Button size="small">Learn More</Button>
</CardActions>
</Card>
Loading States
// Full-page backdrop
<LoadingBackdrop open={loading} />
// Inline spinner
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
// Content skeletons
<Stack spacing={1}>
<Skeleton variant="text" width="60%" />
<Skeleton variant="rectangular" height={200} />
</Stack>
// Button with loading
<SubmitButton loading={saving}>Save</SubmitButton>
Form Layout
<Box component="form" onSubmit={handleSubmit}>
<Stack spacing={2}>
<TextField
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
fullWidth
required
error={!!errors.email}
helperText={errors.email}
/>
<SubmitButton loading={submitting}>Submit</SubmitButton>
</Stack>
</Box>
Best Practices
Style inline, type only the passthrough
// Good — inline sx literal, the repo convention
<Box sx={{ p: 2, gap: 1 }} />
// Good — when a component forwards caller styling, type the prop
function Card({ sx }: { sx?: SxProps<Theme> }) { return <Box sx={sx} /> }
// Bad — extracted styles maps / .styles.ts files (not used anywhere here)
const styles: Record<string, SxProps<Theme>> = { container: { p: 2 } };
Use project wrappers over raw MUI
useDialog(provider-portal) /ConfirmDialog(Yoda) over manual open/close state- The app's own snackbar helper over a custom Snackbar setup (see per-app table above)
- Shared
TextFieldover raw MUITextField SubmitButtonover Button + CircularProgressLoadingBackdropover Backdrop + CircularProgress
Use data-testid for test selectors
<Button data-testid="submit-btn" variant="contained">Submit</Button>
styled() is rare — only for non-MUI elements needing theme access
// Only when wrapping third-party components that can't use sx
import { styled } from "@mui/material";
const StyledCalendar = styled(Calendar)(({ theme }) => ({
color: theme.palette.text.primary,
}));