Ir al contenido
Evaluar mi proyecto
Componentes · FormulariosDisponible

Campo de línea con etiqueta flotante

Solo una línea: la etiqueta sube al escribir y una barra se abre desde el centro.

Solo lo usamos para confirmar tu cuenta

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

floating-line-field.tsx62 líneas
Descargar
"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.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/campo-linea-flotante

Ejemplo 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.

ejemplo.tsx5 líneas
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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React · Next.js (quita la directiva use client fuera de Next.js) · Sin Tailwind

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.