Campo de texto Material Design 3
Campos filled y outlined con etiqueta flotante, texto de ayuda, contador y estado de error, según la guía de Material 3.
Te escribiremos solo para avisarte del pedido.
Como aparece en tu factura0/30
Faltan dígitos: usa 10 números.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useId, useState, type CSSProperties } from "react";
/** Campo de texto Material Design 3, relleno o con contorno, con etiqueta flotante, texto de ayuda y error. React + TypeScript, sin dependencias. */
export type CampoMaterialProps = {
label?: string;
variant?: "filled" | "outlined";
value?: string;
defaultValue?: string;
onChange?: (value: string) => void;
helper?: string;
error?: string;
type?: string;
maxLength?: number;
disabled?: boolean;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gccmt-root { box-sizing: border-box; display: block; width: 100%; max-width: 320px; font: 400 16px/24px Roboto, "Google Sans", ui-sans-serif, system-ui, sans-serif; color: #1d1b20; }
.gccmt-root *, .gccmt-root *::before, .gccmt-root *::after { box-sizing: border-box; }
.gccmt-root input { font: inherit; }
@media (prefers-reduced-motion: reduce) { .gccmt-root *, .gccmt-root *::before, .gccmt-root *::after { animation: none !important; transition-duration: .01ms !important; } }
.gccmt-field { --c: var(--gccmt-primary, #6750a4); position: relative; height: 56px; }
.gccmt-field[data-error="true"] { --c: var(--gccmt-error, #b3261e); }
.gccmt-input { position: absolute; inset: 0; width: 100%; height: 56px; padding: 24px 16px 8px; border: 0; background: transparent; color: inherit; caret-color: var(--c); outline: 0; }
.gccmt-input::placeholder { color: transparent; }
.gccmt-input:focus::placeholder { color: #79747e; }
.gccmt-label { position: absolute; left: 16px; top: 16px; max-width: calc(100% - 32px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #49454f; pointer-events: none; transform-origin: left top; transition: transform .2s cubic-bezier(.2, 0, 0, 1), color .2s; }
.gccmt-field[data-variant="filled"] { border-radius: 4px 4px 0 0; background: #e7e0ec; }
.gccmt-field[data-variant="filled"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: #49454f; transition: height .15s, background .15s; }
.gccmt-field[data-variant="filled"]:hover { background: color-mix(in srgb, #e7e0ec 92%, #1d1b20); }
.gccmt-field[data-variant="filled"][data-focus="true"]::after, .gccmt-field[data-variant="filled"][data-error="true"]::after { height: 2px; background: var(--c); }
.gccmt-field[data-variant="outlined"] .gccmt-input { padding: 16px; }
.gccmt-outline { position: absolute; inset: 0; border: 1px solid #79747e; border-radius: 4px; pointer-events: none; transition: border-color .15s; }
.gccmt-field[data-variant="outlined"]:hover .gccmt-outline { border-color: #1d1b20; }
.gccmt-field[data-variant="outlined"][data-focus="true"] .gccmt-outline, .gccmt-field[data-variant="outlined"][data-error="true"] .gccmt-outline { border-width: 2px; border-color: var(--c); }
.gccmt-field[data-variant="outlined"] .gccmt-label { padding: 0 4px; margin-left: -4px; background: var(--gccmt-surface, #fef7ff); }
.gccmt-field[data-up="true"] .gccmt-label { transform: translateY(-8px) scale(.75); }
.gccmt-field[data-variant="outlined"][data-up="true"] .gccmt-label { transform: translateY(-24px) scale(.75); }
.gccmt-field[data-focus="true"] .gccmt-label, .gccmt-field[data-error="true"] .gccmt-label { color: var(--c); }
.gccmt-field[data-disabled="true"] { opacity: .38; }
.gccmt-support { display: flex; justify-content: space-between; gap: 12px; padding: 4px 16px 0; font-size: 12px; line-height: 16px; color: #49454f; }
.gccmt-support[data-error="true"] { color: var(--gccmt-error, #b3261e); }`;
export function CampoMaterial({ label = "Correo electrónico", variant = "filled", value, defaultValue = "", onChange, helper = "Te escribiremos solo para avisarte del pedido.", error, type = "text", maxLength, disabled, className = "", style }: CampoMaterialProps) {
const id = useId();
const [inner, setInner] = useState(defaultValue);
const [focus, setFocus] = useState(false);
const current = value ?? inner;
const note = error ?? helper;
return (
<div className={`gccmt-root ${className}`} style={style}>
<style>{CSS}</style>
<div className="gccmt-field" data-variant={variant} data-focus={focus} data-up={focus || current.length > 0} data-error={error ? "true" : undefined} data-disabled={disabled ? "true" : undefined}>
<input
id={id}
className="gccmt-input"
type={type}
value={current}
maxLength={maxLength}
placeholder={variant === "outlined" ? " " : "name@example.com"}
disabled={disabled}
aria-invalid={error ? true : undefined}
aria-describedby={note ? `${id}-note` : undefined}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
onChange={(event) => {
setInner(event.target.value);
onChange?.(event.target.value);
}}
/>
<label className="gccmt-label" htmlFor={id}>
{label}
</label>
{variant === "outlined" ? <span className="gccmt-outline" /> : null}
</div>
<div className="gccmt-support" data-error={error ? "true" : undefined}>
<span id={`${id}-note`}>{note}</span>
{maxLength ? (
<span>
{current.length}/{maxLength}
</span>
) : null}
</div>
</div>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/campo-materialEjemplo de uso de Campo de texto Material Design 3
Después de copiar campo-material.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { CampoMaterial } from "./campo-material";
export default function Ejemplo() {
return <CampoMaterial variant="filled" label="Ejemplo" />;
}Propiedades de CampoMaterial
CampoMaterial acepta 12 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
label? | string | Opcional. |
variant? | "filled" | "outlined" | Opcional. |
value? | string | Opcional. |
defaultValue? | string | Opcional. |
onChange? | (value: string) => void | Opcional. |
helper? | string | Opcional. |
error? | string | Opcional. |
type? | string | Opcional. |
maxLength? | number | Opcional. |
disabled? | boolean | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Variantes disponibles
variant:filled,outlined
Personaliza Campo de texto Material Design 3 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 |
|---|---|
--gccmt-primary | #6750a4 |
--gccmt-error | #b3261e |
--gccmt-surface | #fef7ff |
Accesibilidad y rendimiento
El archivo tiene 91 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Campo de texto Material Design 3 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-*.
- Calcula tonos con color-mix(), así que un solo color base alcanza.
- Es un componente de cliente de Next.js: incluye la directiva "use client".