Abres una pantalla en el móvil y la fila de botones del encabezado (Importar, Descargar plantilla, Exportar…) se corta por el borde derecho: no caben, no saltan de línea y la página se desplaza en horizontal. En una prueba con cinco botones, la barra de herramientas medía 580 px en una pantalla de 375 px. Este es el patrón que lo produce:
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Listado de registros</CardTitle>
<div className="flex gap-2">
<Button variant="outline">Importar</Button>
<Button variant="outline">Descargar plantilla</Button>
<Button variant="outline">Exportar</Button>
<Button variant="outline">Imprimir</Button>
<Button>Nuevo registro</Button>
</div>
</CardHeader>
El artículo explica por qué la fila no se adapta y cómo resolverlo con dos cambios de clases de Tailwind CSS, sin tocar los botones. Los ejemplos usan Tailwind y el componente CardHeader de shadcn/ui, pero el mecanismo es CSS flexbox y sirve para cualquier barra de herramientas.
Por qué pasa
Hay tres factores que se suman:
- Flexbox no hace saltos de línea por defecto. El valor inicial de
flex-wrapesnowrap: según MDN, los elementos se colocan en una sola línea y eso puede hacer que el contenedor desborde. La claseflexde Tailwind solo aplicadisplay: flex, así que la fila no se parte. - Los botones no se encogen. En la versión actual del registro de shadcn/ui, la clase base del
Buttonincluyeshrink-0ywhitespace-nowrap. El primero impide que el botón se estreche y el segundo impide que su texto ocupe dos líneas, de modo que el ancho mínimo de la fila es la suma de todos los botones y los espacios. - El encabezado tiene una sola disposición. Con
flex-rowsin prefijo, el título y la barra de herramientas se colocan lado a lado también en pantallas pequeñas. Tailwind es mobile-first: una utilidad sin prefijo se aplica a todos los tamaños, y una con prefijo (sm:,md:…) se aplica desde ese punto de ruptura en adelante. El puntosmes 40rem (640 px).
El error clásico de este tema es leer sm: como "en pantallas pequeñas". La documentación de Tailwind lo aclara: para dar estilo a móvil se usa la utilidad sin prefijo, y sm: es el punto de ruptura donde cambia el diseño para pantallas más anchas.
Solución
-
Confirma el desbordamiento. Abre las herramientas de desarrollo, activa la vista de dispositivo móvil con un ancho de 375 px y ejecuta en la consola:
const ancho = document.documentElement.clientWidth const desborda = document.documentElement.scrollWidth > ancho const culpables = Array.from(document.querySelectorAll("body *")) .filter((el) => el.getBoundingClientRect().right > ancho) .map((el) => el.tagName.toLowerCase() + (el.id ? "#" + el.id : "")) console.log({ desborda, culpables })Si
desbordaestrue, la listaculpablesmuestra qué elementos sobrepasan el borde derecho. Normalmente aparece el contenedor de los botones y los propios botones. -
Apila el encabezado en móvil y ponlo en fila desde
sm. Sustituye la disposición fija por una responsive:<CardHeader className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">flex flex-col gap-2es la disposición base (móvil): título arriba y botones debajo, separados por un espacio. Desde 640 px,sm:flex-row sm:items-center sm:justify-betweenrecupera la fila con el título a la izquierda y los botones a la derecha. -
Permite que los botones salten de línea. En el contenedor de los botones, agrega
flex-wrap:<div className="flex flex-wrap gap-2">La propiedad
gapfunciona en contenedores flex y define el espacio tanto entre elementos como entre líneas, por eso conviene más que márgenes individuales cuando hay saltos de línea. -
Resultado completo:
<CardHeader className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <CardTitle>Listado de registros</CardTitle> <div className="flex flex-wrap gap-2"> <Button variant="outline">Importar</Button> <Button variant="outline">Descargar plantilla</Button> <Button variant="outline">Exportar</Button> <Button variant="outline">Imprimir</Button> <Button>Nuevo registro</Button> </div> </CardHeader> -
Evita repetirlo en cada pantalla. Si varias pantallas comparten la estructura, extrae un componente y cambia las clases en un solo lugar:
type ToolbarHeaderProps = { title: React.ReactNode actions: React.ReactNode } export function ToolbarHeader({ title, actions }: ToolbarHeaderProps) { return ( <CardHeader className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <CardTitle>{title}</CardTitle> <div className="flex flex-wrap gap-2">{actions}</div> </CardHeader> ) }
Cómo verificar que quedó resuelto
- Con la vista de dispositivo a 375 px, vuelve a ejecutar el script del paso 1:
desbordadebe serfalseyculpablesdebe quedar vacío. - Comprueba que los botones se reparten en varias filas y que el título ocupa todo el ancho disponible.
- Amplía la ventana a 640 px o más: el título y los botones deben volver a una sola fila, con los botones a la derecha.
- Prueba también anchos intermedios (por ejemplo 360 px y 414 px), porque el número de filas de botones cambia con el ancho.
En la prueba con cinco botones, a 375 px el encabezado original desbordó (la página medía 676 px de ancho de contenido). Con los cambios no hubo desbordamiento, el título pasó a medir 325 px y los botones ocuparon dos filas. A 800 px el resultado fue el mismo que antes: una sola fila.
Variantes y errores relacionados
Solo flex-wrap evita el desbordamiento, pero estrecha el título
Si únicamente agregas flex-wrap al contenedor de botones y dejas el encabezado en flex-row, el desbordamiento desaparece, pero el título y los botones siguen compitiendo por la misma fila. En la misma prueba el título quedó en 71 px de ancho, partido en tres líneas, y los botones se repartieron en tres filas a su derecha. Por eso conviene apilar el encabezado en móvil.
El desbordamiento viene de otro elemento
El script del paso 1 sirve para cualquier desbordamiento horizontal. Otras causas frecuentes son tablas anchas, imágenes sin ancho máximo y textos largos sin espacios dentro de un hijo flex, que necesita min-w-0 para poder estrecharse.
Cuando prefieres una sola fila con scroll
Si los botones deben permanecer siempre en una línea, la alternativa es dar al contenedor de los botones su propio overflow-x-auto. Así el desplazamiento queda contenido en la barra de herramientas y la página no se desborda, aunque el usuario debe desplazarla para ver todos los botones.
Usar sm: para el diseño móvil
Escribir sm:flex-col para apilar en móvil no funciona: esa clase solo se aplica desde 640 px. El diseño móvil va en la clase sin prefijo (flex-col) y el cambio para pantallas anchas en la clase con prefijo (sm:flex-row).
Resumen
- Una fila flex no hace saltos de línea por defecto (
flex-wrap: nowrap) y los botones de shadcn/ui no se encogen, así que la barra desborda en pantallas estrechas. - Tailwind es mobile-first: el diseño móvil va sin prefijo y
sm:(640 px) cambia el diseño para pantallas más anchas. - En el encabezado usa
flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between. - En el contenedor de botones usa
flex flex-wrap gap-2. - Verifica a 375 px que
scrollWidthno supereclientWidth, y extrae un componente si repites el patrón en varias pantallas.
Comentarios
Publicar un comentario