A la primera lliçó del mòdul vam dir que un component encapsula tres coses: marcatge, lògica i estil. Les dues primeres ja estan resoltes: el marcatge viu al JSX i la lògica al cos de la funció. L'estil, en canvi, continua amuntegat en un únic src/index.css que creix sense control i on res no impedeix que d'aquí a tres mesos dues persones diferents defineixin una classe .targeta amb regles incompatibles. React no imposa cap solució per a això: t'ofereix diverses estratègies i espera que triïs amb criteri. En aquesta lliçó veuràs les quatre que s'usen a la pràctica, totes aplicades al mateix component —TargetaBicicleta— per poder-les comparar de debò, aprendràs a construir noms de classe dinàmics a partir de l'estat d'una bicicleta, ordenaràs la paleta de CicloUrbano amb variables CSS i adoptaràs l'estratègia que el projecte usarà d'ara endavant.

Contingut

  1. El problema: l'estil també és part del component
  2. Estratègia 1: CSS global importat
  3. Estratègia 2: estils en línia amb objectes
  4. Estratègia 3: CSS Modules amb Vite
  5. Estratègia 4: CSS-in-JS i frameworks d'utilitats
  6. Classes condicionals segons l'estat de la bicicleta
  7. Variables CSS per a la paleta de CicloUrbano
  8. Comparativa final i decisió del curs

  1. El problema: l'estil també és part del component

Avui TargetaBicicleta és incompleta com a unitat: el seu marcatge i la seva lògica són a TargetaBicicleta.jsx, però les seves regles d'estil són a cent línies de distància, en un fitxer que comparteix amb tota la resta. Això produeix quatre problemes concrets:

Problema Com es manifesta
Col·lisions de noms Tot el CSS viu en un espai global. Una classe .targeta a dos llocs es trepitja; guanya la que es carregui després
Estils orfes En esborrar un component, ningú es recorda d'esborrar el seu CSS. El fitxer global només creix
Por de modificar Puc canviar .estat? Ningú sap quants components la fan servir. S'acaba afegint .estat-nou en lloc de tocar l'existent
Sense dinamisme El CSS no coneix les dades. Pintar la vora del color del tipus de bicicleta exigeix que el component hi participi

React no resol això per tu, però sí que et dona l'ingredient que faltava: el component pot decidir les seves classes en temps d'execució, perquè className és una expressió JavaScript com qualsevol altra.

Les quatre estratègies que existeixen es poden ordenar per on viu l'estil:

flowchart TD
    A["On viu l'estil?"] --> B["En un .css global<br/>(Estratègia 1)"]
    A --> C["En un objecte JS dins del JSX<br/>(Estratègia 2)"]
    A --> D["En un .module.css al costat del component<br/>(Estratègia 3)"]
    A --> E["Al mateix JSX com a classes utilitàries<br/>o generat per JS (Estratègia 4)"]

  1. Estratègia 1: CSS global importat

És la que has usat fins ara, i consisteix a escriure CSS normal i importar-lo.

/* src/index.css */
.targeta-bicicleta {
  background: #ffffff;
  border: 1px solid #d9e2ec;
  border-radius: 8px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
  max-width: 22rem;
}

.targeta-bicicleta h3 {
  margin-bottom: 0.5rem;
  color: #12805c;
}
// src/main.jsx  — s'importa una vegada i afecta tota l'aplicació
import './index.css';
// src/components/TargetaBicicleta.jsx
function TargetaBicicleta({ bicicleta }) {
  return (
    <article className="targeta-bicicleta">
      <h3>{bicicleta.model}</h3>
    </article>
  );
}

Vite també permet importar un .css des del propi component, cosa que millora una mica l'organització:

// src/components/TargetaBicicleta.jsx
import './TargetaBicicleta.css';   // el fitxer viu al costat del component

Però compte: això no el fa local. Vite recull tots els .css importats i els ajunta en el full d'estils de la pàgina. El fitxer és més a prop del component, sí, però les seves classes continuen sent globals.

El problema de les col·lisions, amb un exemple real

Imagina que d'aquí a dos mesos algú afegeix una pantalla de reserves amb el seu propi full:

/* src/components/TargetaReserva.css */
.targeta-bicicleta {          /* reutilitza el nom "perquè s'hi assembla" */
  max-width: 100%;
  border: none;
  background: #f5f7fa;
}

Aquest fitxer s'importa i, sense que ningú ho pretengui, les targetes del catàleg perden la vora i l'amplada màxima. No hi ha error, no hi ha avís: només un disseny trencat que apareix en navegar en cert ordre. Depurar-ho ocupa mitja tarda.

