caso de estudio
LinkedIn Modo Oscuro
Una migración de tokens de dos años que convirtió el modo oscuro de un ajuste en infraestructura.
El modo oscuro parece un toggle y en realidad es una reescritura de tokens. En un producto del tamaño de LinkedIn, “lanzar modo oscuro” significa inventariar cada hex hardcodeado en el código, decidir qué significaba semánticamente, y luego hilar un token semántico a través de suficientes superficies para que el toggle tenga dónde actuar.
Contexto
La mayoría de los fallos de “lanzar modo oscuro” no son visuales — son organizativos. El equipo que posee una superficie particular tiene sus propios valores de color, sus propias opiniones de marca, sus propios plazos. Un esfuerzo central de design-systems necesita una historia de migración que permita a cada superficie aterrizar a su propio ritmo sin bifurcar la paleta.
Movimiento
La gente cree que el modo oscuro es fácil. Es una media query prefers-color-scheme y una segunda paleta. En una empresa donde cada equipo pillar es dueño de una parte de la aplicación, no lo es — es un cambio de mentalidad de años que toca cada PR.
El primer problema fue acostumbrar a la organización a pensar en semántica. Todos hablaban en “hey, esto es blue70” — pero en un mundo themable y consciente del esquema, blue70 no significa nada. Es un valor hoja, no un contrato. El modelo de tokens que adopté fue estrictamente semántico — --color-action-primary, --color-surface-elevated, --color-text-subtle — y el valor de ese token vivía en un solo lugar por modo. El error de nombrado que descarté pronto fueron los tokens nombrados por la propiedad en la que terminaban (--color-button-bg); los tokens nombrados por propiedad te atrapan en la siguiente refactorización, los semánticos la sobreviven. Conseguir que la gente usara el nuevo modelo requirió hosting de office hours, escribir documentación extensa, y aparecer en code reviews hasta que convertir blue70 a --color-action-primary fuera hábito en lugar de pedido.
El patrón de migración fue el siguiente problema. LinkedIn tiene cientos de superficies y el equipo dueño de cada una tiene sus propios plazos. No podía gatillar el modo oscuro para que cada superficie hiciera flip al mismo tiempo; tampoco podía lanzar un producto a medio oscurecer. La salida: cada superficie conservaba sus valores hex hasta que la capa semántica estuviera cableada, momento en el que un solo swap de token hacía toda la superficie correcta en modo oscuro en un único PR. Los equipos podían programar ese swap cuando les calzara; el sistema central iba publicando la capa semántica por delante de ellos.
El tracking del tema fue la decisión que cargó con todo lo demás. Partes de la aplicación se cargaban dentro de iframes, lo que significaba que en cualquier momento necesitábamos saber dos cosas — la opción que el usuario había elegido, y el tema actual en el que la aplicación se estaba ejecutando:
- light → fácil, el usuario eligió light
- dark → fácil, el usuario eligió dark
- system → el usuario eligió system, y el tema actual depende del SO
Por el caso del iframe, deliberadamente me alejé del enfoque CSS-only prefers-color-scheme y construí la capa de tema sobre matchMedia — rastreábamos tanto la elección del usuario como la resolución viva del SO, y pasábamos un prop theme explícito a cada superficie embebida que no controlábamos del todo. CSS-only es elegante cuando sos dueño de toda la página; cuando la mitad de tus superficies son iframes, necesitás un valor que puedas pasar a través de la frontera.
La cola larga fueron las ilustraciones, los colores de marca y los embeds de terceros. No intentamos hacer eso adaptativo — ese es un problema de diseño distinto — los hicimos aceptables en cualquiera de los dos modos. Una capa ligera de fallback se encargó de las superficies embebidas que no controlábamos: tratamientos ligeramente desaturados, fondos conscientes del modo donde el arte lo necesitaba, una vía de escape sancionada para los colores que de verdad tenían que quedarse constantes.
Resultado
El lanzamiento fue interno-primero por diseño. Los empleados corrieron modo oscuro durante semanas antes de que cualquier usuario externo lo recibiera. Eso nos compró la lista de bugs pequeños-pero-reales — el badge que se leía bien en blanco y desaparecía en slate, el embed de terceros con un #fff hardcodeado — y un cuadro claro de qué significaba completo. Para cuando lanzamos externamente, los bugs de cola larga estaban en el changelog, no en los reportes de usuarios.
Lo que más me enorgullece no es visible en el toggle. Es que los tokens semánticos siguieron pagando dividendos después de lanzar el modo oscuro. El siguiente refresh de marca se movió por la org en días, no en meses. Una variante de alto contraste encajó encima de la misma capa de tokens. El modo oscuro dejó de ser un proyecto y pasó a ser infraestructura.
Qué haría diferente
Reordenaría un paso. Intentamos migrar superficies a nivel de componente antes de tener los shells de layout sobre tokens semánticos, y eso significó que algunos componentes salieron viéndose correctos sobre un fondo aún hardcodeado — una regresión que se leía como “el sistema no está listo” aunque sí lo estaba. Layouts shells primero; componentes después.
También empujaría antes el hueco entre las librerías de color de Figma y las semánticas de producción. Los diseñadores trabajaban en colores con nombre que no mapeaban limpiamente al modelo de tokens — lo suficiente cerca para que lo lanzáramos, lo suficiente lejos para que cada handoff necesitara una pasada de traducción. Eso debió ser una única fuente de verdad entre las plataformas. Que es exactamente el trabajo que terminé construyendo — ver LinkedIn design tooling para el repo de tokens-como-fuente-de-verdad que salió de esta era, y Superhuman design tooling para hacia dónde va.