Tabla y layout

Encabezados fijos, densidad, reordenar y redimensionar columnas, tamaño y truncado por columna, indicadores de scroll y la barra de paginación.

Defaults de tabla y layout

Estos comportamientos pasaron de opt-in a default, porque cada uno se estaba reimplementando en todas las apps que consumen listkit. La columna de versión es el release que lo cambió:

ComportamientoDefaultCómo salirDesde
Las columnas recortan el texto que desbordaontruncate: false por columna (wrap/grow lo implican)4.0
Layout de paginación'sticky'paginationVariant='fixed' | 'inline'4.0
Selector de filas por página[20, 50, 100, 200]pageSizeOptions: false4.0
Scroll al tope al cambiar de páginaonscrollToTopOnPageChange: false4.0
Ancho mínimo por columna sin width140pxtable.minColumnWidth (0 desactiva el piso)4.1

El piso de columna. Un layout 'fixed' reparte el contenedor en partes iguales entre las columnas que no declaran width, así que la cantidad de columnas sola decide cuánto le toca a cada una: doce columnas en un shell de 1280px reciben 106px — insuficiente para una fecha, ya no digamos para un par de botones de acción. La tabla por eso lleva un min-width de columnas sin width x minColumnWidth (más los width declarados, sumados con calc porque son longitudes CSS), y scrollea en horizontal cuando el contenedor baja de ahí en vez de seguir apretando.

Es un min-width, no un breakpoint medido: el browser lo reevalúa en cada resize sin re-render, sin lectura de layout y con la misma respuesta en SSR. Combínalo con sticky: 'right' en la columna de acciones para que los botones se queden quietos mientras lo demás scrollea — esa combinación es la que hace usable una tabla ancha.

Una celda fijada pinta con bg-inherit, así que el fondo de la fila tiene que ser opaco. Los estados propios de listkit lo son; si rowClassName devuelve algo como bg-amber-50/60, el contenido que scrollea se transparenta a través de la columna fijada.

El fijado arranca en md. Un checkbox más una columna de acciones son ~150px, un quinto de un teléfono, y la razón por la que vale la pena gastarlos en una pantalla ancha es exactamente la razón por la que no en una angosta. El checkbox de selección se fija solo cuando selection está activo — una selección que no ves es una selección que pierdes de vista a media tabla.

Acciones de borde. overlay: true renderiza una columna como el espejo del checkbox de selección en el otro extremo: siempre visible, padding px-2 delgado, un divisor border-l nítido en vez de una etiqueta de header, y fijada a la derecha desde md:

{ key: 'actions', header: 'Acciones', width: '7rem', overlay: true,
  render: (item, i) => <RowActions item={item} index={i} variant='inline' actions={…} /> }

overlay también implica exportable: false — la columna contiene botones y su key no nombra ningún valor de la fila, así que de otro modo escribiría una columna vacía y marcada en cada CSV. Pasa exportable: true si de verdad la quieres.

Dale un width del tamaño de sus botones más el padding — 28px por botón de icono, 4px por gap, 16px de padding, así que tres botones ≈ '7rem'. No se revela en hover a propósito: las acciones que nadie ve son acciones que nadie usa, y una pantalla táctil nunca hace hover.

UX de tabla: encabezado fijo, densidad, reordenar, redimensionar

Vienen activadas por defecto. Cualquier config con table obtiene el gestor de columnas, el toggle de densidad, reordenar arrastrando el encabezado, redimensionar desde el borde y el menú de opciones — sin banderas — y cada elección se persiste en localStorage. Escribe false para quitar alguna:

table: {
  columns,
  // Todo lo de abajo es opcional; el valor por defecto ya es `true`.
  columnControl: false,   // fija las columnas (sin panel de ocultar/mostrar)
  reorderable: false,     // sin reordenar arrastrando el encabezado
  resizable: false,       // sin redimensionar desde el borde
  density: false,         // sin toggle cómoda/compacta
  optionsMenu: false,     // quita por completo el menú de opciones
  defaultDensity: 'comfortable',
  stickyHeader: true,     // el encabezado permanece visible mientras la tabla hace scroll
  maxBodyHeight: '70vh',  // altura del área de scroll del encabezado fijo (por defecto '70vh')
}
  • stickyHeader le da a la tabla un área de scroll acotada (limitada por maxBodyHeight, por defecto '70vh') para que el encabezado quede fijo arriba y la barra de paginación abajo — ambos visibles mientras haces scroll. El scroll horizontal queda contenido en la misma caja, así una tabla ancha nunca se desborda fuera de la página en pantallas pequeñas. Solo en vista de tabla.
  • density + defaultDensity exponen el toggle cómoda ↔ compacta (sobrescribe el compact estático).
  • reorderable / resizable agregan reordenar arrastrando encabezados y redimensionar por el borde; los anchos redimensionados persisten por columna.

Tamaño de columnas y truncado

