Saltar al contenido
Índice de Kit UI
Kit UI Componentes

Barra de filtros.

Patrón canónico de barra de filtros para cuenta, admin, usaia, firmas y documentos.

Verlo por niveles

Primero el componente, después la variación de marca y finalmente su uso dentro de una página.

Nivel 1Componente canónico
Filtrar licitaciones

Sin filtros aplicados

Nivel 2Círculo de Santiago · variación
Filtrar licitaciones

2 filtros activos: Adjudicada, Biobío

Nivel 3Dentro de una página
Sitio · sección
Filtrar licitaciones

Sin filtros aplicados

Ver y copiar el código de este render
HTML del nivel canónico
<form class="ui-sample ui-filter-bar" data-filter-bar="true"><fieldset><legend>Filtrar licitaciones</legend><div><label for="filter-bar-0-estado">Estado<select class="input" id="filter-bar-0-estado" name="estado"><option value="">Todos</option><option value="publicada">Publicada</option><option value="cerrada">Cerrada</option><option value="adjudicada">Adjudicada</option><option value="desierta">Desierta</option></select></label><label for="filter-bar-0-region">Región<select class="input" id="filter-bar-0-region" name="region"><option value="">Todas</option><option value="metropolitana">Metropolitana de Santiago</option><option value="valparaiso">Valparaíso</option><option value="biobio">Biobío</option></select></label><label for="filter-bar-0-anio">Año de publicación<select class="input" id="filter-bar-0-anio" name="anio"><option value="">Todos</option><option value="2026">2026</option><option value="2025">2025</option><option value="2024">2024</option></select></label><label for="filter-bar-0-monto">Monto estimado<select class="input" id="filter-bar-0-monto" name="monto"><option value="">Cualquiera</option><option value="menos-100">Menos de 100 UTM</option><option value="100-1000">Entre 100 y 1.000 UTM</option><option value="mas-1000">Más de 1.000 UTM</option></select></label></div></fieldset><div class="ui-actions"><button class="btn btn--primary" type="submit">Aplicar filtros</button><button class="btn btn--quiet" type="reset">Limpiar</button><p>Sin filtros aplicados</p></div></form>

Variaciones por marca y sitio

Cada cambio visible declara su autoridad; lo que permanece canónico no se vuelve a inventar en el producto.

innovacionsantiago.cl

Compañía de Innovación de Santiago

Autoridad: CONSTITUTION.md + LOGOS-CANONICOS.md
Filtrar licitaciones

Sin filtros aplicados

Cambia en esta marca

  • Navegación y contenido propios del servicio
  • Acento reservado para acciones y datos

Permanece canónico

  • Mark CIS y namebrand de tres líneas
  • IBM Plex Sans, foco, espaciado y anatomía
circulodesantiago.cl

Círculo de Santiago

Autoridad: core-style/DECISIONES.md M10 y LOGOS-CANONICOS.md
Filtrar licitaciones

2 filtros activos: Adjudicada, Biobío

Cambia en esta marca

  • Mark circular y namebrand Círculo de Santiago en caja normal
  • Contenido societario y navegación propia

Permanece canónico

  • Misma estructura, orden de cuenta y footer
  • Tipografía, foco y comportamiento canónicos

Comparación histórica recuperable

Compara la propuesta con cortes reales del repositorio. La ausencia de una muestra se declara y nunca se reconstruye de memoria.

Estado actual
Barra de filtros tiene 2 implementaciones observadas en 2 repositorios: cds/cis/cis-admin, cochid/cochid-datos.
Cambio propuesto
Publicar en r19 el contrato de Barra de filtros para Patrón canónico de barra de filtros para cuenta, admin, usaia, firmas y documentos.
Qué ocurre si respondes No
No se adopta el contrato r19 de Barra de filtros; las 2 implementaciones observadas permanecen sin migración.

Preparando comparación histórica…

Antes / r19

La comparación convierte una regla abstracta en una diferencia visible y verificable.

r19 · correctoContrato canónico renderizado
Filtrar licitaciones

Sin filtros aplicados

  • Usa HTML semántico, foco visible y estados explícitos.
  • Conserva contenido útil a 320 px y sin JavaScript.
Antes · problema observadoAnti-ejemplo no interactivo
correo@ejemplo.cl!Algo salió mal
  • No agregues sombras, radios ni franjas laterales decorativas.
  • No uses color, placeholder o icono como única explicación.

Responsive

La misma anatomía debe conservar lectura, foco y acciones sin cortar contenido ni crear scroll horizontal en la página.

320 pxMóvil y zoom
Filtrar licitaciones

Sin filtros aplicados

1280 pxEscritorio
Filtrar licitaciones

2 filtros activos: Adjudicada, Biobío

Cobertura

DimensiónContrato
Propietario canónicoSistema de diseño CIS y cis-auth · core/core-style
ConsumidoresCuenta, Admin, usaia, Firmas y Documentos
DependenciasHTML nativo, validación de servidor
Estadocandidato

Evidencia de adopción

Patrón observado. 2 implementaciones observadas en 2 repositorios.

