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.
---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.
---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.
---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.
---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—.
---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.