Tesela v1.4.0 Galería
Biblioteca de componentes · 0 componentes

Tesela.

Componentes propios sin dependencias inspirados en el lenguaje visual de devl.dev: tokens en CSS, HTML semántico y Custom Elements en light DOM. Copia el HTML de cualquier demo, enlaza dos archivos y funciona en cualquier proyecto (estático, SSR o framework).

Instalación

Copia la carpeta lib/ a tu proyecto. El CSS funciona solo; el JS añade interactividad (Custom Elements y atributos data-tsl-*). Pon la clase tsl en <body> para heredar fuente y colores. Tema oscuro por defecto; data-theme="light" en <html> para claro.

<link rel="stylesheet" href="lib/tesela-ui.css" />
<script src="lib/tesela-ui.js" defer></script>

<body class="tsl"> … </body>
API globalDescripción
TeselaUI.toast(opts)Notificación efímera. { title, description, variant, duration, action }. Devuelve una función para cerrarla.
TeselaUI.copy(text)Copia al portapapeles (con respaldo para file://). Promise<boolean>.
TeselaUI.confirm(opts)Diálogo de confirmación modal. Devuelve Promise<boolean>.
TeselaUI.position(ancla, el, opts)Posiciona un elemento flotante junto a un ancla o punto, con volteo y ajuste a la ventana.
TeselaUI.setTheme(t) / getTheme()"dark" | "light". Se guarda en localStorage.
TeselaUI.init(root)Inicializa ayudantes estáticos (rangos, tablas) tras insertar HTML dinámico.
EventosTodos los componentes emiten eventos tsl-* que burbujean: tsl-change, tsl-select, tsl-complete, tsl-copy, tsl-theme.

Módulos y empaquetado

La carpeta lib/ trae el mismo código en varios formatos; elige el que encaje con tu proyecto. Todo se genera desde src/ con npm run build (solo el desarrollo usa esbuild; la biblioteca no tiene dependencias).

ArchivoPara qué sirve
lib/tesela-ui.js · .min.jsBundle completo para <script src>. Registra todos los elementos y expone window.TeselaUI.
lib/esm/index.mjsBundle ESM: import { toast, confirm } from "./lib/esm/index.mjs". Registra todos los elementos.
lib/esm/components/*.mjsUn módulo por componente: importarlo registra solo ese elemento (y los que necesite).
lib/esm/api/*.mjsFunciones sueltas: toast, confirm, copy, theme.
lib/tesela-ui.css · .min.cssHoja completa.
lib/css/*.cssUna hoja por sección (tokens, botones, tablas…) para cargar solo lo necesario.
<!-- Solo los componentes que usas -->
<link rel="stylesheet" href="lib/css/01-tokens.css" />
<link rel="stylesheet" href="lib/css/03-botones.css" />
<script type="module">
  import "./lib/esm/components/tabs.mjs";
  import "./lib/esm/components/select.mjs";
  import { toast } from "./lib/esm/api/toast.mjs";
</script>

El servidor debe enviar los .mjs como text/javascript (en deploy/nginx.conf hay un ejemplo para nginx).

Temas y presets

Cada tema tiene dos modos (oscuro y claro) más system, que sigue al sistema operativo. Un preset cambia colores, radios y tipografías de una vez con <html data-preset="esmeralda"> (o en cualquier contenedor, y entonces solo afecta a su contenido). Todos cumplen las reglas de contraste de WCAG en ambos modos, y alto-contraste llega a AAA.

Contraste del tema activo

Se calcula en vivo con los colores reales del tema, el preset y el acento elegidos (WCAG 2.x: AA = 4,5:1 para texto, 3:1 para componentes y texto grande; AAA = 7:1).

Par de coloresMuestraContrasteNivel

Aplicar sin parpadeo

La biblioteca recuerda el tema, el preset y el acento en localStorage. Para que se apliquen antes del primer pintado, carga este script clásico en <head>, sin defer:

<script src="lib/theme-init.js"></script>

<!-- API -->
TeselaUI.setTheme("system");      // "dark" | "light" | "system"
TeselaUI.setPreset("medianoche"); // ver la lista de arriba
TeselaUI.setAccent("#10b981");    // elige solo un texto legible (blanco o casi negro)
TeselaUI.setAccent(null);         // vuelve al acento del preset

Exportar los tokens

Los 37 tokens viven en un solo archivo (src/tokens/tokens.json, formato inspirado en W3C Design Tokens) y de él se generan todos los demás.

ArchivoPara qué sirve
lib/tokens/tokens.jsonTokens con descripción y valor por modo. Se puede importar en Style Dictionary, Tokens Studio o Figma.
lib/tokens/presets.jsonLos 11 presets: qué tokens cambia cada uno.
lib/tokens/tokens.cssSolo las variables CSS (sin componentes).
lib/tokens/tokens.scssVariables de Sass ($tsl-bg…) y mapas por modo y preset.
lib/tokens/tokens.mjsMódulo JavaScript con tipos (tokens.d.mts).
lib/tokens/tailwind.preset.cjsPreset de Tailwind: bg-tsl-bg, text-tsl-fg, rounded-tsl…
lib/themes/<preset>.cssUn CSS por preset, por si solo necesitas uno.

Derecha a izquierda (RTL)

TeselaUI.setDirection("rtl") pone dir="rtl" en <html> (se guarda y theme-init.js lo aplica antes de pintar); también sirve dir="rtl" en cualquier contenedor. El CSS usa propiedades lógicas (inline-start, inline-end), así que márgenes, bordes, iconos y paneles se espejan solos. El comportamiento también: las flechas ← → se invierten en pestañas, menús, árbol, calendario, slider, valoración, radios, carrusel y paneles redimensionables; los paneles flotantes se anclan al borde de inicio y «derecha»/«izquierda» de position() significan fin/inicio. Se mantienen de izquierda a derecha las gráficas, los minigráficos, los selectores de color y el OTP. Prueba el cambio con el selector «Dirección» del panel lateral. Evento tsl-direction.

Tokens

Todo el estilo sale de variables --tsl-*. Sobrescríbelas después de cargar tesela-ui.css para adaptar la marca (o usa el panel lateral y "Copiar overrides CSS").

Tipografía

Mono por defecto, sans para titulares. Utilidades: tsl-eyebrow, tsl-heading, tsl-muted, tsl-kbd.

Icono

<tsl-icon name="…"> pinta un SVG en línea que hereda el color (currentColor) y mide 1em; cámbialo con font-size, --tsl-icon-size o size="xs|sm|md|lg|xl", y el grosor del trazo con --tsl-icon-stroke. Sin label es decorativo (aria-hidden); con label se anuncia como imagen. Un botón solo con icono necesita su aria-label.

El set base (183 iconos de Lucide, licencia ISC) va incluido y se pinta sin red. Para el resto se registra una biblioteca: TeselaUI.registerIconLibrary("tabler", TeselaUI.iconLibraries.tabler()). Hay adaptadores para Lucide completo, Tabler, Phosphor, Heroicons, Material Symbols y Simple Icons (logotipos), con versión fijada y la opción base para servir los SVG desde tu propio servidor. Tus iconos van con TeselaUI.registerIcons({ marca: "<svg …>…</svg>" }) o en un archivo suelto con src. Todo SVG externo se limpia antes de insertarse.

Botón

Variantes --primary, --accent, --subtle, --danger; tamaños --sm, --lg, --icon, --block, --pill. aria-busy="true" muestra un spinner.

Grupo de botones

Une botones adyacentes en un solo control.

Chip / interruptor

Chips de filtro. Con data-tsl-toggle alternan aria-pressed y emiten tsl-change.

Botón de copiar

data-tsl-copy="texto" copia un valor fijo; data-tsl-copy-target="#id" copia el contenido de otro elemento.

Input y campo

tsl-field agrupa tsl-label, control, tsl-hint y tsl-error. Usa aria-invalid="true" para el estado de error.

Grupo de inputs

Añade prefijos/sufijos con tsl-input-group__addon.

Select y textarea

Controles nativos con el estilo de la biblioteca.

Casilla y radio

<input type="checkbox|radio" class="tsl-check">, envuelto en tsl-choice para la etiqueta. Soporta indeterminate.

Grupo de radios

<tsl-radio-group> agrupa opciones button.tsl-radio con etiqueta y descripción. Tiene un solo punto de tabulación: las flechas mueven y seleccionan (respetando RTL y saltando las deshabilitadas), Espacio selecciona y Inicio/Fin saltan a los extremos. Participa en formularios (name, required, reset, fieldset disabled), con orientation="horizontal" y la variante .tsl-radio-group--cards. Evento tsl-change.

Interruptor

Checkbox nativo con role="switch": accesible y compatible con formularios.

Control deslizante

Relleno de color automático. data-tsl-output="#id" muestra el valor; data-tsl-suffix añade unidad.

Selector numérico

Botones con data-step="-1|1" que respetan min, max y step del input.

Importe

<tsl-currency> da formato al importe mientras se escribe con los separadores del idioma (locale) y pone el símbolo de la currency delante o detrás, como corresponda; los decimales son los de la moneda (el yen no tiene). El punto o la coma del teclado numérico valen como separador decimal, ↑ ↓ suman o restan step y al salir se completan los decimales. En el formulario viaja el valor canónico (1250.50); min y max se validan con su mensaje.

Teléfono

<tsl-phone> une un selector de país (un <select> nativo bajo el distintivo «MX +52») y el número, al que da la forma de ese país mientras se escribe. Si se pega o escribe un número internacional (+34… o 0034…) cambia solo de país. Envía el valor en E.164 (+525512345678) y no es válido hasta tener todos los dígitos («Faltan 3 dígitos»). countries limita y ordena los países.

Control segmentado

<tsl-segmented value="…"> con role="radiogroup", flechas ← → y evento tsl-change.

Campo OTP

<tsl-otp length="6" name="code">: avance automático, retroceso, pegado y evento tsl-complete. mode="text" acepta letras.

Campo de etiquetas

<tsl-tags>: Enter o coma para añadir, Retroceso para borrar. Atributos value, name, max, placeholder.

Zona de carga

<tsl-dropzone accept multiple name>: arrastrar o hacer clic; lista los archivos y emite tsl-change con files.

Subida de archivos

<tsl-upload> amplía la zona para soltar con una fila por archivo: miniatura o icono, tamaño, barra de progreso y las acciones que tocan en cada estado (cancelar mientras sube, reintentar si falló, quitar al terminar). Sube con action (POST multipart con progreso real) o con tu función uploader (async (file, { signal, progress }) => resultado), hasta concurrency a la vez. En el formulario viaja el id o la url de cada resultado, y mientras algo sube el formulario no es válido. En la demostración la subida es simulada y los archivos con «error» en el nombre fallan.

Calendario

<tsl-calendar value="YYYY-MM-DD"> con nombres localizados (locale, por defecto el lang del documento), week-start y navegación con flechas (PgUp/PgDn cambian de mes). Opciones: min y max deshabilitan días, mode="range" selecciona un intervalo (value="inicio/fin") y months="2" muestra varios meses. Para un campo desplegable usa <tsl-date-picker>.

Select personalizado

<tsl-select> reemplaza al <select> nativo con listbox accesible (aria-activedescendant), teclado completo (↑ ↓ Home End Enter Esc, búsqueda al teclear) y panel posicionado automáticamente. Se declara con <option> y <optgroup>. Atributos: searchable, multiple, creatable, clearable, placeholder, required, disabled. Participa en <form> (FormData, reset, validación). Evento tsl-change con { value, values }; setOptions([...]) carga opciones dinámicas.

Combobox / Autocomplete

<tsl-combobox> (o <tsl-select autocomplete>) es un campo de texto que filtra las opciones mientras escribes (sin distinguir acentos). Con creatable ofrece «Crear …» para añadir un valor nuevo. Si sales sin elegir, se restaura la selección anterior. Con multiple acumula varias opciones.

Selector de fecha

<tsl-date-picker> muestra la fecha formateada con Intl y abre un calendario en un popover. Atributos: value (ISO), min, max, locale, week-start, date-style (short|medium|long|full), clearable, required. El valor enviado en el formulario es la fecha ISO (2026-10-14). Teclado en el calendario: flechas, PgUp/PgDn (mes) y con Shift (año).

Rango de fechas

Con el atributo range, <tsl-date-picker> elige un intervalo: dos meses a la vez, vista previa del rango al mover el cursor y cierre automático al completarlo. El valor es un intervalo ISO 8601 inicio/fin; el evento tsl-change trae { value, start, end }. El <tsl-calendar> también acepta mode="range", months, min y max directamente.

Selector de hora

<tsl-time-picker> ofrece columnas de hora y minutos (y AM/PM con hour12). Atributos: value (HH:MM), step (minutos entre opciones), min, max, clearable, required. El valor siempre se envía en 24 h. En el panel: ↑ ↓ cambian el valor, ← → cambian de columna, Enter confirma.

Campos dentro de un formulario

Los campos de esta sección (tsl-select, tsl-combobox, tsl-date-picker, tsl-time-picker) son form-associated: sus valores aparecen en new FormData(form), respetan required en la validación nativa, se restablecen con form.reset() y se deshabilitan con un <fieldset disabled>.

Slider y rango doble

<tsl-slider> con uno o dos pulgares (range o value="20,80"); los pulgares no se cruzan y muestran su valor al arrastrar o enfocar. Atributos: min, max, step, prefix, suffix. Teclado: ← → (±paso), PgUp/PgDn (±10 pasos), Home/End. Eventos tsl-input (en vivo) y tsl-change (al soltar). En un formulario, el rango envía dos entradas con el mismo name.

Valoración (rating)

<tsl-rating> es un único control role="slider": ← → cambian el valor, Home/End saltan a los extremos y Supr lo borra (con clearable). Atributos: max, value, half (medias estrellas), readonly, size="sm|lg".

Selector de color

<tsl-color-picker> con área de saturación/brillo, tono, opacidad (alpha), campo hexadecimal, muestras (swatches="#f00,#0f0") y cuentagotas donde el navegador lo soporta. Valor #rrggbb (o #rrggbbaa con opacidad). Cada control se maneja con flechas (Shift = ×10). Eventos tsl-input (al arrastrar) y tsl-change.

Contraseña con medidor

<tsl-password> añade a un <input> el botón mostrar/ocultar y, con meter, un medidor de seguridad (longitud mínima con min-length, mayúsculas y minúsculas, números y símbolos). El input real sigue dentro de tu formulario. Evento tsl-change con { value, score } (0 a 4).

Textarea autoajustable y contador

Atributos sobre un <textarea class="tsl-textarea">: data-tsl-autosize crece con el contenido hasta data-max-rows filas (luego aparece scroll) y data-tsl-count="#id" muestra la cuenta de caracteres, usando maxlength como tope.

Menciones (@)

<tsl-mention> envuelve un <textarea> (o input) y ofrece usuarios al escribir @ (carácter configurable con trigger), en un panel junto al cursor. ↑ ↓ eligen, Enter o Tab insertan, Esc cierra. Las opciones se declaran con <option> o se asignan con element.users = [{ value, label, hint }]. Evento tsl-mention.

Editor de texto enriquecido

<tsl-editor> es un editor mínimo con barra de formato: negrita, cursiva, subrayado, tachado, títulos, listas, cita, código y enlaces, con deshacer y rehacer. La barra es una sola parada de tabulación (las flechas recorren los botones) y cada botón refleja con aria-pressed el formato del texto seleccionado. Ctrl+K abre el campo del enlace, Tab sangra dentro de una lista y lo que se pega o se escribe en HTML se filtra a una lista corta de etiquetas, sin estilos ni scripts. El valor que viaja en el formulario es ese HTML saneado; maxlength muestra el contador y valida.

Tarjetas de elección

CSS puro: un <label class="tsl-choice-card"> con un tsl-check (radio o checkbox) dentro. El estado marcado, el foco y el deshabilitado se reflejan en toda la tarjeta mediante :has().

Toggle group

<tsl-toggle-group type="single|multiple"> agrupa botones con aria-pressed. En single volver a pulsar deselecciona (salvo con required); en multiple se combinan. ← → mueven el foco con tabulación itinerante. Evento tsl-change.

Pestañas

<tsl-tabs> enlaza data-tab con data-panel y añade los roles ARIA y la navegación con flechas.

Paginación

Enlaces o botones; la página activa usa aria-current="page".

Pasos

Progreso por pasos: data-state="done" y aria-current="step".

Asistente por pasos

<tsl-wizard> reparte un <form> en pasos (fieldset.tsl-wizard__step con data-title). Antes de avanzar valida los campos del paso con la validación nativa (también los tsl-select, tsl-date-picker… asociados a formularios), señala el primero que falla y no deja saltar pasos. Dibuja el indicador .tsl-steps: los pasos visitados se pueden pulsar para volver. Enter en un campo avanza; al cambiar de paso enfoca su título y lo anuncia. Al enviar revisa todo, lleva al primer paso con errores si lo hay y emite tsl-submit con values.

Paleta de comandos

<tsl-command hotkey="k"> crea un <dialog> nativo con búsqueda difusa, grupos (data-group), palabras clave (data-keywords) y evento tsl-select. Ábrelo con data-tsl-open="id" o ⌘K.

Alerta

Variantes --info, --success, --warning, --danger, --accent.

Toast

TeselaUI.toast() apila notificaciones abajo a la derecha. Se pausan al pasar el ratón; duration: 0 las deja fijas; action añade un botón.

Progreso y medidor

<progress class="tsl-progress"> nativo (sin value = indeterminado) y tsl-meter por segmentos con --w y --c.

Spinner y skeleton

Estados de carga. Tamaño con --size (spinner) o --w/--h (skeleton).

Tooltip

Solo CSS: data-tsl-tooltip="texto" y opcionalmente data-tsl-placement="bottom". Aparece con hover y foco de teclado.

Diálogo

<dialog class="tsl-dialog"> nativo: atrapa el foco y cierra con Esc. data-tsl-open="id" lo abre, data-tsl-close lo cierra, clic en el fondo también (desactívalo con data-tsl-static).

Panel lateral

Variante lateral del diálogo: añade tsl-dialog--drawer.

Popover

<tsl-popover> usa la API nativa popover (capa superior, cierre con Esc y clic fuera). Se posiciona junto al disparador, se voltea si no cabe y sigue al scroll. Atributos: placement (top|bottom|left|right + -start|-end), offset, manual. Métodos open(), close(), toggle(); eventos tsl-open y tsl-close. Cualquier data-tsl-close dentro del panel lo cierra.

Hover card

<tsl-hovercard> muestra una vista previa al pasar el cursor o enfocar con el teclado, con retardos configurables (open-delay, close-delay). El panel sigue abierto mientras el cursor esté sobre él, y Esc lo cierra. Reutiliza .tsl-popover__panel.

Menú contextual

<tsl-context-menu> se abre con clic derecho, con la tecla de menú contextual (o Shift+F10) y con pulsación larga en pantallas táctiles. Se abre en el punto del cursor, navega con ↑ ↓ Home End y emite tsl-select.

Diálogo de confirmación

TeselaUI.confirm(opciones) abre un alertdialog modal y devuelve una Promise<boolean>. Esc o «Cancelar» resuelven false; el clic en el fondo no lo cierra. Con variant: "danger" el foco inicial queda en «Cancelar». Con cancelLabel: null funciona como alerta simple.

Sheet y drawer izquierdo

Variantes de <dialog class="tsl-dialog">: tsl-dialog--sheet (panel inferior, ideal en móvil; el asa .tsl-sheet__handle se arrastra hacia abajo para cerrar) y tsl-dialog--drawer tsl-dialog--drawer-start (drawer por la izquierda).

Tarjeta

tsl-card con __header, __body, __footer. --interactive añade elevación y spotlight que sigue al cursor.

Métrica y minigráfico

tsl-stat para KPIs y <tsl-sparkline values="…" fill> que dibuja un SVG con el color actual (cámbialo con color).

Insignia y punto

Estados compactos. tsl-dot--pulse anima un indicador en vivo.

Avatar

Iniciales o imagen, tamaños --sm/--lg, --square, estado con data-status="online|busy|away" y grupos solapados.

Tabla

Tabla con hover y números tabulares (is-num). Con data-tsl-sortable y th[data-sort="text|number"] se ordena por clic o teclado.

Lista

Filas con título, descripción y acciones al final (tsl-list__end).

Item

.tsl-item es una fila genérica con .tsl-item__media (--icon o --image), .tsl-item__content (__title y __desc) y .tsl-item__actions. Puede ser <div>, <a> o <button> (con hover y foco). Variantes --outline y --muted, tamaño --sm; .tsl-item-group los apila. Si no caben, las acciones pasan a la línea siguiente.

Línea de tiempo

Historial vertical; colorea cada nodo con --c.

Estado vacío

Estado vacío con icono, título, descripción y acción.

Acordeón

Basado en <details> nativo: accesible sin JS. Usa el mismo name en los details para que solo uno quede abierto.

Bloque de código

tsl-code sobre <pre>; añade un botón de copiar con data-tsl-copy.

Divisor

Separador con o sin etiqueta.

Tree view

<tsl-tree> convierte una lista anidada <ul> en un árbol accesible (role="tree", aria-expanded, aria-level). Teclado: ↑ ↓ navegan, → expande o entra, ← colapsa o sube, Inicio/Fin, Enter selecciona, * expande los hermanos y las letras saltan por nombre. Con checkable cada nodo tiene casilla, con estado mixto en los padres (Espacio marca). Atributos de nodo: data-value, data-expanded, data-selected. Eventos tsl-select, tsl-toggle y tsl-check.

Lista virtualizada

<tsl-virtual-list> solo dibuja las filas visibles: con 10 000 elementos el DOM tiene unas docenas de nodos. Asigna list.items (cualquier arreglo) y, si quieres un diseño propio, list.renderItem = (item, i) => Node. Con selectable es un listbox navegable (↑ ↓ PgUp PgDn Inicio Fin, Enter) y emite tsl-select. Alto de fila fijo (item-height); scrollToIndex(i, "center") desplaza hasta un elemento.

Listas ordenables

<tsl-sortable> reordena con el puntero y con el teclado. Con handle=".tsl-sortable__handle" solo el asa arrastra (recomendado en pantallas táctiles). Teclado: Espacio toma el elemento, ↑ ↓ lo mueven, Espacio lo suelta y Esc cancela; cada paso se anuncia por una región aria-live. direction="vertical|horizontal|grid". Las listas con el mismo group intercambian elementos. Evento tsl-sort con { item, from, to }.

Kanban

Un tablero es un conjunto de <tsl-sortable> con el mismo group, dentro de .tsl-kanban / .tsl-kanban__col. Las tarjetas se arrastran entre columnas (con autoscroll) y con el teclado: Espacio toma, ↑ ↓ reordenan, ← → cambian de columna, Espacio suelta. Cada cambio de columna se anuncia con su nombre (label) y emite tsl-sort en la columna de destino.

Tabla de datos

<tsl-data-table> reúne búsqueda, orden (clic en la cabecera: ascendente → descendente → sin orden), selección con casillas (Shift para rangos), columnas visibles, densidad, paginación con tamaño de página y exportación CSV. Se configura con table.columns (key, label, sort, align, format, render) y table.rows; también acepta un <table> HTML normal dentro. Atributos: page-size, selectable, searchable, exportable, height (cabecera fija), density y loading. Eventos tsl-sort, tsl-page, tsl-selection y tsl-row-click.

Gráfica de líneas y áreas

<tsl-chart type="line|area"> dibuja series SVG con rejilla, leyenda interactiva (clic para ocultar una serie), tooltip y cursor al pasar el puntero. Datos: chart.data = { labels, series: [{ name, values }] } (o el atributo data en JSON). Opciones: smooth, prefix, suffix, height. Es accesible: el elemento recibe el foco, ← → recorren los puntos, el valor se anuncia en una región aria-live y hay una tabla de datos para lectores de pantalla. Los colores salen de --tsl-chart-1…8.

Gráfica de barras

<tsl-chart type="bar"> con barras agrupadas por defecto, stacked para apiladas y horizontal para barras horizontales (útil con etiquetas largas). Admite valores negativos.

Dona y gauge

type="donut" reparte segmentos (data = { segments: [{ label, value }] }); al pasar el cursor el centro muestra el segmento y su porcentaje, y la leyenda oculta segmentos. type="gauge" muestra un valor dentro de un rango (data = { value, min, max, label, thresholds: [{ to, color }] }), coloreado según el umbral.

Mapa de calor

type="heatmap" pinta una matriz (data = { rows, cols, values }) con la intensidad del color según el valor. Las celdas vacías (null) se dibujan con borde punteado. Con el teclado, las cuatro flechas recorren las celdas.

Mensaje, burbuja y marcador

.tsl-message ordena avatar, cabecera, burbuja y pie; --end lo alinea al lado de quien escribe (el acento hace de fondo). .tsl-message-group apila mensajes seguidos (deja un .tsl-message__avatar vacío en los anteriores para mantener la alineación). .tsl-marker son notas de sistema en línea, con borde (--border) o como separador con etiqueta (--separator); con role="status" se anuncian. .tsl-shimmer anima el texto de lo que aún se está generando. Todo usa propiedades lógicas, así que se espeja en RTL.

Zona de conversación

<tsl-message-scroller> envuelve una conversación de altura propia. Mientras la persona está al final, sigue los mensajes nuevos (y el texto que crece en streaming); si sube con la rueda, el dedo o el teclado deja de seguirlos y aparece «Ir al final». El contenido es una región role="log" y el visor es enfocable. scrollToEnd(), scrollToStart(), atEnd, manual y el evento tsl-scroll-state.

Adjunto

.tsl-attachment muestra un archivo con medio (__media: icono o imagen), título y descripción, y acciones. data-state="uploading|processing|error" anima el título o marca el error (deja el motivo en la descripción); tamaños --sm y --xs (chip) y --vertical. Un .tsl-attachment__trigger convierte toda la tarjeta en enlace o botón sin tapar las acciones, y .tsl-attachment-group las ordena en una fila desplazable.

Traza de pasos

.tsl-trace lista los pasos de un agente: herramientas, razonamiento, fuentes. Cada .tsl-trace__line lleva icono, título (se trunca) y descripción (duración, cantidad). data-status="running" anima el título, pending lo atenúa y error lo marca; una línea que sea <button> o <a> es interactiva. Combina con <tsl-collapsible> para mostrar el detalle de cada paso.

Bloque de código completo

.tsl-codeblock amplía tsl-code: cabecera con título y acciones, números de línea (data-numbers, --tsl-start cambia el primero), ajuste de línea (data-wrap), líneas added, removed y highlighted con su marca (que no se copia ni se selecciona) y pie. El botón copia el texto que le pases en data-tsl-copy, sin números ni marcas. El contenido es enfocable y siempre va de izquierda a derecha, también en RTL.

Cuestionario

<tsl-questionnaire> muestra una pregunta por pantalla dentro de un <form> normal. Las preguntas son fieldset.tsl-questionnaire__item: data-required obliga a responder (con aviso accesible), las demás se pueden omitir, y data-when="rol=dev|ops" las hace depender de una respuesta anterior (el progreso solo cuenta las que aplican). Las teclas 1–9 eligen una opción y Enter avanza desde un campo de texto. Al terminar emite tsl-submit con answers; tsl-step informa cada cambio de pregunta.

Entrada de prompt

<tsl-prompt-input> convierte un <form> con un <textarea> en la caja de un chat: crece con el texto, Enter envía y Mayús+Enter salta de línea (sin cortar la composición de un IME). Los archivos llegan por el botón [data-attach], pegándolos o arrastrándolos sobre la caja, y se validan con accept, max-size y max-files. Con status="submitted" el botón muestra espera y con streaming se convierte en «Detener» (tsl-stop). tsl-submit trae text, files y los demás campos del formulario, como el modelo elegido.

Sugerencias

.tsl-suggestions es una fila desplazable de .tsl-suggestion (--wrap la deja en varias líneas). Con data-tsl-suggest="#id" un botón rellena esa entrada de prompt con su texto (o con data-value) y la enfoca; con data-send además la envía.

Razonamiento

<tsl-reasoning> pliega el razonamiento de un asistente. Mientras tiene streaming se abre, anuncia «Pensando…» con brillo y cuenta los segundos; al quitarlo pasa a «Pensó durante N s», emite tsl-complete y se pliega tras close-delay, salvo que la persona lo haya abierto o cerrado a mano. duration sirve para razonamientos ya terminados y los textos se cambian con thinking-label y done-label.

Fuentes y citas

.tsl-sources es un <tsl-collapsible> con un disparador .tsl-sources__trigger y una lista de .tsl-source (icono o inicial, título que se trunca y dominio). .tsl-citation es la cita en línea dentro del texto; envuelta en <tsl-hovercard class="tsl-citation-card"> muestra al pasar o enfocar una tarjeta .tsl-citation__panel con dominio, título y fragmento citado. Escribe la tarjeta con <span>: así la cita puede ir dentro de un <p> sin que el navegador lo cierre antes de tiempo.

Versiones y acciones de un mensaje

<tsl-branch> guarda varias versiones de un mensaje (.tsl-branch__item), muestra una y añade el selector «2 de 3» entre flechas (dentro de un .tsl-branch__selector propio si lo pones, por ejemplo en el pie). Empieza en la última, da la vuelta en los extremos y, si llega una versión nueva, salta a ella. .tsl-message__actions agrupa copiar, repetir o valorar: aparecen al pasar por el mensaje o al llegar con el teclado, y en pantallas táctiles siempre (--visible las fija).

Uso de contexto

<tsl-context> mide cuánto de la ventana de contexto se ha usado (used de max). El anillo muestra el porcentaje y, al pulsarlo, un panel con la barra (role="meter"), el desglose de input, output, reasoning y cached (solo las filas que reciba) y el coste en la moneda de currency. Desde el 75 % pasa a aviso y desde el 90 % a peligro. Las cifras se abrevian con el idioma de la página.

Utilidades de diseño

tsl-stack (vertical), tsl-row (horizontal con wrap) y tsl-grid (auto-fit). Ajusta con --gap y --min; tsl-spacer empuja elementos.

Collapsible

<tsl-collapsible open> muestra u oculta contenido con altura animada. El disparador recibe aria-expanded y aria-controls; el contenido cerrado queda inert (no se enfoca ni se lee). Métodos toggle(), open(), close() y evento tsl-toggle. A diferencia del acordeón, es un único bloque independiente.

Scroll area

<tsl-scroll-area> da a una zona de altura fija una barra discreta y un degradado en los bordes que aún tienen contenido (expone data-top, data-bottom, data-left y data-right). El área es enfocable para desplazarla con el teclado. orientation="vertical|horizontal|both".

Paneles redimensionables

<tsl-resizable> inserta separadores arrastrables entre paneles (.tsl-resizable__panel) con tamaño inicial (data-size), mínimo y máximo (data-min, data-max, en %). Admite varios paneles y anidación (direction="vertical"). Teclado: flechas (Shift ×5), Home/End; doble clic restablece. Con storage-key recuerda los tamaños. Evento tsl-resize.

Separator

<div class="tsl-separator" role="separator"> (línea horizontal) y tsl-separator--vertical para barras de herramientas. Es un separador puro, sin texto; para uno con etiqueta usa tsl-divider.

Aspect ratio

.tsl-aspect mantiene una proporción con la variable --ratio (por defecto 16 / 9). Los hijos img, video, iframe, svg o .tsl-aspect__fill la rellenan.

Panel de control

Barra lateral con modo de iconos, barra superior con migas y búsqueda, métricas con minigráfico, gráficas de área y dona, y la tabla de pedidos. El periodo (tsl-segmented) recalcula todo. .tsl-dashboard convierte la zona de contenido en contenedor: las métricas pasan de cuatro a dos columnas y las gráficas se apilan según el ancho del propio panel, no el de la ventana.

Autenticación

Entrar, crear cuenta, verificar el correo con un código y recuperar la contraseña en un solo bloque. .tsl-auth pone a un lado la presentación y al otro una vista (.tsl-auth__view) cada vez; cuando el bloque es estrecho la presentación desaparece. Los errores se muestran junto a cada campo con aria-invalid y aria-describedby, y al cambiar de vista el foco va a su título. En la demostración, cualquier código de 6 cifras salvo 000000 es válido.

Ajustes con navegación lateral

.tsl-settings pone las secciones en una navegación lateral (.tsl-nav con aria-current) y cada sección en filas .tsl-settings__row: título y explicación a un lado, control al otro. Al tocar cualquier campo aparece .tsl-savebar con Guardar y Descartar (que restablece el formulario); la zona de peligro pide confirmación. En un contenedor estrecho la navegación pasa a ser una fila desplazable y las filas se apilan.

Alta de un espacio de trabajo

Un <tsl-wizard> de cuatro pasos: datos del espacio (la dirección se propone sola a partir del nombre), invitaciones con <tsl-tags>, plan con tarjetas de elección y una revisión .tsl-summary que se rellena con tsl-step. Cada paso valida antes de avanzar, el indicador deja volver a los ya visitados y, al terminar, el formulario se sustituye por un estado vacío de confirmación.

Chat con asistente

Toda la familia de conversación junta. .tsl-chat reparte el marco en cabecera (__header, con el medidor de contexto), la zona de mensajes y la caja de entrada (__composer). Las respuestas usan .tsl-message--wide para ocupar todo el ancho y apilan razonamiento con su traza, texto con citas, bloque de código, versiones, acciones y fuentes. Escribe algo, usa una sugerencia, detén la respuesta a medias o pulsa «Regenerar»: la respuesta es simulada, pero los estados son los reales.