A functional component is great, but a beautiful component is even better! React gives you multiple ways to style your components, each with its own strengths. In this lesson, you'll learn all the approaches to styling in React - from inline styles to CSS modules, dynamic styling, and best practices for creating maintainable, beautiful UIs.
Styling Approaches in React
In React, you have several options for styling your components:
- Inline Styles - JavaScript objects directly in JSX
- CSS Files - Regular CSS with className
- CSS Modules - Scoped CSS files
- CSS-in-JS Libraries - styled-components, Emotion, etc.
- Utility-First CSS - Tailwind CSS, UnoCSS
We'll focus on the first three approaches, which are the most common and don't require additional libraries.
Method 1: Inline Styles
Inline styles in React are JavaScript objects, not strings like in HTML. This is a key difference from regular HTML!
Basic Inline Styles
function StyledButton() {
const buttonStyle = {
backgroundColor: '#6366f1',
color: 'white',
padding: '12px 24px',
border: 'none',
borderRadius: '8px',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer'
}
return <button style={buttonStyle}>Click Me</button>
}
// Or directly inline:
function InlineButton() {
return (
<button style={{
backgroundColor: '#6366f1',
color: 'white',
padding: '12px 24px'
}}>
Click Me
</button>
)
}š” Double Curly Braces
style={{ color: 'red' }}
The outer braces mean "JavaScript expression"
The inner braces are the JavaScript object
It's not a special JSX syntax - just an object inside an expression!
Property Names: camelCase
CSS properties in JavaScript use camelCase instead of kebab-case:
// CSS (kebab-case)
// background-color: blue;
// font-size: 16px;
// margin-top: 20px;
// border-radius: 8px;
// JavaScript/React (camelCase)
const styles = {
backgroundColor: 'blue',
fontSize: '16px',
marginTop: '20px',
borderRadius: '8px'
}Numeric Values
For most numeric CSS properties, you can omit the px unit:
const boxStyle = {
width: 200, // Becomes '200px'
height: 100, // Becomes '100px'
padding: 20, // Becomes '20px'
margin: 10, // Becomes '10px'
fontSize: 16, // Becomes '16px'
zIndex: 10, // No unit added
opacity: 0.5 // No unit added
}
// But for other units, use strings:
const otherStyle = {
width: '50%',
height: '100vh',
fontSize: '1.5rem',
margin: '10px 20px'
}Dynamic Inline Styles
function DynamicBox({ color, size, isActive }) {
const boxStyle = {
backgroundColor: color,
width: size,
height: size,
border: isActive ? '3px solid black' : '1px solid gray',
transform: isActive ? 'scale(1.1)' : 'scale(1)',
transition: 'all 0.3s ease'
}
return <div style={boxStyle}>Dynamic Box</div>
}
// Usage:
<DynamicBox color="blue" size={100} isActive={true} />When to Use Inline Styles:
- Dynamic styles based on props or state
- One-off styles that won't be reused
- Prototype or quick styling
- Styles that need JavaScript calculations
When NOT to Use Inline Styles:
⢠Complex styles with many properties
⢠Pseudo-classes (hover, focus, active)
⢠Media queries
⢠Animations and keyframes
Method 2: CSS Files with className
The most traditional approach - write CSS in separate files and apply them using the className prop:
Basic CSS File Approach
.button {
background-color: #6366f1;
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
}
.button:hover {
background-color: #4f46e5;
}
.button:active {
transform: scale(0.98);
}
.button-large {
padding: 16px 32px;
font-size: 18px;
}
.button-small {
padding: 8px 16px;
font-size: 14px;
}
.button-danger {
background-color: #ef4444;
}
.button-danger:hover {
background-color: #dc2626;
}import './Button.css'
function Button({ size = 'medium', variant = 'primary', children }) {
return (
<button className={`button button-${size} button-${variant}`}>
{children}
</button>
)
}
// Usage:
<Button size="large">Large Button</Button>
<Button variant="danger">Delete</Button>
<Button size="small" variant="primary">Small Primary</Button>Dynamic className
Combine classes conditionally using template literals or helper functions:
function Card({ isPremium, isActive, className }) {
// Method 1: Template literal
const cardClasses = `card ${isPremium ? 'premium' : ''} ${isActive ? 'active' : ''} ${className || ''}`
return <div className={cardClasses}>Content</div>
}
// Method 2: Array filter
function Card2({ isPremium, isActive, className }) {
const classes = [
'card',
isPremium && 'premium',
isActive && 'active',
className
].filter(Boolean).join(' ')
return <div className={classes}>Content</div>
}
// Method 3: Object with classnames utility (if installed)
import classNames from 'classnames'
function Card3({ isPremium, isActive, className }) {
return (
<div className={classNames('card', {
premium: isPremium,
active: isActive
}, className)}>
Content
</div>
)
}Global vs Component Styles
// Global styles (App.css or index.css)
// Applied to entire app
import './index.css'
// Component-specific styles
// Applied to specific component
import './Button.css'
import './Card.css'
function App() {
return (
<div className="app">
<Button />
<Card />
</div>
)
}Watch Out for Global Scope!
Regular CSS files are global - styles from one component can affect another. Use unique class names or CSS Modules (next section) to avoid conflicts.
Method 3: CSS Modules
CSS Modules automatically scope CSS to the component, preventing style conflicts. Files end with .module.css:
Creating a CSS Module
.button {
background-color: #6366f1;
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
}
.button:hover {
background-color: #4f46e5;
}
.large {
padding: 16px 32px;
font-size: 18px;
}
.danger {
background-color: #ef4444;
}
.danger:hover {
background-color: #dc2626;
}import styles from './Button.module.css'
function Button({ size, variant, children }) {
return (
<button className={`${styles.button} ${styles[size]} ${styles[variant]}`}>
{children}
</button>
)
}
// The className might become something like:
// "Button_button__2x3f4 Button_large__1a2b3"
// This prevents conflicts with other components!Combining Multiple Module Classes
import styles from './Card.module.css'
function Card({ isPremium, isHighlighted }) {
// Method 1: Template literal
const className = `${styles.card} ${isPremium ? styles.premium : ''} ${isHighlighted ? styles.highlighted : ''}`
// Method 2: Array join
const className2 = [
styles.card,
isPremium && styles.premium,
isHighlighted && styles.highlighted
].filter(Boolean).join(' ')
return <div className={className}>Card Content</div>
}Global Classes in CSS Modules
Sometimes you need to use global classes. Use :global selector:
/* Local (scoped) */
.container {
padding: 20px;
}
/* Global - not scoped */
:global(.highlight) {
background-color: yellow;
}
/* Mix local and global */
.container :global(.error) {
color: red;
}⨠CSS Modules Benefits
- Automatic scoping prevents style conflicts
- Can use simple class names without worrying about collisions
- Works with build tools out of the box (Vite, Create React App)
- Better for component-based architecture
Combining Styling Approaches
You can (and often should) mix different styling approaches:
import styles from './ProfileCard.module.css'
function ProfileCard({ user, isOnline, customColor }) {
// Inline styles for dynamic values
const avatarStyle = {
borderColor: isOnline ? '#10b981' : '#6b7280',
backgroundColor: customColor
}
return (
<div className={styles.card}>
{/* CSS Module classes */}
<div
className={styles.avatar}
style={avatarStyle} {/* Inline for dynamic color */}
>
{user.initials}
</div>
<div className={styles.info}>
<h3 className={styles.name}>{user.name}</h3>
<p className={styles.email}>{user.email}</p>
</div>
{/* Conditional className */}
<span className={`${styles.status} ${isOnline ? styles.online : styles.offline}`}>
{isOnline ? 'Online' : 'Offline'}
</span>
</div>
)
}Conditional and Dynamic Styling
Conditional Styles with Inline
function Alert({ type, message }) {
const alertStyle = {
padding: '16px',
borderRadius: '8px',
backgroundColor:
type === 'success' ? '#d1fae5' :
type === 'error' ? '#fee2e2' :
type === 'warning' ? '#fef3c7' : '#e0e7ff',
color:
type === 'success' ? '#065f46' :
type === 'error' ? '#991b1b' :
type === 'warning' ? '#92400e' : '#3730a3',
border: `2px solid ${
type === 'success' ? '#10b981' :
type === 'error' ? '#ef4444' :
type === 'warning' ? '#f59e0b' : '#6366f1'
}`
}
return <div style={alertStyle}>{message}</div>
}Conditional Classes
function Button({ variant, size, disabled, loading, children }) {
const buttonClass = [
'btn',
`btn-${variant}`,
`btn-${size}`,
disabled && 'btn-disabled',
loading && 'btn-loading'
].filter(Boolean).join(' ')
return (
<button className={buttonClass} disabled={disabled || loading}>
{loading ? 'Loading...' : children}
</button>
)
}State-Based Styling
function InteractiveCard() {
const [isHovered, setIsHovered] = React.useState(false)
const [isPressed, setIsPressed] = React.useState(false)
const cardStyle = {
padding: '20px',
borderRadius: '12px',
backgroundColor: isPressed ? '#e0e7ff' : isHovered ? '#f5f3ff' : 'white',
transform: isPressed ? 'scale(0.98)' : isHovered ? 'scale(1.02)' : 'scale(1)',
transition: 'all 0.2s',
cursor: 'pointer',
boxShadow: isHovered ? '0 10px 25px rgba(0,0,0,0.1)' : '0 2px 8px rgba(0,0,0,0.05)'
}
return (
<div
style={cardStyle}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => { setIsHovered(false); setIsPressed(false) }}
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
>
Hover and click me!
</div>
)
}š” Tip: Edit the code above and click "Run" to see your changes
Vendor Prefixes and Browser Compatibility
React automatically adds vendor prefixes for some CSS properties when using inline styles:
const style = {
// React adds -webkit- automatically
transform: 'translateX(10px)',
userSelect: 'none',
// For others, you may need to add manually
WebkitTransform: 'translateX(10px)', // Capitalized!
msTransform: 'translateX(10px)'
}For CSS files, use a tool like Autoprefixer (included in most build tools) to automatically add vendor prefixes.
Styling Best Practices
1. Choose the Right Approach
Use Inline Styles For:
- Truly dynamic styles (colors from props, calculated sizes)
- One-off styles that won't be reused
Use CSS Files For:
- Global styles and resets
- Complex animations and keyframes
- Media queries and responsive design
- Pseudo-classes (:hover, :focus, :active)
Use CSS Modules For:
- Component-specific styles
- Avoiding naming conflicts
- Better organization in larger apps
2. Keep Styles Maintainable
// ā Hard to maintain
<div style={{
backgroundColor: '#6366f1',
padding: '12px 24px',
borderRadius: '8px',
color: 'white',
fontSize: '16px'
}}>
Button 1
</div>
<div style={{
backgroundColor: '#6366f1',
padding: '12px 24px',
borderRadius: '8px',
color: 'white',
fontSize: '16px'
}}>
Button 2
</div>
// ā
Reusable and maintainable
const buttonStyle = {
backgroundColor: '#6366f1',
padding: '12px 24px',
borderRadius: '8px',
color: 'white',
fontSize: '16px'
}
<div style={buttonStyle}>Button 1</div>
<div style={buttonStyle}>Button 2</div>
// ā
Even better - extract to CSS class
.button {
background-color: #6366f1;
padding: 12px 24px;
border-radius: 8px;
color: white;
font-size: 16px;
}3. Use CSS Variables for Theming
:root {
--color-primary: #6366f1;
--color-secondary: #8b5cf6;
--color-success: #10b981;
--color-danger: #ef4444;
--color-text: #1f2937;
--color-bg: #ffffff;
--border-radius: 8px;
--spacing-unit: 8px;
}
[data-theme="dark"] {
--color-primary: #818cf8;
--color-text: #f9fafb;
--color-bg: #1f2937;
}// In CSS
.button {
background-color: var(--color-primary);
border-radius: var(--border-radius);
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
}
// In inline styles
const buttonStyle = {
backgroundColor: 'var(--color-primary)',
borderRadius: 'var(--border-radius)'
}4. Organize Your Styles
src/
āāā components/
ā āāā Button/
ā ā āāā Button.jsx
ā ā āāā Button.module.css
ā āāā Card/
ā ā āāā Card.jsx
ā ā āāā Card.module.css
ā āāā ...
āāā styles/
ā āāā globals.css # Global styles
ā āāā variables.css # CSS variables
ā āāā reset.css # CSS reset
āāā App.jsx5. Avoid Deep Nesting
/* ā Too deeply nested */
.card .header .title .text span {
color: blue;
}
/* ā
Flatter structure */
.card-title-text {
color: blue;
}
/* ā
Or with CSS Modules */
.title {
color: blue;
}6. Use Consistent Naming
/* BEM Naming */
.button { }
.button--primary { }
.button--large { }
.button__icon { }
/* Or simple naming with CSS Modules */
.button { }
.primary { }
.large { }
.icon { }Responsive Styling
Media Queries in CSS
.container {
padding: 20px;
max-width: 1200px;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
}
@media (max-width: 480px) {
.container {
padding: 5px;
}
}Responsive with JavaScript
function ResponsiveComponent() {
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth)
React.useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const isMobile = windowWidth < 768
const containerStyle = {
padding: isMobile ? '10px' : '20px',
fontSize: isMobile ? '14px' : '16px'
}
return <div style={containerStyle}>Responsive Content</div>
}Animations and Transitions
CSS Transitions
.button {
background-color: #6366f1;
transform: scale(1);
transition: all 0.3s ease;
}
.button:hover {
background-color: #4f46e5;
transform: scale(1.05);
}
.button:active {
transform: scale(0.98);
}CSS Keyframe Animations
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.5s ease-out;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}Dynamic Animations with Inline Styles
function AnimatedBox() {
const [rotation, setRotation] = React.useState(0)
const boxStyle = {
width: 100,
height: 100,
backgroundColor: '#6366f1',
transform: `rotate(${rotation}deg)`,
transition: 'transform 0.3s ease'
}
return (
<div>
<div style={boxStyle} />
<button onClick={() => setRotation(rotation + 45)}>
Rotate
</button>
</div>
)
}Common Styling Pitfalls
1. Forgetting className vs class
// ā Wrong - 'class' is a reserved word
<div class="container">Content</div>
// ā
Correct - use 'className'
<div className="container">Content</div>2. String Instead of Object for Inline Styles
// ā Wrong - style must be an object
<div style="color: red; font-size: 16px">Text</div>
// ā
Correct
<div style={{ color: 'red', fontSize: '16px' }}>Text</div>3. Using Kebab-Case in Style Objects
// ā Wrong - kebab-case doesn't work
<div style={{ 'background-color': 'blue' }}>Text</div>
// ā
Correct - use camelCase
<div style={{ backgroundColor: 'blue' }}>Text</div>4. Creating New Style Objects in Render
// ā Creates new object on every render
function Component() {
return <div style={{ padding: 20 }}>Content</div>
}
// ā
Define outside component or use useMemo
const padding Style = { padding: 20 }
function Component() {
return <div style={paddingStyle}>Content</div>
}
// ā
Or when truly dynamic
function Component({ size }) {
const style = React.useMemo(() => ({
padding: size * 2
}), [size])
return <div style={style}>Content</div>
}Key Takeaways
- React offers multiple styling approaches: inline, CSS files, CSS modules
- Inline styles are JavaScript objects with camelCase properties
- Use
classNamenotclassin JSX - CSS Modules provide automatic scoping to prevent conflicts
- Numeric values default to px, but you can use string for other units
- Combine approaches - inline for dynamic, CSS for static/complex
- Use CSS variables for theming and consistency
- Pseudo-classes, media queries, and keyframes require CSS files
- Keep styles maintainable by extracting reusable values
- Organize styles logically - component-specific or global
- Use transitions and animations for better UX
- Avoid creating new style objects on every render
What's Next?
Congratulations! You've completed the JSX Fundamentals section. You now know how to:
- Write JSX and understand how it differs from HTML
- Embed JavaScript expressions and variables
- Render content conditionally
- Display lists with proper keys
- Style your components beautifully
With these fundamentals mastered, you're ready to dive into the heart of React: Components! In the next section, you'll learn about function components, props, children, destructuring, and component composition. Get ready to build reusable, modular React applications! šÆ