Les mitigacions tradicionals existeixen i funcionen a mitges:

  • Convencions de noms tipus BEM: .targeta-bicicleta__titol, .targeta-bicicleta--urbana. Redueixen molt el risc, però depenen de la disciplina de tot l'equip, per sempre.
  • Prefixos per component: la mateixa idea, amb la mateixa fragilitat.
Avantatges Inconvenients
CSS pur, sense res a aprendre Espai de noms global: col·lisions garantides a llarg termini
Funciona en qualsevol projecte L'estil no viatja amb el component
Ideal per a reinicis, tipografia i variables Ningú s'atreveix a esborrar regles: el fitxer només creix

Quan usar-lo: per al que ha de ser global. Reinicis (box-sizing), tipografia base, colors del body i, sobretot, les variables CSS de la paleta, com veuràs a l'apartat 7.

  1. Estratègia 2: estils en línia amb objectes

React permet passar un objecte JavaScript a l'atribut style:

function TargetaBicicleta({ bicicleta }) {
  return (
    <article
      style={{
        background: '#ffffff',
        border: '1px solid #d9e2ec',
        borderRadius: '8px',
        padding: '1rem 1.25rem',
        maxWidth: '22rem'
      }}
    >
      <h3 style={{ color: '#12805c', marginBottom: '0.5rem' }}>{bicicleta.model}</h3>
    </article>
  );
}

Recorda de la lliçó 01-04 les regles d'aquest objecte:

  • Doble clau: les de fora obren l'expressió JSX, les de dins són el literal d'objecte.
  • Propietats en camelCase: borderRadius, no border-radius.
  • Valors com a cadenes, amb unitats incloses. Els números sense unitat s'interpreten com a píxels: padding: 16 equival a 16px.

On brillen: valors calculats en temps d'execució

Aquest és el seu cas legítim, i és real. Una barra d'ocupació d'una estació l'amplada de la qual depèn de les dades no es pot expressar amb una classe CSS, perquè el percentatge només es coneix en executar:

// src/components/DisponibilitatEstacio.jsx

/**
 * Barra d'ocupació d'una estació.
 * Props:
 *  - estacio (objecte, obligatori) { id, nom, barri, places }
 *  - ocupades (número, opcional, per defecte 0)
 */
function DisponibilitatEstacio({ estacio, ocupades = 0 }) {
  const percentatge = Math.round((ocupades / estacio.places) * 100);

  return (
    <div className="disponibilitat-estacio">
      <p>
        {estacio.nom}: {ocupades} de {estacio.places} places ocupades
      </p>
      <div className="disponibilitat-estacio__barra">
        {/* L'amplada només es coneix en execució: cas legítim d'estil en línia */}
        <div
          className="disponibilitat-estacio__ompliment"
          style={{ width: `${percentatge}%` }}
        />
      </div>
    </div>
  );
}

export default DisponibilitatEstacio;

Aquí la divisió del treball és la correcta: la classe aporta tot allò estàtic (colors, alçada, vores) i l'estil en línia aporta l'únic valor que depèn de les dades.

Els seus límits, que són severs

Limitació Conseqüència
Sense pseudoclasses No hi ha :hover, :focus, :active, :disabled
Sense pseudoelements No hi ha ::before ni ::after
Sense media queries No hi ha disseny adaptable
Sense selectors descendents Cal estilar cada node un per un
Sense animacions No hi ha @keyframes
Especificitat màxima Un estil en línia guanya a gairebé tot; sobreescriure'l obliga a !important
Objecte nou a cada render El literal {{…}} crea un objecte diferent cada vegada, amb implicacions per a les optimitzacions del Mòdul 8
Sense memòria cau ni compressió L'estil viatja al JavaScript, no en un full cacheable

Quan usar-lo: només per a valors dinàmics que el CSS no pot conèixer —amplades percentuals, posicions, transformacions calculades— i sempre combinat amb una classe que aporti la resta. Mai com a estratègia principal.

  1. Estratègia 3: CSS Modules amb Vite

Un CSS Module és un fitxer CSS normal el nom del qual acaba en .module.css. Vite el detecta automàticament —sense cap configuració— i fa dues coses: renombra cada classe perquè sigui única en tota l'aplicació i et retorna un objecte que tradueix els teus noms als generats.

Com s'escriu

/* src/components/TargetaBicicleta.module.css */
.targeta {
  background: #ffffff;
  border: 1px solid #d9e2ec;
  border-radius: 8px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
  max-width: 22rem;
  border-left: 4px solid #d9e2ec;
}

.titol {
  margin: 0 0 0.5rem;
  color: #12805c;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.dada {
  margin: 0.25rem 0;
  font-size: 0.95rem;
}

