Campo de búsqueda con limpieza
Búsqueda con etiqueta visible, botón para limpiar y envío por Enter o botón; expone onSearch para conectarla a datos.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useState, useRef, useId, type CSSProperties } from "react";
/** Campo de búsqueda con limpieza. React + TypeScript, sin dependencias. Personaliza con variables --gcxs-*. */
export type CampoBusquedaProps = {
label?: string;
placeholder?: string;
onSearch?: (query: string) => void;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcxs-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcxs-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcxs-root *, .gcxs-root *::before, .gcxs-root *::after { box-sizing: border-box; }
.gcxs-root button, .gcxs-root input, .gcxs-root textarea, .gcxs-root select { font: inherit; }
.gcxs-root button, .gcxs-root a { -webkit-tap-highlight-color: transparent; }
.gcxs-root :focus-visible { outline: 2px solid var(--gcxs-accent, #25724b); outline-offset: 4px; }
.gcxs-root ::selection { background: var(--gcxs-accent, #25724b); color: #fff; }
.gcxs-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcxs-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcxs-root *, .gcxs-root *::before, .gcxs-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcxs-root { width: 480px; }
.gcxs-root label { display: block; font-weight: 600; margin-bottom: 12px; }
.gcxs-field { display: flex; align-items: center; gap: 8px; padding: 7px 7px 7px 16px; border: 1px solid #c2c9c2; border-radius: 12px; background: var(--gcxs-bg, #fff); }
.gcxs-field:focus-within { border-color: #25724b; }
.gcxs-field input { width: 0; flex: 1; min-height: 44px; border: 0; background: transparent; color: #202421; outline-offset: 0; }
.gcxs-field input::placeholder { color: #606c61; }
.gcxs-field input::-webkit-search-cancel-button { display: none; }
.gcxs-field button { border: 0; background: #e7ede7; color: #244630; border-radius: 7px; min-height: 44px; min-width: 44px; padding: 0 12px; cursor: pointer; display: grid; place-items: center; }
.gcxs-field button:hover { background: #d4dfd4; }
.gcxs-root p { margin: 10px 0 0; color: #59645b; font-size: 12px; }`;
export function CampoBusqueda({
label = "Buscar en tu biblioteca",
placeholder = "Documentos, proyectos…",
onSearch,
className = "",
style,
}: CampoBusquedaProps) {
const [value, setValue] = useState("");
const [submitted, setSubmitted] = useState("");
const id = useId();
const input = useRef<HTMLInputElement>(null);
return (
<form
className={`gcxs-root ${className}`}
style={style}
onSubmit={(event) => {
event.preventDefault();
setSubmitted(value.trim());
onSearch?.(value.trim());
}}
>
<style>{CSS}</style>
<label htmlFor={id}>{label}</label>
<div className="gcxs-field">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.7"
aria-hidden="true"
>
<circle cx="10" cy="10" r="6" />
<path d="m15 15 5 5" />
</svg>
<input
ref={input}
id={id}
type="search"
value={value}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
/>
{value && (
<button
type="button"
aria-label="Limpiar búsqueda"
onClick={() => {
setValue("");
setSubmitted("");
input.current?.focus();
}}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.7"
aria-hidden="true"
>
<path d="m6 6 12 12M18 6 6 18" />
</svg>
</button>
)}
<button type="submit">Buscar</button>
</div>
<p role="status">
{submitted
? `Búsqueda enviada: ${submitted}`
: "Escribe y presiona Enter."}
</p>
</form>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/campo-busquedaEjemplo de uso de Campo de búsqueda con limpieza
Después de copiar campo-busqueda.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { CampoBusqueda } from "./campo-busqueda";
export default function Ejemplo() {
return <CampoBusqueda label="Ejemplo" />;
}Propiedades de CampoBusqueda
CampoBusqueda acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
label? | string | Opcional. |
placeholder? | string | Opcional. |
onSearch? | (query: string) => void | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Campo de búsqueda con limpieza con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gcxs-fg | #202421 |
--gcxs-accent | #25724b |
--gcxs-bg | #fff |
Accesibilidad y rendimiento
El archivo tiene 112 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Campo de búsqueda con limpieza por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Declara roles ARIA para lectores de pantalla: status.
- Es un componente de cliente de Next.js: incluye la directiva "use client".