Campo de línea con etiqueta flotante
Solo una línea: la etiqueta sube al escribir y una barra se abre desde el centro.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
/**
* Campo de texto minimalista: solo una línea, con etiqueta flotante y una barra de color que se abre desde el centro.
*
* <FloatingLineField label="Correo electrónico" type="email" />
* <FloatingLineField label="Contraseña" type="password" error="Mínimo 8 caracteres" />
*
* Recibe todas las propiedades de un <input>. Su placeholder se ignora porque la etiqueta ocupa ese lugar.
*/
import { useId, type InputHTMLAttributes } from "react";
export type FloatingLineFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "placeholder"> & {
label: string;
/** Texto de ayuda bajo el campo. */
hint?: string;
/** Mensaje de error: pinta el campo de rojo y se anuncia a lectores de pantalla. */
error?: string;
};
const CSS = `
.gcl-field { --gcl-accent: #3d3df5; --gcl-ink: #111114; --gcl-muted: #6c6c78; --gcl-line: #cfcfd8; --gcl-error: #d12b1f; display: block; width: 100%; font-family: inherit; color: var(--gcl-ink); }
.gcl-box { position: relative; padding-top: 20px; }
.gcl-input { display: block; width: 100%; box-sizing: border-box; padding: 10px 0 9px; border: 0; border-bottom: 1.5px solid var(--gcl-line); border-radius: 0; outline: none; background: transparent; color: inherit;
font-family: inherit; font-size: 18px; line-height: 1.4; transition: border-color .25s; }
.gcl-input:hover { border-bottom-color: #9a9aa8; }
.gcl-label { position: absolute; left: 0; top: 31px; color: var(--gcl-muted); font-size: 18px; line-height: 1.4; pointer-events: none; transform-origin: left top; transition: transform .3s cubic-bezier(.16, 1, .3, 1), color .25s; }
.gcl-input:focus ~ .gcl-label, .gcl-input:not(:placeholder-shown) ~ .gcl-label { transform: translateY(-31px) scale(.72); }
.gcl-input:focus ~ .gcl-label { color: var(--gcl-accent); }
.gcl-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--gcl-accent); transform: scaleX(0); transition: transform .4s cubic-bezier(.16, 1, .3, 1); }
.gcl-input:focus ~ .gcl-bar { transform: scaleX(1); }
.gcl-field[data-invalid="true"] .gcl-input { border-bottom-color: var(--gcl-error); }
.gcl-field[data-invalid="true"] .gcl-bar { background: var(--gcl-error); transform: scaleX(1); }
.gcl-field[data-invalid="true"] .gcl-label { color: var(--gcl-error); }
.gcl-note { margin: 7px 0 0; font-size: 13px; line-height: 1.4; color: var(--gcl-muted); }
.gcl-field[data-invalid="true"] .gcl-note { color: var(--gcl-error); }
.gcl-input:disabled { opacity: .5; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .gcl-label, .gcl-bar, .gcl-input { transition: none; } }
`;
export function FloatingLineField({ label, hint, error, id, className, ...props }: FloatingLineFieldProps) {
const auto = useId();
const inputId = id ?? auto;
const noteId = `${inputId}-note`;
const note = error ?? hint;
return (
<>
<style>{CSS}</style>
<div className={["gcl-field", className].filter(Boolean).join(" ")} data-invalid={error ? "true" : undefined}>
<div className="gcl-box">
<input {...props} id={inputId} className="gcl-input" placeholder=" " aria-invalid={error ? true : undefined} aria-describedby={note ? noteId : undefined} />
<label className="gcl-label" htmlFor={inputId}>{label}</label>
<span className="gcl-bar" aria-hidden="true" />
</div>
{note ? <p className="gcl-note" id={noteId} role={error ? "alert" : undefined}>{note}</p> : null}
</div>
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/campo-linea-flotanteEjemplo de uso de Campo de línea con etiqueta flotante
Después de copiar floating-line-field.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { FloatingLineField } from "./floating-line-field";
export default function Ejemplo() {
return <FloatingLineField />;
}Accesibilidad y rendimiento
El archivo tiene 62 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Campo de línea con etiqueta flotante por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Expone su estado a tecnologías de apoyo con atributos aria-*.
- Es un componente de cliente de Next.js: incluye la directiva "use client".
Acerca del producto
Un campo de texto minimalista sin caja: una línea, una etiqueta que flota hacia arriba al enfocar o escribir, y una barra de color que se abre desde el centro. Incluye texto de ayuda y mensaje de error anunciado a lectores de pantalla.
Incluye
Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.
- Etiqueta flotante con solo CSS (:placeholder-shown)
- Ayuda y error con aria-describedby y role=alert
- Acepta todas las propiedades de un input
- Colores en variables CSS
Cómo usarlo
Pasa label y las propiedades normales de un input (type, name, value, onChange…). Su placeholder se ignora porque la etiqueta ocupa ese lugar.