<task-card> ya tiene su propia hoja de estilos, declarada con static styles en la lección anterior. Pero TaskFlow es una aplicación con varios componentes, y en la siguiente lección de este curso <task-list> necesitará también sus propios estilos: un contenedor para el tablero, un título, quizá una tipografía base coherente con la de las tarjetas. Copiar y pegar el mismo CSS de colores, tipografía y espaciados en cada componente nuevo sería repetir trabajo y, peor aún, generaría inconsistencias en cuanto alguien actualizara un color en un componente sin acordarse de actualizarlo también en el resto. Esta lección resuelve ese problema extrayendo un CSS reutilizable a un módulo compartido y combinándolo con los estilos propios de cada componente, aplicándolo a <task-card> y a una nueva versión con estilo de <task-list>.

Contenido

  1. El problema: CSS duplicado entre varios componentes
  2. Un módulo de estilos compartidos con css
  3. static styles como array: combinar varias hojas
  4. Creando shared-styles.js para TaskFlow
  5. Aplicando la hoja compartida a <task-card>
  6. Dando estilo a <task-list>: layout de columna para las tarjetas
  7. Qué CSS conviene compartir y cuál conviene dejar local

  1. El problema: CSS duplicado entre varios componentes

Imagina que, siguiendo el mismo patrón que se usó en la lección anterior para <task-card>, se escribiera también un static styles completo y autocontenido para <task-list>, repitiendo ahí los mismos valores de color de texto, la misma familia tipográfica y el mismo tipo de borde redondeado que ya existen en <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;
  }
`;

Este planteamiento funciona, pero tiene un coste que se paga a medida que TaskFlow crece: si más adelante se decide cambiar la familia tipográfica de toda la aplicación, o ajustar el tono del color de texto principal, haría falta localizar y actualizar esa misma declaración en cada componente que la haya copiado, con el riesgo real de olvidar alguno y acabar con una aplicación visualmente inconsistente. Es exactamente el mismo problema que la duplicación de código plantea siempre en programación, aplicado ahora al CSS.

La solución, igual que con cualquier otro fragmento de código repetido, es extraer esa parte común a un único sitio y reutilizarla desde donde haga falta.

  1. Un módulo de estilos compartidos con css

Como css es simplemente una función de JavaScript que se importa de lit, el valor que devuelve —igual que el valor que devuelve html— se puede guardar en una variable, exportarla desde un módulo, e importarla en cualquier otro fichero, exactamente igual que se haría con cualquier otra constante o función de JavaScript.

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

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

Este fichero no define ningún componente ni ninguna clase: es, simplemente, un módulo de JavaScript normal cuyo único contenido es una constante con un fragmento de CSS ya "envuelto" por la función css. No hay nada específico de Lit en la forma de organizarlo más allá de usar la propia función css; es el mismo patrón de "extraer a un módulo y exportar" que ya se habrá usado en cualquier proyecto de JavaScript moderno para compartir constantes o funciones entre varios ficheros.

  1. static styles como array: combinar varias hojas

Con el CSS compartido ya extraído a su propio módulo, cada componente necesita combinarlo con su propio CSS específico (el borde y el padding de <task-card>, por ejemplo, que no tiene sentido compartir con <task-list>). Para esto, static styles acepta, además de un único valor css, un array de varios valores css, que Lit combina automáticamente en una sola hoja de estilos aplicada al shadow root del componente:

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;
      }
    `,
  ];

  // ...
}

El orden dentro del array importa, igual que en cualquier hoja de estilos CSS normal: las reglas del segundo elemento del array se aplican después de las del primero, así que, en caso de que dos reglas compitan exactamente por la misma especificidad sobre el mismo selector, gana la que aparece más tarde en el array. En la práctica, con hojas compartidas pensadas para no solaparse con el CSS específico de cada componente (como se recomienda en el apartado 7), este matiz de orden rara vez llega a notarse, pero conviene tenerlo presente si alguna vez aparece un comportamiento de estilo inesperado que no se explica mirando solo una de las dos hojas.

  1. Creando shared-styles.js para TaskFlow

Aplicando lo anterior a TaskFlow, el módulo compartido puede crecer un poco más allá de la tipografía básica del apartado 2, incorporando también los colores y espaciados que se repetirán en varios componentes del tablero:

// 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;
  }
