Popup

Show themed messages, confirmations, and input prompts with SweetAlert2.

Popup brings messages, confirmations, and input prompts into your Materialize theme. It uses SweetAlert2 for dialog behavior, keyboard navigation, and focus management.

await M.Popup.fire({
  titleText: 'Welcome back',
  text: 'Your workspace is ready. Pick up where you left off.'
});

Setup

SweetAlert2 is an optional peer dependency. Install it alongside Kmaterialize when using Popup. The JavaScript loads on the first call to Popup.fire(); no element markup or AutoInit() call is required.

npm install kmaterialize sweetalert2

Load SweetAlert2’s base stylesheet before Materialize so the popup uses your theme colors and button styles.

import 'sweetalert2/dist/sweetalert2.min.css';
import 'kmaterialize/dist/css/materialize.min.css';
import { Popup } from 'kmaterialize';

await Popup.fire({ titleText: 'Hello', text: 'Ready when you are.' });
Without a bundler

Copy the following distribution files into your assets folder. SweetAlert2’s script exposes window.Swal, which Popup can use directly.

<link rel="stylesheet" href="assets/sweetalert2.min.css">
<link rel="stylesheet" href="assets/materialize.min.css">
<script src="assets/sweetalert2.min.js"></script>
<script src="assets/materialize.min.js"></script>
<button type="button" onclick="M.Popup.fire({ titleText: 'Hello' })">Open popup</button>

Messages

Use an icon to communicate the type of message. The surface, text, and actions follow the active Materialize theme, including dark mode.

await M.Popup.fire({
  icon: 'success', // success, error, warning, info, or question
  titleText: 'Changes saved',
  text: 'Your project settings are up to date.'
});

Confirmation

Await the result and check isConfirmed before performing the action. Cancelling, closing, pressing Escape, or clicking the backdrop dismisses the dialog.

const result = await M.Popup.fire({
  icon: 'question',
  titleText: 'Archive this project?',
  text: 'You can restore it later from the archive.',
  showCancelButton: true,
  confirmButtonText: 'Archive project',
  cancelButtonText: 'Keep project',
  focusCancel: true
});

if (result.isConfirmed) {
  // Archive the project here.
}

Input prompts

Collect a value using SweetAlert2’s input types. Give each field an inputLabel and return a message from inputValidator to keep the dialog open when validation fails.

const result = await M.Popup.fire({
  titleText: 'Name your workspace',
  input: 'text',
  inputLabel: 'Workspace name',
  inputPlaceholder: 'Animation studio',
  showCancelButton: true,
  confirmButtonText: 'Create workspace',
  inputValidator: (value) => value.trim() ? undefined : 'Enter a workspace name.'
});

if (result.isConfirmed) {
  console.log(result.value);
}

The input-width adjustment is adapted from the Material popup enhancement, keeping fields inside the popup’s margins on small screens.

await M.Popup.fire({
  titleText: 'Choose a department',
  input: 'select',
  inputLabel: 'Department',
  inputPlaceholder: 'Select a department',
  inputOptions: {
    animation: 'Animation',
    lighting: 'Lighting',
    compositing: 'Compositing'
  },
  showCancelButton: true,
  inputValidator: (value) => value ? undefined : 'Choose a department.'
});

Tabs inside a popup

Pass the fill-height tabs layout through html and set customClass.htmlContainer to popup-content-fill. The tabs fill the popup’s content area edge to edge, without inset padding or a nested border. The popup keeps its normal size and Done button; the tab area is 400px high.

import { Popup, Tabs } from 'kmaterialize';

let tabs: Tabs | undefined;

await Popup.fire({
  width: 640,
  confirmButtonText: 'Done',
  customClass: { htmlContainer: 'popup-content-fill' },
  html: `
    <div class="tabs-fill" data-tab-position="top" style="height: 400px">
      <ul class="tabs tabs-fixed-width">
        <li class="tab"><a class="active" href="#popup-first-view">First</a></li>
        <li class="tab"><a href="#popup-second-view">Second</a></li>
      </ul>
      <div id="popup-first-view" class="tabs-fill-panel">First view</div>
      <div id="popup-second-view" class="tabs-fill-panel">Second view</div>
    </div>
  `,
  didOpen: (popup) => {
    tabs = Tabs.init(popup.querySelector<HTMLElement>('.tabs')!);
  },
  didDestroy: () => tabs?.destroy()
});

Initialize Tabs in didOpen, after the popup markup is visible, and destroy the instance in didDestroy. Use panel IDs that are unique on the page. The layout and tab behavior are provided by kmaterialize; use the styles from Setup.

For a fullscreen layout, use grow: 'fullscreen' with popup-content-fill and remove the fixed 400px height. Kmaterialize gives the content the remaining height and keeps the close button out of the layout. Set data-tab-position="bottom" to put the navigation below the panels, even when the list comes first in the HTML.

import { Popup, Tabs } from 'kmaterialize';

let tabs: Tabs | undefined;

