Ir al contenido
Evaluar mi proyecto
Componentes · FootersDisponible

Footer postal con nota editable

Footer con composición postal y textarea real que prepara un correo; el usuario revisa y envía desde su aplicación de correo.

Una idea empieza con una nota.

Escribe unas líneas.
El resto lo conversamos.

Correspondencia
Abrir en mi correoSe abrirá tu aplicación de correo. No se envía automáticamente.

Código completo

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

footer-postal.tsx105 líneas
Descargar
"use client";

import { useState, useId, type CSSProperties } from "react";

/** Footer postal con nota editable. React + TypeScript, sin dependencias. Personaliza con variables --gcyn-*. */
export type FooterPostalProps = {
  brand?: string;
  email?: string;
  title?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcyn-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcyn-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcyn-root *, .gcyn-root *::before, .gcyn-root *::after { box-sizing: border-box; }
.gcyn-root button, .gcyn-root input, .gcyn-root textarea, .gcyn-root select { font: inherit; }
.gcyn-root button, .gcyn-root a { -webkit-tap-highlight-color: transparent; }
.gcyn-root :focus-visible { outline: 2px solid var(--gcyn-accent, #25724b); outline-offset: 4px; }
.gcyn-root ::selection { background: var(--gcyn-accent, #25724b); color: #fff; }
.gcyn-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcyn-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcyn-root *, .gcyn-root *::before, .gcyn-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcyn-root { width:100%; display:grid; grid-template-columns:1fr 1fr; gap:48px; background:var(--gcyn-bg,#e4d8c6); color:var(--gcyn-fg,#4c3a2d); padding:56px clamp(24px,5vw,64px); container-type:inline-size; }
.gcyn-left h2 { font-size:clamp(36px,4.5vw,58px); line-height:1.08; letter-spacing:-.03em; font-weight:600; margin:0 0 24px; text-wrap:balance; }
.gcyn-left p { font-size:15px; color:#6c5947; margin:0 0 60px; }
.gcyn-left strong { font-size:18px; font-weight:500; }
.gcyn-card { position:relative; padding:32px; background:#faf4e8; border-radius:4px; box-shadow:0 14px 32px #6f493b16; min-width:0; transform:rotate(1deg); }
.gcyn-stamp { position:absolute; top:20px; right:20px; color:#9b694b; }
.gcyn-card label { font-weight:600; font-size:13px; display:block; margin-bottom:18px; }
.gcyn-card textarea { resize:vertical; min-height:140px; width:100%; border:0; border-bottom:1px solid #cabb9f; padding:16px 0; margin:14px 0 16px; background:transparent; color:#4c3a2d; font-size:17px; line-height:1.6; }
.gcyn-card textarea::placeholder { color:#81705c; }
.gcyn-card a { display:flex; align-items:center; justify-content:space-between; color:#624128; min-height:44px; text-underline-offset:4px; font-size:13px; font-weight:600; }
.gcyn-card small { display:block; font-size:11px; color:#6e5e4d; margin-top:16px; }
@container(max-width:600px) { .gcyn-left h2 { font-size:40px; } }
@media(max-width:620px) { .gcyn-root { grid-template-columns:1fr; gap:30px; }
.gcyn-left p { margin-bottom:24px; }
.gcyn-card { padding:24px; transform:none; } }`;

export function FooterPostal({
  brand = "Correspondencia",
  email = "hola@example.com",
  title = "Una idea empieza con una nota.",
  className = "",
  style,
}: FooterPostalProps) {
  const [note, setNote] = useState("");
  const id = useId();
  const href = `mailto:${email}?subject=${encodeURIComponent("Una nueva idea")}&body=${encodeURIComponent(note)}`;
  return (
    <footer className={`gcyn-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gcyn-left">
        <h2>{title}</h2>
        <p>
          Escribe unas líneas.
          <br />
          El resto lo conversamos.
        </p>
        <strong>{brand}</strong>
      </div>
      <div className="gcyn-card">
        <svg
          className="gcyn-stamp"
          width="42"
          height="48"
          viewBox="0 0 42 48"
          fill="none"
          stroke="currentColor"
          strokeWidth="1"
          aria-hidden="true"
        >
          <rect x="3" y="3" width="36" height="42" strokeDasharray="2 3" />
          <path d="M10 15h22M10 22h22M10 29h15M10 36h22" />
        </svg>
        <label htmlFor={id}>Tu nota</label>
        <textarea
          id={id}
          value={note}
          onChange={(e) => setNote(e.target.value)}
          placeholder="Tengo una idea para…"
          maxLength={1500}
        />
        <a href={href}>
          Abrir en mi correo
          <svg
            width="18"
            height="18"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
            aria-hidden="true"
          >
            <path d="M5 12h14m-6-6 6 6-6 6" />
          </svg>
        </a>
        <small>
          Se abrirá tu aplicación de correo. No se envía automáticamente.
        </small>
      </div>
    </footer>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/footer-postal

Ejemplo de uso de Footer postal con nota editable

Después de copiar footer-postal.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { FooterPostal } from "./footer-postal";

export default function Ejemplo() {
  return <FooterPostal brand="Ejemplo" />;
}

Propiedades de FooterPostal

FooterPostal acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
brand?stringOpcional.
email?stringOpcional.
title?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Footer postal con nota editable 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.

VariableValor por defecto
--gcyn-fg#202421
--gcyn-accent#25724b
--gcyn-bg#e4d8c6

Accesibilidad y rendimiento

El archivo tiene 105 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Footer postal con nota editable 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).
  • 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 18 y 19 · Next.js (componente de cliente)