Skip to Content

Modal

Dialog overlay with Trigger, Overlay, Dialog, Content, Title, and Close sub-components. Click outside or the X to dismiss.

Design Language Comparison

Neobrutalism
Shadcn-inspired
Flowbite-inspired
Glassmorphism
Material Design 3
Neumorphism

Neobrutalism

Bold border, flat 6px shadow, yellow hover on close button.

const ModalContext = createContext(undefined);
const useModal = () => useContext(ModalContext);

const Trigger = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button
      onClick={() => setShow(true)}
      className="px-4 py-2 bg-yellow-400 text-black font-bold border-[3px] border-black shadow-[4px_4px_0_#000] rounded-[2px] hover:shadow-[6px_6px_0_#000] hover:-translate-x-px hover:-translate-y-px transition-all"
    >
      {children ?? "Open Modal"}
    </button>
  );
};

const Overlay = ({ children }) => {
  const { show, setShow } = useModal();
  if (!show) return null;
  return (
    <div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setShow(false)}>
      {children}
    </div>
  );
};

const Dialog = ({ children }) => (
  <div className="relative bg-white border-[3px] border-black shadow-[6px_6px_0_#000] rounded-[2px] w-full max-w-md" onClick={(e) => e.stopPropagation()}>
    {children}
  </div>
);

const Title = ({ children }) => {
  const { setShow } = useModal();
  return (
    <div className="flex items-center justify-between px-6 pt-6 pb-4 border-b-[3px] border-black">
      <h2 className="text-lg font-bold">{children}</h2>
      <button onClick={() => setShow(false)} className="text-black hover:text-yellow-600 transition-colors">
        <CircleX size={22} />
      </button>
    </div>
  );
};

const Content = ({ children }) => <div className="p-6">{children}</div>;

const Close = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button onClick={() => setShow(false)} className="px-4 py-2 bg-white text-black font-bold border-[3px] border-black shadow-[4px_4px_0_#000] rounded-[2px] hover:bg-yellow-400 transition-colors">
      {children ?? "Close"}
    </button>
  );
};

const NeobrutalismModal = ({ children, initialOpen = false }) => {
  const [show, setShow] = useState(initialOpen);
  return <ModalContext.Provider value={{ show, setShow }}>{children}</ModalContext.Provider>;
};

NeobrutalismModal.Trigger = Trigger;
NeobrutalismModal.Overlay = Overlay;
NeobrutalismModal.Dialog = Dialog;
NeobrutalismModal.Title = Title;
NeobrutalismModal.Content = Content;
NeobrutalismModal.Close = Close;

// Usage
<NeobrutalismModal>
  <NeobrutalismModal.Trigger>Open Modal</NeobrutalismModal.Trigger>
  <NeobrutalismModal.Overlay>
    <NeobrutalismModal.Dialog>
      <NeobrutalismModal.Title>Modal Title</NeobrutalismModal.Title>
      <NeobrutalismModal.Content>
        <p>Modal content goes here.</p>
      </NeobrutalismModal.Content>
      <div className="px-6 pb-6 flex justify-end">
        <NeobrutalismModal.Close>Close</NeobrutalismModal.Close>
      </div>
    </NeobrutalismModal.Dialog>
  </NeobrutalismModal.Overlay>
</NeobrutalismModal>

Shadcn-inspired

Zinc border, shadow-lg, subtle hover states.

const ModalContext = createContext(undefined);
const useModal = () => useContext(ModalContext);

const Trigger = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button
      onClick={() => setShow(true)}
      className="px-4 py-2 bg-zinc-900 text-zinc-50 font-medium rounded-[6px] border border-transparent hover:opacity-90 transition-opacity text-sm"
    >
      {children ?? "Open Modal"}
    </button>
  );
};

const Overlay = ({ children }) => {
  const { show, setShow } = useModal();
  if (!show) return null;
  return (
    <div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setShow(false)}>
      {children}
    </div>
  );
};

