Una tabla de contenido perfecta con HTML + CSS PlatoBlockchain Data Intelligence. Búsqueda vertical. Ai.

Una tabla de contenido perfecta con HTML + CSS

A principios de este año, publiqué un libro electrónico llamado Comprender las promesas de JavaScript (gratis para descargar). A pesar de que no tenía ninguna intención de convertirlo en un libro impreso, suficientes personas se acercaron para preguntar sobre una versión impresa, así que decidí autoeditarla también. Pensé que sería un ejercicio fácil usar HTML y CSS para generar un PDF y luego enviarlo a la impresora. Lo que no me di cuenta fue que no tenía una respuesta a una parte importante de un libro impreso: la tabla de contenido.

La composición de una tabla de contenido.

En esencia, una tabla de contenido es bastante simple. Cada línea representa una parte de un libro o página web e indica dónde puede encontrar ese contenido. Por lo general, las líneas contienen tres partes:

  1. El título del capítulo o sección.
  2. Líderes (es decir, esos puntos, guiones o líneas) que conectan visualmente el título con el número de página
  3. el numero de pagina

Una tabla de contenido es fácil de generar dentro de las herramientas de procesamiento de texto como Microsoft Word o Google Docs, pero debido a que mi contenido estaba en Markdown y luego se transformó en HTML, esa no era una buena opción para mí. Quería algo automatizado que funcionara con HTML para generar la tabla de contenido en un formato adecuado para imprimir. También quería que cada línea fuera un enlace para que pudiera usarse en páginas web y archivos PDF para navegar por el documento. También quería líderes de puntos entre el título y el número de página.

Y así comencé a investigar.

Encontré dos excelentes publicaciones de blog sobre la creación de una tabla de contenido con HTML y CSS. el primero fue “Cree una tabla de contenido a partir de su HTML” de Julie Blanc. Julia trabajó en PaginadoJS, un polyfill para las funciones de medios paginados que faltan en los navegadores web que formatea correctamente los documentos para su impresión. Empecé con el ejemplo de Julie, pero descubrí que no me funcionaba del todo. A continuación, encontré la de Christoph Grabo "Líneas de líder TOC receptivas con CSS" post, que introdujo el concepto de usar CSS Grid (a diferencia del enfoque basado en elementos flotantes de Julie) para facilitar la alineación. Una vez más, sin embargo, su enfoque no era del todo correcto para mis propósitos.

Sin embargo, después de leer estas dos publicaciones, sentí que tenía una comprensión lo suficientemente buena de los problemas de diseño para embarcarme en la mía. Utilicé piezas de ambas publicaciones del blog y agregué algunos conceptos nuevos de HTML y CSS al enfoque para obtener un resultado con el que estoy satisfecho.

Elegir el marcado correcto

Al decidir sobre el marcado correcto para una tabla de contenido, pensé principalmente en la semántica correcta. Fundamentalmente, una tabla de contenido se trata de un título (capítulo o subsección) vinculado a un número de página, casi como un par clave-valor. Eso me llevó a dos opciones:

  • Una opción es usar una tabla (<table>) con una columna para el título y una columna para la página.
  • Luego está la lista de definiciones que a menudo no se usa y se olvida (<dl>) elemento. También actúa como un mapa clave-valor. Entonces, una vez más, la relación entre el título y el número de página sería obvia.

Cualquiera de estas parecía una buena opción hasta que me di cuenta de que en realidad solo funcionan para tablas de contenido de un solo nivel, es decir, solo si quería tener una tabla de contenido con solo nombres de capítulos. Sin embargo, si quería mostrar subsecciones en la tabla de contenido, no tenía buenas opciones. Los elementos de la tabla no son buenos para los datos jerárquicos, y aunque técnicamente las listas de definiciones se pueden anidar, la semántica no parecía correcta. Entonces, volví a la mesa de dibujo.

Decidí basarme en el enfoque de Julie y usar una lista; sin embargo, opté por una lista ordenada (<ol>) en lugar de una lista desordenada (<ul>). Creo que una lista ordenada es más apropiada en este caso. Una tabla de contenido representa una lista de capítulos y subtítulos en el orden en que aparecen en el contenido. El orden es importante y no debe perderse en el marcado.

