Customizing the UI Theme
Learn how to customize colors, fonts, and styling to match your brand.
Overview
This tutorial shows you how to customize the visual appearance of your Fireact.dev application to match your brand identity.
What you’ll learn:
- Customizing TailwindCSS colors
- Changing fonts and typography
- Modifying component styles
- Creating custom UI components
- Implementing dark mode
Time to complete: ~30 minutes
Prerequisites
- Completed Getting Started Guide
- Basic understanding of CSS and TailwindCSS
- Working Fireact.dev application
Step 1: Customize TailwindCSS Configuration
Update Color Palette
Edit tailwind.config.js:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
// Your brand colors
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6', // Main primary color
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
secondary: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef', // Main secondary color
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
},
// Custom colors
brand: {
light: '#your-light-color',
DEFAULT: '#your-main-color',
dark: '#your-dark-color',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Poppins', 'system-ui', 'sans-serif'],
},
borderRadius: {
'brand': '0.75rem',
},
boxShadow: {
'brand': '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
},
},
},
plugins: [],
}
Add Custom Fonts
- Add fonts to
index.html:
<head>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700&display=swap" rel="stylesheet">
</head>
- Update global styles in
src/index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply font-sans text-gray-900 bg-gray-50;
}
h1, h2, h3, h4, h5, h6 {
@apply font-heading;
}
}
Step 2: Create Custom Component Variants
Create Reusable Button Component
Create src/components/common/Button.tsx:
import React from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
children: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
loading = false,
children,
className = '',
disabled,
...props
}) => {
const baseStyles = 'inline-flex items-center justify-center font-medium rounded-brand transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500',
secondary: 'bg-secondary-600 text-white hover:bg-secondary-700 focus:ring-secondary-500',
outline: 'border-2 border-primary-600 text-primary-600 hover:bg-primary-50 focus:ring-primary-500',
ghost: 'text-primary-600 hover:bg-primary-50 focus:ring-primary-500',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
disabled={disabled || loading}
{...props}
>
{loading && (
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
)}
{children}
</button>
);
};
Create Custom Card Component
Create src/components/common/Card.tsx:
import React from 'react';
interface CardProps {
children: React.ReactNode;
className?: string;
hover?: boolean;
padding?: 'none' | 'sm' | 'md' | 'lg';
}
export const Card: React.FC<CardProps> = ({
children,
className = '',
hover = false,
padding = 'md',
}) => {
const paddingClasses = {
none: '',
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
};
const hoverClass = hover ? 'hover:shadow-lg transition-shadow' : '';
return (
<div
className={`bg-white rounded-brand shadow-brand border border-gray-200 ${paddingClasses[padding]} ${hoverClass} ${className}`}
>
{children}
</div>
);
};
Step 3: Update Existing Components
Update Sign In Component
Edit src/components/SignIn.tsx to use custom components:
import { Button } from './common/Button';
import { Card } from './common/Card';
export const SignIn: React.FC = () => {
// ... existing state and logic
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary-50 to-secondary-50 py-12 px-4 sm:px-6 lg:px-8">
<Card className="max-w-md w-full" padding="lg">
<div className="text-center mb-8">
<h2 className="text-3xl font-heading font-bold text-gray-900">
Welcome Back
</h2>
<p className="mt-2 text-sm text-gray-600">
Sign in to your account
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="rounded-brand bg-red-50 border border-red-200 p-4">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
Email address
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 rounded-brand placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 rounded-brand placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
required
/>
</div>
<Button
type="submit"
variant="primary"
size="lg"
className="w-full"
loading={loading}
>
Sign In
</Button>
</form>
</Card>
</div>
);
};
Step 4: Implement Dark Mode
Add Dark Mode Support to TailwindCSS
Update tailwind.config.js:
export default {
darkMode: 'class', // Enable class-based dark mode
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
// ... existing config
},
},
}
Create Dark Mode Context
Create src/contexts/ThemeContext.tsx:
import React, { createContext, useContext, useState, useEffect } from 'react';
interface ThemeContextType {
isDark: boolean;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isDark, setIsDark] = useState(() => {
// Check localStorage or system preference
const saved = localStorage.getItem('theme');
if (saved) {
return saved === 'dark';
}
return window.matchMedia('(prefers-color-scheme: dark)').matches;
});
useEffect(() => {
// Update DOM and localStorage
if (isDark) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
}, [isDark]);
const toggleTheme = () => {
setIsDark(!isDark);
};
return (
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
Add Dark Mode Toggle Component
Create src/components/ThemeToggle.tsx:
import React from 'react';
import { useTheme } from '../contexts/ThemeContext';
export const ThemeToggle: React.FC = () => {
const { isDark, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="p-2 rounded-brand bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
aria-label="Toggle theme"
>
{isDark ? (
<svg className="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" />
</svg>
) : (
<svg className="w-5 h-5 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)}
</button>
);
};
Update App.tsx
Wrap your app with the ThemeProvider:
import { ThemeProvider } from './contexts/ThemeContext';
function App() {
return (
<ThemeProvider>
<ConfigProvider>
<LoadingProvider>
<AuthProvider>
{/* ... rest of your app */}
</AuthProvider>
</LoadingProvider>
</ConfigProvider>
</ThemeProvider>
);
}
Add Dark Mode Styles
Update src/index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply font-sans text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-900 transition-colors;
}
}
@layer components {
.card-dark {
@apply dark:bg-gray-800 dark:border-gray-700;
}
.input-dark {
@apply dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400;
}
.button-dark {
@apply dark:bg-primary-500 dark:hover:bg-primary-600;
}
}
Step 5: Create Custom Logo Component
Create src/components/common/Logo.tsx:
import React from 'react';
import { Link } from 'react-router-dom';
interface LogoProps {
size?: 'sm' | 'md' | 'lg';
showText?: boolean;
}
export const Logo: React.FC<LogoProps> = ({ size = 'md', showText = true }) => {
const sizes = {
sm: 'h-6 w-6',
md: 'h-8 w-8',
lg: 'h-12 w-12',
};
const textSizes = {
sm: 'text-lg',
md: 'text-xl',
lg: 'text-3xl',
};
return (
<Link to="/" className="flex items-center space-x-2">
<div className={`${sizes[size]} bg-gradient-to-br from-primary-500 to-secondary-500 rounded-brand flex items-center justify-center`}>
<span className="text-white font-bold">F</span>
</div>
{showText && (
<span className={`font-heading font-bold text-gray-900 dark:text-white ${textSizes[size]}`}>
YourApp
</span>
)}
</Link>
);
};
Step 6: Test Your Theme
Start development server:
npm run devVerify customizations:
- Check color palette in different components
- Test button variants
- Toggle dark mode
- Test responsive design
- Verify typography
Tips for Branding
Color Selection
- Use a color picker tool like Coolors
- Generate TailwindCSS colors with Tailwind Color Generator
- Ensure sufficient contrast for accessibility
Typography
- Limit to 2-3 font families
- Use font weights consistently
- Maintain readable font sizes (minimum 16px for body text)
Component Consistency
- Create reusable components for common patterns
- Document component usage
- Maintain a component library
Next Steps
- Create a style guide document
- Build a component showcase page
- Implement animation utilities
- Add custom illustrations or icons
Resources
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.