Styling Patterns¶
Tailwind CSS configuration, Tremor components, and NVIDIA dark theme implementation
Key Files¶
frontend/tailwind.config.js:1-295- Tailwind CSS configurationfrontend/src/styles/index.css:1-664- Global CSS stylesfrontend/src/components/common/Button.tsx:1-80- Styled button componentfrontend/src/theme/colors.ts:1-60- Color constants
Overview¶
The frontend uses Tailwind CSS for utility-first styling with a custom NVIDIA-inspired dark theme. Tremor provides pre-built data visualization components that integrate with the theme. The design prioritizes WCAG 2.1 AA accessibility compliance with proper contrast ratios.
Color System¶
NVIDIA Brand Colors¶
The primary brand color is NVIDIA Green (#76B900) with a full shade scale:
// frontend/tailwind.config.js:32-44
primary: {
DEFAULT: '#76B900',
50: '#E8F5D9',
100: '#D4EBB3',
200: '#B8DD80',
300: '#9CCF4D',
400: '#89C226',
500: '#76B900', // Primary
600: '#619900',
700: '#4C7900',
800: '#375900',
900: '#223A00',
},
Dark Theme Background Colors¶
// frontend/tailwind.config.js:26-29
background: '#0E0E0E', // Darkest - page background
panel: '#1A1A1A', // Panel/card containers
card: '#1E1E1E', // Card surfaces
Risk Level Colors (WCAG Compliant)¶
Colors are brightened for 4.5:1 contrast ratio on dark backgrounds:
// frontend/tailwind.config.js:46-55
risk: {
low: '#76B900', // Green - good contrast
medium: '#FFB800', // Amber - good contrast
high: '#FFCDD2', // Light coral for contrast on bg-risk-high/10
critical: '#FFE0E0', // Very light pink for 4.5:1 on blended dark bg
},
Semantic Status Colors¶
// frontend/tailwind.config.js:98-130
status: {
healthy: {
DEFAULT: '#10B981', // emerald-500
light: 'rgba(16, 185, 129, 0.1)', // 10% for backgrounds
text: '#34D399', // emerald-400 for text
border: 'rgba(16, 185, 129, 0.3)', // 30% for borders
},
warning: {
DEFAULT: '#F59E0B',
light: 'rgba(245, 158, 11, 0.1)',
text: '#FBBF24',
border: 'rgba(245, 158, 11, 0.3)',
},
error: {
DEFAULT: '#EF4444',
light: 'rgba(239, 68, 68, 0.1)',
text: '#F87171',
border: 'rgba(239, 68, 68, 0.3)',
},
inactive: {
DEFAULT: '#6B7280',
light: 'rgba(107, 114, 128, 0.1)',
text: '#9CA3AF',
border: 'rgba(107, 114, 128, 0.3)',
},
},
Typography¶
Font Families¶
// frontend/tailwind.config.js:142-153
fontFamily: {
sans: [
'Inter',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'sans-serif',
],
mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', 'monospace'],
},
Font Size Scale¶
// frontend/tailwind.config.js:156-167
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }], // 12px
sm: ['0.875rem', { lineHeight: '1.25rem' }], // 14px
base: ['1rem', { lineHeight: '1.5rem' }], // 16px
lg: ['1.125rem', { lineHeight: '1.75rem' }], // 18px
xl: ['1.25rem', { lineHeight: '1.75rem' }], // 20px
'2xl': ['1.5rem', { lineHeight: '2rem' }], // 24px
'3xl': ['1.875rem', { lineHeight: '2.25rem' }], // 30px
'4xl': ['2.25rem', { lineHeight: '2.5rem' }], // 36px
},
Text Colors (WCAG Compliant)¶
// frontend/tailwind.config.js:74-80
text: {
primary: '#FFFFFF', // White - full contrast
secondary: '#B0B0B0', // 5.17:1 contrast on gray-700
muted: '#919191', // 4.81:1 contrast on #222222
},
Touch Target Compliance¶
WCAG 2.5.5 AAA minimum touch targets (44px):
// frontend/tailwind.config.js:11-23
minHeight: {
11: '2.75rem', // 44px - WCAG minimum
12: '3rem', // 48px - larger target
touch: '2.75rem', // alias for 44px
},
minWidth: {
11: '2.75rem',
12: '3rem',
touch: '2.75rem',
},
Usage with custom variants:
// frontend/tailwind.config.js:286-293
plugins: [
function ({ addVariant }) {
addVariant('touch', '@media (pointer: coarse)');
addVariant('mouse', '@media (pointer: fine)');
},
],
<!-- Touch devices get larger targets -->
<button class="h-10 mouse:h-10 touch:h-12">Click Me</button>
Animations¶
Custom Keyframes¶
// frontend/tailwind.config.js:235-272
keyframes: {
'pulse-glow': {
'0%, 100%': { boxShadow: '0 0 10px rgba(118, 185, 0, 0.2)' },
'50%': { boxShadow: '0 0 20px rgba(118, 185, 0, 0.4)' },
},
'pulse-critical': {
'0%, 100%': { boxShadow: '0 0 8px rgba(255, 107, 107, 0.3)' },
'50%': { boxShadow: '0 0 16px rgba(255, 107, 107, 0.6)' },
},
'slide-in': {
'0%': { transform: 'translateX(100%)', opacity: '0' },
'100%': { transform: 'translateX(0)', opacity: '1' },
},
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
shimmer: {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' },
},
},
Animation Classes¶
// frontend/tailwind.config.js:274-283
animation: {
'pulse-glow': 'pulse-glow 2s ease-in-out infinite',
'pulse-critical': 'pulse-critical 2s ease-in-out infinite',
'pulse-subtle': 'pulse-subtle 2s ease-in-out infinite',
'slide-in': 'slide-in 0.3s ease-out',
'fade-in': 'fade-in 0.2s ease-in',
'ambient-pulse': 'ambient-pulse 2s ease-in-out infinite',
shimmer: 'shimmer 1.5s ease-in-out infinite',
},
Box Shadows for Dark Theme¶
// frontend/tailwind.config.js:194-201
boxShadow: {
'dark-sm': '0 1px 2px 0 rgba(0, 0, 0, 0.5)',
dark: '0 1px 3px 0 rgba(0, 0, 0, 0.6), 0 1px 2px -1px rgba(0, 0, 0, 0.5)',
'dark-md': '0 4px 6px -1px rgba(0, 0, 0, 0.6), 0 2px 4px -2px rgba(0, 0, 0, 0.5)',
'dark-lg': '0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.5)',
'dark-xl': '0 20px 25px -5px rgba(0, 0, 0, 0.6), 0 8px 10px -6px rgba(0, 0, 0, 0.5)',
'nvidia-glow': '0 0 20px rgba(118, 185, 0, 0.3)',
},
Tremor Components¶
Tremor provides data visualization components that integrate with the dark theme:
Card Component¶
import { Card, Title, Text, Metric } from '@tremor/react';
<Card className="bg-card border-gray-700">
<Title className="text-text-primary">Active Cameras</Title>
<Metric className="text-primary">{count}</Metric>
<Text className="text-text-secondary">Online and monitoring</Text>
</Card>;
Badge Component¶
import { Badge } from '@tremor/react';
<Badge color="emerald">Healthy</Badge>
<Badge color="amber">Warning</Badge>
<Badge color="red">Critical</Badge>
Chart Components¶
import { AreaChart, BarChart, DonutChart } from '@tremor/react';
<AreaChart
className="h-72"
data={data}
index="date"
categories={['Events']}
colors={['primary']}
showLegend={false}
/>;
Component Styling Patterns¶
Card Pattern¶
// Consistent card styling across components
<div className="rounded-lg border border-gray-700 bg-card p-4 shadow-dark">
<h3 className="text-lg font-semibold text-text-primary">{title}</h3>
<p className="mt-1 text-sm text-text-secondary">{description}</p>
</div>
Button Variants¶
// frontend/src/components/common/Button.tsx
const variants = {
primary: 'bg-primary hover:bg-primary-600 text-white',
secondary: 'bg-gray-700 hover:bg-gray-600 text-text-primary',
danger: 'bg-danger hover:bg-danger-hover text-white',
ghost: 'bg-transparent hover:bg-gray-700 text-text-primary',
};
<Button variant="primary">Save Changes</Button>
<Button variant="danger">Delete</Button>
Input Styling¶
// Consistent input styling
<input
className="w-full rounded-md border border-gray-600 bg-gray-800 px-3 py-2
text-text-primary placeholder-text-muted
focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
placeholder="Search events..."
/>
Status Indicators¶
// Using semantic status colors
<div className="flex items-center gap-2">
<span
className={`h-2 w-2 rounded-full ${
status === 'healthy'
? 'bg-status-healthy'
: status === 'warning'
? 'bg-status-warning'
: 'bg-status-error'
}`}
/>
<span
className={`text-sm ${
status === 'healthy'
? 'text-status-healthy-text'
: status === 'warning'
? 'text-status-warning-text'
: 'text-status-error-text'
}`}
>
{status}
</span>
</div>
Responsive Design¶
Grid Layouts¶
// frontend/tailwind.config.js:229-232
gridTemplateColumns: {
dashboard: 'repeat(auto-fit, minmax(300px, 1fr))',
'camera-grid': 'repeat(auto-fit, minmax(280px, 1fr))',
},
Breakpoint Usage¶
// Mobile-first responsive design
<div
className="
grid grid-cols-1 // Mobile: single column
sm:grid-cols-2 // Small: 2 columns
lg:grid-cols-3 // Large: 3 columns
xl:grid-cols-4 // Extra large: 4 columns
gap-4
"
>
{cameras.map((camera) => (
<CameraCard key={camera.id} {...camera} />
))}
</div>
Mobile Visibility¶
// Hide on mobile, show on desktop
<Sidebar className="hidden md:block" />
// Show on mobile only
<MobileBottomNav className="md:hidden" />
Accessibility Patterns¶
Focus Styles¶
/* Global focus styles in index.css */
*:focus-visible {
outline: 2px solid #76b900;
outline-offset: 2px;
}
Screen Reader Only¶
Color Contrast¶
All text colors meet WCAG 2.1 AA requirements:
| Element | Color | Background | Contrast Ratio |
|---|---|---|---|
| Primary text | #FFFFFF | #1A1A1A | 15.3:1 |
| Secondary text | #B0B0B0 | #1A1A1A | 8.5:1 |
| Muted text | #919191 | #1A1A1A | 5.8:1 |
| Risk critical | #FFE0E0 | #1A1A1A | 12.8:1 |
Loading States¶
Skeleton Components¶
// frontend/src/components/common/Skeleton.tsx
<Skeleton variant="rectangular" width="100%" height={200} className="rounded-lg" />
<Skeleton variant="text" width={192} height={32} />
<Skeleton variant="circular" width={48} height={48} />
Shimmer Animation¶
// Shimmer effect for loading states
<div className="relative overflow-hidden bg-gray-800 rounded-lg">
<div
className="absolute inset-0 -translate-x-full animate-shimmer
bg-gradient-to-r from-transparent via-gray-700 to-transparent"
/>
</div>
Dark Mode Configuration¶
The theme uses class strategy for dark mode:
The dark class is always applied to the root element since this is a dark-theme-only application.
Related Documentation¶
- Component Hierarchy - Component structure
- Testing Patterns - Visual testing
- Frontend Hub - Overview
Last updated: 2026-01-24 - Initial styling patterns documentation for NEM-3462