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.
Ver y copiar el código de este render
<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.
Compañía de Innovación de Santiago
Autoridad: CONSTITUTION.md + LOGOS-CANONICOS.mdCambia 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
Círculo de Santiago
Autoridad: core-style/DECISIONES.md M10 y LOGOS-CANONICOS.mdCambia 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.
- Usa HTML semántico, foco visible y estados explícitos.
- Conserva contenido útil a 320 px y sin JavaScript.
- 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.
Cobertura
| Dimensión | Contrato |
|---|---|
| Propietario canónico | Sistema de diseño CIS y cis-auth · core/core-style |
| Consumidores | Cuenta, Admin, usaia, Firmas y Documentos |
| Dependencias | HTML nativo, validación de servidor |
| Estado | candidato |
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.
| Dato | Resultado |
|---|---|
| Clasificación | Patrón observado |
| Implementaciones | 2 implementaciones observadas |
| Repositorios | cds/cis/cis-admin, cochid/cochid-datos |
| Snapshot | kit/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.
<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.
<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.
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>
);
}