Ir al contenido
Evaluar mi proyecto
Componentes · Componentes baseDisponible

Alerta contextual descartable

Aviso de información, éxito, advertencia o error con descarte, anuncio accesible y opción de volver a mostrarlo.

Cambios sin publicar

Puedes seguir editando. Publica cuando todo esté listo.

Código completo

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

base-alerta.tsx107 líneas
Descargar
"use client";

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

/** Alerta contextual descartable. React + TypeScript, sin dependencias. Personaliza con variables --gcxl-*. */
export type BaseAlertaProps = {
  title?: string;
  message?: string;
  tone?: "info" | "success" | "warning" | "error";
  dismissible?: boolean;
  onDismiss?: () => void;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcxl-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcxl-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcxl-root *, .gcxl-root *::before, .gcxl-root *::after { box-sizing: border-box; }
.gcxl-root button, .gcxl-root input, .gcxl-root textarea, .gcxl-root select { font: inherit; }
.gcxl-root button, .gcxl-root a { -webkit-tap-highlight-color: transparent; }
.gcxl-root :focus-visible { outline: 2px solid var(--gcxl-accent, #25724b); outline-offset: 4px; }
.gcxl-root ::selection { background: var(--gcxl-accent, #25724b); color: #fff; }
.gcxl-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcxl-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcxl-root *, .gcxl-root *::before, .gcxl-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcxl-root { width:500px; display:flex; gap:14px; align-items:flex-start; padding:20px; border-radius:12px; color:var(--gcxl-fg,#294c68); background:var(--gcxl-bg,#e5eef4); }
.gcxl-root[data-tone="success"] { color:#285938; background:#e6f0e4; }
.gcxl-root[data-tone="warning"] { color:#72521d; background:#f8efda; }
.gcxl-root[data-tone="error"] { color:#863c35; background:#fae9e5; }
.gcxl-root>svg { margin-top:2px; }
.gcxl-root>div { flex:1; min-width:0; }
.gcxl-root strong { font-weight:600; font-size:14px; }
.gcxl-root p { margin:5px 0 0; font-size:13px; overflow-wrap:anywhere; }
.gcxl-root button { display:grid; place-items:center; flex-shrink:0; min-height:44px; min-width:44px; padding:5px 10px; border:0; border-radius:6px; background:transparent; color:inherit; cursor:pointer; margin:-10px -10px -10px 0; }
.gcxl-root button:hover { background:#00000009; }
.gcxl-undo { align-items:center; font-size:13px; }`;

export function BaseAlerta({
  title = "Cambios sin publicar",
  message = "Puedes seguir editando. Publica cuando todo esté listo.",
  tone = "info",
  dismissible = true,
  onDismiss,
  className = "",
  style,
}: BaseAlertaProps) {
  const [dismissed, setDismissed] = useState(false);
  if (dismissed)
    return (
      <div className={`gcxl-root gcxl-undo ${className}`} style={style}>
        <style>{CSS}</style>
        <span role="status">Aviso cerrado.</span>
        <button type="button" onClick={() => setDismissed(false)}>
          Mostrar de nuevo
        </button>
      </div>
    );
  return (
    <div
      className={`gcxl-root ${className}`}
      style={style}
      data-tone={tone}
      role={tone === "error" ? "alert" : "status"}
    >
      <style>{CSS}</style>
      <svg
        width="22"
        height="22"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.5"
        aria-hidden="true"
      >
        <circle cx="12" cy="12" r="9" />
        <path d={tone === "success" ? "m7 12 3 3 6-6" : "M12 10v6m0-9v1"} />
      </svg>
      <div>
        <strong>{title}</strong>
        <p>{message}</p>
      </div>
      {dismissible && (
        <button
          type="button"
          aria-label="Cerrar aviso"
          onClick={() => {
            setDismissed(true);
            onDismiss?.();
          }}
        >
          <svg
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.6"
            aria-hidden="true"
          >
            <path d="m6 6 12 12M18 6 6 18" />
          </svg>
        </button>
      )}
    </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/base-alerta

Ejemplo de uso de Alerta contextual descartable

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

ejemplo.tsx5 líneas
import { BaseAlerta } from "./base-alerta";

export default function Ejemplo() {
  return <BaseAlerta tone="info" title="Ejemplo" dismissible />;
}

Propiedades de BaseAlerta

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

PropiedadTipoDescripción
title?stringOpcional.
message?stringOpcional.
tone?"info" | "success" | "warning" | "error"Opcional.
dismissible?booleanOpcional.
onDismiss?() => voidOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Variantes disponibles

  • tone: info, success, warning, error

Personaliza Alerta contextual descartable 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
--gcxl-fg#202421
--gcxl-accent#25724b
--gcxl-bg#e5eef4

Accesibilidad y rendimiento

El archivo tiene 107 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Alerta contextual descartable 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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)