Desafortunadamente, usar una lista ordenada significa perder la relación semántica entre el título y el número de página, por lo que mi siguiente paso fue restablecer esa relación dentro de cada elemento de la lista. La forma más fácil de resolver esto es simplemente insertar la palabra "página" antes del número de página. De esa forma, la relación del número con respecto al texto es clara, incluso sin ninguna otra distinción visual.

Aquí hay un esqueleto HTML simple que formó la base de mi marcado:

<ol class="toc-list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol> <!-- subsection items --> </ol> </li>
</ol>

Aplicación de estilos a la tabla de contenido

Una vez que establecí el marcado que planeaba usar, el siguiente paso fue aplicar algunos estilos.

Primero, eliminé los números generados automáticamente. Si lo desea, puede optar por mantener los números generados automáticamente en su propio proyecto, pero es común que los libros tengan prólogos y epílogos no numerados incluidos en la lista de capítulos, lo que hace que los números generados automáticamente sean incorrectos.

Para mi propósito, completaría los números de los capítulos manualmente y luego ajustaría el diseño para que la lista de nivel superior no tenga ningún relleno (por lo tanto, la alinearía con los párrafos) y cada lista incrustada tiene una sangría de dos espacios. Elegí usar un 2ch valor de relleno porque todavía no estaba muy seguro de qué fuente usaría. los ch La unidad de longitud permite que el relleno sea relativo al ancho de un carácter, sin importar qué fuente se use, en lugar de un tamaño de píxel absoluto que podría terminar pareciendo inconsistente.

Aquí está el CSS con el que terminé:

.toc-list, .toc-list ol { list-style-type: none;
} .toc-list { padding: 0;
} .toc-list ol { padding-inline-start: 2ch;
}

sara soueidan me señaló que los navegadores WebKit eliminan la semántica de lista cuando list-style-type is none, así que necesitaba agregar role="list" en el HTML para preservarlo:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>
CodePen Incrustar respaldo

Dar estilo al título y al número de página

Con la lista diseñada a mi gusto, era hora de pasar a diseñar un elemento de lista individual. Para cada elemento de la tabla de contenido, el título y el número de página deben estar en la misma línea, con el título a la izquierda y el número de página alineado a la derecha.

Puede que estés pensando: “¡No hay problema, para eso está Flexbox!”. ¡No te equivocas! De hecho, Flexbox puede lograr la alineación correcta de la portada. Pero hay algunos problemas de alineación complicados cuando se agregan los líderes, por lo que opté por seguir el enfoque de Christoph usando una cuadrícula, que es una ventaja, ya que también ayuda con los títulos de varias líneas. Aquí está el CSS para un elemento individual:

.toc-list li > a { text-decoration: none; display: grid; grid-template-columns: auto max-content; align-items: end;
} .toc-list li > a > .page { text-align: right;
}

La cuadrícula tiene dos columnas, la primera de las cuales es auto-dimensionado para llenar todo el ancho del contenedor, menos la segunda columna, que tiene el tamaño de max-content. El número de página está alineado a la derecha, como es tradicional en una tabla de contenido.

El único otro cambio que hice en este punto fue ocultar el texto "Página". Esto es útil para lectores de pantalla pero visualmente innecesario, así que usé un tradicional visually-hidden clase para ocultarlo de la vista:

.visually-hidden { clip: rect(0 0 0 0); clip-path: inset(100%); height: 1px; overflow: hidden; position: absolute; width: 1px; white-space: nowrap;
}

Y, por supuesto, el HTML debe actualizarse para usar esa clase:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

Con esta base en su lugar, pasé a dirigirme a los líderes entre el título y la página.

CodePen Incrustar respaldo

Creando líderes de puntos

Los líderes son tan comunes en los medios impresos que es posible que se pregunte, ¿por qué CSS ya no admite eso? La respuesta es: lo hace. Bueno, más o menos.

En realidad hay un leader() función definida en el Contenido generado CSS para la especificación de medios paginados. Sin embargo, como ocurre con gran parte de las especificaciones de medios paginados, esta función no está implementada en ningún navegador, por lo que se excluye como una opción (al menos en el momento en que escribo esto). Ni siquiera está en la lista caniuse.com, presumiblemente porque nadie lo ha implementado y no hay planes o señales de que lo harán.