La observación demuestra uso existente; la aceptación canónica sigue dependiendo de esta ficha y de su propietario.

DatoResultado
ClasificaciónPatrón observado
Implementaciones2 implementaciones observadas
Repositorioscds/cis/cis-admin, cochid/cochid-datos
Snapshotkit/componentes/componentes-observados.json

Consumidores observados

El inventario conserva repositorio, archivo fuente y criterio de asociación. Observar una implementación no la convierte automáticamente en canónica.

Estados

Estado requerido
vacío
completo
inválido
disabled

Accesibilidad

  • Conserva nombre accesible, orden de lectura y foco visible.
  • Funciona a 320 px, con zoom de 200% y texto largo.
  • El color nunca es la única señal de estado o acción.
  • El contenido esencial permanece disponible sin JavaScript.

Código HTML

Copia el bloque completo; después cambia contenido y destinos, no su anatomía.

Código de referencia
<form class="ui-sample ui-filter-bar" data-filter-bar="true"><fieldset><legend>Filtrar licitaciones</legend><div><label for="filter-bar-0-estado">Estado<select class="input" id="filter-bar-0-estado" name="estado"><option value="">Todos</option><option value="publicada">Publicada</option><option value="cerrada">Cerrada</option><option value="adjudicada">Adjudicada</option><option value="desierta">Desierta</option></select></label><label for="filter-bar-0-region">Región<select class="input" id="filter-bar-0-region" name="region"><option value="">Todas</option><option value="metropolitana">Metropolitana de Santiago</option><option value="valparaiso">Valparaíso</option><option value="biobio">Biobío</option></select></label><label for="filter-bar-0-anio">Año de publicación<select class="input" id="filter-bar-0-anio" name="anio"><option value="">Todos</option><option value="2026">2026</option><option value="2025">2025</option><option value="2024">2024</option></select></label><label for="filter-bar-0-monto">Monto estimado<select class="input" id="filter-bar-0-monto" name="monto"><option value="">Cualquiera</option><option value="menos-100">Menos de 100 UTM</option><option value="100-1000">Entre 100 y 1.000 UTM</option><option value="mas-1000">Más de 1.000 UTM</option></select></label></div></fieldset><div class="ui-actions"><button class="btn btn--primary" type="submit">Aplicar filtros</button><button class="btn btn--quiet" type="reset">Limpiar</button><p>Sin filtros aplicados</p></div></form>

Código TAILWIND

Copia el bloque completo; después cambia contenido y destinos, no su anatomía.

Código de referencia
<form class="ui-sample ui-filter-bar border border-slate-300 bg-white p-4 text-slate-950 grid gap-3" data-filter-bar="true"><fieldset class="grid gap-3"><legend>Filtrar licitaciones</legend><div><label for="filter-bar-0-estado">Estado<select class="input border border-slate-400 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2" id="filter-bar-0-estado" name="estado"><option value="">Todos</option><option value="publicada">Publicada</option><option value="cerrada">Cerrada</option><option value="adjudicada">Adjudicada</option><option value="desierta">Desierta</option></select></label><label for="filter-bar-0-region">Región<select class="input border border-slate-400 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2" id="filter-bar-0-region" name="region"><option value="">Todas</option><option value="metropolitana">Metropolitana de Santiago</option><option value="valparaiso">Valparaíso</option><option value="biobio">Biobío</option></select></label><label for="filter-bar-0-anio">Año de publicación<select class="input border border-slate-400 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2" id="filter-bar-0-anio" name="anio"><option value="">Todos</option><option value="2026">2026</option><option value="2025">2025</option><option value="2024">2024</option></select></label><label for="filter-bar-0-monto">Monto estimado<select class="input border border-slate-400 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2" id="filter-bar-0-monto" name="monto"><option value="">Cualquiera</option><option value="menos-100">Menos de 100 UTM</option><option value="100-1000">Entre 100 y 1.000 UTM</option><option value="mas-1000">Más de 1.000 UTM</option></select></label></div></fieldset><div class="ui-actions"><button class="btn btn--primary border border-slate-950 px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed" type="submit">Aplicar filtros</button><button class="btn btn--quiet border border-slate-950 px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed" type="reset">Limpiar</button><p>Sin filtros aplicados</p></div></form>

Código REACT

Copia el bloque completo; después cambia contenido y destinos, no su anatomía.

Código de referencia
import type { ChangeEvent, FormEventHandler } from "react";

export interface FilterBarOption {
  /** Texto visible de la opción. */
  label: string;
  /** Valor enviado; si falta, se envía el texto. */
  value?: string;
}

export interface FilterBarFilter {
  /** Clave del filtro; el selector usa el id `${id}-${key}`. */
  key: string;
  /** Etiqueta visible del selector. */
  label: string;
  options: FilterBarOption[];
  /** Nombre del parámetro enviado con el formulario. */
  name?: string;
  /** Opción elegida al inicio en un selector no controlado; si falta, la primera. */
  defaultValue?: string;
  /** Opción elegida en un selector controlado; va junto con `onFilterChange`. */
  value?: string;
}

