Botón
Nació de lo que los componentes necesitaban por dentro — el "Deshacer" del toast, el "Aplicar" del
calendario. En lugar de que cada uno inventara el suyo, todo sale de aquí, y queda disponible para tu proyecto. Son
solo clases: un <button> sigue siendo un <button>.
Ejemplos
Variante, tamaño y estado son clases y atributos que se combinan.
Tamaños
is-sm mide 30px, el predeterminado sale de --tuc-control-height e is-lg mide 44px.
Con icono
El <svg> toma el tamaño del botón por sí solo. is-icon lo vuelve cuadrado — y entonces el aria-label es obligatorio.
Ocupado
aria-busy="true" con .tuc-spinner. Haz clic en "Guardar" para ver el cambio.
Desactivado
disabled no recibe clic ni foco; aria-disabled="true" sigue siendo enfocable y anunciado.
Ancho completo
is-block ocupa toda la línea — el botón de un formulario estrecho o de un móvil.
Como enlace
En un <a> la clase quita el subrayado; es lo correcto cuando el botón navega, y no ejecuta una acción.
Cómo usar
tuc-btn y una variante. Sin variante el botón queda transparente, sin borde — la base de
la que parten las variantes.
<button type="submit" class="tuc-btn is-primary">Guardar</button>
<button type="button" class="tuc-btn is-outline is-sm">Cancelar</button>
<button type="button" class="tuc-btn is-ghost is-icon" aria-label="Cerrar">
<svg>...</svg>
</button>
<a class="tuc-btn is-link" href="/help/">Más información</a>En el formulario de Django
El botón es de la plantilla, no del form. Con type="submit" envía el formulario como siempre.
<form method="post">
{% csrf_token %}
{{ form }}
<a class="tuc-btn is-outline" href="{% url 'customers' %}">Cancelar</a>
<button type="submit" class="tuc-btn is-primary">Guardar</button>
</form>En JavaScript
No hay componente que instanciar. Lo que se hace en código es cambiar el estado — por ejemplo, ocupado durante un envío:
const btn = document.querySelector('#save');
btn.setAttribute('aria-busy', 'true');
btn.disabled = true; // impide el segundo clic
btn.prepend(Object.assign(document.createElement('span'), { className: 'tuc-spinner' }));
// ...cuando termine
btn.removeAttribute('aria-busy');
btn.disabled = false;
btn.querySelector('.tuc-spinner').remove();Variantes y cuándo usarlas
Una acción principal por área. Las demás ceden peso visual, y la destructiva se distingue por el color.
| Clase | Para qué |
|---|---|
is-primary | Acción principal, en el color de acento (--tuc-accent) |
is-outline | Acción secundaria, con borde |
is-ghost | Terciaria, sin peso visual — acciones dentro de una tabla, barra de herramientas |
is-danger | Destructiva, pintada con --tuc-danger-fill |
is-link | Parece un enlace, se comporta como botón |
Por qué el rojo del botón es otro token
--tuc-danger es más claro en modo oscuro, para tener contraste como texto — y un botón pintado con él quedaría pastel con texto blanco. --tuc-danger-fill es el mismo en los dos temas.
Botón y campo tienen la misma altura
Los dos salen de --tuc-control-height — 38px, o 44px en el diseño compacto, donde el área táctil necesita ser mayor. Radio y borde vienen de --tuc-radius-md y --tuc-border-width. Un botón junto a un select queda alineado sin ajustes.
Ocupado y desactivado
Los dos impiden la acción, pero dicen cosas distintas, y por eso se ven distintos.
<button class="tuc-btn is-primary" aria-busy="true" disabled>
<span class="tuc-spinner"></span> Guardando
</button>
<button class="tuc-btn is-outline" aria-disabled="true">Exportar</button>Ocupado no es desactivado
El disabled junto al aria-busy impide el segundo clic, pero el atenuado al 50% diría "no disponible" — y el botón está trabajando. Con aria-busy="true" mantiene el color pleno y el cursor de espera. El spinner se mide en em y se pinta con currentColor: dentro del botón ocupa el lugar del icono y sale blanco en un primario, sin variante.
Desactivado muestra el cursor de bloqueo
Nada de pointer-events: none: sin recibir el puntero el elemento no cambia el cursor, y la persona se queda sin señal de que el botón no responde. En un <button disabled> el navegador ya impide el clic. Con aria-disabled="true" el clic sigue llegando a propósito — es el botón que sigue enfocable y anunciado, y quien usa el atributo trata el clic en su propio manejador.
Desactivado tampoco reacciona al hover — cambiar de color sugeriría que todavía funciona — y pierde la sombra.
Al pulsarlo, el botón se encoge un poco: en un toque, sin hover, el color por sí solo no confirma que el dedo acertó
el objetivo. Con prefers-reduced-motion ese movimiento desaparece.
Teclado y accesibilidad
Es el <button> del navegador: foco, Enter y Espacio vienen gratis.
Lo que le toca a la plantilla es elegir el elemento correcto y darle nombre a lo que no tiene texto.
| Tecla | Acción |
|---|---|
Tab | Llega al botón, con el anillo de foco de los campos; salta el disabled, no el aria-disabled |
Enter Espacio | Activa el <button>; en un <a class="tuc-btn">, solo Enter |
is-iconsin texto necesitaaria-label; el<svg>llevaaria-hidden="true".<a>para navegar,<button>para actuar — el lector de pantalla anuncia uno y otro de forma distinta.- Dentro de un
<form>, pontype="button"a lo que no envía: el valor por defecto del navegador essubmit.
Clases
Solo CSS, sin JavaScript. No escribas un segundo diseño de botón: una acción dentro de una tabla o de un bloque de
código también es .tuc-btn, con una clase propia solo para posicionar.
| Clase o atributo | Para qué |
|---|---|
tuc-btn | La base: altura, fuente, radio, foco. Vale en <button> y <a> |
is-primary is-outline is-ghost is-danger is-link | Variante |
is-sm is-lg | Tamaño: 30px y 44px; el icono acompaña |
is-icon | Cuadrado, solo icono; se combina con is-sm e is-lg |
is-block | Ocupa todo el ancho |
disabled | Atenuado, cursor de bloqueo, sin hover; el navegador bloquea el clic |
aria-disabled="true" | El mismo diseño, pero enfocable y con el clic llegando |
aria-busy="true" | Ocupado: color pleno y cursor de espera, incluso con disabled |
.tuc-spinner dentro | Ocupa el lugar y el tamaño del icono |
--tuc-btn-icon | Tamaño del <svg> hijo; cada tamaño de botón define el suyo |