Saltar al contenido

Atributos dinámicos y class:list

Interpolar valores en atributos sin comillas, esparcir objetos de atributos con la sintaxis de propagacion, componer clases condicionales con la directiva class:list, y la logica de presencia de los atributos booleanos de HTML frente a los aria.

Atributos dinámicos

Cualquier atributo acepta una expresión entre llaves, y —detalle importante— sin comillas alrededor. Se escribe href={url}, no href="{url}": esto último pondría la cadena literal con las llaves incluidas. El valor se calcula en el servidor y se escapa igual que el texto, así que interpolar en un atributo es tan seguro como interpolar en el cuerpo.

ASTRO
---const enlace = { url: '/docs', texto: 'Documentación' };---<a href={enlace.url} title={enlace.texto}>{enlace.texto}</a>

Cuando ya tienes un objeto de atributos, la sintaxis de propagación los vuelca todos de golpe. Es el patrón para reenviar props recibidas a un elemento nativo sin listarlas una a una, algo habitual al construir componentes que envuelven etiquetas del DOM.

ASTRO
---const attrs = { id: 'cta', 'data-track': 'hero', role: 'button' };---<div {...attrs}>Llamada a la acción</div>

Y del mismo modo que en el texto, un valor undefined o null en un atributo hace que Astro lo omita por completo, en vez de escribir un valor basura como alt="undefined". Es coherente con lo que verás en los booleanos: ante la duda, Astro prefiere la ausencia de un atributo a un valor sin sentido. Esa regla convierte los atributos opcionales en algo trivial —pasas undefined y el atributo desaparece—, sin ternarios que decidan entre ponerlo o no.

ASTRO
---const textoAlt = undefined;---<img src="/logo.svg" alt={textoAlt} /><!-- salida: <img src="/logo.svg">  sin ningun atributo alt -->

class:list: componer clases condicionales

Construir una cadena de clases a mano —concatenando con espacios y ternarios— es frágil: sobran o faltan espacios, se cuela un undefined, se duplican clases. class:list es la directiva que compone la clase final a partir de varias fuentes y limpia el resultado. Acepta una mezcla de formas:

  • cadenas sueltas, como 'boton', que se incluyen tal cual;
  • objetos, donde la clave se incluye solo si su valor es verdadero: { activo: esActivo };
  • arrays, que se aplanan y se recorren en profundidad;
  • un Set, que además deduplica de forma natural;

y descarta cualquier valor falsy que encuentre por el camino.

ASTRO
---const esActivo = true;const deshabilitado = false;---<button  class:list={[    'boton',    { activo: esActivo, deshabilitado },    esActivo && 'resaltado',  ]}>  Guardar</button><!-- salida: <button class="boton activo resaltado">Guardar</button> -->

El resultado se resuelve a un atributo class corriente, y si la lista queda vacía Astro no emite ningún atributo class. Es el equivalente integrado de librerías como clsx, sin instalar nada. La distinción práctica es clara: para elegir entre dos cadenas basta un class={cond ? 'a' : 'b'} y se lee bien; reserva class:list para cuando sumas condiciones independientes que pueden coincidir o no. No necesitas ambos en el mismo elemento, porque class:list ya admite una cadena estática como una entrada más del array.

Atributos booleanos: presencia, no valor

HTML tiene una familia de atributos —disabled, checked, required, hidden, readonly, selected— cuya semántica es binaria y peculiar: lo que cuenta es que estén o no, no su valor. Un disabled="false" sigue deshabilitando el botón, porque el navegador solo mira si el atributo existe. Un motor ingenuo que escriba literalmente disabled="false" produce el bug clásico: el elemento queda deshabilitado justo cuando querías lo contrario.

Astro conoce ese conjunto y hace lo correcto: mapea un booleano de JavaScript a presencia o ausencia. <button disabled={estaOcupado}> emite el atributo disabled cuando estaOcupado es verdadero y lo omite por completo cuando es falso —no disabled="false", sino nada—.

ASTRO
---const estaOcupado = false;const esRequerido = true;---<button disabled={estaOcupado}>Enviar</button><input required={esRequerido} /><!-- salida: <button>Enviar</button>  y  <input required> -->

Esa lista de atributos booleanos no la inventa Astro: la fija la especificación de HTML, y Astro la conoce de antemano. Por eso no tienes que declararle cuáles tratar así; reconoce disabled, checked, multiple, open, autoplay y toda la familia, y les aplica la regla de presencia por igual. Tu trabajo se reduce a pasar un booleano y confiar en que el marcado saldrá correcto.

Cuidado, eso sí, con confundir estos booleanos del estándar con los atributos aria-, que no son booleanos de HTML: toman una cadena de texto, y aria-hidden="false" significa algo distinto de omitir el atributo. Por eso Astro renderiza aria-expanded={false} como aria-expanded="false", con su valor de texto, y no lo borra. La regla mental que evita el error: los booleanos reales del estándar se rigen por presencia; los aria- y los data- siempre llevan valor.