const Dialog = ({ children }) => (
  <div className="relative bg-white border border-zinc-200 rounded-[6px] shadow-lg w-full max-w-md" onClick={(e) => e.stopPropagation()}>
    {children}
  </div>
);

const Title = ({ children }) => {
  const { setShow } = useModal();
  return (
    <div className="flex items-center justify-between px-6 pt-6 pb-4 border-b border-zinc-200">
      <h2 className="text-lg font-semibold text-zinc-900">{children}</h2>
      <button onClick={() => setShow(false)} className="text-zinc-400 hover:text-zinc-700 transition-colors">
        <CircleX size={20} />
      </button>
    </div>
  );
};

const Content = ({ children }) => <div className="p-6">{children}</div>;

const Close = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button onClick={() => setShow(false)} className="px-4 py-2 bg-zinc-100 text-zinc-900 font-medium rounded-[6px] text-sm hover:bg-zinc-200 transition-colors">
      {children ?? "Close"}
    </button>
  );
};

const ShadcnModal = ({ children, initialOpen = false }) => {
  const [show, setShow] = useState(initialOpen);
  return <ModalContext.Provider value={{ show, setShow }}>{children}</ModalContext.Provider>;
};

ShadcnModal.Trigger = Trigger;
ShadcnModal.Overlay = Overlay;
ShadcnModal.Dialog = Dialog;
ShadcnModal.Title = Title;
ShadcnModal.Content = Content;
ShadcnModal.Close = Close;

// Usage
<ShadcnModal>
  <ShadcnModal.Trigger>Open Modal</ShadcnModal.Trigger>
  <ShadcnModal.Overlay>
    <ShadcnModal.Dialog>
      <ShadcnModal.Title>Modal Title</ShadcnModal.Title>
      <ShadcnModal.Content>
        <p>Modal content goes here.</p>
      </ShadcnModal.Content>
      <div className="px-6 pb-6 flex justify-end">
        <ShadcnModal.Close>Close</ShadcnModal.Close>
      </div>
    </ShadcnModal.Dialog>
  </ShadcnModal.Overlay>
</ShadcnModal>

Flowbite-inspired

Gray border, blue trigger button, rounded-lg container.

const ModalContext = createContext(undefined);
const useModal = () => useContext(ModalContext);

const Trigger = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button
      onClick={() => setShow(true)}
      className="px-4 py-2 bg-[#1c64f2] text-white font-medium rounded-lg border border-transparent hover:bg-[#1a56db] transition-colors text-sm"
    >
      {children ?? "Open Modal"}
    </button>
  );
};

const Overlay = ({ children }) => {
  const { show, setShow } = useModal();
  if (!show) return null;
  return (
    <div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setShow(false)}>
      {children}
    </div>
  );
};

const Dialog = ({ children }) => (
  <div className="relative bg-white border border-gray-200 rounded-lg shadow-md w-full max-w-md" onClick={(e) => e.stopPropagation()}>
    {children}
  </div>
);

const Title = ({ children }) => {
  const { setShow } = useModal();
  return (
    <div className="flex items-center justify-between px-6 pt-6 pb-4 border-b border-gray-200">
      <h2 className="text-lg font-semibold text-gray-800">{children}</h2>
      <button onClick={() => setShow(false)} className="text-gray-400 hover:text-gray-600 transition-colors">
        <CircleX size={20} />
      </button>
    </div>
  );
};

const Content = ({ children }) => <div className="p-6">{children}</div>;

const Close = ({ children }) => {
  const { setShow } = useModal();
  return (
    <button onClick={() => setShow(false)} className="px-4 py-2 bg-gray-100 text-gray-700 font-medium rounded-lg text-sm hover:bg-gray-200 transition-colors">
      {children ?? "Close"}
    </button>
  );
};

const FlowbiteModal = ({ children, initialOpen = false }) => {
  const [show, setShow] = useState(initialOpen);
  return <ModalContext.Provider value={{ show, setShow }}>{children}</ModalContext.Provider>;
};