Afortunadamente, tanto Julie como Christoph ya abordaron este problema en sus respectivas publicaciones. Para insertar los puntos líderes, ambos usaron un ::after pseudo-elemento con su content propiedad establecida en una cadena muy larga de puntos, como esta:

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .title::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

El ::after el pseudo-elemento se establece en una posición absoluta para sacarlo del flujo de la página y evitar que se ajuste a otras líneas. El texto está alineado a la derecha porque queremos que los últimos puntos de cada línea coincidan con el número al final de la línea. (Más sobre las complejidades de esto más adelante). .title elemento está configurado para tener una posición relativa por lo que el ::after pseudo-elemento no sale de su caja. Mientras tanto, el overflow está oculto, por lo que todos esos puntos adicionales son invisibles. El resultado es una bonita tabla de contenido con líderes de puntos.

Sin embargo, hay algo más que necesita consideración.

Sara también me señaló que todos esos puntos cuentan como texto para los lectores de pantalla. Entonces, ¿qué escuchas? “Introducción punto punto punto punto…” hasta que se anuncien todos los puntos. Esa es una experiencia horrible para los usuarios de lectores de pantalla.

La solución es insertar un elemento adicional con aria-hidden establecido en true y luego use ese elemento para insertar los puntos. Entonces el HTML se convierte en:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title<span class="leaders" aria-hidden="true"></span></span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

Y el CSS se convierte en:

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .leaders::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

Ahora los lectores de pantalla ignorarán los puntos y ahorrarán a los usuarios la frustración de escuchar el anuncio de varios puntos.

CodePen Incrustar respaldo

Últimos retoques

En este punto, el componente de la tabla de contenido se ve bastante bien, pero podría necesitar algunos detalles menores. Para empezar, la mayoría de los libros compensan visualmente los títulos de los capítulos de los títulos de las subsecciones, así que puse en negrita los elementos de nivel superior e introduje un margen para separar las subsecciones de los capítulos siguientes:

.toc-list > li > a { font-weight: bold; margin-block-start: 1em;
}

A continuación, quería limpiar la alineación de los números de página. Todo se veía bien cuando usaba una fuente de ancho fijo, pero para las fuentes de ancho variable, los puntos guía podrían terminar formando un patrón en zigzag a medida que se ajustan al ancho de un número de página. Por ejemplo, cualquier número de página con un 1 sería más angosto que otros, lo que daría como resultado puntos de guía desalineados con los puntos de las líneas anteriores o siguientes.

Números y puntos desalineados en una tabla de contenido.
Una tabla de contenido perfecta con HTML + CSS

Para solucionar este problema, configuré font-variant-numeric a tabular-nums por lo que todos los números se tratan con el mismo ancho. Al establecer también el ancho mínimo en 2ch, me aseguré de que todos los números con uno o dos dígitos estén perfectamente alineados. (Es posible que desee establecer esto en 3ch si su proyecto tiene más de 100 páginas). Aquí está el CSS final para el número de página:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Puntos guía alineados en una tabla de contenido.
Una tabla de contenido perfecta con HTML + CSS

¡Y con eso, la tabla de contenido está completa!

CodePen Incrustar respaldo

Conclusión

Crear una tabla de contenido con nada más que HTML y CSS fue más desafiante de lo que esperaba, pero estoy muy feliz con el resultado. Este enfoque no solo es lo suficientemente flexible para acomodar capítulos y subsecciones, sino que maneja las sub-subsecciones muy bien sin actualizar el CSS. El enfoque general funciona en páginas web en las que desea vincular a las distintas ubicaciones de contenido, así como en archivos PDF en los que desea que la tabla de contenido se vincule a diferentes páginas. Y, por supuesto, también se ve muy bien impreso si alguna vez se siente inclinado a usarlo en un folleto o libro.

Me gustaría agradecer a Julie Blanc y Christoph Grabo por sus excelentes publicaciones de blog sobre la creación de una tabla de contenido, ya que ambas fueron invaluables cuando estaba comenzando. También me gustaría agradecer a Sara Soueidan por sus comentarios sobre accesibilidad mientras trabajaba en este proyecto.


Una tabla de contenido perfecta con HTML + CSS publicado originalmente el Trucos CSS. Debieras obtener el boletín.

Sello de tiempo:

Mas de Trucos CSS