.preu {
  margin-top: 0.75rem;
  font-weight: 700;
}

/* Variants per tipus de bicicleta */
.urbana { border-left-color: #12805c; }
.electrica { border-left-color: #b45309; }
.carga { border-left-color: #9b1c1c; }

/* Aquí SÍ funcionen pseudoclasses, medias i tota la resta */
.targeta:hover {
  box-shadow: 0 2px 8px rgba(31, 41, 51, 0.12);
}

@media (max-width: 30rem) {
  .targeta {
    max-width: 100%;
  }
}

Fixa't en els noms: .targeta, .titol, .dada. Curts i genèrics, perquè l'àmbit local fa innecessari el prefix. No hi ha cap risc que xoquin amb les .targeta d'un altre component.

Com s'usa

// src/components/TargetaBicicleta.jsx
import estils from './TargetaBicicleta.module.css';
import EtiquetaEstat from './EtiquetaEstat.jsx';

function TargetaBicicleta({ bicicleta, nomEstacio = 'Estació desconeguda' }) {
  const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');

  return (
    <article className={`${estils.targeta} ${estils[bicicleta.tipus]}`}>
      <h3 className={estils.titol}>
        {bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
      </h3>
      <p className={estils.dada}>Tipus: {bicicleta.tipus}</p>
      <p className={estils.dada}>Estació: {nomEstacio}</p>
      <p className={estils.preu}>{preuFormatat} € / hora</p>
    </article>
  );
}

export default TargetaBicicleta;

Què passa per sota

import estils from './TargetaBicicleta.module.css' no importa text: importa un objecte que Vite genera en processar el fitxer.

// Contingut aproximat de l'objecte 'estils' en desenvolupament
{
  targeta: '_targeta_1x9k2_1',
  titol: '_titol_1x9k2_11',
  dada: '_dada_1x9k2_18',
  preu: '_preu_1x9k2_23',
  urbana: '_urbana_1x9k2_28',
  electrica: '_electrica_1x9k2_29',
  carga: '_carga_1x9k2_30'
}

I l'HTML que arriba al navegador:

<article class="_targeta_1x9k2_1 _urbana_1x9k2_28">
  <h3 class="_titol_1x9k2_11">Urbana Clàssica <span class="estat estat--disponible">disponible</span></h3>
  …
</article>

Aquest sufix es calcula a partir del nom del fitxer i del seu contingut, de manera que és impossible que dos components diferents generin la mateixa classe. El problema de les col·lisions desapareix per construcció, no per disciplina.

Dos detalls de sintaxi útils:

  • estils[bicicleta.tipus]: accés per claudàtors. Com que bicicleta.tipus val 'urbana', 'electrica' o 'carga', obtenim la classe de la variant corresponent. Així es connecten les dades amb l'estil sense cap if.
  • Classes amb guions: si anomenes una classe .targeta-destacada, en JS hauràs d'escriure estils['targeta-destacada']. Per això a CSS Modules s'estila usar camelCase: .targetaDestacadaestils.targetaDestacada.

Avantatges i inconvenients

Avantatges Inconvenients
Àmbit local garantit per l'eina Un fitxer extra per component
És CSS de debò: pseudoclasses, medias, animacions, tot Els noms generats són il·legibles en inspeccionar (configurable)
L'estil viatja amb el component: s'esborra junt amb ell Els valors dinàmics continuen necessitant estils en línia o variables CSS
Sense dependències: Vite el porta de sèrie Cal recordar estils. davant de cada classe
El navegador pot cachejar el full resultant Compartir estils entre components requereix pensar-hi (composes o un mòdul comú)

Aquesta és l'estratègia per defecte de CicloUrbano a partir d'ara, per tres raons: no afegeix cap dependència, resol el problema real (les col·lisions) sense demanar-te disciplina, i continua sent CSS, de manera que tot el que saps de la cascada et serveix igual.

Estils compartits entre mòduls

Quan dos components necessiten les mateixes regles, hi ha dues vies netes:

/* src/estils/comuns.module.css */
.caixa {
  background: #ffffff;
  border: 1px solid #d9e2ec;
  border-radius: 8px;
  padding: 1rem 1.25rem;
}
/* src/components/TargetaBicicleta.module.css */
.targeta {
  composes: caixa from '../estils/comuns.module.css';
  margin-bottom: 1rem;
  max-width: 22rem;
}

composes és una funcionalitat pròpia de CSS Modules: la classe resultant inclou totes dues. L'alternativa, més senzilla, és posar aquestes regles comunes al CSS global i combinar-les amb el helper de l'apartat 6: className={classes('caixa', estils.targeta)}.

  1. Estratègia 4: CSS-in-JS i frameworks d'utilitats

Les dues alternatives restants són molt populars i convé conèixer-les encara que no les fem servir al curs.

CSS-in-JS

Llibreries com styled-components o Emotion permeten escriure CSS dins de JavaScript i obtenir un component ja estilat:

// Exemple amb styled-components (NO s'usa en aquest curs)
import styled from 'styled-components';

const Targeta = styled.article`
  background: #ffffff;
  border: 1px solid #d9e2ec;
  border-radius: 8px;
  padding: 1rem 1.25rem;

  /* Les props arriben a la plantilla: dinamisme total */
  border-left: 4px solid ${props =>
    props.$tipo === 'electrica' ? '#b45309' : '#12805c'};

  &:hover {
    box-shadow: 0 2px 8px rgba(31, 41, 51, 0.12);
  }
`;

<Targeta $tipo={bicicleta.tipus}>…</Targeta>

El seu gran atractiu és que les props determinen l'estil de forma directa, amb tota la potència de CSS. Els seus inconvenients: una dependència més, un cost en temps d'execució (els estils es generen mentre l'aplicació corre), fricció amb el renderitzat en servidor i una tendència clara de l'ecosistema a allunyar-se d'aquest enfocament en favor de solucions que resolen en temps de compilació (zero-runtime), com Vanilla Extract o Panda CSS.

Frameworks d'utilitats (Tailwind CSS)

L'enfocament oposat: en lloc d'escriure CSS, es componen classes atòmiques predefinides directament al JSX.

// Exemple amb Tailwind CSS (NO s'usa en aquest curs)
<article className="bg-white border border-slate-200 rounded-lg p-4 mb-4 max-w-sm border-l-4 border-l-emerald-700 hover:shadow-md">
  <h3 className="text-emerald-700 font-bold mb-2">{bicicleta.model}</h3>
</article>

Avantatges: no es canvia de fitxer per estilar, el CSS final és minúscul perquè només s'inclouen les utilitats usades, i el sistema de disseny (espaiats, colors, tipografies) ve imposat, cosa que dona molta consistència. Inconvenients: el JSX s'omple de classes i perd llegibilitat, hi ha una corba d'aprenentatge de noms, i compondre condicionals llargs exigeix una llibreria auxiliar.

Les quatre, en perspectiva

Global En línia CSS Modules CSS-in-JS Utilitats
Àmbit Global De l'element Local Local Global (atòmic)
Pseudoclasses i medias No
Dinamisme per props No Total Per classes o variables Total Per classes
Dependències Cap Cap Cap (Vite) Una llibreria Una eina
Cost en execució Nul Baix Nul Mitjà Nul

  1. Classes condicionals segons l'estat de la bicicleta

Sigui quina sigui l'estratègia, hi ha una necessitat constant: triar classes en funció de les dades. A CicloUrbano, el distintiu d'estat ha de ser verd si la bicicleta està disponible, taronja si està llogada i vermell si està en manteniment.

Amb plantilles de cadena

La forma més directa, i la que ja vas usar a EtiquetaEstat:

// src/components/EtiquetaEstat.jsx
function EtiquetaEstat({ estat = 'disponible' }) {
  return <span className={`estat estat--${estat}`}>{estat}</span>;
}

estat--${estat} produeix estat--disponible, estat--alquilada o estat--mantenimiento. Una sola línia cobreix els tres casos i afegir un quart estat no exigeix tocar el component, només el CSS.

Compte amb un límit important: això funciona amb CSS global, però no amb CSS Modules, perquè les classes d'un mòdul estan renombrades. Aquí cal accedir a l'objecte:

import estils from './EtiquetaEstat.module.css';

function EtiquetaEstat({ estat = 'disponible' }) {
  return <span className={`${estils.etiqueta} ${estils[estat]}`}>{estat}</span>;
}

Amb condicionals

Quan la classe depèn d'un booleà:

// Ternari: una condició
<article className={destacada ? 'targeta-bicicleta destacada' : 'targeta-bicicleta'}>

// Amb && dins de la plantilla: compte, si és false insereix "false"
<article className={`targeta-bicicleta ${destacada ? 'destacada' : ''}`}>

Això es torna il·legible de seguida. Amb tres o quatre condicions apareixen plantilles monstruoses, espais dobles i undefined colant-se a l'atribut.

Un petit helper

La solució és una funció que ajunti els noms vàlids i descarti la resta. Ocupa tres línies i la faràs servir a tots els teus projectes:

// src/utilitats/classes.js

/**
 * Uneix noms de classe descartant els valors falsy.
 * classes('targeta', false, undefined, 'activa')  ->  'targeta activa'
 */
export function classes(...noms) {
  return noms.filter(Boolean).join(' ');
}

Com funciona, pas a pas:

  • ...noms és un paràmetre rest: recull tots els arguments en un array.
  • .filter(Boolean) elimina els valors falsy de l'array: false, undefined, null, '' i 0. És una drecera idiomàtica equivalent a .filter(valor => Boolean(valor)).
  • .join(' ') uneix els que queden separats per espais, que és justament el format de className.

Amb ell, el JSX torna a ser llegible:

// src/components/TargetaBicicleta.jsx
import { classes } from '../utilitats/classes.js';
import estils from './TargetaBicicleta.module.css';
import EtiquetaEstat from './EtiquetaEstat.jsx';

function TargetaBicicleta({ bicicleta, nomEstacio = 'Estació desconeguda', destacada = false }) {
  const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');
  const noDisponible = bicicleta.estat !== 'disponible';

  return (
    <article
      className={classes(
        estils.targeta,
        estils[bicicleta.tipus],
        destacada && estils.destacada,
        noDisponible && estils.atenuada
      )}
    >
      <h3 className={estils.titol}>
        {bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
      </h3>
      <p className={estils.dada}>Tipus: {bicicleta.tipus}</p>
      <p className={estils.dada}>Estació: {nomEstacio}</p>
      <p className={estils.preu}>{preuFormatat} € / hora</p>
    </article>
  );
}

export default TargetaBicicleta;

Si destacada és false, l'expressió destacada && estils.destacada val false, i filter(Boolean) la descarta. Sense ternaris, sense cadenes buides, sense espais sobrants.

En projectes reals veuràs la llibreria clsx (o classnames), que fa exactament això amb més funcionalitats. El nostre helper de tres línies cobreix el cas habitual sense afegir dependències.

I les classes que falten al mòdul:

/* Afegir a src/components/TargetaBicicleta.module.css */
.destacada {
  box-shadow: 0 0 0 2px #12805c;
}

.atenuada {
  opacity: 0.7;
}

  1. Variables CSS per a la paleta de CicloUrbano

Els colors de CicloUrbano es repeteixen literalment des de la lliçó 01-03: #12805c a cinc llocs, #d9e2ec en uns altres tants. Canviar el verd de marca implicaria avui una cerca i substitució per tot el projecte, amb el risc de deixar-se'n algun.

Les variables CSS (propietats personalitzades) resolen això de forma nativa, sense eines ni preprocessadors:

/* src/index.css — al principi del fitxer */
:root {
  /* Paleta de marca */
  --color-marca: #12805c;
  --color-llogada: #b45309;
  --color-manteniment: #9b1c1c;

  /* Superfícies i text */
  --color-fons: #f5f7fa;
  --color-superficie: #ffffff;
  --color-text: #1f2933;
  --color-vora: #d9e2ec;

  /* Formes i espaiat */
  --radi: 8px;
  --ombra: 0 2px 8px rgba(31, 41, 51, 0.12);
  --espai: 1rem;
}

I ara qualsevol fitxer d'estil del projecte les pot fer servir, inclosos els .module.css, perquè les variables sí que travessen l'àmbit local: el que CSS Modules renomena són les classes, no les propietats personalitzades.

/* src/components/TargetaBicicleta.module.css */
.targeta {
  background: var(--color-superficie);
  border: 1px solid var(--color-vora);
  border-radius: var(--radi);
  padding: var(--espai) 1.25rem;
  border-left: 4px solid var(--color-vora);
}

.targeta:hover {
  box-shadow: var(--ombra);
}

.titol {
  color: var(--color-marca);
}

.urbana { border-left-color: var(--color-marca); }
.electrica { border-left-color: var(--color-llogada); }
.carga { border-left-color: var(--color-manteniment); }

Avantatges concrets:

Avantatge Exemple
Una única font de veritat Canviar --color-marca repinta tota l'aplicació
Es poden canviar en execució Un tema fosc és qüestió de redefinir les variables sota un altre selector
Funcionen amb CSS Modules L'àmbit local no afecta les propietats personalitzades
Pont perfecte amb JS Un estil en línia pot fixar una variable, i el CSS la consumeix

Aquest últim punt mereix un exemple, perquè combina el millor de les estratègies 2 i 3:

// El component calcula el valor i l'entrega com a variable CSS
<div
  className={estils.ompliment}
  style={{ '--amplada-ocupacio': `${percentatge}%` }}
/>
.ompliment {
  width: var(--amplada-ocupacio, 0%);
  height: 100%;
  background: var(--color-marca);
  transition: width 0.3s ease;   /* la transició SÍ és possible: és CSS de debò */
}

El JavaScript només aporta la dada; tota la presentació —inclosa la transició, impossible en un estil en línia— continua al CSS. És el patró recomanat per a estils dinàmics.

  1. Comparativa final i decisió del curs

Criteri CSS global Estils en línia CSS Modules CSS-in-JS Utilitats (Tailwind)
Àmbit Global Només aquell element Local automàtic Local automàtic Global atòmic
Risc de col·lisió Alt Nul Nul Nul Nul
Pseudoclasses i :hover No
Media queries No
Animacions No
Dinamisme amb dades Només per classes Total Classes + variables CSS Total Classes condicionals
Eines necessàries Cap Cap Cap amb Vite Una llibreria Una eina de compilació
Cost en execució Nul Baix Nul Mitjà Nul
L'estil viatja amb el component No
Corba d'aprenentatge Nul·la Baixa Baixa Mitjana Mitjana
Quan usar-lo Reinicis, tipografia, variables Valors calculats en execució Estil de components: per defecte Sistemes de disseny amb molt dinamisme Equips que volen consistència i velocitat

La combinació que adopta CicloUrbano

No és «triar-ne una i descartar la resta»: les estratègies es complementen, i aquest repartiment és el que veuràs a la majoria de projectes amb Vite:

flowchart TD
    A["src/index.css<br/>GLOBAL"] --> A1["Reinici, tipografia,<br/>variables de la paleta"]
    B["Component.module.css<br/>CSS MODULES"] --> B1["Tot l'estil del component:<br/>layout, colors, hover, medias"]
    C["style={{ }}<br/>EN LÍNIA"] --> C1["Només valors calculats,<br/>preferiblement com a variable CSS"]

Estructura de fitxers resultant:

src/
├── components/
│   ├── TargetaBicicleta.jsx
│   ├── TargetaBicicleta.module.css      <- l'estil viu al costat del component
│   ├── EtiquetaEstat.jsx
│   ├── EtiquetaEstat.module.css
│   └── …
├── estils/
│   └── comuns.module.css                <- regles compartides entre mòduls
├── utilitats/
│   └── classes.js                       <- el helper de classes condicionals
├── dades/
│   └── domini.js
├── App.jsx
├── index.css                            <- reinici + variables globals
└── main.jsx

Amb això, TargetaBicicleta és per fi una unitat completa: el seu marcatge, la seva lògica i el seu estil són a la mateixa carpeta, amb el mateix nom, i es poden esborrar junts sense deixar rastre. Aquesta era la promesa de la primera lliçó del mòdul.

Errors Habituals i Consells

  • Usar class en lloc de className. L'error més repetit de React. class és paraula reservada de JavaScript.
  • Oblidar el .module al nom del fitxer. TargetaBicicleta.css és global; només TargetaBicicleta.module.css activa l'àmbit local. El símptoma és que l'import estils arriba buit i les classes no s'apliquen.
  • Escriure className={estils} en lloc de className={estils.targeta}. estils és un objecte; React acabarà pintant [object Object] com a classe.
  • Referir-se a una classe que no existeix al mòdul. estils.targeat val undefined i l'element es queda sense classe, sense cap error. És el fallo més difícil de veure: revisa sempre l'HTML generat a l'inspector.
  • Escriure CSS amb guions en un mòdul i accedir-hi amb punt. .targeta-destacada obliga a estils['targeta-destacada']. Usa camelCase als mòduls.
  • Inserir false en una plantilla de classe. `targeta ${destacada && 'destacada'}` produeix literalment "targeta false" quan la condició és falsa. Usa el helper classes o un ternari amb cadena buida.
  • Posar unitats on React ja les posa, o no posar-les on calen. A style, width: 300 és 300px, però width: '80%' necessita ser cadena. I flex: 1 o zIndex: 10 van sense unitat.
  • Intentar :hover amb estils en línia. No existeix. Si el necessites, cal una classe.
  • Consell: comença per les variables CSS. Abans d'escriure el primer component d'un projecte, defineix la paleta a :root. Estalvia una refactorització garantida.
  • Consell: anomena les classes d'un mòdul pel seu paper, no pel seu aspecte. .titol i .preu envelleixen bé; .textVerd no.
  • Consell: si un component necessita més de tres o quatre classes condicionals, revisa el seu disseny. Sol ser senyal que calen dos components diferents.
  • Nota sobre accessibilitat: el color mai no ha de ser l'únic portador d'informació. El nostre EtiquetaEstat encerta en mostrar també el text de l'estat junt amb el color. Això es tracta a fons a Accessibilitat en Components Interactius.

Exercicis

Exercici 1

Migra EtiquetaEstat de CSS global a CSS Modules. Crea src/components/EtiquetaEstat.module.css amb una classe base i una classe per cada estat (disponible, alquilada, mantenimiento), fent servir les variables CSS de la paleta. Adapta el component perquè combini la classe base amb la de l'estat rebut per props, i afegeix un efecte de :hover que augmenti lleugerament l'opacitat.

Pista: la classe de l'estat cal obtenir-la de l'objecte d'estils, no construir-la amb una plantilla de cadena.

Exercici 2

Crea el component DisponibilitatEstacio complet (src/components/DisponibilitatEstacio.jsx i el seu mòdul CSS). Ha de rebre una estacio del domini i un nombre de places ocupades, i mostrar el nom, el recompte i una barra de progrés. L'amplada de la barra s'ha de passar com a variable CSS des del component, i el color ha de canviar segons l'ocupació: verd per sota del 60 %, taronja entre el 60 % i el 85 %, vermell per sobre.

Usa el helper classes per triar la classe de color.

Exercici 3

Per a cada situació, indica quina estratègia d'estil triaries i per què. Justifica-ho en dues o tres frases.

  1. El reinici de marges i el box-sizing de tota l'aplicació.
  2. El color de fons d'una fila de taula que depèn de l'estat de la bicicleta (tres valors possibles).
  3. La posició exacta d'un tooltip, calculada a partir de les coordenades del ratolí.
  4. Un botó de marca reutilitzable amb estats :hover, :focus i :disabled.
  5. La paleta de colors corporativa de CicloUrbano.

Solucions

Solució 1.

/* src/components/EtiquetaEstat.module.css */
.etiqueta {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  vertical-align: middle;
  color: var(--color-superficie);
  opacity: 0.92;
  transition: opacity 0.2s ease;
}

.etiqueta:hover {
  opacity: 1;
}

.disponible { background-color: var(--color-marca); }
.alquilada { background-color: var(--color-llogada); }
.mantenimiento { background-color: var(--color-manteniment); }
// src/components/EtiquetaEstat.jsx
import { classes } from '../utilitats/classes.js';
import estils from './EtiquetaEstat.module.css';

/**
 * Distintiu visual de l'estat d'una bicicleta.
 * Props:
 *  - estat (cadena, opcional, per defecte 'disponible'):
 *    'disponible' | 'alquilada' | 'mantenimiento'
 */
function EtiquetaEstat({ estat = 'disponible' }) {
  return (
    <span className={classes(estils.etiqueta, estils[estat])}>{estat}</span>
  );
}

export default EtiquetaEstat;

El punt clau: className={`estat estat--${estat}`} deixa de funcionar amb mòduls, perquè les classes estan renombrades. Cal consultar-les a l'objecte amb estils[estat]. I si arribés un estat inesperat, estils[estat] seria undefined i el helper classes el descartaria sense trencar res: l'etiqueta es pintaria sense color de fons en lloc de fallar.

Solució 2.

/* src/components/DisponibilitatEstacio.module.css */
.contenidor {
  background: var(--color-superficie);
  border: 1px solid var(--color-vora);
  border-radius: var(--radi);
  padding: var(--espai) 1.25rem;
  margin-bottom: var(--espai);
  max-width: 32rem;
}

.text {
  margin: 0 0 0.5rem;
  font-size: 0.95rem;
}

.barra {
  height: 10px;
  background: var(--color-fons);
  border: 1px solid var(--color-vora);
  border-radius: 999px;
  overflow: hidden;
}

.ompliment {
  /* L'amplada arriba com a variable CSS des del component */
  width: var(--ocupacio, 0%);
  height: 100%;
  transition: width 0.3s ease;
}

.baixa { background: var(--color-marca); }
.mitjana { background: var(--color-llogada); }
.alta { background: var(--color-manteniment); }
// src/components/DisponibilitatEstacio.jsx
import { classes } from '../utilitats/classes.js';
import estils from './DisponibilitatEstacio.module.css';

/**
 * Ocupació d'una estació de CicloUrbano.
 * Props:
 *  - estacio (objecte, obligatori) { id, nom, barri, places }
 *  - ocupades (número, opcional, per defecte 0)
 */
function DisponibilitatEstacio({ estacio, ocupades = 0 }) {
  const percentatge = Math.round((ocupades / estacio.places) * 100);
  const nivell = percentatge < 60 ? 'baixa' : percentatge <= 85 ? 'mitjana' : 'alta';

  return (
    <div className={estils.contenidor}>
      <p className={estils.text}>
        {estacio.nom} ({estacio.barri}): {ocupades} de {estacio.places} places
        ocupades · {percentatge} %
      </p>
      <div className={estils.barra}>
        <div
          className={classes(estils.ompliment, estils[nivell])}
          style={{ '--ocupacio': `${percentatge}%` }}
        />
      </div>
    </div>
  );
}

export default DisponibilitatEstacio;
// src/App.jsx (fragment)
<DisponibilitatEstacio estacio={estacions[0]} ocupades={11} />
<DisponibilitatEstacio estacio={estacions[1]} ocupades={13} />
<DisponibilitatEstacio estacio={estacions[2]} ocupades={9} />

Repartiment de responsabilitats: el component calcula només la dada (percentatge i nivell), el mòdul CSS aporta tota la presentació —inclosa la transició— i l'estil en línia es limita a entregar una variable. Tant percentatge com nivell són valors derivats: cap dels dos és estat, com vas estudiar a la lliçó anterior.

Solució 3.

Cas Estratègia Justificació
1. Reinici i box-sizing CSS global És global per naturalesa: afecta tots els elements del document. Encapsular-ho per component no tindria sentit
2. Fons de fila segons l'estat CSS Modules amb classe condicional Són tres valors coneguts d'antuvi: tres classes i un estils[estat]. No cal dinamisme en execució
3. Posició d'un tooltip Estil en línia (o variable CSS) Les coordenades només es coneixen en execució i són un continu, no un conjunt de casos. És el cas legítim de l'estil en línia
4. Botó de marca amb :hover, :focus i :disabled CSS Modules Les pseudoclasses són impossibles en línia, i l'àmbit local evita que un .boto d'un altre component el trepitgi
5. Paleta corporativa Variables CSS a :root (global) Font de veritat única, accessible des de qualsevol mòdul i modificable en execució per a temes alternatius

Conclusió

Ja saps donar estil a un component de React amb criteri, no per costum. Has vist les quatre estratègies sobre el mateix TargetaBicicleta: el CSS global, còmode però amb un espai de noms compartit on les col·lisions són qüestió de temps; els estils en línia, imbatibles per a valors calculats en execució i inútils per a tota la resta —sense :hover, sense media queries, sense animacions—; els CSS Modules, que Vite suporta de sèrie i que resolen l'àmbit local per construcció; i les alternatives de l'ecosistema, CSS-in-JS i frameworks d'utilitats, amb els seus compromisos ben identificats.

CicloUrbano adopta la combinació que ofereix el millor equilibri: variables CSS a :root per a la paleta i les formes, un .module.css al costat de cada component per a tot el seu estil, i estils en línia només per entregar valors calculats, preferiblement en forma de variable CSS perquè la presentació continuï vivint al CSS. Pel camí has incorporat dues eines que faràs servir constantment: construir classes a partir de les dades amb estils[bicicleta.tipus] i compondre classes condicionals amb el helper classes, que descarta els valors falsos sense embrutar el JSX.

Amb això tanques el Mòdul 2. Saps què és un component com a unitat de disseny i com traçar-ne els límits; saps llegir el codi heretat escrit amb classes i per què tot el nou s'escriu amb funcions; saps parametritzar un component amb props de només lectura i compondre contingut amb children; saps donar-li memòria amb estat, actualitzar-lo de forma immutable i distingir el que es guarda del que es deriva; i ara saps vestir-lo sense que l'estil es descontroli. TargetaBicicleta és, per fi, una unitat completa: marcatge, lògica i estil a la mateixa carpeta.

Però el catàleg continua sent un aparador. El SelectorTipus guarda el tipus triat i no filtra res; ComptadorPlaces resta places que no li importen a ningú més; no hi ha formularis, no hi ha validació i les llistes s'escriuen targeta a targeta. Falta la interacció de debò. Al Mòdul 3: Treballant amb Esdeveniments aprendràs a gestionar els esdeveniments de React amb totes les seves regles, a mostrar o amagar parts de la interfície amb renderitzat condicional, a pintar col·leccions completes amb map i a entendre per fi per a què serveixen les claus que vas veure en la reconciliació, a construir formularis controlats i a validar-los, i a fer que tot això sigui accessible. La lliçó següent és Gestió d'Esdeveniments a React, i a partir d'aquí CicloUrbano deixarà de mirar-se i començarà a respondre.

Curs de React

Mòdul 1: Introducció a React

Mòdul 2: Components de React

Mòdul 3: Treballar amb Esdeveniments

Mòdul 4: Conceptes Avançats de Components

Mòdul 5: Hooks de React

Mòdul 6: Enrutament a React

Mòdul 7: Gestió de l'Estat

Mòdul 8: Optimització del Rendiment

Mòdul 9: Proves a React

Mòdul 10: Temes Avançats

Mòdul 11: Projecte: Construir una Aplicació Completa

© Copyright 2026. Tots els drets reservats