<task-card> ja té la seva pròpia fulla d'estils, declarada amb static styles a la lliçó anterior. Però TaskFlow és una aplicació amb diversos components, i a la següent lliçó d'aquest curs <task-list> necessitarà també els seus propis estils: un contenidor per al tauler, un títol, potser una tipografia base coherent amb la de les targetes. Copiar i enganxar el mateix CSS de colors, tipografia i espaiats a cada component nou seria repetir feina i, pitjor encara, generaria inconsistències quan algú actualitzés un color en un component sense recordar-se d'actualitzar-lo també a la resta. Aquesta lliçó resol aquest problema extraient un CSS reutilitzable a un mòdul compartit i combinant-lo amb els estils propis de cada component, aplicant-lo a <task-card> i a una nova versió amb estil de <task-list>.

Contingut

  1. El problema: CSS duplicat entre diversos components
  2. Un mòdul d'estils compartits amb css
  3. static styles com a array: combinar diverses fulles
  4. Creant shared-styles.js per a TaskFlow
  5. Aplicant la fulla compartida a <task-card>
  6. Donant estil a <task-list>: layout de columna per a les targetes
  7. Quin CSS convé compartir i quin convé deixar local

  1. El problema: CSS duplicat entre diversos components

Imagina que, seguint el mateix patró que es va usar a la lliçó anterior per a <task-card>, s'escrivís també un static styles complet i autocontingut per a <task-list>, repetint-hi els mateixos valors de color de text, la mateixa família tipogràfica i el mateix tipus de vora arrodonida que ja existeixen a <task-card>:

// task-card.js
static styles = css`
  article {
    font-family: system-ui, sans-serif;
    color: #1f2933;
    border-radius: 8px;
  }
`;

// task-list.js — mismo CSS, copiado y pegado
static styles = css`
  section {
    font-family: system-ui, sans-serif;
    color: #1f2933;
    border-radius: 8px;
  }
`;

Aquest plantejament funciona, però té un cost que es paga a mesura que TaskFlow creix: si més endavant es decideix canviar la família tipogràfica de tota l'aplicació, o ajustar el to del color de text principal, caldria localitzar i actualitzar aquesta mateixa declaració a cada component que l'hagi copiat, amb el risc real d'oblidar-se'n algun i acabar amb una aplicació visualment inconsistent. És exactament el mateix problema que la duplicació de codi planteja sempre en programació, aplicat ara al CSS.

La solució, igual que amb qualsevol altre fragment de codi repetit, és extreure aquesta part comuna a un únic lloc i reutilitzar-la des d'on calgui.

  1. Un mòdul d'estils compartits amb css

Com css és simplement una funció de JavaScript que s'importa de lit, el valor que retorna —igual que el valor que retorna html— es pot guardar en una variable, exportar-la des d'un mòdul, i importar-la a qualsevol altre fitxer, exactament igual que es faria amb qualsevol altra constant o funció de JavaScript.

// src/styles/shared-styles.js
import { css } from 'lit';

export const estilosCompartidos = css`
  :host {
    font-family: system-ui, sans-serif;
    color: #1f2933;
  }
`;

Aquest fitxer no defineix cap component ni cap classe: és, simplement, un mòdul de JavaScript normal el contingut únic del qual és una constant amb un fragment de CSS ja "embolicat" per la funció css. No hi ha res específic de Lit en la manera d'organitzar-lo més enllà d'usar la pròpia funció css; és el mateix patró d'"extreure a un mòdul i exportar" que ja s'haurà usat en qualsevol projecte de JavaScript modern per compartir constants o funcions entre diversos fitxers.

  1. static styles com a array: combinar diverses fulles

Amb el CSS compartit ja extret al seu propi mòdul, cada component necessita combinar-lo amb el seu propi CSS específic (la vora i el padding de <task-card>, per exemple, que no té sentit compartir amb <task-list>). Per a això, static styles accepta, a més d'un únic valor css, un array de diversos valors css, que Lit combina automàticament en una sola fulla d'estils aplicada al shadow root del component:

import { LitElement, html, css } from 'lit';
import { estilosCompartidos } from '../styles/shared-styles.js';

class TaskCard extends LitElement {
  static styles = [
    estilosCompartidos,
    css`
      article {
        border: 1px solid #d0d5dd;
        border-radius: 8px;
        padding: 1rem;
      }
    `,
  ];

  // ...
}

