So far, we’ve focused on making our React application work and manage data. But we haven’t done anything aesthetically, so they look horrible.
It’s time to make them pretty and apply styles, if only so they don’t look like Word documents from the 90s.
In the React world, there are many ways to apply styles (SASS, Styled Components, Tailwind, Emotion…). Many of them aren’t even used anymore.
In any case, the most important thing is to start with the standard, good old standard CSS.
The good news is that if you already know CSS, you already know how to style in React. There are only a couple of syntactic rules that change.
From class to className
The first slap React will give you if you copy and paste old HTML code is this:
Warning: Invalid DOM property
class. Did you meanclassName?
As we saw in the first chapters, JSX is JavaScript, not HTML. And in JavaScript, class is a reserved word… so we can’t use it.
To avoid syntax conflicts, React decided to rename the HTML attribute class to className.
// ❌ HTML / Incorrect React
<div class="contenedor">...</div>
// ✅ Correct JSX
<div className="contenedor">...</div>
Otherwise, it works exactly the same. It accepts a text string with the names of the classes you want to apply.
Dynamic Classes
The great advantage of React is that className is just another prop. We can use JavaScript to decide which classes to apply in real time.
If we only have two states (for example, active/inactive), a ternary inside a Template Literal is the fastest.
function Item({ isActive }) {
return (
<div className={`item ${isActive ? 'item-active' : ''}`}>
Content
</div>
);
}
Notice the whitespace after ‘item ’, it’s necessary so the classes don’t stick together (itemitem-active)
If the logic gets complicated, using concatenated strings is a pain. Using arrays can be more elegant.
const clases = [
'btn',
props.primary ? 'btn-primary' : '',
props.large ? 'btn-lg' : ''
];
// We filter out the empty ones and join with spaces
return <button className={clases.filter(Boolean).join(' ')}>Click</button>;
In real projects, almost everyone uses a small library called clsx or classnames. It’s like a “well-made evolution” of the previous array example.
It allows us to write conditional classes in a much more readable way, and the syntax has almost become a standard.
import clsx from 'clsx';
// The result is a clean string with the classes
const className = clsx('btn', {
'btn-primary': isPrimary,
'btn-disabled': isDisabled,
'btn-loading': isLoading
});
Importing Style Sheets
To import a .css, file in traditional web development, we add a <link> tag in the head of our HTML.
In React (especially with Vite), we import CSS directly into our JavaScript files.
Imagine we have a Boton.jsx component. We can create a Boton.css file next to it.
/* Boton.css */
.btn-primary {
background-color: blue;
color: white;
padding: 10px 20px;
border-radius: 5px;
}
And in our component, we simply import the file:
// Boton.jsx
import React from 'react';
import './Boton.css'; // <--- HERE
export default function Boton() {
return <button className="btn-primary">Click me</button>;
}
When Vite detects that import, it takes the CSS, processes it, and automatically injects it into the browser (usually by adding a <style> tag in the head or generating a final .css file in production).
The Global Scope Problem
Even though you imported Boton.css inside Boton.jsx, the styles are NOT encapsulated, everything is global.
That is, if you define the .tarjeta class in Boton.css and also define .tarjeta in Header.css, both rules will clash.
The one that loaded last will win (it’s part of how the “Cascade” in CSS works). In our case, basically a random one.
Importing a CSS file in a component only serves to organize your files. It does not isolate styles.
A style imported in the deepest component of the app will affect the outermost component if the class names match.
To avoid this using traditional CSS, one way is to be very disciplined with naming conventions. This is how methodologies like BEM (Block Element Modifier) arise:
/* Using BEM to avoid collisions */
.Boton__container--primary { ... }
.Header__title { ... }
In general, it’s pretty horrible. Personally, I don’t like BEM.
We’ll see other solutions in upcoming posts.
Inline Styles (style)
Although this article is about classes, it’s worth mentioning that you can also apply styles directly with the style prop.
Unlike HTML (where style is a string), in React style accepts a JavaScript object. CSS properties with hyphens are converted to camelCase.
// HTML: <div style="background-color: red; margin-top: 10px;">
// React
const estilos = {
backgroundColor: 'red', // camelCase
marginTop: '10px' // camelCase
};
return <div style={estilos}>Hello</div>;
When to use inline style? As a general rule, avoid inline styles. It clutters the code and doesn’t allow media queries or pseudo-selectors (:hover).
Use it only for dynamically calculated values (e.g., x,y coordinates of a draggable element, a dynamic background image, or a progress bar).
