MQTT Explorer uses Material-UI (MUI) v7 with JSS styling for consistent, maintainable visual design across the application.
UI Framework
Stack:
- @mui/material (v7) - Core components and theming
- @mui/icons-material (v7) - Icon library
- @mui/styles (v6) - JSS styling with
withStyles HOC
- @emotion/react & @emotion/styled - CSS-in-JS foundation
MQTT Explorer uses Material-UI v7, which is the latest major version with React 19 support.
Theming
Theme Configuration
Location: app/src/theme.ts
import { createTheme } from '@mui/material/styles'
const theme = createTheme({
palette: {
mode: 'light', // or 'dark'
primary: {
main: '#335C67', // Teal/blue-green
},
secondary: {
main: '#FFA726', // Amber
},
},
typography: {
fontSize: 14, // 0.9rem base
userSelect: 'none',
},
})
Applying Theme
import { ThemeProvider } from '@mui/material/styles'
import { ThemeProvider as LegacyThemeProvider } from '@mui/styles'
import theme from './theme'
function App() {
return (
<ThemeProvider theme={theme}>
<LegacyThemeProvider theme={theme}>
{/* Your app */}
</LegacyThemeProvider>
</ThemeProvider>
)
}
Both ThemeProvider components are required: one for Material-UI v7 components and one for legacy @mui/styles integration.
Colors
Theme Palette
Access theme colors through the palette:
backgroundColor: theme.palette.background.default // #fff (light) / #121212 (dark)
backgroundColor: theme.palette.background.paper // #fff (light) / #1e1e1e (dark)
Material-UI Color Palettes
Import color palettes for extended colors:
import { blueGrey, amber, green, red, orange } from '@mui/material/colors'
const styles = (theme: Theme) => ({
lightShade: {
backgroundColor: blueGrey[100],
},
darkShade: {
backgroundColor: blueGrey[700],
},
})
Available shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400, A700
Theme-Conditional Colors
const color = theme.palette.mode === 'light'
? blueGrey[300]
: theme.palette.primary.main
Code Editor Colors
Defined in app/src/components/Sidebar/CodeBlockColors.ts:
export const codeBlockColors = {
keyword: '#0000ff',
string: '#a31515',
number: '#098658',
comment: '#008000',
// ...
}
Typography
Typography Variants
<Typography variant="h1">Heading 1</Typography>
<Typography variant="h2">Heading 2</Typography>
<Typography variant="h6">Heading 6</Typography>
Font Sizes
const styles = (theme: Theme) => ({
text: {
fontSize: theme.typography.pxToRem(15), // 15px
},
})
Monospace Font
font: "12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace"
Used for code blocks, message payloads, and topic paths.
Spacing
8px Grid System
Material-UI uses an 8px grid system:
const styles = (theme: Theme) => ({
container: {
margin: theme.spacing(1), // 8px
padding: theme.spacing(2), // 16px
marginLeft: theme.spacing(1.5), // 12px (tree indentation)
},
})
Common spacing values:
theme.spacing(0.5) = 4px
theme.spacing(1) = 8px
theme.spacing(1.5) = 12px
theme.spacing(2) = 16px
theme.spacing(3) = 24px
Border Radius
borderRadius: theme.shape.borderRadius // 4px (default)
Component Styling
Primary Approach: withStyles HOC
The recommended way to style components:
import { withStyles } from '@mui/styles'
import { Theme } from '@mui/material/styles'
const styles = (theme: Theme) => ({
root: {
backgroundColor: theme.palette.background.default,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
},
title: {
color: theme.palette.text.primary,
fontSize: theme.typography.pxToRem(20),
marginBottom: theme.spacing(1),
},
})
interface Props {
classes: any
title: string
}
function MyComponent({ classes, title }: Props) {
return (
<div className={classes.root}>
<h2 className={classes.title}>{title}</h2>
</div>
)
}
export default withStyles(styles)(MyComponent)
Use withStyles for component-level styles that need theme access. Keep styles co-located with components.
Type Assertions
Some CSS properties require type assertions:
const styles = (theme: Theme) => ({
node: {
display: 'block' as 'block',
whiteSpace: 'nowrap' as 'nowrap',
overflow: 'hidden' as 'hidden',
textOverflow: 'ellipsis',
},
})
Responsive Styles
Use breakpoints for responsive design:
const styles = (theme: Theme) => ({
sidebar: {
display: 'none',
[theme.breakpoints.up(750)]: {
display: 'block',
},
[theme.breakpoints.down('sm')]: {
width: '100%',
},
},
})
sx Prop (Simple Cases)
For simple, one-off styles:
<Button sx={{ color: 'primary.contrastText', mt: 2 }}>
Click Me
</Button>
Use sx prop for simple styles. Use withStyles for complex component styles that need theme access.
Animations
CSS Animations
const styles = (theme: Theme) => ({
'@keyframes updateLight': {
'0%': { opacity: 0.3 },
'50%': { opacity: 1 },
'100%': { opacity: 0.3 },
},
updating: {
animation: 'updateLight 0.5s ease-in-out',
},
})
Theme Transitions
const styles = (theme: Theme) => ({
drawer: {
transition: theme.transitions.create('transform', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
})
Available easing functions:
theme.transitions.easing.easeInOut
theme.transitions.easing.easeOut
theme.transitions.easing.easeIn
theme.transitions.easing.sharp
Duration presets:
theme.transitions.duration.shortest (150ms)
theme.transitions.duration.shorter (200ms)
theme.transitions.duration.short (250ms)
theme.transitions.duration.standard (300ms)
theme.transitions.duration.leavingScreen (195ms)
theme.transitions.duration.enteringScreen (225ms)
Interactive States
Hover States
const styles = (theme: Theme) => ({
treeNode: {
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.mode === 'light'
? blueGrey[100]
: theme.palette.primary.light,
},
},
})
Selection States
const styles = (theme: Theme) => ({
selected: {
backgroundColor: (theme.palette.mode === 'light'
? blueGrey[300]
: theme.palette.primary.main) + ' !important',
fontWeight: 'bold',
},
})
Use !important sparingly and only when necessary to override Material-UI default styles.
Focus States
const styles = (theme: Theme) => ({
input: {
'&:focus': {
borderColor: theme.palette.primary.main,
outline: `2px solid ${theme.palette.primary.main}`,
},
},
})
Common Patterns
Tree Nodes
const styles = (theme: Theme) => ({
node: {
overflow: 'hidden' as 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap' as 'nowrap',
cursor: 'pointer',
padding: theme.spacing(0.5, 1),
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
},
subnodes: {
marginLeft: theme.spacing(1.5), // 12px indentation
},
})
<Button variant="contained" color="primary">
Submit
</Button>
Icons
import HomeIcon from '@mui/icons-material/Home'
<HomeIcon fontSize="inherit" />
Cards
import { Card, CardContent } from '@mui/material'
<Card>
<CardContent>
<Typography variant="h6">Card Title</Typography>
<Typography variant="body2">Card content</Typography>
</CardContent>
</Card>
Best Practices
DO ✅
- ✅ Use theme variables (
theme.palette.*, theme.spacing(), theme.typography.*)
- ✅ Use
withStyles HOC for component styles
- ✅ Use
theme.palette.mode for light/dark conditional styling
- ✅ Import Material-UI color palettes for extended colors
- ✅ Keep styles co-located with components
- ✅ Test in both light and dark themes
- ✅ Use responsive breakpoints for mobile compatibility
- ✅ Follow accessibility guidelines (WCAG 2.1)
DON’T ❌
- ❌ Hardcode colors or spacing values
- ❌ Create global CSS files
- ❌ Duplicate style definitions
- ❌ Use inline styles for complex patterns
- ❌ Ignore theme mode (always test both light/dark)
- ❌ Use
!important unless absolutely necessary
- ❌ Override Material-UI component internals
Mobile Compatibility
MQTT Explorer targets Google Pixel 6 (412x915px viewport) for mobile optimization:
Touch-Friendly Targets
const styles = (theme: Theme) => ({
button: {
minHeight: '44px', // Minimum tap target size
minWidth: '44px',
},
})
Responsive Layouts
const styles = (theme: Theme) => ({
container: {
flexDirection: 'column',
[theme.breakpoints.up('sm')]: {
flexDirection: 'row',
},
},
})
See MOBILE_COMPATIBILITY.md for mobile strategy.
Testing Styles
Visual Regression Testing
# Generate screenshots
yarn build
yarn test:demo-video
# Compare with baseline
# (Manual process - compare screenshots)
Theme Testing
import { renderWithProviders } from '../../utils/spec/testUtils'
import { createTheme } from '@mui/material/styles'
it('should render in dark theme', () => {
const darkTheme = createTheme({ palette: { mode: 'dark' } })
const { container } = renderWithProviders(<MyComponent />, {
theme: darkTheme,
})
expect(container).to.exist
})
Accessibility Testing
- Contrast ratio: Check WCAG 2.1 AA (4.5:1 for normal text)
- Focus indicators: Visible focus states for keyboard navigation
- Touch targets: Minimum 44x44px for mobile
Use browser DevTools accessibility inspector to validate ARIA labels and contrast ratios.
Resources
Material-UI Docs
Official Material-UI documentation
Color System
Material-UI color customization guide
Theming Guide
Complete theming documentation
Component API
Material-UI component API reference
Examples
Complete Component with Styles
import React from 'react'
import { withStyles } from '@mui/styles'
import { Theme } from '@mui/material/styles'
import { Card, CardContent, Typography, Button } from '@mui/material'
import { blueGrey } from '@mui/material/colors'
const styles = (theme: Theme) => ({
card: {
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2),
marginBottom: theme.spacing(2),
'&:hover': {
backgroundColor: theme.palette.mode === 'light'
? blueGrey[50]
: theme.palette.primary.dark,
},
},
title: {
color: theme.palette.text.primary,
fontSize: theme.typography.pxToRem(18),
fontWeight: 'bold',
marginBottom: theme.spacing(1),
},
content: {
color: theme.palette.text.secondary,
fontSize: theme.typography.pxToRem(14),
},
button: {
marginTop: theme.spacing(2),
},
})
interface Props {
classes: any
title: string
content: string
onAction: () => void
}
function InfoCard({ classes, title, content, onAction }: Props) {
return (
<Card className={classes.card}>
<CardContent>
<Typography className={classes.title}>{title}</Typography>
<Typography className={classes.content}>{content}</Typography>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={onAction}
>
Learn More
</Button>
</CardContent>
</Card>
)
}
export default withStyles(styles)(InfoCard)
This example demonstrates:
- Theme variable usage
- Spacing system
- Typography
- Hover states
- Theme-conditional colors
- Material-UI component integration