csv-table: shortcode de Hugo para tablas interactivas desde CSV
Pon un CSV en tu proyecto, añade una línea a tu post y obtén una tabla HTML ordenable, adaptable y accesible sin escribir ni una línea de marcado.

Si alguna vez intentaste mostrar datos tabulares en un sitio Hugo, sabes lo rápido que todo se complica. Digamos que tienes un buen archivo CSV con rankings de aplicaciones, comparaciones de precios o resultados de benchmarks. Para convertirlo en una tabla bonita en la página, tendrás que pelear con el marcado Markdown o escribir HTML a mano. Las dos opciones son regulares, y las dos escalan mal.
Me topé con esto lo suficiente como para terminar construyendo una herramienta decente, un shortcode llamado csv-table. Le apuntas a un archivo CSV y obtienes una tabla HTML estilizada, ordenable y adaptable. Una línea en tu contenido, cero marcado manual.
Por qué las tablas Markdown no funcionan
Cualquiera que haya intentado armar una tabla Markdown más grande que 3x3 conoce el dolor. Brian Wisti lo formuló bien en su post sobre CSV y tablas de datos en Hugo: leerlas es fácil, pero mantenerlas sin plugins de editor es un suplicio. Y tiene razón. Los problemas empiezan de inmediato:
El formato de las tablas Markdown es frágil. Si te falta un carácter pipe o accidentalmente añades una columna extra en una fila, en vez de tabla obtienes un desastre de caracteres. Sin mensaje de error, solo maquetación rota.
No escalan. Una tabla 5x5 funciona bien. Treinta filas de Excel, ya no. Cada vez que los datos cambian, te toca realinear todas esas barras verticales a mano.
Cero interactividad. Treinta filas de datos y el lector no puede ordenar por precio, valoración o nombre. Markdown simplemente no puede hacer eso.
Y por último, los datos están incrustados en el texto del artículo. No existen por separado. No puedes reutilizar la misma tabla en otro post, actualizar los datos independientemente del texto, ni generarlos con un script y simplemente conectarlos.
Qué existe ya y por qué no alcanza
Hugo tiene herramientas integradas para trabajar con datos. La función transform.Unmarshal puede parsear CSV en arrays, y varias personas en la comunidad han mostrado cómo aprovechar esto.
El shortcode csv-to-table de Joe Mooring es una solución sólida. Trabaja con recursos de página, de sección y globales, maneja errores correctamente, soporta títulos, delimitadores personalizados y filas de encabezado opcionales. Para una conversión directa de CSV a HTML es un excelente punto de partida.
Brian Wisti (enlace arriba) fue por otro camino: insertaba los datos CSV directamente dentro de las etiquetas del shortcode en lugar de referenciar un archivo externo. También experimentó con tablas basadas en JSON y un formato list-table orientado a líneas. El post es interesante y muestra bien hasta dónde se puede llegar con los shortcodes de Hugo con algo de ingenio.
Ambos enfoques me dieron ideas, pero ninguno resolvía mi problema por completo. Necesitaba ordenamiento, adaptabilidad para dispositivos móviles, alineación por columna, la posibilidad de ocultar columnas y limitar filas, más un marcado de accesibilidad correcto. Y todo esto no por separado, sino como una herramienta única que pudiera usar en todas partes.
Cómo funciona csv-table
Tus datos viven en un archivo CSV en el directorio assets de Hugo. En el texto del artículo, la llamada se ve así:
{{< csv-table file="app-rankings.csv" >}}
Eso es todo. Hugo parsea el CSV durante la compilación a través de transform.Unmarshal (sin parser propio) y genera una tabla HTML semántica con los <thead>, <tbody>, atributos scope, roles ARIA e indicadores aria-sort correctos.
Del lado del cliente, tablesort.js se encarga de la interactividad: un clic en el encabezado de columna ordena de forma ascendente, otro clic ordena de forma descendente. El ordenamiento funciona correctamente con números formateados, porcentajes y valores monetarios.
Simple por defecto, flexible a pedido
El principio es sencillo: convención sobre configuración. Los ajustes por defecto cubren el 80% de los casos. El 20% restante se resuelve con parámetros, no con un fork de la plantilla.
Así se ve una llamada más detallada:
{{< csv-table
file="sales.csv"
caption="Q1 Sales by Region"
sortBy="Revenue"
order="desc"
limit=10
col_right="Revenue,Units"
col_hide="Internal_ID"
compact=true
>}}
Aquí se toma el archivo sales.csv, se muestran solo 10 filas con el Revenue más alto, las columnas numéricas se alinean a la derecha, la columna Internal ID se oculta y se activa el modo compacto con maquetación más ajustada. El título de la tabla aparece arriba.
El conjunto completo de parámetros cubre todo lo que he necesitado en la práctica:
caption- título de la tablasortByyorder- ordenamiento inicial (ascendente por defecto)limit- número máximo de filas mostradascol_hide- ocultar columnas sin editar el archivo fuentecol_nowrap- evitar el salto de línea en una columnacol_left,col_center,col_right- alineación por columnacol_width- anchos CSS explícitos vía<colgroup>header(valor “hide”) - ocultar la fila de encabezadofont_monoyfont_size- control de tipografíacompact- maquetación compactaresponsive- modo de adaptabilidad: scroll horizontal o tarjetas en móvilesclass- clases CSS adicionales
Algo que vale la pena destacar: todos los parámetros de columna funcionan por nombre, no por índice. Escribes col_hide="Email,Phone", no col_hide="3,5". Internamente, el shortcode construye un mapa de encabezado a índice, así que la búsqueda es rápida mientras la llamada sigue siendo legible. Y si añades o reordenas columnas en tu CSV, el shortcode no se rompe.
Tablas en pantallas pequeñas
Tablas y dispositivos móviles son una combinación dolorosa. El shortcode ofrece dos modos.
Por defecto está activado scroll: la tabla se envuelve en un contenedor con scroll horizontal. La estructura de la tabla se mantiene, lo cual importa cuando las columnas necesitan compararse entre sí (tablas comparativas, especificaciones).
La alternativa es stack: cada fila se convierte en una tarjeta en pantallas estrechas. Cada celda muestra el nombre de su columna mediante atributos data-label y pseudo-elementos CSS ::before. Es CSS puro, sin JavaScript. Funciona bien para datos donde cada fila es un registro independiente (una lista de aplicaciones, un catálogo de productos) y al lector le resulta más cómodo ver todos los campos de un elemento a la vez.
Qué pasa por debajo
Algunos detalles técnicos para los curiosos.
El CSS y JavaScript del shortcode se cargan una sola vez por página, aunque haya cinco tablas en ella. El mecanismo Scratch de Hugo controla si los recursos ya fueron incluidos y evita duplicar etiquetas <link> y <script>.
Tanto el CSS como el JS pasan por el pipeline de recursos de Hugo: en producción están minificados y los nombres de archivo incluyen un hash para invalidar la caché. La biblioteca tablesort se puede cargar desde un CDN (configurable en los ajustes del tema) o incluir localmente si el sitio prescinde de peticiones externas.
El CSS está construido sobre custom properties y soporta modo oscuro mediante el selector [data-theme="dark"]. Si tu tema ya alterna ese atributo, las tablas adoptan el tema automáticamente.
En cuanto a accesibilidad: el marcado incluye atributos role, scope en las celdas de encabezado e indicadores aria-sort que se actualizan dinámicamente al ordenar. Los lectores de pantalla pueden navegar por la estructura de la tabla y entienden el orden de clasificación actual.
Si algo sale mal (archivo no encontrado, ruta incorrecta, CSV dañado), el shortcode muestra un mensaje de error claro justo en el lugar de la tabla. Sin fallos silenciosos, sin maquetación rota.
Donde realmente ahorra tiempo
El shortcode lleva funcionando en mi blog un buen tiempo. Estos son los escenarios donde más valor aporta.
Cuando los datos cambian con frecuencia (rankings mensuales, cifras trimestrales), simplemente actualizo el CSV y recompilo el sitio. El post en sí no lo toco.
Por ejemplo, mi post Top Raycast Extensions usa csv-table para mostrar un ranking completo de extensiones con ordenamiento, límite de filas y columnas ocultas. Todos los datos son un solo archivo CSV que regenero periódicamente. Actualizar el post = reemplazar el archivo + recompilar.
Cuando los datos vienen de un script o una exportación de hoja de cálculo, el CSV va directo al proyecto sin ningún tipo de reformateo.
Cuando necesito mostrar un “Top 10” de un conjunto de datos más grande, el parámetro limit resuelve la tarea sin recortar el archivo fuente.
Cuando los mismos datos se necesitan en varios posts con diferente presentación (diferentes columnas visibles, diferente ordenamiento), referencio un solo CSV con diferentes parámetros. Una fuente de datos, múltiples vistas.
¡Yo también quiero uno!
El código todavía no está disponible públicamente. Lo hice para mi propio tema de Hugo (“domino”) y aún no me he puesto a empaquetarlo en un módulo documentado por separado. Si hay suficiente interés, lo publicaré como un módulo reutilizable de Hugo. Escríbeme si te resultaría útil.
Todas las piezas están bien documentadas: transform.Unmarshal para parsear CSV, un sistema de parámetros basado en map para operaciones con columnas, tablesort.js para ordenamiento en el cliente, y CSS custom properties para gestionar el tema.
La complejidad no está en ninguna de estas piezas por separado, sino en hacer que funcionen juntas: atributos ARIA, modos responsivos, deduplicación de recursos, manejo de errores y configuraciones por defecto sensatas para que los casos simples sigan siendo simples.
Un correo cuando publique algo nuevo.



