CSS Styling Standards
The frontend uses Mantine v8 primitives + CSS Modules for page-local styling + a small global stylesheet. There is no Tailwind, no styled-components, no BEM. Don't introduce one.
Style architecture
| Layer |
File(s) |
Purpose |
| Mantine reset & component CSS |
imported in main.tsx |
base reset, Mantine component styling |
| App-shell layout |
frontend/src/App.css |
header, navbar, content frame |
| Global typography / overrides |
frontend/src/index.css |
body font, link defaults |
| Theme tokens |
frontend/src/theme.ts |
colors, radii, font stacks |
| Page-local styles |
frontend/src/pages/<Page>.module.css |
scoped to one page |
CSS Module files in use: Compose.module.css, Grammar.module.css, Sentences.module.css, Verbs.module.css. Plain .css (not modules) is used for older pages: Database.css, Login.css, PublicationDetail.css, TranslationGame.css. Prefer Modules for any new page.
Mantine theme (current)
theme.ts — keep changes here, not scattered in components:
primaryColor: 'violet' (custom 10-shade palette).
- Secondary palette
ocean (also 10 shades).
primaryShade: { light: 6, dark: 8 }.
fontFamily: native system stack (-apple-system, BlinkMacSystemFont, "Segoe UI", ...) — explicitly not Noto. Don't switch.
headings.fontFamily: same system stack.
defaultRadius: 'md', cursorType: 'pointer', autoContrast: true, luminanceThreshold: 0.3.
Color references in components should use Mantine tokens (c="violet.7", bg="gray.0") so theme changes propagate.
App is light-oriented
Despite the loading screen using #1a1b1e, there is no dark-mode toggle and no colorScheme: 'dark' configuration. The shipped UI is light. Don't author dark-first overrides unless we add a real toggle.
Mantine vs CSS — when to use what
- Spacing, layout, color, typography, radii, shadows: Mantine props (
p, m, gap, c, bg, radius, shadow).
- Page-specific composition (a 3-column dictionary grid, a verb conjugation table): CSS Modules.
- App-shell, header, nav:
App.css. Use class names already wired into <AppShell classNames={{ header: 'app-header', navbar: 'app-navbar' }}>.
- Inline
style={{ ... }}: acceptable for one-off dynamic values; reach for a Module if it grows past ~3 declarations.
Multilingual / accented-character rules
- The native font stack renders Chuukese accents (
á é í ó ú, ā ē ī ō ū) without extra config.
- Don't
text-transform: uppercase Chuukese strings — it strips diacritics on some platforms. If you need visual emphasis, use weight/color.
- Word-break: Chuukese words are short; avoid
word-break: break-all which breaks accent ligatures. Use overflow-wrap: anywhere only as a last resort on tight columns.
- For dictionary entries, monospace looks bad — stick with the proportional system stack.
Responsive breakpoints
Use Mantine's defaults via the visibleFrom / hiddenFrom props or theme.breakpoints rather than hand-rolled media queries. Existing custom breakpoints in App.css match Mantine's sm (48em) — keep alignment if you add new ones.
Accessibility
- Aim for WCAG AA.
autoContrast: true plus luminanceThreshold: 0.3 handles most text-on-color cases automatically.
- Don't disable focus outlines globally. If they're ugly on a custom button, restyle them, don't hide them.
- All interactive elements get a real
<button> or <a> (Mantine components do this by default).
Adding a new page's styles
// MyPage.tsx
import classes from './MyPage.module.css';
<div className={classes.grid}>...</div>
/* MyPage.module.css */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: var(--mantine-spacing-md);
}
Use Mantine CSS variables (--mantine-color-*, --mantine-spacing-*, --mantine-radius-*) inside Module files instead of hard-coded values — they re-theme automatically.
Pitfalls
- Loading the Mantine CSS imports after
index.css reorders the cascade and breaks several components — keep the order in main.tsx.
@mantine/notifications and @mantine/dropzone need their own CSS imports; both are already wired up. Adding @mantine/charts (or any other Mantine sub-package) requires adding its styles.css import too.
- Don't import a CSS Module from a non-page component without prefixing class names — names get hashed but
:global leaks if used carelessly.
ModalsProvider is not currently mounted in main.tsx. If you start using @mantine/modals's imperative API (modals.open(...)), wire the provider first.
1---2name: css-styling-standards3description: Styling conventions for the Chuuk Dictionary frontend — Mantine v8 theme, CSS Modules per page, global app-shell CSS, multilingual / accented-character considerations. Use when adding or modifying styles in `frontend/src/`.4---56# CSS Styling Standards78The frontend uses **Mantine v8** primitives + **CSS Modules** for page-local styling + a small global stylesheet. There is no Tailwind, no styled-components, no BEM. Don't introduce one.910## Style architecture1112| Layer | File(s) | Purpose |13|---|---|---|14| Mantine reset & component CSS | imported in [`main.tsx`](../../../frontend/src/main.tsx) | base reset, Mantine component styling |15| App-shell layout | [`frontend/src/App.css`](../../../frontend/src/App.css) | header, navbar, content frame |16| Global typography / overrides | [`frontend/src/index.css`](../../../frontend/src/index.css) | body font, link defaults |17| Theme tokens | [`frontend/src/theme.ts`](../../../frontend/src/theme.ts) | colors, radii, font stacks |18| Page-local styles | `frontend/src/pages/<Page>.module.css` | scoped to one page |1920CSS Module files in use: `Compose.module.css`, `Grammar.module.css`, `Sentences.module.css`, `Verbs.module.css`. Plain `.css` (not modules) is used for older pages: `Database.css`, `Login.css`, `PublicationDetail.css`, `TranslationGame.css`. **Prefer Modules for any new page.**2122## Mantine theme (current)2324[`theme.ts`](../../../frontend/src/theme.ts) — keep changes here, not scattered in components:2526- `primaryColor: 'violet'` (custom 10-shade palette).27- Secondary palette `ocean` (also 10 shades).28- `primaryShade: { light: 6, dark: 8 }`.29- `fontFamily`: native system stack (`-apple-system, BlinkMacSystemFont, "Segoe UI", ...`) — explicitly **not** Noto. Don't switch.30- `headings.fontFamily`: same system stack.31- `defaultRadius: 'md'`, `cursorType: 'pointer'`, `autoContrast: true`, `luminanceThreshold: 0.3`.3233Color references in components should use Mantine tokens (`c="violet.7"`, `bg="gray.0"`) so theme changes propagate.3435## App is light-oriented3637Despite the loading screen using `#1a1b1e`, there is no dark-mode toggle and no `colorScheme: 'dark'` configuration. The shipped UI is light. Don't author dark-first overrides unless we add a real toggle.3839## Mantine vs CSS — when to use what4041- **Spacing, layout, color, typography, radii, shadows:** Mantine props (`p`, `m`, `gap`, `c`, `bg`, `radius`, `shadow`).42- **Page-specific composition (a 3-column dictionary grid, a verb conjugation table):** CSS Modules.43- **App-shell, header, nav:** [`App.css`](../../../frontend/src/App.css). Use class names already wired into `<AppShell classNames={{ header: 'app-header', navbar: 'app-navbar' }}>`.44- **Inline `style={{ ... }}`:** acceptable for one-off dynamic values; reach for a Module if it grows past ~3 declarations.4546## Multilingual / accented-character rules4748- The native font stack renders Chuukese accents (`á é í ó ú`, `ā ē ī ō ū`) without extra config.49- Don't `text-transform: uppercase` Chuukese strings — it strips diacritics on some platforms. If you need visual emphasis, use weight/color.50- Word-break: Chuukese words are short; avoid `word-break: break-all` which breaks accent ligatures. Use `overflow-wrap: anywhere` only as a last resort on tight columns.51- For dictionary entries, monospace looks bad — stick with the proportional system stack.5253## Responsive breakpoints5455Use Mantine's defaults via the `visibleFrom` / `hiddenFrom` props or `theme.breakpoints` rather than hand-rolled media queries. Existing custom breakpoints in [`App.css`](../../../frontend/src/App.css#L178) match Mantine's `sm` (`48em`) — keep alignment if you add new ones.5657## Accessibility5859- Aim for WCAG AA. `autoContrast: true` plus `luminanceThreshold: 0.3` handles most text-on-color cases automatically.60- Don't disable focus outlines globally. If they're ugly on a custom button, restyle them, don't hide them.61- All interactive elements get a real `<button>` or `<a>` (Mantine components do this by default).6263## Adding a new page's styles6465```tsx66// MyPage.tsx67import classes from './MyPage.module.css';68<div className={classes.grid}>...</div>69```7071```css72/* MyPage.module.css */73.grid {74 display: grid;75 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));76 gap: var(--mantine-spacing-md);77}78```7980Use Mantine CSS variables (`--mantine-color-*`, `--mantine-spacing-*`, `--mantine-radius-*`) inside Module files instead of hard-coded values — they re-theme automatically.8182## Pitfalls8384- Loading the Mantine CSS imports **after** `index.css` reorders the cascade and breaks several components — keep the order in [`main.tsx`](../../../frontend/src/main.tsx).85- `@mantine/notifications` and `@mantine/dropzone` need their own CSS imports; both are already wired up. Adding `@mantine/charts` (or any other Mantine sub-package) requires adding its `styles.css` import too.86- Don't import a CSS Module from a non-page component without prefixing class names — names get hashed but `:global` leaks if used carelessly.87- `ModalsProvider` is **not** currently mounted in `main.tsx`. If you start using `@mantine/modals`'s imperative API (`modals.open(...)`), wire the provider first.