Full-codebase-audit 2026-05-30 outputs: - Audit report: 09 - Audits/Full Codebase Audit - 2026-05-30.md - 81 issue files ISSUE-055..135 (decisions + 1 skipped no-brainer). - Scanner docs from scratch (was zero): architecture, data model, API ref, payment flow, operations runbook + repo README. - Doc-sync updates across API reference, data models, flows, design system. - Secret Rotation Runbook (08 - Operations) for the exposed credentials. - Reusable workflow guide (07 - Development) + .claude/workflows/full-codebase-audit.js. Issues remain status:open intentionally — the code fixes are uncommitted-then-committed working-tree changes per repo and aren't "resolved" until merged/deployed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
255 lines
8.1 KiB
Markdown
255 lines
8.1 KiB
Markdown
---
|
||
title: Theme Configuration
|
||
tags: [design-system, theme, mui]
|
||
created: 2026-05-23
|
||
updated: 2026-05-30
|
||
---
|
||
|
||
# Theme Configuration
|
||
|
||
> [!info] Amaneh v2.7.0 (commit 56fc84e)
|
||
> The active theme now uses the Amaneh warm-earth palette and the three-font stack (Source Serif 4 / IBM Plex Sans / IBM Plex Mono). MUI component overrides were updated for `Button`, `Card`, `Paper`, `AppBar`, `Chip`, and `Label`. The settings-drawer color-preset swatch picker was simplified to a single Amaneh entry.
|
||
|
||
The MUI theme is constructed in `frontend/src/theme/index.ts` and composed from option modules in `frontend/src/theme/options/`. The resulting theme is provided at the root layout, wrapped by an RTL-aware emotion cache.
|
||
|
||
---
|
||
|
||
## 1. Construction pipeline
|
||
|
||
```ts
|
||
// approximate — read theme/index.ts for the canonical version
|
||
import { createTheme } from '@mui/material/styles';
|
||
import { palette } from './options/palette';
|
||
import { typography } from './options/typography';
|
||
import { shadows, customShadows } from './options/shadows';
|
||
import { componentsOverrides } from './options/overrides';
|
||
|
||
export function buildTheme(opts: { mode: 'light' | 'dark'; direction: 'ltr' | 'rtl'; preset: string; }) {
|
||
return createTheme({
|
||
direction: opts.direction,
|
||
palette: palette(opts.mode, opts.preset),
|
||
typography,
|
||
shape: { borderRadius: 8 },
|
||
shadows: shadows(opts.mode),
|
||
customShadows: customShadows(opts.mode),
|
||
components: componentsOverrides(opts),
|
||
breakpoints: {
|
||
values: { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 },
|
||
},
|
||
});
|
||
}
|
||
```
|
||
|
||
The settings context calls `buildTheme()` whenever any axis changes, then re-mounts `<ThemeProvider>`. Because MUI v7 supports CSS variables, the swap is cheap (no React tree thrash).
|
||
|
||
---
|
||
|
||
## 2. Provider wiring (root layout)
|
||
|
||
```tsx
|
||
// frontend/src/app/layout.tsx (simplified)
|
||
<AppRouterCacheProvider options={{ key: dir === 'rtl' ? 'css-rtl' : 'css', prepend: true, stylisPlugins: dir === 'rtl' ? [rtlPlugin] : [] }}>
|
||
<ThemeProvider theme={theme}>
|
||
<CssBaseline />
|
||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||
<I18nProvider>
|
||
<QueryClientProvider client={queryClient}>
|
||
<SocketProvider>
|
||
<SnackbarProvider>{children}</SnackbarProvider>
|
||
</SocketProvider>
|
||
</QueryClientProvider>
|
||
</I18nProvider>
|
||
</LocalizationProvider>
|
||
</ThemeProvider>
|
||
</AppRouterCacheProvider>
|
||
```
|
||
|
||
> [!warning]
|
||
> The cache `key` MUST differ between LTR and RTL (`'css'` vs `'css-rtl'`) — otherwise switching direction at runtime keeps the previous direction's CSS in the head and the layout breaks.
|
||
|
||
---
|
||
|
||
## 3. Palette
|
||
|
||
See [[Colors]] for the full table. Structure:
|
||
|
||
```ts
|
||
{
|
||
mode: 'light' | 'dark',
|
||
primary: { lighter, light, main, dark, darker, contrastText },
|
||
secondary: { lighter, light, main, dark, darker, contrastText },
|
||
info: { lighter, light, main, dark, darker, contrastText },
|
||
success: { lighter, light, main, dark, darker, contrastText },
|
||
warning: { lighter, light, main, dark, darker, contrastText },
|
||
error: { lighter, light, main, dark, darker, contrastText },
|
||
grey: { 100…900 },
|
||
text: { primary, secondary, disabled },
|
||
background:{ default, paper, neutral },
|
||
action: { active, hover, selected, focus, disabled, disabledBackground },
|
||
}
|
||
```
|
||
|
||
Color presets (selectable in settings) swap the `primary` + `secondary` color groups while leaving `grey`, `error`, etc. untouched.
|
||
|
||
---
|
||
|
||
## 4. Typography
|
||
|
||
```ts
|
||
{
|
||
fontFamily: '"Public Sans Variable", "Helvetica", "Arial", sans-serif',
|
||
fontWeightRegular: 400,
|
||
fontWeightMedium: 500,
|
||
fontWeightSemiBold: 600,
|
||
fontWeightBold: 700,
|
||
h1: { fontSize: 64, lineHeight: 80/64, letterSpacing: -1 },
|
||
h2: { fontSize: 48, lineHeight: 64/48 },
|
||
h3: { fontSize: 32, lineHeight: 48/32 },
|
||
h4: { fontSize: 24, lineHeight: 36/24 },
|
||
h5: { fontSize: 20, lineHeight: 30/20 },
|
||
h6: { fontSize: 18, lineHeight: 28/18 },
|
||
subtitle1: { fontSize: 16, fontWeight: 600 },
|
||
subtitle2: { fontSize: 14, fontWeight: 600 },
|
||
body1: { fontSize: 16 },
|
||
body2: { fontSize: 14 },
|
||
caption: { fontSize: 12 },
|
||
overline: { fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1.1 },
|
||
button: { fontSize: 14, fontWeight: 700, textTransform: 'unset' },
|
||
}
|
||
```
|
||
|
||
Variants `h1` and `h2` scale down responsively. Use the `responsiveFontSizes(theme)` helper if you need automatic scaling.
|
||
|
||
See [[Typography]] for font loading, secondary font (Barlow), and i18n font fallbacks.
|
||
|
||
---
|
||
|
||
## 5. Spacing
|
||
|
||
```ts
|
||
spacing: 8 // unit
|
||
// theme.spacing(1) === '8px'
|
||
// theme.spacing(2) === '16px'
|
||
// theme.spacing(0.5) === '4px'
|
||
```
|
||
|
||
Use the shorthand props on the `sx`:
|
||
|
||
```tsx
|
||
<Box sx={{ p: 2, mt: 3, mx: { xs: 1, md: 4 } }} />
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Shape & radii
|
||
|
||
```ts
|
||
shape: { borderRadius: 8 } // default
|
||
// cards typically: borderRadius: 16
|
||
// pills: borderRadius: 9999
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Shadows
|
||
|
||
MUI's default `shadows` array (25 levels: `none`, then `1`–`24`) is replaced with a softer custom set. Additionally, `customShadows` defines brand-specific tinted shadows used by the `Card`, `Button`, `Dialog` overrides:
|
||
|
||
```ts
|
||
customShadows: {
|
||
z1, z4, z8, z12, z16, z20, z24,
|
||
primary: 'rgba(<primary.main>, 0.24) 0 8 16 0',
|
||
secondary: '…',
|
||
info: '…',
|
||
success: '…',
|
||
warning: '…',
|
||
error: '…',
|
||
card: 'rgba(0,0,0,0.04) 0 0 2 0, rgba(0,0,0,0.08) 0 12 24 -4',
|
||
dialog: '…',
|
||
dropdown: '…',
|
||
}
|
||
```
|
||
|
||
Read inside `sx`:
|
||
|
||
```tsx
|
||
sx={{ boxShadow: (theme) => theme.customShadows.z8 }}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Component overrides
|
||
|
||
`theme/options/overrides/` contains one file per overridden component, each exporting `{ defaultProps, styleOverrides }` merged into `components`:
|
||
|
||
| Component | Notable override |
|
||
|---|---|
|
||
| `MuiButton` | `disableRipple`, custom `customShadows.primary` on contained variant |
|
||
| `MuiCard` | `borderRadius: 16`, `customShadows.card` |
|
||
| `MuiPaper` | elevation flattened to `0` by default (use `card` shadow explicitly) |
|
||
| `MuiTextField` | `variant: 'outlined'` default, custom focus ring |
|
||
| `MuiTooltip` | dark background regardless of mode, smaller padding |
|
||
| `MuiAvatar` | letter-based fallback with hashed background color |
|
||
| `MuiTableCell` | denser padding, header weight 600 |
|
||
| `MuiDialog` | `customShadows.dialog`, max-width responsive |
|
||
| `MuiAlert` | tinted background using palette `lighter` |
|
||
| `MuiTab` | uppercase off, color from `primary.main` when selected |
|
||
| `MuiAutocomplete` | custom popup paper shadow |
|
||
|
||
Add an override:
|
||
|
||
1. Create `theme/options/overrides/<Component>.ts` exporting a function `(theme) => ({...})`.
|
||
2. Register it in `theme/options/overrides/index.ts`.
|
||
3. It will be merged into the next `createTheme` call automatically.
|
||
|
||
---
|
||
|
||
## 9. CSS variables (MUI v7)
|
||
|
||
`createTheme` with `cssVariables: true` (recommended) emits CSS custom properties at the `<html>` root and switches them per mode without re-rendering. The default is on in v7 — verify in `theme/index.ts`.
|
||
|
||
This means a custom CSS rule can reference:
|
||
|
||
```css
|
||
.my-thing {
|
||
background: var(--mui-palette-primary-main);
|
||
padding: calc(var(--mui-spacing) * 2);
|
||
}
|
||
```
|
||
|
||
Use this sparingly — prefer the `sx` prop.
|
||
|
||
---
|
||
|
||
## 10. Per-locale font swap
|
||
|
||
When the active locale is `fa` or `ar`, the typography option module can layer a Persian/Arabic font (e.g., **Vazirmatn**, **IRANSans**) ahead of `Public Sans Variable`:
|
||
|
||
```ts
|
||
fontFamily: dir === 'rtl'
|
||
? '"Vazirmatn", "Public Sans Variable", sans-serif'
|
||
: '"Public Sans Variable", sans-serif',
|
||
```
|
||
|
||
Otherwise Latin glyphs still render via the Public Sans fallback. See [[Internationalization & RTL]].
|
||
|
||
---
|
||
|
||
## 11. Customisation checklist
|
||
|
||
- [ ] New brand color → update `palette.ts` (light + dark) and consider a preset.
|
||
- [ ] New variant → add to typography options and the `TypographyOptions` interface augmentation.
|
||
- [ ] Component override → file in `overrides/`.
|
||
- [ ] Verify in both modes (light + dark) AND both directions (ltr + rtl).
|
||
- [ ] Verify against WCAG AA contrast.
|
||
- [ ] Snapshot test the component if visual stability matters.
|
||
|
||
---
|
||
|
||
## Related
|
||
|
||
- [[Design System Overview]]
|
||
- [[Colors]] · [[Typography]] · [[Components]] · [[Layouts]]
|
||
- [[Internationalization & RTL]] · [[Settings & Theming]]
|
||
- [[Frontend Architecture]]
|