README · by ansango
← Volver al libro

Lists y generated content

Cómo las listas y el contenido generado se estilizan en CSS. List markers, counters, content, comillas y los trucos con ::before y ::after

~1 min de lectura
Resumen

Esta nota cubre el capítulo 16 del libro: cómo CSS maneja las listas y el contenido generado. List markers (bullets, números), counters automáticos, las propiedades content, quotes y los trucos con ::before y ::after. Una herramienta potente para añadir estructura visual sin HTML extra.

Tipos de listas

<!-- Lista desordenada -->
<ul>
    <li>Manzanas</li>
    <li>Naranjas</li>
    <li>Plátanos</li>
</ul>

<!-- Lista ordenada -->
<ol>
    <li>Primero</li>
    <li>Segundo</li>
    <li>Tercero</li>
</ol>

<!-- Lista de definiciones -->
<dl>
    <dt>HTML</dt>
    <dd>Lenguaje de marcado.</dd>
    <dt>CSS</dt>
    <dd>Lenguaje de estilos.</dd>
</dl>
Tipos:

  - <ul>: unordered list, bullets.
  - <ol>: ordered list, números.
  - <li>: list item.
  - <dl>: description list.
  - <dt>: description term.
  - <dd>: description detail.

list-style-type

ul {
    list-style-type: disc;       /* default */
    list-style-type: circle;
    list-style-type: square;
    list-style-type: none;
}

ol {
    list-style-type: decimal;       /* 1, 2, 3 */
    list-style-type: decimal-leading-zero;   /* 01, 02, 03 */
    list-style-type: lower-roman;    /* i, ii, iii */
    list-style-type: upper-roman;    /* I, II, III */
    list-style-type: lower-alpha;    /* a, b, c */
    list-style-type: upper-alpha;    /* A, B, C */
    list-style-type: lower-greek;    /* α, β, γ */
    list-style-type: none;          /* sin marker */
}
list-style-type:

  - disc, circle, square: para ul.
  - decimal, roman, alpha: para ol.
  - "marker" genérico: el navegador decide.
  - Variantes específicas del idioma.
  - Casi cualquier string (con list-style: content).

list-style-image

ul {
    list-style-image: url("bullet.png");
}
list-style-image:

  - Imagen en lugar del marker.
  - Limitado en control (no se puede redimensionar).
  - Mejor usar ::marker.

list-style-position

ul {
    list-style-position: outside;     /* default */
    list-style-position: inside;      /* el marker dentro */
}
list-style-position:

  - outside: el marker a la izquierda del texto.
  - inside: el marker dentro del bloque.
  - Útil para alinear la lista con otros elementos.

list-style shorthand

ul {
    list-style: square inside;
    list-style: url("bullet.png") inside;
    list-style: none;
}
list-style:

  - shorthand para type, image, position.
  - Cualquier orden.
  - Si uno falta, valor inicial.

::marker

li::marker {
    color: red;
    font-size: 1.2em;
    font-weight: bold;
    content: "→ ";
}

li::before {
    content: "✓ ";
    color: green;
}
::marker:

  - El pseudo-element del bullet.
  - Pocas propiedades compatibles: color, font-size, font-weight, content.
  - Limitado en control.
  - Para más control: usar ::before.
::marker es moderno

El libro recoge: ::marker es relativamente nuevo. Antes había que usar ::before con content: counter(). Sigue siendo válido, pero ::marker es más limpio.

Trucos para listas

Sin bullets

.list {
    list-style: none;
    padding: 0;
    margin: 0;
}

Bullets custom

.list {
    list-style: none;
    padding-left: 0;
}

.list li {
    position: relative;
    padding-left: 1.5em;
}

.list li::before {
    content: "→";
    position: absolute;
    left: 0;
    color: red;
}

Listas inline

.inline-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    gap: 1em;
}

Listas con columnas

.multi-column-list {
    column-count: 3;
    column-gap: 2em;
    list-style-position: inside;
}

Lista de definiciones estilizada

.dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.5em 1em;
}

.dt {
    font-weight: bold;
}

.dd {
    margin: 0;
}

Counters

CSS tiene contadores que se incrementan automáticamente:

ol {
    counter-reset: section;      /* reset a 0 */
    list-style: none;
}

li {
    counter-increment: section;  /* incrementa */
}

li::before {
    content: counter(section) ". ";
}
Counters:

  - counter-reset: nombre: inicializa el counter.
  - counter-increment: nombre: incrementa.
  - counter(nombre): muestra el valor.
  - counter(nombre, style): con formato (decimal, roman, etc.).
  - Útil para numeración automática.

Contadores anidados

ol {
    counter-reset: item;
    list-style: none;
}

li {
    counter-increment: item;
}

li::before {
    content: counters(item, ".") " ";
}

/* HTML:
   <ol>
     <li>Capítulo 1
       <ol>
         <li>Sección 1.1</li>
       </ol>
     </li>
   </ol>
   
   Resultado:
   1.
   1.1.
*/
Counters anidados:

  - counters(item, "."): genera "1.1.1".
  - Funcionan sin reset adicional.
  - Útil para outline de libros.

Estilo de counter

li::before {
    content: counter(item, lower-roman) ". ";
}
Formato:

  - decimal, decimal-leading-zero.
  - lower-roman, upper-roman.
  - lower-alpha, upper-alpha.
  - lower-greek.
  - lower-latin, upper-latin.
  - armenian, georgian, hebrew.