await Popup.fire({
  grow: 'fullscreen',
  showConfirmButton: false,
  showCloseButton: true,
  customClass: { htmlContainer: 'popup-content-fill' },
  html: `
    <div class="tabs-fill" data-tab-position="bottom">
      <ul class="tabs tabs-fixed-width">
        <li class="tab"><a class="active" href="#popup-first-view">First</a></li>
        <li class="tab"><a href="#popup-second-view">Second</a></li>
      </ul>
      <div id="popup-first-view" class="tabs-fill-panel">First view</div>
      <div id="popup-second-view" class="tabs-fill-panel">Second view</div>
    </div>
  `,
  didOpen: (popup) => {
    tabs = Tabs.init(popup.querySelector<HTMLElement>('.tabs')!);
  },
  didDestroy: () => tabs?.destroy()
});

Asynchronous actions

Return a promise from preConfirm and enable showLoaderOnConfirm to show progress while it completes. This example simulates two seconds of work.

const result = await M.Popup.fire({
  titleText: 'Prepare a preview?',
  text: 'This demo simulates a two-second operation.',
  showCancelButton: true,
  confirmButtonText: 'Prepare preview',
  showLoaderOnConfirm: true,
  allowOutsideClick: false,
  allowEscapeKey: false,
  preConfirm: () => new Promise((resolve) => {
    setTimeout(() => resolve('Preview ready'), 2000);
  })
});

if (result.isConfirmed) console.log(result.value);

Async stepper

Run a sequence of async functions in a Material Design popup. Each step shows its number, progress, and completion state. The next step starts only after the current function resolves.

These demos simulate checking assets, rendering a preview, and packaging the result. In the retry demo, rendering fails once; Retry step resumes there and keeps the completed asset check.

import { Popup } from 'kmaterialize';

// Simulated async work. Replace with your own API calls.
async function work(signal: AbortSignal) {
  await new Promise(resolve => setTimeout(resolve, 1000));
  signal.throwIfAborted();
}

const result = await Popup.steps<string>({
  title: 'Prepare a preview',
  description: 'Check assets, render, then package the result.',
  steps: [
    {
      title: 'Check assets',
      run: async ({ signal, setMessage }) => {
        setMessage('Checking source files…');
        await work(signal);
        return 'Assets checked';
      }
    },
    {
      title: 'Render preview',
      run: async ({ signal, results, setMessage }) => {
        setMessage(`${results[0]}. Rendering…`);
        await work(signal);
        // Throw an Error to show the failed state and Retry step action.
        return 'Preview rendered';
      }
    },
    {
      title: 'Package result',
      run: async ({ signal }) => {
        await work(signal);
        return 'Preview ready';
      }
    }
  ]
});

if (result.isConfirmed) console.log(result.value); // Ordered step results.

The full feature, including styling and async control, is included in kmaterialize. Use the same SweetAlert2 setup as other Popup examples; no extra styles or initialization are needed.

Option or contextPurpose
stepsA nonempty array of { title, description?, run } objects.
signalAborted when dismissed or replaced. Pass it to fetch(url, { signal }) or check it inside longer work.
resultsA read-only snapshot of completed step results.
setMessage(text)Update the active step’s plain-text message.
cancellableDefaults to true. Set false to prevent cancellation during work; failures can still be dismissed.
cancelButtonText / doneButtonText / retryButtonTextCustomize action labels.

Done confirms with all results. Cancellation stops further steps and signals the running function to abort; work that ignores the signal may continue in the background. Completed work is not rolled back. Make a failed step safe to retry if it writes data.

TypeScript types: PopupStep, PopupStepContext, and PopupStepsOptions, and PopupStepConfirmationOptions. Step labels, descriptions, and error messages are rendered as plain text.

Async steps with human interaction

Combine background work with steps that wait for you. Step 2 contains a custom Materialize input with required-field validation. Step 4 shows a summary built from the earlier results and waits for Confirm preview.

Try continuing with an empty title to see validation. Cancel is available while working or waiting; this example does not upload, save, or send anything.

import { Popup } from 'kmaterialize';

function stepperDelay(milliseconds: number, signal: AbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    const abort = () => {
      window.clearTimeout(timer);
      reject(new DOMException('Cancelled', 'AbortError'));
    };
    const timer = window.setTimeout(() => {
      signal.removeEventListener('abort', abort);
      resolve();
    }, milliseconds);
    if (signal.aborted) abort();
    else signal.addEventListener('abort', abort, { once: true });
  });
}