`;

Nótese que esta hoja compartida ya incluye :host { display: block; ... }, lo que significa que cualquier componente que la incorpore a su static styles obtiene automáticamente el comportamiento de bloque explicado en la lección anterior, sin tener que repetir esa regla en cada componente por separado. También define un color de texto base (#1f2933 para títulos, #52606d para texto secundario) y una familia tipográfica, exactamente los dos valores que, en el apartado 1, se identificaron como candidatos claros a duplicación entre <task-card> y <task-list>.

  1. Aplicando la hoja compartida a <task-card>

Con shared-styles.js ya creado, <task-card> se actualiza para usarlo, retirando de su propio static styles las reglas que ya cubre la hoja compartida:

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

class TaskCard extends LitElement {
  static properties = {
    // ...igual que en la lección 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 y render
  // se mantienen exactamente igual que en la lección anterior.
}

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

Compara este static styles con el de la lección anterior: las reglas :host, h3 y p han desaparecido de aquí porque ahora las aporta estilosCompartidos, y solo quedan las reglas realmente específicas de <task-card> —el borde y el fondo de article, el aspecto de .insignia y de .aviso—. El resultado visual, tras esta refactorización, es idéntico al que ya se tenía; lo que ha cambiado es de dónde viene cada regla, no cómo se ve la tarjeta en pantalla.

  1. Dando estilo a <task-list>: layout de columna para las tarjetas

Con la hoja compartida ya disponible, <task-list> puede recibir sus primeros estilos reutilizando exactamente el mismo mecanismo, y añadiendo además un layout propio para organizar las tarjetas 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 clase .lista, aplicada al <div> que envuelve el Array.map con las tarjetas (ya presente en la plantilla desde el módulo 2), usa display: flex; flex-direction: column; con un gap para separar visualmente cada <task-card> de la siguiente, sin necesidad de márgenes manuales en cada tarjeta. h2 recibe automáticamente el estilo ya definido en estilosCompartidos (el mismo color y margen que también usa el h3 de <task-card>), así que el título "Mis tareas" queda visualmente coherente con el título de cada tarjeta sin haber escrito ninguna regla nueva para ello.

Es interesante notar un detalle sobre el layout: display: flex se declara sobre .lista, que vive dentro del shadow root de <task-list>, y afecta a los elementos <task-card> que son hijos directos de ese <div> en el DOM ligero (la luz, en la terminología de Web Components) de <task-list>. Esto funciona porque Flexbox, como cualquier propiedad de layout de CSS, opera sobre los elementos hijos reales en el árbol de renderizado final, independientemente de que esos hijos sean elementos nativos o elementos personalizados con su propio Shadow DOM interno: <task-list> no necesita "ver dentro" de cada <task-card> para poder organizarlas en columna, del mismo modo que tampoco necesitaba ver dentro de ellas para pasarles sus propiedades.

  1. Qué CSS conviene compartir y cuál conviene dejar local

No todo el CSS de un proyecto con varios componentes es buen candidato a vivir en una hoja compartida. Conviene un criterio claro para decidir qué extraer y qué dejar en el static styles propio de cada componente:

Candidato a compartir Candidato a quedarse local
Tipografía base (font-family, tamaños de texto generales) Estructura de layout específica de un componente (.lista con flex-direction: column en <task-list>)
Colores de texto base (título, texto secundario) Bordes, sombras o fondos propios de la "forma" visual de un componente concreto (la tarjeta de <task-card>)
:host { display: block; }, cuando aplica a casi todos los componentes del proyecto Clases muy específicas de un componente (.insignia, .aviso), que no tienen sentido fuera de él
Espaciados o radios de borde muy generales, usados de forma consistente en toda la aplicación Cualquier estilo que dependa de un dato propio del componente (por ejemplo, un color que cambia según la propiedad estado, que se trata en la siguiente lección)

La guía práctica es: si cambiar ese valor debería cambiar, a la vez, el aspecto de varios componentes distintos de TaskFlow, es un buen candidato a shared-styles.js. Si solo tiene sentido para la forma o el comportamiento visual particular de un componente concreto, debe quedarse en su propio static styles. Forzar demasiado CSS hacia el módulo compartido, en cambio, corre el riesgo contrario: convertirlo en una hoja de estilos genérica y sobrecargada, con reglas que en realidad solo usa uno de los componentes, lo que dificulta razonar sobre qué CSS afecta a qué.

Errores Comunes y Consejos

  • Olvidar envolver el CSS compartido con css, y exportar una cadena de texto normal: si shared-styles.js exportara simplemente una cadena (export const estilosCompartidos = '...'; sin la función css), Lit no la reconocería como una hoja de estilos válida al incluirla en el array de static styles, y produciría un error en tiempo de ejecución. El css de la lección anterior es imprescindible también aquí.
  • Duplicar por error una misma regla en la hoja compartida y en la hoja local de un componente: si estilosCompartidos ya define color para p, y el componente vuelve a declarar color para p en su propio bloque css, la segunda declaración gana (por ir después en el array, como se explicó en el apartado 3), lo que puede generar una inconsistencia sutil difícil de detectar a simple vista. Conviene revisar, al añadir una regla local, que no esté ya cubierta por la hoja compartida.
  • Meter en la hoja compartida CSS que solo usa un componente: como se explicó en el apartado 7, forzar demasiado CSS hacia shared-styles.js (por ejemplo, la clase .insignia, que solo existe en <task-card>) hace que la hoja compartida crezca de forma desordenada y dificulta saber, sin buscar en todos los componentes, qué reglas están realmente en uso.
  • Esperar que combinar hojas en un array cambie el orden de aplicación del CSS respecto a la especificidad normal de CSS: static styles como array no introduce ningún mecanismo especial de prioridad más allá del orden de aparición y las reglas normales de especificidad de CSS (id, clase, etiqueta); sigue siendo CSS corriente por debajo, solo que repartido en varios fragmentos que Lit combina antes de insertarlos en el shadow root.

Ejercicios

  1. Añade a shared-styles.js una nueva regla para la etiqueta button (pensando en botones que se añadirán a TaskFlow en módulos posteriores), con una tipografía y un padding básicos, y compruébala aplicándola temporalmente en un botón de prueba dentro de <task-list>.
  2. Elimina intencionadamente la importación de estilosCompartidos en task-card.js (dejando el resto del array de static styles igual) y observa, recargando la página, qué aspecto pierde la tarjeta. Explica con tus propias palabras por qué desaparece justo ese aspecto y no otro.
  3. Usando el criterio de la tabla del apartado 7, decide si una regla .detalle { background-color: #f2f4f7; } (el bloque de detalle expandido de <task-card>, visto en la lección anterior) debería moverse a shared-styles.js o quedarse en el static styles propio de <task-card>. Justifica la respuesta.

Soluciones

// 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;
  }
`;

