1 GitHub star = 1 new component.

Star the repo
Feedbackmodalglass

Glass Modal

A glassmorphism styled modal dialog with backdrop blur.

Live Preview

Usage

Copy and paste this code into your React component:

tsx
import { GlassModal } from 'react-component-kit-ui/feedback';

const [open, setOpen] = useState(false);

<GlassButton onClick={() => setOpen(true)}>Delete project</GlassButton>
<GlassModal
  open={open}
  title="Delete project?"
  onClose={() => setOpen(false)}
  onConfirm={() => { remove(); setOpen(false); }}
>
  This action cannot be undone.
</GlassModal>

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/GlassModal.tsx + GlassModal.css:

GlassModal.tsx
'use client';

import { useEffect, useRef } from 'react';
import type { DialogHTMLAttributes, ReactNode } from 'react';
import './GlassModal.css';

/**
 * GlassModal โ€” A premium overlay dialog for critical confirmations or focused interactions.
 * Built on the native <dialog> element (focus trap, Esc to close, top layer).
 *
 * @param open - Required. Controls visibility.
 * @param onClose - Called on cancel, Esc, or backdrop click.
 * @param onConfirm - Called when the confirm button is pressed.
 * @param title - Optional. Default 'Confirm Action'.
 * @param confirmLabel - Optional. Default 'Confirm'.
 * @param cancelLabel - Optional. Default 'Cancel'.
 */
export interface GlassModalProps extends Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'title'> {
  open: boolean;
  onClose?: () => void;
  onConfirm?: () => void;
  title?: ReactNode;
  confirmLabel?: string;
  cancelLabel?: string;
  children?: ReactNode;
}

export function GlassModal({
  open,
  onClose,
  onConfirm,
  title = 'Confirm Action',
  confirmLabel = 'Confirm',
  cancelLabel = 'Cancel',
  className,
  children,
  ...rest
}: GlassModalProps) {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    const dialog = ref.current;
    if (!dialog) return;
    if (open && !dialog.open) dialog.showModal();
    if (!open && dialog.open) dialog.close();
  }, [open]);

  return (
    <dialog
      {...rest}
      ref={ref}
      className={['modal-dialog', className].filter(Boolean).join(' ')}
      onClose={() => onClose?.()}
      onClick={(e) => {
        // Clicking the backdrop targets the <dialog> itself.
        if (e.target === e.currentTarget) onClose?.();
      }}
    >
      <div className="modal-box">
        <div className="modal-header"><h3>{title}</h3></div>
        <div className="modal-body">{children}</div>
        <div className="modal-footer">
          <button className="modal-btn modal-btn--sec" type="button" onClick={() => onClose?.()}>
            {cancelLabel}
          </button>
          <button className="modal-btn modal-btn--pri" type="button" onClick={() => onConfirm?.()}>
            {confirmLabel}
          </button>
        </div>
      </div>
    </dialog>
  );
}

export default GlassModal;
GlassModal.css
/* GlassModal โ€” ported from astro-component-kit (feedback/GlassModal.astro) */

.modal-dialog { 
  background: transparent; 
  border: none; 
  padding: 0;
  max-width: 400px;
  width: calc(100% - 2rem);
  margin: auto; /* the global reset zeroes margins; re-center the native dialog */
  overflow: visible;
}

.modal-dialog[open] .modal-box { animation: glass-modal-scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; }

.modal-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(8px);
  animation: glass-modal-fade-in 0.3s forwards;
}

.modal-box { 
  background: var(--c-bg-elev, rgba(255,255,255,0.05)); 
  border: 1px solid var(--c-border, rgba(255,255,255,0.1)); 
  border-radius: var(--r-xl, 24px); 
  padding: var(--sp-8, 2rem); 
  box-shadow: 0 30px 100px rgba(0,0,0,0.6);
  color: var(--c-text-1, #fff);
}

.animate-open { animation: glass-modal-scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }

.modal-header h3 { margin: 0 0 var(--sp-4, 1rem); color: var(--c-text-1, #fff); font-size: 1.25rem; }

.modal-body { color: var(--c-text-2, #94a3b8); font-size: 0.95rem; line-height: 1.6; }

.modal-footer { display: flex; justify-content: flex-end; gap: var(--sp-4, 1rem); margin-top: var(--sp-8, 2rem); }

.modal-btn { 
  padding: 0.7rem 1.4rem; 
  border-radius: var(--r-md, 12px); 
  border: none; 
  font-weight: 700; 
  cursor: pointer; 
  font-family: inherit;
  transition: 0.2s;
}

.modal-btn--sec { background: transparent; color: var(--c-text-1, #fff); border: 1px solid var(--c-border, rgba(255,255,255,0.1)); }
.modal-btn--sec:hover { background: rgba(255,255,255,0.05); }

.modal-btn--pri { 
  background: var(--c-primary, #6366f1); 
  color: #fff; 
  box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
}
.modal-btn--pri:hover { transform: translateY(-2px); filter: brightness(1.1); }

@keyframes glass-modal-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes glass-modal-scale-up { 0% { transform: scale(0.8); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }

Quick Info

Category
Feedback
Filename
GlassModal.tsx
Dependencies
None โ€” pure React + CSS
Tags
modalglass