async function runInteractiveStepper() {
  const result = await Popup.steps<string>({
    title: 'Create a review preview',
    description: 'Choose a title, prepare the preview, then approve the result. Nothing is uploaded or saved.',
    doneButtonText: 'Close',
    steps: [
      {
        title: 'Check assets',
        run: async ({ signal, setMessage }) => {
          setMessage('Checking the demo assets…');
          await stepperDelay(700, signal);
          setMessage('12 source files are ready.');
          return '12 source files';
        },
      },
      {
        title: 'Choose a preview title',
        description: 'Enter a title, then select Continue. This step waits for you.',
        run: async ({ waitForConfirmation, setMessage }) => {
          const content = document.createElement('div');
          content.innerHTML = `
            <div class="input-field outlined">
              <input id="stepper-preview-title" type="text" placeholder=" " maxlength="80" required>
              <label for="stepper-preview-title">Preview title</label>
            </div>`;
          const input = content.querySelector<HTMLInputElement>('input')!;
          const title = await waitForConfirmation({
            content,
            confirmButtonText: 'Continue',
            readValue: () => {
              const value = input.value.trim();
              input.setAttribute('aria-invalid', String(!value));
              if (!value) throw new Error('Enter a preview title before continuing.');
              return value;
            },
          });
          setMessage(`Title: ${title}`);
          return title;
        },
      },
      {
        title: 'Generate the preview',
        run: async ({ signal, results, setMessage }) => {
          setMessage(`Preparing “${results[1]}”…`);
          await stepperDelay(1000, signal);
          setMessage('Preview ready: 120 frames at 24 fps.');
          return '120 frames · 24 fps · 5 seconds';
        },
      },
      {
        title: 'Review and confirm',
        description: 'Review your custom result before approving it.',
        run: async ({ results, waitForConfirmation, setMessage }) => {
          const content = document.createElement('div');
          content.className = 'card-panel surface-variant';
          const title = document.createElement('strong');
          title.textContent = results[1];
          const details = document.createElement('p');
          details.textContent = `${results[0]} · ${results[2]}`;
          content.append(title, details);
          const approval = await waitForConfirmation({
            content,
            confirmButtonText: 'Confirm preview',
            readValue: () => `Confirmed “${results[1]}”`,
          });
          setMessage('Preview approved. This demo has not saved any files.');
          return approval;
        },
      },
    ],
  });
  console.log(result.isConfirmed
    ? `Interactive stepper: ${result.value?.[3]}.`
    : 'Preview creation cancelled. No preview was saved.');
}

await runInteractiveStepper();

await waitForConfirmation({ content, confirmButtonText, readValue }) displays your DOM content inside the active step and pauses until confirmation. readValue may be async: its return value resolves the wait, while a thrown error displays validation and keeps the same step open. Dismissing the popup rejects the wait with an AbortError.

Use textContent for user-provided values. Custom content from completed steps is removed when the next step begins; the final result stays visible until Close. The reusable interaction and styling live in kmaterialize.

Toast mode

SweetAlert2’s toast mode provides a compact notification that closes automatically.

await M.Popup.fire({
  toast: true,
  position: 'top-end',
  icon: 'success',
  titleText: 'Draft saved',
  showConfirmButton: false,
  timer: 2500,
  timerProgressBar: true
});

Draggable popup

Set draggable: true to add a line handle at the top. Drag it with a mouse or touch, or focus it and use the arrow keys. Hold Shift for larger steps and press Home to reset the position.

await M.Popup.fire({
  titleText: 'Move this popup',
  text: 'Drag the line at the top to reposition this dialog.',
  draggable: true,
  dragHandleLabel: 'Move popup',
  showCloseButton: true,
  confirmButtonText: 'Done'
});

Dragging is restricted to the handle, keeping text and form fields usable. The popup stays within the viewport, and each new popup starts at its normal position. Toasts do not show a drag handle.

Options and methods

Popup.fire(options) accepts SweetAlert2 options and resolves with its result object. Use titleText and text for plain text; title and html interpret HTML.

Materialize defaultValuePurpose
buttonsStylingfalseUse Materialize button classes.
heightAutofalsePreserve the page’s existing height.
draggablefalseShow a handle for moving the dialog.
dragHandleLabelMove popupAccessible label for the drag handle.
customClass.confirmButtonbtn filledPrimary action.
customClass.cancelButtonbtn outlinedCancel action.
customClass.denyButtonbtn tonalAlternative action, shown with showDenyButton.

Supply customClass to customize individual elements. Button class overrides replace the default for that button; your popup and container classes are added alongside the required theme classes.

await M.Popup.fire({
  titleText: 'A softer action',
  customClass: { confirmButton: 'btn tonal rounded' }
});

// Confirm through input validation and preConfirm:
await M.Popup.clickConfirm();

// Close the current dialog programmatically:
await M.Popup.close();
Result fieldDescription
isConfirmedThe confirm action was accepted.
isDeniedThe deny action was selected.
isDismissedThe dialog was dismissed.
valueInput value or the value returned by preConfirm.
dismissDismissal reason, such as cancel, esc, backdrop, close, or timer.

TypeScript consumers can import PopupOptions and PopupResult and call Popup.fire<string>(options) for a typed input result.

Popups use a 16px corner radius and a 120ms opening animation. Override --swal2-border-radius and --swal2-show-animation on .swal2-popup.popup to customize them.

SweetAlert2 manages one current dialog, so opening another replaces it. Await the first result when sequencing dialogs. Reduced-motion preferences disable popup animations, and closing a dialog returns focus to its trigger.