FlowbiteModal.Trigger = Trigger;
FlowbiteModal.Overlay = Overlay;
FlowbiteModal.Dialog = Dialog;
FlowbiteModal.Title = Title;
FlowbiteModal.Content = Content;
FlowbiteModal.Close = Close;

// Usage
<FlowbiteModal>
  <FlowbiteModal.Trigger>Open Modal</FlowbiteModal.Trigger>
  <FlowbiteModal.Overlay>
    <FlowbiteModal.Dialog>
      <FlowbiteModal.Title>Modal Title</FlowbiteModal.Title>
      <FlowbiteModal.Content>
        <p>Modal content goes here.</p>
      </FlowbiteModal.Content>
      <div className="px-6 pb-6 flex justify-end">
        <FlowbiteModal.Close>Close</FlowbiteModal.Close>
      </div>
    </FlowbiteModal.Dialog>
  </FlowbiteModal.Overlay>
</FlowbiteModal>

Glassmorphism

Frosted glass dialog with blurred overlay and translucent border.

const GlassmorphismModal = ({ isOpen, onClose, title, children, footer }) => {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4" onClick={(e) => e.target === e.currentTarget && onClose()}>
      <div className="absolute inset-0 bg-black/30 backdrop-blur-sm" />
      <div className="relative bg-white/15 backdrop-blur-md border border-white/20 rounded-xl shadow-[0_8px_32px_rgba(31,38,135,0.25)] w-full max-w-md font-sans">
        <div className="flex items-center justify-between px-5 py-4 border-b border-white/10">
          <h2 className="text-base font-semibold text-white m-0">{title}</h2>
          <button onClick={onClose} className="text-white/60 hover:text-white text-xl leading-none transition-colors">×</button>
        </div>
        <div className="px-5 py-4 text-sm text-white/80">{children}</div>
        {footer && <div className="px-5 py-4 border-t border-white/10 flex justify-end gap-2">{footer}</div>}
      </div>
    </div>
  );
};

Material Design 3

28 px radius dialog, dark scrim backdrop, MD3 elevation shadow.

const Md3Modal = ({ isOpen, onClose, title, children, footer }) => {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4" onClick={(e) => e.target === e.currentTarget && onClose()}>
      <div className="absolute inset-0 bg-[#1c1b1f]/40" />
      <div className="relative bg-[#fffbfe] rounded-[28px] shadow-[0_6px_10px_rgba(0,0,0,0.14),0_1px_18px_rgba(0,0,0,0.12)] w-full max-w-md font-sans">
        <div className="px-6 pt-6 pb-4">
          <h2 className="text-xl font-semibold text-[#1c1b1f] m-0 mb-2">{title}</h2>
          <div className="text-sm text-[#49454f]">{children}</div>
        </div>
        {footer && <div className="px-6 pb-6 flex justify-end gap-2">{footer}</div>}
      </div>
    </div>
  );
};

Neumorphism

Raised panel on base background with dual-tone soft shadow.

const NmModal = ({ isOpen, onClose, title, children, footer }) => {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-[#e0e5ec]/80" onClick={(e) => e.target === e.currentTarget && onClose()}>
      <div className="bg-[#e0e5ec] rounded-xl shadow-[-10px_-10px_20px_#ffffff,_10px_10px_20px_rgba(163,177,198,0.6)] w-full max-w-md font-sans">
        <div className="flex items-center justify-between px-5 py-4 border-b border-[rgba(163,177,198,0.3)]">
          <h2 className="text-base font-semibold text-[#3d4f6e] m-0">{title}</h2>
          <button onClick={onClose} className="text-[#6c7a9c] hover:text-[#3d4f6e] text-xl leading-none transition-colors">×</button>
        </div>
        <div className="px-5 py-4 text-sm text-[#6c7a9c]">{children}</div>
        {footer && <div className="px-5 py-4 border-t border-[rgba(163,177,198,0.3)] flex justify-end gap-2">{footer}</div>}
      </div>
    </div>
  );
};
Last updated on

© 2026 UI Variants. Built with Nextra.