export interface FilterBarProps {
  /** Prefijo de los ids de los selectores. Usa uno distinto por barra en la página. */
  id?: string;
  /** Título del grupo de filtros. */
  legend?: string;
  filters?: FilterBarFilter[];
  /** Se llama al cambiar un selector, con la clave del filtro. */
  onFilterChange?: (key: string, event: ChangeEvent<HTMLSelectElement>) => void;
  submitLabel?: string;
  resetLabel?: string;
  /** Resumen de los filtros aplicados, junto a los botones. */
  status?: string;
  /** Destino del formulario; sin JavaScript, los filtros navegan ahí. */
  action?: string;
  method?: "get" | "post";
  onSubmit?: FormEventHandler<HTMLFormElement>;
  onReset?: FormEventHandler<HTMLFormElement>;
  className?: string;
}

const FILTER_BAR_DEFAULT_FILTERS: FilterBarFilter[] = [
  {
    key: "estado",
    name: "estado",
    label: "Estado",
    options: [
      { label: "Todos", value: "" },
      { label: "Publicada", value: "publicada" },
      { label: "Cerrada", value: "cerrada" },
      { label: "Adjudicada", value: "adjudicada" },
      { label: "Desierta", value: "desierta" },
    ],
  },
  {
    key: "region",
    name: "region",
    label: "Región",
    options: [
      { label: "Todas", value: "" },
      { label: "Metropolitana de Santiago", value: "metropolitana" },
      { label: "Valparaíso", value: "valparaiso" },
      { label: "Biobío", value: "biobio" },
    ],
  },
  {
    key: "anio",
    name: "anio",
    label: "Año de publicación",
    options: [
      { label: "Todos", value: "" },
      { label: "2026", value: "2026" },
      { label: "2025", value: "2025" },
      { label: "2024", value: "2024" },
    ],
  },
  {
    key: "monto",
    name: "monto",
    label: "Monto estimado",
    options: [
      { label: "Cualquiera", value: "" },
      { label: "Menos de 100 UTM", value: "menos-100" },
      { label: "Entre 100 y 1.000 UTM", value: "100-1000" },
      { label: "Más de 1.000 UTM", value: "mas-1000" },
    ],
  },
];

/**
 * Barra de filtros con selectores, botones para aplicar y limpiar y resumen de filtros activos, para acotar una tabla o lista de resultados.
 *
 * Es un formulario: sin JavaScript, con `action`, `method="get"` y `name` en cada filtro, envía los filtros como parámetros.
 * La primera opción de cada filtro es la que no filtra («Todos», «Cualquiera») con `value: ""`; `status` resume lo que quedó elegido.
 *
 * @example <FilterBar id="filtros" action="/licitaciones" method="get" filters={[{ key: "region", name: "region", label: "Región", options: [{ label: "Todas", value: "" }, { label: "Biobío", value: "biobio" }] }]} status="Sin filtros aplicados" />
 * @example <FilterBar id="filtros" filters={filtros} status="2 filtros activos: Adjudicada, Biobío" onSubmit={aplicar} onReset={limpiar} />
 */
export function FilterBar({
  id = "filter-bar-0",
  legend = "Filtrar licitaciones",
  filters = FILTER_BAR_DEFAULT_FILTERS,
  onFilterChange,
  submitLabel = "Aplicar filtros",
  resetLabel = "Limpiar",
  status = "Sin filtros aplicados",
  action,
  method,
  onSubmit,
  onReset,
  className,
}: FilterBarProps) {
  return (
    <form
      className={["ui-filter-bar", className].filter(Boolean).join(" ")}
      data-filter-bar
      action={action}
      method={method}
      onSubmit={onSubmit}
      onReset={onReset}
    >
      {filters.length ? (
        <fieldset>
          <legend>{legend}</legend>
          <div>
            {filters.map((filtro) => {
              const selectId = `${id}-${filtro.key}`;
              const alCambiar = onFilterChange ? (evento: ChangeEvent<HTMLSelectElement>) => onFilterChange(filtro.key, evento) : undefined;
              const controlado = filtro.value !== undefined && alCambiar !== undefined;
              return (
                <label key={filtro.key} htmlFor={selectId}>
                  {filtro.label}
                  <select
                    className="input"
                    id={selectId}
                    name={filtro.name}
                    onChange={alCambiar}
                    {...(controlado ? { value: filtro.value } : { defaultValue: filtro.value ?? filtro.defaultValue })}
                  >
                    {filtro.options.map((opcion, i) => (
                      <option key={opcion.value ?? `${i}-${opcion.label}`} value={opcion.value}>
                        {opcion.label}
                      </option>
                    ))}
                  </select>
                </label>
              );
            })}
          </div>
        </fieldset>
      ) : null}
      <div className="ui-actions">
        <button className="btn btn--primary" type="submit">{submitLabel}</button>
        <button className="btn btn--quiet" type="reset">{resetLabel}</button>
        {status ? <p>{status}</p> : null}
      </div>
    </form>
  );
}