Theme System¶
The util-global-theme library provides Material UI theme configuration and
utilities.
Overview¶
The theme system provides:
- Light and dark mode support
- High contrast accessibility theme
- Cookie-based theme persistence
- Server-side theme detection (Next.js)
- Material UI integration
Using the Theme¶
ThemeProvider Setup¶
The theme is configured in the root layout:
apps/petfolio-business/src/app/layout.tsx
import { ThemeProvider } from '@util-global-theme';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
Accessing Theme¶
Use the useTheme hook in components:
import { useTheme } from '@util-global-theme';
export function ThemedComponent() {
const theme = useTheme();
return (
<div style={{ backgroundColor: theme.palette.primary.main }}>
Themed content
</div>
);
}
Available Themes¶
Default Theme¶
Standard light/dark mode theme:
{
palette: {
primary: {
main: '#1976d2', // Blue
light: '#42a5f5',
dark: '#1565c0',
},
secondary: {
main: '#dc004e', // Pink
light: '#f73378',
dark: '#9a0036',
},
},
}
High Contrast Theme¶
Accessibility-focused theme with higher contrast ratios:
Theme Switching¶
Client-Side Switching¶
import { useTheme } from '@util-global-theme';
export function ThemeToggle() {
const { mode, toggleColorMode } = useTheme();
return (
<button onClick={toggleColorMode}>
Switch to {mode === 'light' ? 'dark' : 'light'} mode
</button>
);
}
Cookie Persistence¶
Theme preference is stored in the active-theme cookie:
This allows server-side rendering with the correct theme.
Theme Structure¶
graph TD
A[ThemeProvider] --> B{Theme Mode}
B -->|light| C[Light Palette]
B -->|dark| D[Dark Palette]
B -->|high-contrast| E[High Contrast Palette]
C --> F[MUI Theme]
D --> F
E --> F
F --> G[Components]
style A fill:#4fc3f7
style F fill:#81c784
style G fill:#ffb74d
Customizing Colors¶
Palette Customization¶
libs/util-global-theme/src/lib/themes/custom.ts
import { createTheme } from '@mui/material/styles';
export const customTheme = createTheme({
palette: {
primary: {
main: '#6200ea', // Deep purple
},
secondary: {
main: '#00bfa5', // Teal
},
background: {
default: '#f5f5f5',
paper: '#ffffff',
},
},
});
Typography Customization¶
export const customTheme = createTheme({
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 500,
},
body1: {
fontSize: '1rem',
lineHeight: 1.5,
},
},
});
Using MUI Components¶
Components automatically use the theme:
import { Button, Typography } from '@mui/material';
export function Example() {
return (
<>
<Typography variant="h1" color="primary">
Themed Heading
</Typography>
<Button variant="contained" color="secondary">
Themed Button
</Button>
</>
);
}
Emotion Styled Components¶
Use Emotion with theme:
import styled from '@emotion/styled';
import { Theme } from '@mui/material/styles';
interface CardProps {
theme?: Theme;
}
export const Card = styled.div<CardProps>`
background-color: ${({ theme }) => theme.palette.background.paper};
color: ${({ theme }) => theme.palette.text.primary};
padding: ${({ theme }) => theme.spacing(2)};
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
box-shadow: ${({ theme }) => theme.shadows[2]};
`;
Responsive Design¶
Use theme breakpoints:
import { useTheme, useMediaQuery } from '@mui/material';
export function ResponsiveComponent() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
return (
<div>
{isMobile ? 'Mobile View' : 'Desktop View'}
</div>
);
}
Spacing System¶
Use theme spacing for consistency:
import { Box } from '@mui/material';
export function SpacedComponent() {
return (
<Box sx={{
margin: 2, // theme.spacing(2) = 16px
padding: { xs: 1, md: 3 }, // Responsive spacing
}}>
Content
</Box>
);
}
Theme Utilities¶
Color Utilities¶
import { alpha, darken, lighten } from '@mui/material/styles';
const semiTransparent = alpha('#1976d2', 0.5); // 50% opacity
const darker = darken('#1976d2', 0.2); // 20% darker
const lighter = lighten('#1976d2', 0.2); // 20% lighter
Breakpoint Utilities¶
import { useTheme } from '@mui/material';
export function Example() {
const theme = useTheme();
const styles = {
[theme.breakpoints.up('md')]: {
fontSize: '1.5rem',
},
[theme.breakpoints.down('sm')]: {
fontSize: '1rem',
},
};
return <div css={styles}>Responsive text</div>;
}
Dark Mode Best Practices¶
Contrast Ratios¶
Ensure text is readable in both modes:
const theme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#90caf9', // Lighter blue for dark mode
},
text: {
primary: '#ffffff',
secondary: 'rgba(255, 255, 255, 0.7)',
},
},
});
Images and Icons¶
Adjust for dark mode:
export function Logo() {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
return (
<img
src={isDark ? '/logo-dark.svg' : '/logo-light.svg'}
alt="Logo"
/>
);
}
Accessibility¶
Color Contrast¶
Use MUI's built-in contrast helpers:
import { getContrastRatio } from '@mui/material/styles';
const ratio = getContrastRatio('#1976d2', '#ffffff');
// Should be >= 4.5:1 for normal text
// Should be >= 3:1 for large text
Focus Indicators¶
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
'&:focus': {
outline: '2px solid',
outlineColor: theme.palette.primary.main,
outlineOffset: '2px',
},
},
},
},
},
});
Testing with Theme¶
Wrap components in tests:
import { ThemeProvider } from '@util-global-theme';
import { render } from '@testing-library/react';
const renderWithTheme = (component: React.ReactElement) => {
return render(
<ThemeProvider>
{component}
</ThemeProvider>
);
};
test('renders with theme', () => {
renderWithTheme(<MyComponent />);
});
Next Steps¶
- Component Library - Using themed components
- MUI Theming Guide - Official MUI docs
- Emotion Documentation - CSS-in-JS styling