Al añadir temporalmente un <button>Prueba</button> dentro de render() en <task-list>, el botón aparece ya con la tipografía y el padding definidos, sin necesidad de ninguna regla adicional en el static styles propio de <task-list>, porque ambos componentes comparten la misma hoja importada.

  1. Sin estilosCompartidos en el array de static styles de <task-card>, la tarjeta pierde el :host { display: block; } (así que varias tarjetas seguidas volverían a colocarse en línea, como se explicó en la lección anterior) y también pierde el color de texto y la tipografía base definidos para h3 y p, que volverían al estilo por defecto del navegador. El borde, el padding y el fondo del <article>, en cambio, seguirían presentes, porque esas reglas están en el segundo elemento del array, definido localmente en task-card.js y no en la hoja compartida.

  2. Debería quedarse en el static styles propio de <task-card>. Aplicando el criterio del apartado 7: .detalle es una clase que solo existe dentro de la plantilla de <task-card> (el bloque que aparece cuando expandida es true), no tiene sentido reutilizarla en <task-list> ni en ningún otro componente futuro de TaskFlow, y cambiar su aspecto no debería, en principio, afectar a ningún otro componente del tablero. Es exactamente el perfil de "estilo propio de la forma visual de un componente concreto" que la tabla del apartado 7 recomienda dejar local.

Conclusión

En esta lección has aprendido a evitar la duplicación de CSS entre varios componentes Lit extrayendo un módulo de estilos compartidos con css, y a combinarlo con el CSS específico de cada componente mediante static styles como array. Has aplicado este patrón creando shared-styles.js para TaskFlow, usándolo tanto en <task-card> como en la nueva versión con estilo de <task-list>, que ahora organiza sus tarjetas en una columna con espaciado consistente gracias a Flexbox.

TaskFlow tiene ya, por primera vez en el curso, un aspecto visual coherente entre sus dos componentes. Pero todavía queda un problema por resolver: los colores concretos —el de cada insignia de estado, el del aviso de urgencia— están escritos como valores fijos dentro del CSS de <task-card>, sin ninguna forma de que quien use el componente desde fuera pueda personalizarlos sin tocar directamente su código fuente. Ese es exactamente el contenido de la siguiente lección, "Variables CSS Personalizadas y Theming", donde descubrirás por qué las variables CSS son capaces de atravesar la frontera del Shadow DOM que tanto se ha insistido en esta lección, y las usarás para hacer que los colores de TaskFlow sean configurables desde fuera de cada componente.

Curso de Lit

Módulo 1: Introducción a Lit y Web Components

Módulo 2: Plantillas Reactivas y Renderizado

Módulo 3: Propiedades y Estado Reactivo

Módulo 4: Estilos en Componentes Lit

Módulo 5: Eventos y Comunicación entre Componentes

Módulo 6: Ciclo de Vida y Comportamiento Avanzado

Módulo 7: Directivas y Funcionalidades Avanzadas de Plantillas

Módulo 8: Integración, Interoperabilidad y Despliegue

Módulo 9: Pruebas y Buenas Prácticas

Módulo 10: Proyecto: Construyendo TaskFlow

© Copyright 2026. Todos los derechos reservados