L'ordre dins de l'array importa, igual que en qualsevol fulla d'estils CSS normal: les regles del segon element de l'array s'apliquen després de les del primer, així que, en cas que dues regles competeixin exactament per la mateixa especificitat sobre el mateix selector, guanya la que apareix més tard a l'array. A la pràctica, amb fulles compartides pensades per no solapar-se amb el CSS específic de cada component (com es recomana a l'apartat 7), aquest matís d'ordre rarament arriba a notar-se, però convé tenir-lo present si mai apareix un comportament d'estil inesperat que no s'explica mirant només una de les dues fulles.

  1. Creant shared-styles.js per a TaskFlow

Aplicant l'anterior a TaskFlow, el mòdul compartit pot créixer una mica més enllà de la tipografia bàsica de l'apartat 2, incorporant també els colors i espaiats que es repetiran en diversos components del tauler:

// src/styles/shared-styles.js
import { css } from 'lit';

export const estilosCompartidos = css`
  :host {
    display: block;
    font-family: system-ui, sans-serif;
    color: #1f2933;
  }

  h2, h3 {
    margin: 0 0 0.5rem 0;
    color: #1f2933;
  }

  p {
    margin: 0.25rem 0;
    color: #52606d;
    font-size: 0.9rem;
  }
`;

Nota que aquesta fulla compartida ja inclou :host { display: block; ... }, el que significa que qualsevol component que la incorpori al seu static styles obté automàticament el comportament de bloc explicat a la lliçó anterior, sense haver de repetir aquesta regla a cada component per separat. També defineix un color de text base (#1f2933 per a títols, #52606d per a text secundari) i una família tipogràfica, exactament els dos valors que, a l'apartat 1, es van identificar com a candidats clars a duplicació entre <task-card> i <task-list>.

  1. Aplicant la fulla compartida a <task-card>

Amb shared-styles.js ja creat, <task-card> s'actualitza per usar-lo, retirant del seu propi static styles les regles que ja cobreix la fulla compartida:

import { LitElement, html, css } from 'lit';
import { estilosCompartidos } from '../styles/shared-styles.js';

class TaskCard extends LitElement {
  static properties = {
    // ...igual que a la lliçó anterior...
  };

  static styles = [
    estilosCompartidos,
    css`
      article {
        border: 1px solid #d0d5dd;
        border-radius: 8px;
        padding: 1rem;
        margin-bottom: 0.75rem;
        background-color: #ffffff;
      }

      .insignia {
        display: inline-block;
        padding: 0.15rem 0.5rem;
        border-radius: 999px;
        font-size: 0.8rem;
        margin-bottom: 0.5rem;
      }

      .aviso {
        color: #b42318;
        font-weight: bold;
      }
    `,
  ];

  // constructor, alternarExpandida, renderInsigniaEstado, renderFechaLimite i render
  // es mantenen exactament igual que a la lliçó anterior.
}

customElements.define('task-card', TaskCard);

Compara aquest static styles amb el de la lliçó anterior: les regles :host, h3 i p han desaparegut d'aquí perquè ara les aporta estilosCompartidos, i només queden les regles realment específiques de <task-card> —la vora i el fons d'article, l'aspecte de .insignia i de .aviso—. El resultat visual, després d'aquesta refactorització, és idèntic al que ja es tenia; el que ha canviat és d'on ve cada regla, no com es veu la targeta en pantalla.

  1. Donant estil a <task-list>: layout de columna per a les targetes

Amb la fulla compartida ja disponible, <task-list> pot rebre els seus primers estils reutilitzant exactament el mateix mecanisme, i afegint a més un layout propi per organitzar les targetes en columna:

import { LitElement, html, css } from 'lit';
import { estilosCompartidos } from '../styles/shared-styles.js';
import './task-card.js';

class TaskList extends LitElement {
  static properties = {
    tareas: { type: Array },
  };

  static styles = [
    estilosCompartidos,
    css`
      section {
        padding: 1rem;
      }

      .lista {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
      }
    `,
  ];

  constructor() {
    super();
    this.tareas = [
      { id: 1, titulo: 'Preparar la demo del sprint', estado: 'en-progreso', prioridad: 4, urgente: true },
      { id: 2, titulo: 'Revisar el PR de autenticación', estado: 'pendiente', prioridad: 2, urgente: false },
      { id: 3, titulo: 'Desplegar a producción', estado: 'hecha', prioridad: 5, urgente: false },
    ];
  }

  render() {
    return html`
      <section>
        <h2>Mis tareas</h2>
        <div class="lista">
          ${this.tareas.map(
            (tarea) => html`
              <task-card
                .titulo="${tarea.titulo}"
                .estado="${tarea.estado}"
                .prioridad="${tarea.prioridad}"
                .urgente="${tarea.urgente}"
              ></task-card>
            `
          )}
        </div>
      </section>
    `;
  }
}

customElements.define('task-list', TaskList);

La classe .lista, aplicada al <div> que envolta l'Array.map amb les targetes (ja present a la plantilla des del mòdul 2), usa display: flex; flex-direction: column; amb un gap per separar visualment cada <task-card> de la següent, sense necessitat de marges manuals a cada targeta. h2 rep automàticament l'estil ja definit a estilosCompartidos (el mateix color i marge que també usa l'h3 de <task-card>), així que el títol "Mis tareas" queda visualment coherent amb el títol de cada targeta sense haver escrit cap regla nova per a això.

És interessant notar un detall sobre el layout: display: flex es declara sobre .lista, que viu dins del shadow root de <task-list>, i afecta els elements <task-card> que són fills directes d'aquell <div> al DOM lleuger (la llum, en la terminologia de Web Components) de <task-list>. Això funciona perquè Flexbox, com qualsevol propietat de layout de CSS, opera sobre els elements fills reals a l'arbre de renderitzat final, independentment de si aquests fills són elements natius o elements personalitzats amb el seu propi Shadow DOM intern: <task-list> no necessita "veure dins" de cada <task-card> per poder organitzar-les en columna, de la mateixa manera que tampoc necessitava veure dins d'elles per passar-los les seves propietats.

  1. Quin CSS convé compartir i quin convé deixar local

No tot el CSS d'un projecte amb diversos components és bon candidat per viure en una fulla compartida. Convé un criteri clar per decidir què extreure i què deixar al static styles propi de cada component:

Candidat a compartir Candidat a quedar-se local
Tipografia base (font-family, mides de text generals) Estructura de layout específica d'un component (.lista amb flex-direction: column a <task-list>)
Colors de text base (títol, text secundari) Vores, ombres o fons propis de la "forma" visual d'un component concret (la targeta de <task-card>)
:host { display: block; }, quan aplica a gairebé tots els components del projecte Classes molt específiques d'un component (.insignia, .aviso), que no tenen sentit fora d'ell
Espaiats o radis de vora molt generals, usats de forma consistent a tota l'aplicació Qualsevol estil que depengui d'una dada pròpia del component (per exemple, un color que canvia segons la propietat estado, que es tracta a la següent lliçó)

La guia pràctica és: si canviar aquell valor hauria de canviar, a la vegada, l'aspecte de diversos components diferents de TaskFlow, és un bon candidat per a shared-styles.js. Si només té sentit per a la forma o el comportament visual particular d'un component concret, ha de quedar-se en el seu propi static styles. Forçar massa CSS cap al mòdul compartit, en canvi, corre el risc contrari: convertir-lo en una fulla d'estils genèrica i sobrecarregada, amb regles que en realitat només usa un dels components, el que dificulta raonar sobre quin CSS afecta a què.

Errors Comuns i Consells

  • Oblidar embolicar el CSS compartit amb css, i exportar una cadena de text normal: si shared-styles.js exportés simplement una cadena (export const estilosCompartidos = '...'; sense la funció css), Lit no la reconeixeria com una fulla d'estils vàlida en incloure-la a l'array de static styles, i produiria un error en temps d'execució. El css de la lliçó anterior és imprescindible també aquí.
  • Duplicar per error una mateixa regla a la fulla compartida i a la fulla local d'un component: si estilosCompartidos ja defineix color per a p, i el component torna a declarar color per a p en el seu propi bloc css, la segona declaració guanya (per anar després a l'array, com es va explicar a l'apartat 3), el que pot generar una inconsistència subtil difícil de detectar a simple vista. Convé revisar, en afegir una regla local, que no estigui ja coberta per la fulla compartida.
  • Ficar a la fulla compartida CSS que només usa un component: com es va explicar a l'apartat 7, forçar massa CSS cap a shared-styles.js (per exemple, la classe .insignia, que només existeix a <task-card>) fa que la fulla compartida creixi de forma desordenada i dificulta saber, sense buscar a tots els components, quines regles estan realment en ús.
  • Esperar que combinar fulles en un array canviï l'ordre d'aplicació del CSS respecte a l'especificitat normal de CSS: static styles com a array no introdueix cap mecanisme especial de prioritat més enllà de l'ordre d'aparició i les regles normals d'especificitat de CSS (id, classe, etiqueta); segueix sent CSS corrent per sota, només que repartit en diversos fragments que Lit combina abans d'inserir-los al shadow root.

Exercicis

  1. Afegeix a shared-styles.js una nova regla per a l'etiqueta button (pensant en botons que s'afegiran a TaskFlow en mòduls posteriors), amb una tipografia i un padding bàsics, i comprova-la aplicant-la temporalment en un botó de prova dins de <task-list>.
  2. Elimina intencionadament la importació d'estilosCompartidos a task-card.js (deixant la resta de l'array de static styles igual) i observa, recarregant la pàgina, quin aspecte perd la targeta. Explica amb les teves pròpies paraules per què desapareix precisament aquell aspecte i no un altre.
  3. Usant el criteri de la taula de l'apartat 7, decideix si una regla .detalle { background-color: #f2f4f7; } (el bloc de detall expandit de <task-card>, vist a la lliçó anterior) hauria de moure's a shared-styles.js o quedar-se al static styles propi de <task-card>. Justifica la resposta.

Solucions

// src/styles/shared-styles.js
export const estilosCompartidos = css`
  :host {
    display: block;
    font-family: system-ui, sans-serif;
    color: #1f2933;
  }

  h2, h3 {
    margin: 0 0 0.5rem 0;
    color: #1f2933;
  }

  p {
    margin: 0.25rem 0;
    color: #52606d;
    font-size: 0.9rem;
  }

  button {
    font-family: inherit;
    font-size: 0.9rem;
    padding: 0.4rem 0.8rem;
    border-radius: 6px;
    border: 1px solid #d0d5dd;
    cursor: pointer;
  }
`;

En afegir temporalment un <button>Prueba</button> dins de render() a <task-list>, el botó apareix ja amb la tipografia i el padding definits, sense necessitat de cap regla addicional al static styles propi de <task-list>, perquè tots dos components comparteixen la mateixa fulla importada.

  1. Sense estilosCompartidos a l'array de static styles de <task-card>, la targeta perd el :host { display: block; } (així que diverses targetes seguides tornarien a col·locar-se en línia, com es va explicar a la lliçó anterior) i també perd el color de text i la tipografia base definits per a h3 i p, que tornarien a l'estil per defecte del navegador. La vora, el padding i el fons de l'<article>, en canvi, seguirien presents, perquè aquestes regles estan al segon element de l'array, definit localment a task-card.js i no a la fulla compartida.

  2. Hauria de quedar-se al static styles propi de <task-card>. Aplicant el criteri de l'apartat 7: .detalle és una classe que només existeix dins de la plantilla de <task-card> (el bloc que apareix quan expandida és true), no té sentit reutilitzar-la a <task-list> ni a cap altre component futur de TaskFlow, i canviar el seu aspecte no hauria, en principi, d'afectar cap altre component del tauler. És exactament el perfil d'"estil propi de la forma visual d'un component concret" que la taula de l'apartat 7 recomana deixar local.

Conclusió

En aquesta lliçó has après a evitar la duplicació de CSS entre diversos components Lit extraient un mòdul d'estils compartits amb css, i a combinar-lo amb el CSS específic de cada component mitjançant static styles com a array. Has aplicat aquest patró creant shared-styles.js per a TaskFlow, usant-lo tant a <task-card> com a la nova versió amb estil de <task-list>, que ara organitza les seves targetes en una columna amb espaiat consistent gràcies a Flexbox.

TaskFlow té ja, per primera vegada al curs, un aspecte visual coherent entre els seus dos components. Però encara queda un problema per resoldre: els colors concrets —el de cada insígnia d'estat, el de l'avís d'urgència— estan escrits com a valors fixos dins del CSS de <task-card>, sense cap manera que qui usi el component des de fora pugui personalitzar-los sense tocar directament el seu codi font. Aquest és exactament el contingut de la següent lliçó, "Variables CSS Personalitzades i Theming", on descobriràs per què les variables CSS són capaces de travessar la frontera del Shadow DOM en la qual tant s'ha insistit en aquesta lliçó, i les usaràs per fer que els colors de TaskFlow siguin configurables des de fora de cada component.

Curs de Lit

Mòdul 1: Introducció a Lit i Web Components

Mòdul 2: Plantilles Reactives i Renderitzat

Mòdul 3: Propietats i Estat Reactiu

Mòdul 4: Estils en Components Lit

Mòdul 5: Esdeveniments i Comunicació entre Components

Mòdul 6: Cicle de Vida i Comportament Avançat

Mòdul 7: Directives i Funcionalitats Avançades de Plantilles

Mòdul 8: Integració, Interoperabilitat i Desplegament

Mòdul 9: Proves i Bones Pràctiques

Mòdul 10: Projecte: Construint TaskFlow

© Copyright 2026. Tots els drets reservats