Por defecto la tabla usa layout: 'auto' — las columnas se ajustan a su contenido, así una celda larga ensancha su columna y empuja a las demás. Para mantener las columnas estables y recortar el desborde, activa truncate por columna:

table: {
  columns: [
    // Elipsis de una línea. Cambia la tabla a layout: 'fixed' para que el recorte
    // siga el ancho real de la columna — ensánchala/redimensiónala y se ve más texto.
    { key: 'name', header: 'Nombre', truncate: true, width: '14rem' },
    // Recorta a N líneas.
    { key: 'notes', header: 'Notas', truncate: 2 },
    // Vuelve a permitir el salto de línea en una columna.
    { key: 'address', header: 'Dirección', wrap: true },
    // Acota el rango del redimensionado.
    { key: 'sku', header: 'SKU', minWidth: 96, maxWidth: 240 },
  ],
}
  • truncate: true recorta a una línea con elipsis; truncate: N recorta a N líneas. Es dinámico — el recorte sigue el ancho visible de la columna, así que ensancharla o redimensionarla revela más texto en vivo (sin config extra por columna). Para celdas de texto plano se agrega automáticamente un tooltip title con el texto completo; para un render con JSX, pasa tooltip: item => '…' para mostrar el valor completo al hover.
  • truncate pone la tabla en layout: 'fixed' para que el recorte quede atado al ancho real de la columna — esta es la solución cuando el texto sigue cortado aunque ensanches o redimensiones la columna (en layout auto gana el contenido). Para un render personalizado con líneas apiladas (p. ej. un nombre sobre un id), mantén truncate en tus propios elementos internos y pon table.layout: 'fixed' directamente — no envuelvas anchos fijos como max-w-[180px] dentro de la celda, porque ignoran el redimensionado.
  • grow: true marca la columna prioritaria: absorbe el espacio sobrante y nunca se trunca, así el valor más importante siempre se ve completo mientras las vecinas recortan.
  • Auto-ajuste: con resizable, doble clic en el handle de redimensionado de una columna la ajusta a su celda visible más ancha (acotado por maxWidth). No necesitas adivinar un ancho fijo.
  • width es una pista en layout auto y autoritativo en fixed; es solo el tamaño inicial y nunca bloquea el redimensionado. minWidth/maxWidth (px) son topes opcionales para la celda y el handle (piso por defecto 48px, sin techo) — ojo: maxWidth también limita hasta dónde arrastra el handle, así que omítelo para resize sin tope.

El toolbar se mantiene limpio. Densidad, columnas y exportar no agregan un botón cada uno — <ListView> los pliega en un único menú de opciones (⚙), dejando inline solo lo esencial (toggle de vista, conteo de resultados). Es responsivo (disponible también en móvil) y en vista de tarjetas muestra solo exportar. Los componentes DensityToggle, ColumnManager, ExportButton y TableOptionsMenu se exportan por si construyes tu propio toolbar.

Indicadores de scroll

Todo contenedor con scroll acotado en listkit — el sidebar de filtros, el menú de opciones, el gestor de columnas, las opciones de un select, el diálogo de export y el scroll horizontal de la tabla — difumina sus bordes recortados, para que el contenido más allá del corte se anuncie en vez de leerse como el final de la lista. ScrollArea y useScrollFade se exportan para tus paneles.

Todo fade se disuelve contra la superficie por defecto (el tono 'surface', blanco en modo claro). Donde quieras que el borde oscurezca en su lugar, ScrollArea acepta fadeTone='shadow' | 'shadow-strong'.

Donde la tabla tiene columnas pinned, el fade no desaparece: se recorre hacia adentro hasta la costura entre el stack pinned y el contenido que scrollea, así la señal queda visible en cualquier device (un fade dejado en el borde del contenedor pintaría debajo de las celdas pinned opacas). Además, los fades de la tabla empiezan debajo del header: lavan datos que scrollean, nunca los títulos de columna. La celda pinned más externa agrega un divisor hairline en esa misma frontera. ScrollArea expone las piezas para tus propios scrollers: fadeLeft / fadeRight apagan un lado, fadeInsetLeft / fadeInsetRight (una longitud CSS) recorren un fade desde el borde del contenedor a partir de md, y el wrapper es un group/scroll con data-scroll-left / data-scroll-right para que los descendientes estilen según el scroll.

Los diálogos toman un height fijo, así su contenido scrollea en lugar de que el diálogo cambie de tamaño bajo el cursor mientras el usuario filtra.

Desplazar la barra de paginación

La barra de paginación usa position: fixed. Pasa paginationClassName para despejar elementos de la app como una sidebar (mergeado vía tailwind-merge, así que un left-* sobrescribe el left-0 por defecto):

<ListView config={config} adapter={adapter} paginationClassName='lg:left-64' />

Para una sidebar cuyo ancho cambia (colapsable), manéjalo con una variable CSS que la sidebar establezca y una clase que la lea, ej. left-[var(--sidebar-w)].

On this page