Contadores en contenido generado

.chapter {
    counter-reset: section;
}

.chapter > h2 {
    counter-increment: section;
}

.chapter > h2::before {
    content: "Chapter " counter(section) ": ";
}
Contadores en títulos:

  - counter-reset en el contenedor.
  - counter-increment en cada título.
  - counter() en ::before.
  - Numeración automática en h2, h3, etc.

content

.element::before {
    content: "★ ";
}

.url::after {
    content: " (" attr(href) ")";
}

.counter::before {
    content: counter(item) ". ";
}
content:

  - String: contenido textual.
  - attr(name): el valor de un atributo.
  - url(): una URL.
  - counter(): un counter.
  - Combinable: content: "Item " counter(item) ": " attr(name);

Valores especiales

.element::before {
    content: normal;       /* default */
    content: none;         /* no se genera */
    content: open-quote;    /* comilla de apertura */
    content: close-quote;   /* comilla de cierre */
    content: no-open-quote; /* sin comilla de apertura */
    content: no-close-quote; /* sin comilla de cierre */
    content: url("imagen.png");  /* imagen */
}
Valores:

  - normal: default, sin contenido.
  - none: no genera el pseudo-element.
  - open-quote, close-quote: comillas según quotes.
  - quote: usa el quote apropiado.
  - url(): una imagen.
  - counter(), counters(): contadores.
  - attr(): valor de atributo.

Strings con caracteres especiales

.element::before {
    content: "Texto con \"comillas\" y \\backslash";
    content: "Línea 1\ALínea 2";  /* \A es nueva línea */
}
Escape:

  - \" comilla doble.
  - \' comilla simple.
  - \\ backslash.
  - \A nueva línea.
  - \XXXX Unicode.

quotes

.body {
    quotes: "«" "»" "‹" "›";  /* 1: abierto, 1: cerrado, 2: abierto, 2: cerrado */
}

q::before {
    content: open-quote;
}

q::after {
    content: close-quote;
}
quotes:

  - quotes: define pares de comillas.
  - Primer par: para el primer nivel.
  - Segundo par: para el segundo nivel.
  - open-quote, close-quote: usan el nivel apropiado.

quotes en español

.body {
    quotes: "«" "»" "‘" "’";
}
Convenciones por idioma:

  - Español: « y ».
  - Español (citando en cit): « y », ‘ y ’.
  - Inglés: “ y ”, ‘ y ’.
  - Francés: « y », « y ».
  - settings del navegador.

content para iconos

.icon::before {
    content: "\f007";  /* código Unicode */
    font-family: "Font Awesome";
}
Iconos:

  - content: código Unicode.
  - font-family: la fuente de iconos.
  - Mejor: usar SVG.
  - icon fonts son legacy.

content con attr()

/* Tooltip simple */
[data-tooltip] {
    position: relative;
}

[data-tooltip]:hover::after {
    content: attr(data-tooltip);
    position: absolute;
    top: 100%;
    left: 0;
    background: #000;
    color: #fff;
    padding: 0.5em;
    border-radius: 4px;
    white-space: nowrap;
}
<button data-tooltip="Haz clic para más">Botón</button>
attr() en tooltips:

  - Útil para tooltips simples.
  - No reemplaza a una librería de tooltips.
  - Tiene problemas de accesibilidad.
  - Mejor: aria-label + librería.

List-style shorthand

.list {
    list-style: none;                  /* sin marker */
    list-style: square inside;          /* tipo + posición */
    list-style: url("bullet.png");      /* imagen */
    list-style: square url("bullet.png") inside;  /* todo */
}
list-style shorthand:

  - type: disc, circle, square, decimal, etc.
  - image: url().
  - position: inside, outside.
  - Si falta, valor inicial.

Counters anidados en headings

.body {
    counter-reset: h2;
}

h2 {
    counter-increment: h2;
    counter-reset: h3;
}

h3 {
    counter-increment: h3;
}

h2::before {
    content: counter(h2) ". ";
}

h3::before {
    content: counter(h2) "." counter(h3) " ";
}
HTML:

  <h2>Capítulo 1</h2>     → 1. Capítulo 1
  <h3>Sección 1.1</h3>   → 1.1 Sección 1.1
  <h2>Capítulo 2</h2>     → 2. Capítulo 2

Trinkets y técnicas

Tooltip con content

[data-tooltip] {
    position: relative;
}

[data-tooltip]:hover::before {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    background: #333;
    color: white;
    padding: 0.5em 1em;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875em;
}

Quitar bullets sin perder semántica

.list {
    list-style: none;
    padding: 0;
    margin: 0;
}

/* Pero el <ul> sigue siendo accesible */

Lista de definiciones en grid

.dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.5em 1em;
}

.dl dt {
    font-weight: bold;
}

.dl dd {
    margin: 0;
}

Tabla con counters

table {
    counter-reset: row;
}

tbody tr {
    counter-increment: row;
}

tbody tr td:first-child::before {
    content: counter(row) ". ";
    color: #999;
    margin-right: 0.5em;
}

Errores comunes

/* Mal: counter-reset en el lugar incorrecto */
li {
    counter-increment: item;
}

/* El reset debe estar en el padre */
ol {
    counter-reset: item;
}

/* Mal: content mal escapado */
.element::before {
    content: "Hola " mundo;  /* error */
}

/* Mal: content con solo número */
.content {
    content: 42;  /* no funciona */
}

Resumen en tres frases

Próximos pasos