Pestañas

Dale a cada elemento un href y las pestañas serán enlaces: una página por pestaña, sin script, con aria-current en la activa. Dale a cada elemento un panel y pasarán a ser un grupo de radios cuyos paneles se intercambian en el sitio, y sigue sin haber script.

En un sitio estático la forma de enlaces suele ser la correcta: le da una URL a cada vista y sobrevive a que JavaScript esté desactivado. Recurre a los paneles cuando el contenido sea pequeño y cambiar de vista no deba costar una navegación.

Pestañas como enlaces

Son enlaces de verdad: al pulsar uno se navega. El subrayado sale de active o de value al construir, no del clic, así que cada página marca su propia pestaña. Una pestaña-enlace no reacciona por su cuenta a la URL: para eso, cambia en el sitio con los paneles de más abajo.

tabs({
  items: [
    { label: 'Migas de pan', href: '/es/ui/breadcrumbs' },
    { label: 'Pestañas', href: '/es/ui/tabs', active: true },
    { label: 'Paginación', href: '/es/ui/pagination' },
  ],
})

Pestañas con paneles

La pestaña es una <label> de un radio que la hoja de estilos mantiene fuera de la vista, y el panel que sigue al radio marcado es el que se muestra. Esta página no importa nada: cambiar de pestaña, y moverse entre ellas con las flechas, es lo que un grupo de radios ya hace.

npm install sitelo javascript-to-html
import * as ui from 'sitelo/ui'
sitelo build
tabs({
  value: 'install',
  items: [
    { id: 'install', label: 'Instalar', panel: card({ variant: 'flat' }, cardBody(code('npm install sitelo javascript-to-html'))) },
    { id: 'use', label: 'Usar', panel: card({ variant: 'flat' }, cardBody(code("import * as ui from 'sitelo/ui'"))) },
    { id: 'build', label: 'Compilar', panel: card({ variant: 'flat' }, cardBody(code('sitelo build'))) },
  ],
})

Pestañas enlazables

Dale además a los elementos con panel un href con fragmento y los radios dejan paso a enlaces: la URL nombra la pestaña, :target la señala, se muestra el panel que va detrás, y la elección sobrevive a una recarga, a un enlace compartido y al botón de atrás. El id va en la pestaña y no en el panel porque el navegador lleva al borde superior lo que la URL nombre: ponerlo en el panel dejaría las pestañas fuera de la pantalla en la que acabas de pulsarlas. Solo un elemento del documento puede ser :target, así que esta forma es para un único juego de pestañas por página. El desplazamiento en sí no se puede anular: seguir un fragmento es mover la ventana. Solo se puede elegir hacia qué se desplaza y dónde aterriza, y para eso están el id en la pestaña y su scroll-margin-block-start, que se ajusta con la prop scrollMargin: dale a una cabecera fija al menos su propia altura.

Preparar

Este panel es #tab-setup: copia la URL y vuelve.

Desplegar

Y este es #tab-deploy.

tabs({
  items: [
    { id: 'setup', label: 'Preparar', href: '#tab-setup', panel: card({ variant: 'flat' }, cardBody(text({ variant: 'small' }, 'Este panel es #tab-setup: copia la URL y vuelve.'))) },
    { id: 'deploy', label: 'Desplegar', href: '#tab-deploy', panel: card({ variant: 'flat' }, cardBody(text({ variant: 'small' }, 'Y este es #tab-deploy.'))) },
  ],
})

Píldoras

src/index.ht.js
src/index.ht.ts
src/index.ht.jsx
stack({ gap: 'lg' },
  tabs({
    variant: 'pills',
    items: [
      { label: 'Todo', href: '#all', active: true },
      { label: 'Guías', href: '#guides' },
      { label: 'Ejemplos', href: '#examples' },
    ],
  }),
  tabs({
    variant: 'pills',
    value: 'js',
    items: [
      { id: 'js', label: 'JavaScript', panel: card({ variant: 'flat' }, cardBody(code('src/index.ht.js'))) },
      { id: 'ts', label: 'TypeScript', panel: card({ variant: 'flat' }, cardBody(code('src/index.ht.ts'))) },
      { id: 'jsx', label: 'JSX', panel: card({ variant: 'flat' }, cardBody(code('src/index.ht.jsx'))) },
    ],
  }),
)

Colores

stack({ gap: 'lg' },
  tabs({ color: 'primary', items: [{ label: 'Primary', href: '#p', active: true }, { label: 'Otra', href: '#p2' }] }),
  tabs({ color: 'neutral', items: [{ label: 'Neutral', href: '#n', active: true }, { label: 'Otra', href: '#n2' }] }),
  tabs({ color: 'danger', items: [{ label: 'Danger', href: '#d', active: true }, { label: 'Otra', href: '#d2' }] }),
)

Muchas pestañas

La lista de pestañas se desplaza en horizontal en vez de envolver, así que la fila mantiene su forma en el móvil. Las pestañas con panel sí envuelven: cada panel tiene que ir detrás de su pestaña, así que no queda ninguna fila que desplazar.

tabs({
  items: [
    'Resumen', 'Rutas', 'Datos', 'Recursos', 'Imágenes', 'Islas', 'TypeScript', 'CLI', 'Despliegue',
  ].map((label, index) => ({ label, href: '#many-' + index, active: index === 0 })),
})

Deshabilitada

Esta sí funciona.

tabs({
  value: 'now',
  items: [
    { id: 'now', label: 'Disponible', panel: card({ variant: 'flat' }, cardBody(text({ variant: 'small' }, 'Esta sí funciona.'))) },
    { id: 'soon', label: 'Próximamente', disabled: true, panel: card({ variant: 'flat' }, cardBody('')) },
  ],
})

Accesibilidad

La forma con paneles es un grupo de radios de verdad: las pestañas son elementos <label> de radios que comparten name, así que un lector de pantalla anuncia cuál de cuántas está elegida, y las flechas, Inicio y Fin funcionan sin cargar nada. La forma enlazable son enlaces normales y no lleva aria-current: se escribiría una vez y quedaría mal tras el primer clic. A propósito no es un tablist ARIA: aria-selected se escribe una vez, en el servidor, y el CSS no puede mantenerlo cierto a medida que haces clic. La forma de enlaces tampoco es un tablist: los enlaces que navegan son enlaces, y darles semántica de pestaña sería mentir sobre lo que hacen.

Props

PropTipoPor defectoDescripción
itemsArray[]Cadenas, u objetos { id, label, href, panel, active, disabled }.
valuestring—Id del elemento activo. Si no, recae en active, y luego en el primero.
variant'underline' | 'pills''underline'Cómo se marca la pestaña activa.
color'primary' | 'neutral' | 'success' | 'warning' | 'danger''primary'Color de la pestaña activa.
labelstring'Tabs'Nombre accesible del grupo. Solo en la forma con paneles.
namestringid del primer elementoNombre del grupo de radios. Solo hace falta con dos juegos de pestañas con panel en una página.
hrefstring—En un elemento: una página a la que enlazar o, junto a panel, el fragmento que lo nombra.
scrollMarginSpace'md'Cuánto espacio deja la ventana por encima de la pestaña. Solo en la forma :target.