field-sizing y el tamaño de los controles
Por qué un input tiene el ancho que tiene, qué cambia field-sizing: content, el soporte real en agosto de 2026 y los patrones que ya no necesitan JavaScript.
El textarea que crece con el texto es, junto al select estilable, el componente que más veces se ha reescrito en la historia del frontend. La receta clásica —un div invisible que replica el contenido, una medición en cada pulsación, un requestAnimationFrame para no bloquear— aparece en todas las bibliotecas de formularios y todas tienen el mismo bug con las fuentes que cargan tarde. field-sizing: content la sustituye entera por una declaración, y desde junio de 2026 está en los cuatro motores.
- Explicar de dónde sale el ancho por defecto de un
inputy por qué no depende del CSS. - Usar
field-sizing: contentcon los límites correctos para que no colapse ni desborde. - Conocer las fechas de soporte exactas y escribir la degradación adecuada.
- Sustituir el auto-crecimiento de
textareapor CSS sin JavaScript.
De dónde sale el ancho por defecto
Un <input type="text"> sin CSS mide aproximadamente veinte caracteres de ancho. No es una convención de la hoja de estilos del navegador: es el atributo size, cuyo valor por defecto en HTML es 20, y que el motor traduce a un ancho intrínseco medido en unidades de avance del carácter. Lo mismo pasa con <textarea>, que usa rows y cols con valores por defecto de 2 y 20.
Ese ancho tiene una propiedad rara: es intrínseco pero no depende del contenido. Un input con doscientos caracteres dentro sigue midiendo veinte, y un input vacío también. El control se comporta como una caja de tamaño fijo con scroll interno, y esa es exactamente la definición del valor inicial de la propiedad nueva: field-sizing: fixed.
La consecuencia práctica es que width: max-content sobre un input no hace lo que esperas. La resolución de ancho intrínseco de un campo de formulario nunca consultaba su contenido, porque el contenido de un campo no es un hijo del elemento: es un valor. Por eso hacía falta JavaScript. No era una carencia de CSS, era que el modelo no tenía un camino entre el valor del control y el algoritmo de layout.
Qué cambia field-sizing: content
field-sizing: content abre ese camino: el motor pasa a calcular el tamaño intrínseco del control a partir de su valor actual, y lo recalcula en cada cambio. Con eso, width: auto y height: auto empiezan a significar algo, y min-content y max-content también.
input, textarea, select {
field-sizing: content;
}
Se aplica a los campos de texto (text, email, search, url, tel, password, number), a textarea y a select. En select, el efecto es que el botón se ajusta al ancho de la opción seleccionada en lugar de al de la opción más larga, que es un cambio de comportamiento que conviene decidir a conciencia: gana espacio y pierde estabilidad de layout, porque el ancho salta al cambiar de opción.
Cuando field-sizing: content está activo, los atributos size, rows y cols dejan de determinar el tamaño. El control pasa a estar gobernado exclusivamente por CSS, y como cualquier caja dimensionada por contenido, necesita límites.
Los límites no son opcionales
Un campo vacío con field-sizing: content colapsa a cero. Un campo con un párrafo pegado desde el portapapeles se estira hasta romper el layout. Los dos casos son inevitables si no pones cotas, y las cotas son la parte que la gente olvida:
.campo-inline {
field-sizing: content;
min-inline-size: 6ch;
max-inline-size: 100%;
padding-inline: 0.5em;
}
.notas {
field-sizing: content;
min-block-size: 4lh;
max-block-size: 16lh;
inline-size: 100%;
}
Las unidades importan. ch es el avance del carácter cero de la fuente actual: min-inline-size: 6ch significa “al menos lo que ocupan seis dígitos”, que es una cota estable aunque cambie la tipografía. lh es la altura de línea calculada: max-block-size: 16lh significa “hasta dieciséis líneas y luego scroll”, que es exactamente lo que quieres de un campo de notas y no se puede expresar con píxeles sin recalcularlo cada vez que cambia line-height.
Sobre el campo vacío hay un matiz útil: si el control tiene placeholder, los motores lo usan para dimensionar mientras está vacío, de modo que un campo con un texto de ayuda razonable ya no colapsa del todo. No conviene depender de ello como única defensa —el placeholder puede traducirse y cambiar de longitud— pero explica por qué a veces el colapso no aparece en tus pruebas.
Un campo que crece mientras el usuario escribe empuja todo lo que tiene debajo. En un formulario largo eso es un desplazamiento acumulado que el usuario percibe como inestabilidad, y que en las métricas aparece como CLS. Hay dos mitigaciones: dar al campo un min-block-size generoso para que los primeros caracteres no muevan nada, y colocar los campos que crecen al final del formulario o dentro de un contenedor con altura reservada. Que la propiedad exista no significa que crecer sea siempre la decisión correcta.
El soporte real, con fechas
field-sizing alcanzó Baseline recién disponible en junio de 2026, y las fechas son estas:
| Motor | Versión | Fecha |
|---|---|---|
| Chrome y Edge | 123 | 19 de marzo de 2024 |
| Safari | 26.2 | 12 de diciembre de 2025 |
| Firefox | 152 | 16 de junio de 2026 |
En agosto de 2026 han pasado menos de dos meses desde Firefox. “Recién disponible” significa una cosa muy concreta: está en la última versión de todos los navegadores y no está en el parque instalado. La ventana hasta “ampliamente disponible” se estima en treinta meses.
La degradación es benigna, que es lo que hace que merezca la pena adoptarla hoy. Un navegador que no entiende la propiedad la descarta y el control se queda con su tamaño fijo de siempre: usable, correcto, menos elegante. Eso ya es mejora progresiva bien hecha. La única precaución es no eliminar los atributos rows y cols del HTML, porque son tu tamaño de reserva:
<textarea name="notas" rows="4" class="notas"></textarea>
Con soporte, rows se ignora y manda el CSS. Sin soporte, rows da cuatro líneas razonables. No hay rama, no hay detección, no hay JavaScript.
Si necesitas ramificar de verdad —por ejemplo, para cargar el polyfill viejo solo donde hace falta— la detección es directa:
@supports (field-sizing: content) {
.notas { field-sizing: content; max-block-size: 16lh; }
}
const hayFieldSizing = CSS.supports('field-sizing', 'content');
El patrón que sustituye
Este es el componente que desaparece. La versión clásica del textarea que crece necesitaba escuchar input, escribir el valor en un elemento espejo con la misma tipografía y el mismo padding, leer su scrollHeight —lo que fuerza un layout síncrono— y aplicar la altura resultante. Fallaba cuando la fuente web cargaba después de la primera medición, cuando el usuario pegaba texto con saltos de línea, cuando el campo estaba oculto al inicializarse y cuando el box-sizing del espejo no coincidía.
Todo eso se sustituye por:
.autocrece {
field-sizing: content;
min-block-size: 2lh;
max-block-size: 12lh;
resize: none;
}
Y hay un segundo patrón, menos evidente, que también se resuelve: el campo que se ajusta a su valor en una interfaz de edición en línea. Una etiqueta editable dentro de una fila de tabla, un campo de cantidad en un carrito, un buscador que se estrecha cuando está vacío. Antes se hacía con contenteditable —perdiendo la semántica de formulario, la validación y el envío— o con medición en JavaScript. Ahora:
.cantidad {
field-sizing: content;
min-inline-size: 2ch;
max-inline-size: 6ch;
text-align: end;
}
Sigue siendo un input[type="number"], sigue validando, sigue enviándose y sigue siendo accesible.
Conviene entender por qué esto tardó tanto, porque explica dónde están los bordes afilados. El algoritmo de dimensionado intrínseco de CSS recorre el árbol de cajas y pregunta a cada hijo cuánto mide como mínimo y como máximo. El valor de un input no es un hijo: es una propiedad del elemento del DOM que puede cambiar sin que se modifique el árbol, desde el teclado, desde el portapapeles, desde el autorrelleno del navegador o desde una asignación en JavaScript. Meter ese valor en el cálculo de tamaño intrínseco obligó a definir cuándo exactamente se invalida el layout, y ahí está el detalle que te va a morder: el layout se recalcula en cada pulsación de tecla. En un campo suelto es imperceptible. En una tabla con doscientas filas editables y field-sizing: content en todas, cada tecla dispara un recálculo de tamaño intrínseco que puede propagarse hacia arriba hasta la tabla entera, porque el ancho de una columna depende de sus celdas. Es el mismo coste que tenía la versión con JavaScript, solo que ahora ocurre dentro del motor y no lo ves en el perfil como tu código. La mitigación es la de siempre y la vas a reconocer del nivel de rendimiento: aislar cada fila con contain: layout para que la invalidación no suba, o aceptar el tamaño fijo en las tablas grandes. Que una función sea declarativa no la hace gratuita; la hace invisible, que es peor cuando va mal.